一篇文章教你搞懂javaScript原型链
2015-12-27 22:21
591 查看
JavaScript是被称为零入门的语言,但凡学习过编程的同学,入门起JavaScript这门语言都会觉得特别容易,但是深入之后也会有一些很基本却很容易被忽略的点,这篇文章教你重新认识JavaScript。
首先明确一点,JavaScript并不是一门完全面向对象的语言,这也是老生常谈的一个问题,这里就不多讲。只说一点我的理解,既然不是一门完全面向对象的语言,那么它肯定会有面向对象语言的特性,也有非面向对象语言的特性。而针对原型链,我们暂时将它认为是一门完全面向对象的语言。
完全面向对象的语言,经典代表是java,我们经常说java语言中,万物万事皆对象。那么对于JavaScript,我们如果认为它是完全面向对象的,也可以说它万物万事皆对象,那么首先搞清楚最基本的对象。
最基本的对象
这个obj是一个最纯净的对象,它内部什么属性都没有。
请注意!请注意!请注意!(重要的话说三遍),这里的obj与下面这种写法初始化后的对象不同。
这样写法的得出的obj内部并不是空的。在js中,为了简化语法 这种写法其实是下面这种写法的简写: var obj1 = new Object();
两种写法区别主要在于obj内部没有任何属性和方法,obj1内部有一个属性__proto__指向一个Object定义的对象,这个之后再谈。
最基本的传递(Delegation)
所谓一生二,二生三,三生万物,有了这个最基本的 obj 我们就用它来构造整个JavaScript对象模型。首先是描述最基本的属性传递规则,也就是原型链的传递规则。Delegation是指当从一个对象中获取一个属性失败时,JS会自动再尝试去其__proto__链接指向的对象中去查找,如果还找不到,就沿着__proto__一路找上去,直至找到null。这个规则告诉我们一个很重要的事情,就是每个对象都必须有一个属性叫__proto__!按照这个规则,我们的最基本的对象可以描述成如下样子:{
__proto__:undefined
}
还是请大家揣摩这个对象。
构造一个新对象 同时保留老对象的所有特点
有了上面那条最基本的规则,我们现在来构造一个新的对象,并且这个心对象有老对象的所有特性,基本的算法应该这么描述:
1.copy最原始对象得到一个副本
2.再次copy原始对象得到另一个副本
3.在第二个副本中添加新的属性或者方法
4.将第二个副本中__proto__指向第一个副本
更一般的,假设我们不是通过最原始的对象来构造一个新对象,则算法变为
1.copy父级对象得到一个副本
2.再copy原始对象得到另一个原始对象副本
3.在原始对象副本中添加新的属性或者方法
4.将原始对象副本中__proto__指向父对象副本
以上就是构造出一个新对象的过程,并且是继承的过程。这个过程用代码描述 假设a是已有的对象,现在要构造新对象b。var copyA = copy(a);
var b = copy({});
b.newProperty = "我是新的";
b.__proto__ = copyA;
//大家体会这个过程。
大家对js的function应该不陌生,因为刚入门的时候我们就会使用它来封装我们的程序代码模块等等。JavaScript中的function 和 new关键字
其实function也是一个对象,但不同于上文中的那种对象(我们称上文中的对象为普通对象)。
function 这种对象是可以被new关键字激活并且返回一个普通对象,也就是说function是一种可以生产对象的对象,我们一般把这种对象叫做类,或者叫做类类型。
这种类类型有一个属性叫prototype,这个属性指向一个普通对象。而new关键字的作用就是
1.创造一个纯净的对象 obj,
2.执行fun.apply(obj,arguments);
3.将新对象obj的__proto__指向 fun的prototype
4.返回此对象obj
举例说明:var fun = function(){
this.aaa="222";
};
fun.prototype={
property1:"111"
}
var funObj = new fun();
//funObj 对象包含
//{
// aaa:'222',
// __proto__:{
// property1:"111"
// }
//}
而当调用funObj.property1时 按照delegation原则会找到value "111"
由上推断,js中普通对象和function使用new关键字联系在一起,普通对象的__proto__和function的prototype关系密切,其他更深层次的联系读者自己体会。
还有一点需要指明,当function内部有返回值时,new关键字会失效。
最基本的类类型
JavaScript中内置了一些类类型,上文中所说的纯净的对象在js中是不存在的,因为js在出生的那一刻就给我们提供了一些基类的类型,注意这里说的是类类型,也就是说接下来说的几种都是function
1. Object
Object 是js中生产基础对象的类类型,使用new关键字创建的Object对象是js中最简单最基础的对象,是js中一切对象的源,表现为Object的对象的__proto__的__proto__指向null。注意这里用了两个__proto__,分析一下原因:var obj = new Object();
//分析一下new的过程
// obj=Object.create(null); 创造一个纯净对象
// obj.__proto__ = Object.prototype;
// 我们知道Object的prototype为 "{ __proto__:undefined,... }"
// 所以最终我们得到的对象obj.__proto__.__proto__指向undefined
2.Function
Function 是js中生产function的类类型,相比Object,Function似乎更难理解一点,因为Function本身是一个function,同时它的prototype也是一个function,当用new关键字激活Function时,生产的对象也是一个function,但生成的这个funtion的prototype不再是function而是Object对象。
这点和上文中的规则有点相悖,但其实并不是相悖的。下面举例来说明Function内部大概的思路:
如下的实现模仿Function是错误的var fun1 = function(){}
fun1.prototype=function(){}
var fun2 = new fun1();
fun1的prototype是function(){} 此时用new关键字激活fun1返回一个对象,此时fun2的type应该是object而并非一个function,这是因为fun1并没有返回值,此时new关键字激活后会将生产的新对象返回,而这个对象是由fun1构造的普通对象,其实是Object派生出来的一个对象。
如下的实现模仿Function是类似的var fun11 = function(){
<span style="white-space:pre"> </span>return function(){}
<span style="white-space:pre"> </span>}
fun11.prototype=function(){}
var fun12 = new fun11();
此时fun12是一个function,也就是一个类类型,可以继续使用new关键字激活,但是要注意的是,这个fun12和fun11没有继承派生关系,fun12指向的是fun11内部生成的一个function对象,此时的fun11就类似于一个生产function的工厂,作用就和Function类似了。
可以看出来,此时的new关键字已经失效了,因为不使用new关键字得到的fun12也是一样的。这点是js函数式的特点,返回值可以是函数。
再来看原型链继承
根据javascript delegation的特性,再去理解一般的继承写法就不难理解了。var fun1 = function(){
this.a=234
}
fun1.prototype = {
a:123,
b:321
}
var fun2 = function(){
this.c=111;
}
fun2.prototype=new fun1();
var obj1 = new fun1();
var obj2 = new fun2();
//解析obj1内部结构如下
// obj1 = {
// a:234,
// __proto__:{
// a:123,
// b:321
// }
//}
//obj1.a==234 true;
//obj1.b==321 true;
//解析obj2内部结构如下
//obj2={
c:111,
__proto__:{
// a:234,
// __proto__:{
// a:123,
// b:321
// }
// }
//}
//obj2.a==234
//obj2.b==321
//obj2.c==111
这是一般的继承写法,当然这表面上看似乎没有问题,其实却有一点问题,这里简单说明一下
还是以上的写法var fun1 = function(){
this.a=234
}
fun1.prototype = {
a:123,
b:321
}
var fun2 = function(){
this.c=111;
}
fun2.prototype=new fun1();
var obj1 = new fun1();
var obj2 = new fun2();
//此时改变一下fun1的prototype
fun1.prototype.b=322;
//再去看一下obj2的值
//obj2.b==322 !!!
以上可以看出,obj2已经生成,按照一般的逻辑,此时改变类本身,不应该影响obj2,但显然现在不是这种情况。所以应该做如下处理:var fun1 = function(){
this.a=234
}
fun1.prototype = {
a:123,
b:321
}
var fun2 = function(){
this.c=111;
}
function copy(obj){
var objnew = {};
for(var key in obj){
if(typeof obj[key] == "object"){
objnew[key]=copy(obj[key]);
}else{
objnew[key]=obj[key];
}
}
return objnew;
}
//这里多了一个copy方法
fun2.prototype=copy(new fun1());
var obj1 = new fun1();
var obj2 = new fun2();
但是这样问题就结束了么?当然没有,现在会出现一个新的问题,不太好描述,直接看下面的代码:
这个问题似乎比刚才那个问题更严峻,直接屏蔽掉一种prototype结构的合法性。
由此可以看出单纯从代码规则上不能屏蔽掉这个bug问题,所以我们必须遵循一个比较严格的代码规范,即:
类的定义必须在实例化对象之前,对象实例化之后禁止修改类本身。这个规范并非说代码层次的强制禁止,只是告诉大家如果不遵循这个规范有可能会埋下不小的bug风险,其危害也是显而易见的。除非你有特别的产品需求需要这样做,否则建议千万别这么做。
这个问题的出现归根结底是因为js是解释型语言,在任何时候都可以对类本身做出调整,也没有内置的访问权限控制,即使可以使用闭包伪造私有域,但无论如何都不能杜绝对一个类的prototype的访问。
无论如何刚才的继承写法都有点太low的感觉,要直接控制子类的prototype,如果有很多很多类,出了问题排查都不容易!有更好的写法么?
接下来的文章会提供一套js继承系统,探究一下js框架代码中,是如何处理继承这个问题的。敬请期待!
转载请注明出处:http://gagalulu.wang/blog/detail?id=7 您的支持是我最大的动力!
首先明确一点,JavaScript并不是一门完全面向对象的语言,这也是老生常谈的一个问题,这里就不多讲。只说一点我的理解,既然不是一门完全面向对象的语言,那么它肯定会有面向对象语言的特性,也有非面向对象语言的特性。而针对原型链,我们暂时将它认为是一门完全面向对象的语言。
完全面向对象的语言,经典代表是java,我们经常说java语言中,万物万事皆对象。那么对于JavaScript,我们如果认为它是完全面向对象的,也可以说它万物万事皆对象,那么首先搞清楚最基本的对象。
最基本的对象
var obj = Object.create(null);
这个obj是一个最纯净的对象,它内部什么属性都没有。
请注意!请注意!请注意!(重要的话说三遍),这里的obj与下面这种写法初始化后的对象不同。
var obj1 = {};
这样写法的得出的obj内部并不是空的。在js中,为了简化语法 这种写法其实是下面这种写法的简写: var obj1 = new Object();
两种写法区别主要在于obj内部没有任何属性和方法,obj1内部有一个属性__proto__指向一个Object定义的对象,这个之后再谈。
最基本的传递(Delegation)
所谓一生二,二生三,三生万物,有了这个最基本的 obj 我们就用它来构造整个JavaScript对象模型。首先是描述最基本的属性传递规则,也就是原型链的传递规则。Delegation是指当从一个对象中获取一个属性失败时,JS会自动再尝试去其__proto__链接指向的对象中去查找,如果还找不到,就沿着__proto__一路找上去,直至找到null。这个规则告诉我们一个很重要的事情,就是每个对象都必须有一个属性叫__proto__!按照这个规则,我们的最基本的对象可以描述成如下样子:{
__proto__:undefined
}
还是请大家揣摩这个对象。
构造一个新对象 同时保留老对象的所有特点
有了上面那条最基本的规则,我们现在来构造一个新的对象,并且这个心对象有老对象的所有特性,基本的算法应该这么描述:
1.copy最原始对象得到一个副本
2.再次copy原始对象得到另一个副本
3.在第二个副本中添加新的属性或者方法
4.将第二个副本中__proto__指向第一个副本
更一般的,假设我们不是通过最原始的对象来构造一个新对象,则算法变为
1.copy父级对象得到一个副本
2.再copy原始对象得到另一个原始对象副本
3.在原始对象副本中添加新的属性或者方法
4.将原始对象副本中__proto__指向父对象副本
以上就是构造出一个新对象的过程,并且是继承的过程。这个过程用代码描述 假设a是已有的对象,现在要构造新对象b。var copyA = copy(a);
var b = copy({});
b.newProperty = "我是新的";
b.__proto__ = copyA;
//大家体会这个过程。
大家对js的function应该不陌生,因为刚入门的时候我们就会使用它来封装我们的程序代码模块等等。JavaScript中的function 和 new关键字
其实function也是一个对象,但不同于上文中的那种对象(我们称上文中的对象为普通对象)。
function 这种对象是可以被new关键字激活并且返回一个普通对象,也就是说function是一种可以生产对象的对象,我们一般把这种对象叫做类,或者叫做类类型。
这种类类型有一个属性叫prototype,这个属性指向一个普通对象。而new关键字的作用就是
1.创造一个纯净的对象 obj,
2.执行fun.apply(obj,arguments);
3.将新对象obj的__proto__指向 fun的prototype
4.返回此对象obj
举例说明:var fun = function(){
this.aaa="222";
};
fun.prototype={
property1:"111"
}
var funObj = new fun();
//funObj 对象包含
//{
// aaa:'222',
// __proto__:{
// property1:"111"
// }
//}
而当调用funObj.property1时 按照delegation原则会找到value "111"
由上推断,js中普通对象和function使用new关键字联系在一起,普通对象的__proto__和function的prototype关系密切,其他更深层次的联系读者自己体会。
还有一点需要指明,当function内部有返回值时,new关键字会失效。
最基本的类类型
JavaScript中内置了一些类类型,上文中所说的纯净的对象在js中是不存在的,因为js在出生的那一刻就给我们提供了一些基类的类型,注意这里说的是类类型,也就是说接下来说的几种都是function
1. Object
Object 是js中生产基础对象的类类型,使用new关键字创建的Object对象是js中最简单最基础的对象,是js中一切对象的源,表现为Object的对象的__proto__的__proto__指向null。注意这里用了两个__proto__,分析一下原因:var obj = new Object();
//分析一下new的过程
// obj=Object.create(null); 创造一个纯净对象
// obj.__proto__ = Object.prototype;
// 我们知道Object的prototype为 "{ __proto__:undefined,... }"
// 所以最终我们得到的对象obj.__proto__.__proto__指向undefined
2.Function
Function 是js中生产function的类类型,相比Object,Function似乎更难理解一点,因为Function本身是一个function,同时它的prototype也是一个function,当用new关键字激活Function时,生产的对象也是一个function,但生成的这个funtion的prototype不再是function而是Object对象。
这点和上文中的规则有点相悖,但其实并不是相悖的。下面举例来说明Function内部大概的思路:
如下的实现模仿Function是错误的var fun1 = function(){}
fun1.prototype=function(){}
var fun2 = new fun1();
fun1的prototype是function(){} 此时用new关键字激活fun1返回一个对象,此时fun2的type应该是object而并非一个function,这是因为fun1并没有返回值,此时new关键字激活后会将生产的新对象返回,而这个对象是由fun1构造的普通对象,其实是Object派生出来的一个对象。
如下的实现模仿Function是类似的var fun11 = function(){
<span style="white-space:pre"> </span>return function(){}
<span style="white-space:pre"> </span>}
fun11.prototype=function(){}
var fun12 = new fun11();
此时fun12是一个function,也就是一个类类型,可以继续使用new关键字激活,但是要注意的是,这个fun12和fun11没有继承派生关系,fun12指向的是fun11内部生成的一个function对象,此时的fun11就类似于一个生产function的工厂,作用就和Function类似了。
可以看出来,此时的new关键字已经失效了,因为不使用new关键字得到的fun12也是一样的。这点是js函数式的特点,返回值可以是函数。
再来看原型链继承
根据javascript delegation的特性,再去理解一般的继承写法就不难理解了。var fun1 = function(){
this.a=234
}
fun1.prototype = {
a:123,
b:321
}
var fun2 = function(){
this.c=111;
}
fun2.prototype=new fun1();
var obj1 = new fun1();
var obj2 = new fun2();
//解析obj1内部结构如下
// obj1 = {
// a:234,
// __proto__:{
// a:123,
// b:321
// }
//}
//obj1.a==234 true;
//obj1.b==321 true;
//解析obj2内部结构如下
//obj2={
c:111,
__proto__:{
// a:234,
// __proto__:{
// a:123,
// b:321
// }
// }
//}
//obj2.a==234
//obj2.b==321
//obj2.c==111
这是一般的继承写法,当然这表面上看似乎没有问题,其实却有一点问题,这里简单说明一下
还是以上的写法var fun1 = function(){
this.a=234
}
fun1.prototype = {
a:123,
b:321
}
var fun2 = function(){
this.c=111;
}
fun2.prototype=new fun1();
var obj1 = new fun1();
var obj2 = new fun2();
//此时改变一下fun1的prototype
fun1.prototype.b=322;
//再去看一下obj2的值
//obj2.b==322 !!!
以上可以看出,obj2已经生成,按照一般的逻辑,此时改变类本身,不应该影响obj2,但显然现在不是这种情况。所以应该做如下处理:var fun1 = function(){
this.a=234
}
fun1.prototype = {
a:123,
b:321
}
var fun2 = function(){
this.c=111;
}
function copy(obj){
var objnew = {};
for(var key in obj){
if(typeof obj[key] == "object"){
objnew[key]=copy(obj[key]);
}else{
objnew[key]=obj[key];
}
}
return objnew;
}
//这里多了一个copy方法
fun2.prototype=copy(new fun1());
var obj1 = new fun1();
var obj2 = new fun2();
但是这样问题就结束了么?当然没有,现在会出现一个新的问题,不太好描述,直接看下面的代码:
var fun1 = function(){ this.a=234 } fun1.prototype = { a:123, b:321 } fun1.prototype.b=fun1.prototype; //这里真的是神来之笔啊!!!! var fun2 = function(){ this.c=111; } function copy(obj){ var objnew = {}; for(var key in obj){ if(typeof obj[key] == "object"){ objnew[key]=copy(obj[key]); }else{ objnew[key]=obj[key]; } } return objnew; } //因为fun1的变态的prototype结构 下面的函数会递归调用直到栈溢出,直接崩掉了! fun2.prototype=copy(new fun1()); var obj1 = new fun1(); var obj2 = new fun2();
这个问题似乎比刚才那个问题更严峻,直接屏蔽掉一种prototype结构的合法性。
由此可以看出单纯从代码规则上不能屏蔽掉这个bug问题,所以我们必须遵循一个比较严格的代码规范,即:
类的定义必须在实例化对象之前,对象实例化之后禁止修改类本身。这个规范并非说代码层次的强制禁止,只是告诉大家如果不遵循这个规范有可能会埋下不小的bug风险,其危害也是显而易见的。除非你有特别的产品需求需要这样做,否则建议千万别这么做。
这个问题的出现归根结底是因为js是解释型语言,在任何时候都可以对类本身做出调整,也没有内置的访问权限控制,即使可以使用闭包伪造私有域,但无论如何都不能杜绝对一个类的prototype的访问。
无论如何刚才的继承写法都有点太low的感觉,要直接控制子类的prototype,如果有很多很多类,出了问题排查都不容易!有更好的写法么?
接下来的文章会提供一套js继承系统,探究一下js框架代码中,是如何处理继承这个问题的。敬请期待!
转载请注明出处:http://gagalulu.wang/blog/detail?id=7 您的支持是我最大的动力!
相关文章推荐
- JQuery1——基础($对象,选择器,对象转换)
- Android学习笔记(二九):嵌入浏览器
- Android java 与 javascript互访(相互调用)的方法例子
- JavaScript演示排序算法
- javascript实现10进制转为N进制数
- 2019年开发人员应该学习的8个JavaScript框架
- HTML中的script标签研究
- 对一个分号引发的错误研究
- 异步流程控制:7 行代码学会 co 模块
- ES6 走马观花(ECMAScript2015 新特性)
- JavaScript拆分字符串时产生空字符的原因
- Canvas 在高清屏下绘制图片变模糊的解决方法
- JavaScript 各种遍历方式详解
- call/apply/bind 的理解与实例分享
- 如何创建对象以及jQuery中创建对象的方式
- 数组方法汇总