引言 |
js原型链是js面向对象编程的基础和重点,许多文章都对它进行了讲解,这里我想谈谈我对原型链的理解,一方面加深自己的印象,另一方面希望能和大家分享交流。
从关键字 new 说起 |
我来试着模拟一下new的操作过程。
//模拟new关键字的行为
function methodNew( func ) //func:新对象的构造函数
{
if ( func === Object )
return Object(); //Object()方法返回原始的object对象
else
{
var obj = {}; //{}实际上等价于Object()
obj.constructor = func; //更改obj的constructor属性
obj.__proto__ = func.prototype; //设置obj.__proto__属性,从而访问到原型链上的属性,
//比如 func.prototype.name可以通过 obj.name读取,但是不能使用obj.name来修改,这样会为obj添加一个name属性。
//这里只处理func方法参数为空的情况
func.call( obj ); //为obj添加func方法中定义的属性,比如
//function func(){ this.name = "Jerry" }
//相当于执行obj.name = "Jerry"
return obj;
}
}
来测试一下
function People()
{
this.name = "杨幂";
this.introduce = function()
{
alert("My name is "+ this.name );
}
}
var obj = methodNew( Object );
var beauty = methodNew( People );
console.log( obj );
console.log( beauty );
结果如下
Object {}
People {constructor: function, name: "杨幂", introduce: function}
结果与new关键字生成的对象一致。
稍微总结一下,new关键字的行为与后面的方法是不是 Object() 有关,
在 非Object() 方法时,执行下面四步
1. var obj = {}; //生成一个object对象
2. obj.constructor = func; //设置constructor,由此判断对象的类型由constructor属性决定
3. obj.__proto__ = func.prototype; //这一步很关键,将obj连入原型链,从而能访问func.prototype对象以及它的原型链上端对象的属性
4. func.call( obj ); //为obj添加func()方法中声明的属性
__proto__
与prototype
这里我把__proto__
放在前面,因为它是原型链的基础。
People.prototype.hobby = "篮球";
People.prototype.swim = function(){ console.log( "swimming" ) };
var obj = {};
obj.__proto__ = People.prototype;
console.log( obj.hobby );
obj.swim;
对象访问属性的顺序是先查找自身,显然 hobby 和 swim 都不是obj自身的属性,这是系统会查找obj.__proto__
指向的对象是否有这两个属性,这里就是 People.prototype ,如果还没有,会继续找People.prototype.__proto__
指向的对象有没有所需属性,这里有一个知识点,声明一个函数People()时,People.prototype 属性指向谁呢?
根据我的研究,函数的prototype属性的初始化很简单,分为两步
1. People.prototype = {}; //将一个object对象赋给prototype
2. People.prototype.constructor = People; //设置constructor为People,表示原型对象的类型是People
因为People.prototype是一个普通的object对象,所以有
People.prototype.__proto__
指向 Object.prototype ,
这与图中的描述是吻合的。
如此一来,下面的原型链就形成了
obj
−→−−−−−−− __proto__
People.prototype
−→−−−−−−− __proto__
Object.prototype
−→−−−−−−− __proto__
null
有一条对原型链文章的评价:js对象其实就是键值对,说的不错,js中的方法和变量都以对象的形式存在,一个对象能访问到的其实就是 自己的属性 和 原型链上的属性。
看看下面的例子
People.prototype.hobby = "篮球";
People.prototype.swim = function(){ console.log( "swimming" ) };
var obj = {};
obj.__proto__ = People.prototype;
//console.log( obj.hobby );
//obj.swim;
obj.hobby = "撩妹";
console.log( People.prototype.hobby );
发现结果仍然是 篮球
,说明这里是为obj添加了自己的hobby属性,并没有改动原型对象的属性。
我们再做进一步测试
People.prototype.sing = function(){ console.log( this.song ) }
var obj = {};
obj.song = "爱的供养";
obj.__proto__ = People.prototype;
obj.sing();
People.prototype.sing.call( obj );
//结果
//爱的供养*2
这一步解释了原型对象中的方法是怎么被执行的。
这个图解释了原型链的原理,放在这里供大家参考
Function与Object
图中 Function 与 Object 的关系比较令人费解,其实可以这么理解。
js中所有的对象都是由函数通过new操作符生成的,比如
var obj = new People()
则一定有obj.__proto__ == People.prototype'
而所有的函数对象都是由 Function() 函数生成的,Object()方法也不例外,所以有
Object.__proto__ = Function.prototype
当然,Function()方法也是由自己生成的,所以推出
Function.__proto__ = Function.prototype
(其实Object()方法和Function()方法都是 本地代码(native code),也就是事先写好的,并不是生成的对象,原型链中这样设计是为了逻辑的完备)。
这里比较特殊的两个对象是 function.prototype 和 object.prototype,这两个对象的生成不符合上面提到的规律
1. People.prototype = {};
2. People.prototype.constructor = People;
function.prototype 作为Function的原型对象是由Function()函数直接返回的,并不是一个object对象,而且这个属性是只读的,它的值显示如下
function Empty() {}
从逻辑上来说, 所有的函数对象也应该能访问 Object.prototype 的属性,所以有
function.protototype.__proto__ = Object.prototype
让所有的函数对象通过 function.prototype 访问原型链的末端 Object.prototype。
作为原型链的尾端, Object.prototype 的属性可以被js中的所有对象访问, Object.prototype 的__proto__
是只读的,这保证了 Object.prototype 作为原型链的一个出口。
结尾
希望能对读者朋友们有所启发,有想法请留言与我交流,转载请注明作者和地址,谢谢!