js原型,原型链

原型

js原型,原型链

1. prototype

Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。

这意味着,我们可以把那些不变的属性和方法,直接定义在prototype对象上。

  1. Person.prototype ------原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先
  2. 通过该构造函数产生的对象,可以继承该原型的属性和方法。
  3. 原型也是对象,也有自己的属性。
// Person.prototype  = {} 是祖先
Person.prototype.say= "abc";
function Person(){
}
var person = new Person();

js原型,原型链

2.constructor查看对象的构造函数

任何一个prototype对象都有一个constructor属性,指向它的构造函数。每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。

function Person(){
}

Car.prototype.say = "def";
function Car(){

}
//Car.prototype = {
//    constructor : Person
//}
var car = new Car();
console.log(car.constructor);//function Car(){}

//console.log(car.constructor);//function Person(){}

//注释掉的部分:可以更改对象car的构造函数,
//            它本来是function Car(){}实例化的一个对象,
//            现在Car.prototype = {constructor : Person}让它指向构造函数function Person(){}

3. 隐式__proto__属性,查看原型

js在创建对象的时候,都有一个叫做__proto__的内置属性,用于指向创建它的函数对象的原型对象prototype

new一个构造函数,相当于实例化一个对象,这期间其实进行了这三个步骤:

  1. 在构造函数的逻辑最顶端隐式的新建一个this对象,this其实不是不是一个 空对象,
    var this = { __proto__ : Person.prototype}//__proto__属性 指向的是对象原型。 (每个对象都有__proto__属性,该属性指向一个对象,就是构造函数Person的原型对象(Person.prototype))

  2. 去调用构造函数Person,从而设置对象的属性和方法并初始化。并返回this对象。(把this返回,这样每一个实例化的对象就有__proto__属性了。)

  3. 上面步骤完成后,这个对象就与构造函数Person再无联系,这个时候即使构造函数Person再加任何成员,都不再影响已经实例化了的对象了。(此时该对象有 了自己的属性之后,同时具有了构造函数Person的原型对象的所有成员)

1,__proto__的基本用法
Person.prototype.name = 'abc';
function Person(){
    //var this = {
    //  __proto__ : Person.prototype//指向的是对象原型
   // }
}
var obj ={
    name : "sunny"
}   
var person = new Person();
person.__proto__ = obj;
console.log(person.name);//sunny

// // new一个对象的时候,查找属性name,
// // 先看自己的构造函数里面有没有name属性,如果有就直接用,
// // 如果没有就沿着this里面__proto__ 属性去对象的原型里面查找,这个时候我改变了person.__proto__ = obj;让它指向对象obj,那它自然也改变了。


2 '.'的写法改变原型对象属性的值,那么结果也会跟着改
Person.prototype.name = 'sunny';

function Person(){
//var this = {__proto__ : Person.prototype}
}

// Person.prototype.name = 'apple';

var person = new Person();

Person.prototype.name = 'apple';

console.log(person.name)//apple

无论Person.prototype.name = 'apple'放在var person = new Person();上面还是下面,person.name的值都是apple,因为它改变的是__proto__ 指向的Person.prototype上的值。值都变了,person.name必然会变化。

3,改变原型让它指向另外的一个空间,原理类似于下面的:

var obj = {name : "a"};
var obj1 = obj;//obj和obj1先指向同一个房间,
obj = {name : "b"};//obj它又指向另外一个房间
console.log(obj1.name);//a
console.log(obj.name);//b

------------------------------------------------------------------------------
Person.prototype.name = 'sunny';
function Person(){
//var this = {__proto__ : Person.prototype}
}
var person = new Person();
Person.prototype = {          //这是把原型给改了,换了一个新对象
    name : 'cherry'
}
console.log(person.name)//sunny

分析程序执行的顺序:
首先new一个对象person,调用构造函数,里面隐式的var this = {__proto__ : Person.prototype},让__proto__  和 Person.prototype指向同一个空间,然后返回this,这个对象就构建完了。
然后Person.prototype = {   name : 'cherry' }把自己的空间换了,但是 __proto__ 没有换,__proto__它还是指向原来的Person.prototype的空间值就是sunny。那么查找name属性的时候,去__proto__里面找到Person.prototype.name  ,结果就是sunny.。
就像下面的过程:
 Person.prototype = {name : "sunny"};
 _proto_ = Person.prototype;
 Persom.prototype = {name : "cherry"};
 console.log(_proto_.name);//sunny
 
 ---------------------------------------------------------------
 
Person.prototype.name = 'sunny';
function Person(){
//var this = {__proto__ : Person.prototype}
}
Person.prototype = {         
    name : 'cherry'
}
var person = new Person();
console.log(person.name)//cherry
分析:这个就是Person.prototype = {name : 'cherry' }改变了原型对象让它的值为cherry,然后再实例化一个对象的时候再调用都早函数的时候就会查找__proto__的Person.prototype为cherry。

4, 改变原型和改变原型属性的值综合起来:

Person.prototype.name = 'apple';
function Person(){
//var this = {__proto__ : Person.prototype}
}
Person.prototype = {  //改变原型    
    name : 'cherry'
}
Person.prototype.name = 'sunny';//改变原型属性的值
var person = new Person();
console.log(person.name)//sunny

首先是__proto__和 Person.prototype一起指向一个空间值是apple,
然后Person.prototype指向另外一个空间值是cherry,
然后再把第二次Person.prototype指向的空间的内容替换为sunny,最后实例化对象的时候,发现自己没有这个name属性,
根据__proto__ 去原型里面查找,找到的就是第二次指向的空间里里面的值sunny。

