原型链断链的原因

之前提到的对象的继承中,我们提到的常用的继承方式,就是组合继承,组合继承中,对于原型链中属性和方法的继承使用的就是原型链继承的方法,而原型链继承是有几个常见的问题的,并且一个必须要注意的问题,就是原型链的断链问题。

1:原型链继承的三个常见问题

  • 原型链的共享特性
  • 原型链的属性值如果为引入类型
  • 原型链的断链

前两个问题,在对象的继承中,已经做了简单的说明,可以点击:对象继承的方法小结查看,这里不再多说,仅仅对原型链的断链进行说明,其实之前在CSDN的博客中,我曾经写过了一篇这样的关于原型链断链的文章,原文:原型链断链的情况,这里又正好写了对象的问题,就重新整理一下,放在这里了。

2:示例,说明一下原型链断链会出现的症状

直接上代码:


function A(name){
    this.name = name;
}

A.prototype.sayName = function(){
    return this.name;
}


我们最初的思想是,定义一个对象,该对象实例内部有一个name属性,这个name属性,在每个实例中是不同的,还想定义一个原型链中的方法sayName,这个方法会读取实例中的这个name的属性值。

测试一下吧:


var a = new A("zhang");    //实例化一个
console.log(a.name);       //zhang 
console.log(a.sayAge());   //zhang


功能完成的不错,挺好。

这个时候呢,因为需求变更,我需要添加一个age的属性,这个属性是和name属性相同,只是这个属性值,是有一个默认的取值的,并且定义两个方法,setAgegetAge方法,分别用于设置和获取age属性的属性值,我就很简洁的把代码做了如下的修改:


function A(){
    this.name = "zhang";
    this.age = 28;
}

A.prototype.sayName = function(){
    return this.name;
}
A.prototype = {
    setAge:function(age){
	this.age = age;
    },
    sayAge:function(){
	return this.age;
    }
};


看起来很不错,age属性在实例内部,实例化后,每个实例有自己特有的age属性值(这里会有个默认值,可以通过setAge更改),而setAgegetAge在原型链上,每一个实例都可以共享这两个方法,看起来一切都这么完美。

开开心心的测试一下吧:


var a = new A("zhang");
console.log(a.name);      //zhang
console.log(a.age);       //28,读取的默认值
a.setAge(22);             //重新设置age的属性值
console.log(a.sayAge());  //22  读取新的属性值
console.log(a.sayName()); //出错了,提示对象没有sayName方法


出错了,没有sayName方法,原型链断链了。这就是原型链断链会出现的症状,至于原因呢,下面试着进行说明。

3:原型链断链的原因

每一个继承自Object对象的对象,都是存在一个继承来的prototype属性,也就是我们说的原型链,这个属性的值,就是一个新的对象。

看下面的例子:


function A(){
    this.name = "zhang";
}
console.log(typeof A.prototype);                             //object 
console.log(Object.prototype.toString.call(A.prototype));    //[object Object]


从上面,我们可以看出,构造函数的prototype,其实质就是一个类似于对象字面量一样的对象。这个主要是从第二个Object.prototype.toString.call(A.prototype)的返回值,确定的,如果你对这个方法不是很了解,那么你可以自己试试,把输入的值,改成function对象,Array对象,RegExp对象等,你可以想到的所有的对象类型,试试结果是怎么显示的,这里不做解释。

记不记得我们ES5中新规定的一个创建对象的新方法,Object.create(),使用这个方法,我们可以创建一个不继承自Object对象的新对象。

看代码:


var A = Object.create(null,{
    name:{
	value:"ling"
    }
});
A.name = "zhang";
console.log(A.name);                                       //ling
console.log(typeof A.prototype);                           //undefined
console.log(Object.prototype.toString.call(A.prototype));  //[object Undefined]


从上面的代码中,你可以看出几个问题?

  • 1:我对name属性,进行了重新赋值,为什么读取的值,依旧是定义时的初始值。
  • 2:A对象,是没有prototype属性的,所以后面两个的值,都是undefined。
  • 3:添加的数值,为什么不能和对象字面量的写法一样,name:”ling”的写法不行吗?

A初始化之后,如果需要再次添加新的属性,我在上面代码的基础上,添加了以下代码:


for(i in A){
    console.log(i+"="+A[i]);   //这里并没有输出任何属性和属性值
}
delete A.name;
console.log(A.name);           //ling 这里依然可以读取到name的属性值,并没有被删除


  • 4:为什么使用for in没有读取到name相关的信息?
  • 5:为什么不能删除name属性?

