之前提到的对象的继承中,我们提到的常用的继承方式,就是组合继承,组合继承中,对于原型链中属性和方法的继承使用的就是原型链继承的方法,而原型链继承是有几个常见的问题的,并且一个必须要注意的问题,就是原型链的断链问题。
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
属性相同,只是这个属性值,是有一个默认的取值的,并且定义两个方法,setAge
和getAge
方法,分别用于设置和获取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
更改),而setAge
和getAge
在原型链上,每一个实例都可以共享这两个方法,看起来一切都这么完美。
开开心心的测试一下吧:
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
中,有定义一些属性或者方法,这行代码之后,就不会存在之前的那些属性和方法了。
以前也是写过一篇说原型链断链的,现在看起来,当时只是说了一下,哪几种情况会导致原型链断链,可以点击查看:原型链断链的情况。
如果发现文中有问题,或者描述不当等情况,欢迎提出交流。
突然想明白了,上一条是
= sound;和.sound;出问题了。
OK
感觉通了。
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不存在。
//我都打错字吗?
能想通就好,这里的关键点在于:这是在赋值(而这个值是一个新的引用),而不是把一个引用赋值给另外一个引用。
好好理解赋值操作符其实可以解决很多地方的问题,比如这里的,再比如,原型链上,使用引用类型导致的问题,
再比如function fn(){},这种定义函数的方法,和var fn = function(){}的区别。
这几个问题的根本原因,都是因为,在赋值时,等号右侧,是一个新的引用类型,还是一个引用指针,指向一个已经存在的引用。
感觉说的有点小乱…