每当代码读取某个对象的某个属性的时候,都会执行一次搜索。首先从对象实例本身开始,如果在实例中找到了该属性,则返回该属性的值,如果没有找到,则顺着原型链指针向上,到原型对象中去找,如果如果找到就返回该属性值。
这里要提一点,如果为对象实例添加了一个属性与原型中同名,则该属性会屏蔽掉原型中的同名属性,不会去修改它!使用delete可以删除自己实例中的属性,但是原型中的属性是删除不了的。

原型链

js原型,原型链

1,构成原型链


//原型链  是一个链条的 形式,可以把对象串联起来
function CreateDog(name,color){
	this.name = name;
	this.color = color;
}

CreateDog.prototype.say = function(){
	console.log(this.color + this.name + '在叫!');
};

var dog  = new CreateDog('萨摩耶','白色');
dog.say();
         
//访问原型的方法
//1.通过构造函数来访问
console.log(CreateDog.prototype);//Object { say: say(), … }
//2. 通过实例化的对象来访问
console.log(dog.__proto__);//Object { say: say(), … }

//js 在创建对象(任何对象,普通对象和函数对象)的时候,都有一个__proto__的属性,
//这个属性用于指向创建他的函数对象的原型对象prototype
console.log(dog.__proto__ === CreateDog.prototype);//true
//同样的,CreateDog.prototype 对象也有一个__proto__   指向创建他的函数的原型对象 (object)的prototype
console.log(CreateDog.prototype.__proto__ === Object.prototype);//true
//Object.prototype  也有一个__proto__   指向null
console.log(Object.prototype.__proto__ === null);//true
//原型链  特点是:__proto__  属性,

js原型,原型链

2,Object.prototype是原型链的终端。

字面量创建对象的方法其实和new Object()的方法是一模一样的。

var obj = {};
var obj1 = new Object();
它们是相等的。
obj.__proto__ ---->Object.prototype
obj1.__proto__ ---->Object.prototype

js原型,原型链

Person.prototype = {} ------->Object.prototype
function Person(){
}
函数的原型就是一个字面量的形式,所以原型链的终端就是Object.prototype

js原型,原型链
js原型,原型链

3 , 利用Object.create(原型)创建一个对象

//var obj = Object.create(原型)
 Person.prototype.name = "sunny";
 function Person() {

 }
 var person = Object.create(Person.prototype);
 console.log(person.name);//sunny

js原型,原型链

4 , 绝大多数对象的最终都会继承于Object.prototype,但是也有不继承的情况

Object.create(原型)里面的"原型"必须是一个Object对象或者空值。(放原始值会报错)

var obj = Object.create(null);
var obj1 = Object.create(123);//报错

现在创建一个对象,把null放进去,发现这个对象什么属性都没有了,没原型了。调用toString()不行,它自己没有toString方法,它也没原型连__proto__属性都没有,所以根本找不到toString方法,就会报错。
js原型,原型链
那现在人为的给它加上__proto__属性,再去看它不会有继承特性,人为的加上,系统不认。
js原型,原型链
所以说是绝大多数对象继承于Object.prototype,不是所有的对象,因为现在的这个Object.create(null)创建的对象根本没有继承属性,连原型都没有。

5, 关于toString()方法

1,

undefined和null不能调用tostring方法。
var num = 123;num.toString()可以调用tostring()方法,因为数字可以通过包装类来一层层访问,包装类肯定是对象,然后对象的原型链的终端是Object,它有tostring方法。
但是undefined和null也不是对象,也没有包装类,他就是原始值,没有原型,不可能可以调用tostring()方法。

js原型,原型链

2,数字num调用toString()方法的原理:

var num = 123;
//num.toString();------->new Number(num).toString();
//Number.prototype.toString = function() {}
//Number.prototype.__proto__ = Object.prototype
//Object.prototype.toString = function () {}

首先Object.prototype上有一个toString方法,每一个继承Object.prototype的都可以调用,但是他们自己也重写了这个方法,就是Number.prototype.toString = function() {},每次调用toString()就是调用的自己重写的toString()方法,方便打印出自己想要的结果。
例如
js原型,原型链
但是如果不重写自己 的方法那么打印出来的就是下面这样的形式(call就是说让它调用顶端的方法)
js原型,原型链
js原型,原型链
不仅是Number重写了toString,还有其他的
js原型,原型链

3,document.write()本质上是调用tostirng方法

var obj = Object.create(null);
document.write(obj);//会报错
document.write(obj.toString());//会报错,因为它没有原型,更不会有toString()方法
-------------------------------------------------------
//现在人为的加上toString()方法
var obj = Object.create(null);
obj.toString = function() {
return '访问访问';
}
document.write(obj);//访问访问
document.write(obj.toString());//访问访问
//证明document.write()本质上是调用tostirng方法

6, 附上经典的原型链图解

js原型,原型链

继承实现方式:
为了实现继承,__proto__属性会指向上一层的原型对象,而上一层的结构依然类似,那么就利用__proto__一直指向Object的原型对象上!
Object.prototype.proto= null;表示到达最顶端。如此形成了原型链继承。

大致总结一下就是:
1、Object是作为众多new出来的实例的基类 function Object(){ [ native code ] }
2、Function是作为众多function出来的函数的基类 function Function(){ [ native code ] }
3、构造函数的__proto__(包括Function.prototype和Object.prototype)都指向Function.prototype
4、原型对象的__proto__都指向Object.prototype
5、Object.prototype.__proto__指向null