主要的原因,因为objcet.create方法的第二个参数,它的格式是类似Object.defineProperty方法中,定义属性值的格式一样,可以参考:defineProperty。但是这里的一些默认值,和objcet.create是不同的,Object.defineProperty中,如果没有设置configurale,enumerable,writable的值,那么会被默认为true。即:可以删除,可以使用for in获取,可以更改。

但是这里呢,这里使用Object.create方法定义的时候,如果没有设置configurale,enumerable,writable的值,那么会被默认为false。即:不可以删除,不可以使用for in获取,不可以更改。如果想要了解更多更详细的,请点击:Object.create

这样就可以明白,为什么问题1,3,4,5的原因了吧。至于第二个问题,那是因为Object.create的第一个参数,是输入的NULL,表示该对象不继承任何其他的对象,是一个全新的对象。

前面这些,只是想要说明,对于继承了Object对象的所有对象,prototype都是一个纯正的对象。

说了这么多,好像扯远了,继续回到,原型链断链的根本原因。既然原型链的值,是一个对象,那么本质上,prototype只是保存了一个指向这个对象的一个指针,而如果我对这个原型链重新赋值,那么prototype保存的指针所指向的对象,还是原来的那个对象吗?

这个说起来好像有些模糊,我们就举一个我们很常用的例子吧:


A = {
    name:"zhang"
};
B = {
    name:"zhang"
};

console.log(A == B);   //false


我们定义完全相同的对象,它们是相同的吗?很明显吧,原型链的prototype也是这样的原因。

再看:


A = {
    name:"zhang"
};
A.age = 28;
console.log(A.name);         //zhang
console.log(A.age);          //28

A = {
    sex:"male",
    friend:"andy"
}
console.log(A.name);         //undefined
console.log(A.age);          //undefined
console.log(A.sex);          //male
console.log(A.friend);       //andy


看到这段代码的人,得是99.999%的人,都知道,那么既然这里,你能看到原因,那么原型链也是相同的原因,千万不要把原型链想的太复杂了。

4:总结

本来还想要举一些原型链断链的例子,但是说到这里,我觉得就没有必要了,一句话来总结出现断链的情况:对象的prototype被重新赋值(也就是赋值表达式),就会导致原型链断链。

一段代码来表示:obj.prototype = newObj;那么obj的原型链就会断了,如果之前prototype中,有定义一些属性或者方法,这行代码之后,就不会存在之前的那些属性和方法了。

以前也是写过一篇说原型链断链的,现在看起来,当时只是说了一下,哪几种情况会导致原型链断链,可以点击查看:原型链断链的情况

如果发现文中有问题,或者描述不当等情况,欢迎提出交流。

本文地址:http://www.zhangyunling.com/?p=61

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

原型链断链的原因》有3个想法

  1. 井中月说道:

    突然想明白了,上一条是
    = sound;和.sound;出问题了。
    OK
    感觉通了。

  2. 井中月说道:

    obj.prototype = newObj//–第x行–
    就是说,obj被重置了,而在第x行之前创建的obj实例就没了原型(obj1.0消失),在x行之后创建的obj实例都是obj2.0版本。

    //—–但是:

    ///////////////////////////////////////////////////////////////////////////////////////////
    var sound = {echo: function() { alert(“sound”); } };
    function Person() {this.name=”name_”;};

    var test_1 = new Person();//test_1先实例
    Person.prototype.sound;
    var test_2 = new Person();//test_2后实例

    var xxxxxx=Person.prototype;//—-迟了绑上去—-
    xxxxxx.yyyy=”yyyy_1″;

    alert(test_1.echo);//undefined
    alert(test_1.yyyy);//alert(“yyyy_”);
    alert(test_2.echo);//undefined
    alert(test_2.yyyy);//alert(“yyyy_”);
    //结果什么事 yyyy存在,而echo不存在。
    //我都打错字吗?

    1. yunling_zhang说道:

      能想通就好,这里的关键点在于:这是在赋值(而这个值是一个新的引用),而不是把一个引用赋值给另外一个引用。

      好好理解赋值操作符其实可以解决很多地方的问题,比如这里的,再比如,原型链上,使用引用类型导致的问题,

      再比如function fn(){},这种定义函数的方法,和var fn = function(){}的区别。

      这几个问题的根本原因,都是因为,在赋值时,等号右侧,是一个新的引用类型,还是一个引用指针,指向一个已经存在的引用。

      感觉说的有点小乱…