前端入门13-JavaScript进阶之原型 (2)

而更多的时候,我们需要借助原型来让对象继承一些公有行为,有两种做法,一种是通过直接在原型对象上动态添加相关属性,这种方式不破坏原型链,比较推荐。

还有一种,定义一个新的原型对象,然后重新赋值构造函数的 prototype 属性值,将它指向新的原型对象。但这种方式会破坏默认的原型链,同时也会破坏构造函数、原型、实例化对象三者间的默认关联关系。

举个例子:

function A(){} //定义构造函数A A.prototype.c = 1; var b = new A(); //通过构造函数创建对象b

通过构造函数创建一个新对象b,且在构造函数的 prototype 上手动添加新的属性c,会被 b 继承,由于这种方式是没有破坏原型链的,所以三者间关系如下:

构造函数示例

b.__proto__ 表示 b 的原型,原型对象的 constructor 属性指向构造函数 A,name 是函数对象的属性,用于输出函数名。

而且对象 b 由于继承自原型 A.prototype,所以也继承它的 constructor 属性,所以也指向构造函数 A。

此时对象 b 的继承关系:b -> {} -> Object.prototype

以上是默认的不破坏原型链下三者的关系,但如果手动破坏了原型链呢:

function A(){} //定义构造函数A A.prototype.c = 1; var a = []; //创建数组对象a a.c = 0; A.prototype = a; //手动修改构造函数A的prototype,让其指向 a var b = new A(); //通过构造函数创建对象b,b继承自原型a

上面的代码手动修改了 A.prototype 的属性值,让 b 是继承自手动创建的对象 a,所以这里就破坏了默认的原型链,同时,三者间的关系也被破坏了:

修改原型示例

首先,c 属性验证了 b 是继承自对象 a了。

而我们说过,b.__proto__ 指向 b 的原型,在这里,b 的原型就是对象 a 了。而对象 a 是手动创建的,所以它的 constructor 属性是继承自它的原型对象。数组直接量创建的数组对象,本质上是通过 new Array(),所以a的构造函数是 Array(),对象 a 继承自 Array.prototype。

对于对象 a,我们创建它的方式并没有手动去修改它的原型链,所以按默认的三者间的关系,Array.prototype 的 constructor 属性指向构造函数 Array(),这就是为什么 b.__proto__.constructor.name 的值会是 Array 了。

而,对象 b 继承自对象 a,所以 b.constructor 的值也才会是 Array。

此时,对象 b 的继承关系: b-> a -> Array.prototype -> Object.prototype

所以,在这个例子中,虽然对象 b 是从构造函数 A 创建的,但它的 constructor 其实并不指向 A,这点也可以稍微说明,构造函数的作用其实更类似于作为第三方协调原型和实例对象两者的角色。

通常是不建议通过这种方式来实现继承,因为这样会破坏默认的三者间的联系,除非手动修复,手动对 a 的 constructor 属性赋值为 A,这样可以手动修复三者间默认的关联。

来稍微小结一下

因为原型本质上也是对象,所以它也具有对象的特性,同时它也有自己的一些特性,总结下:

所有的引用类型(数组、对象、函数),都具有对象特性,都可以自由扩展属性,null除外。

所有的引用类型(数组、对象、函数),都有一个 __proto__ 属性,属性值的数据类型是对象,含义是隐式原型,指向这个对象的原型。

所有的函数(不包括数组、对象),都有一个 prototype 属性,属性值的数据类型是对象,含义是显式原型。因为函数都可以当做构造函数来使用,当被用于构造函数创建新对象时,新对象的原型就是指向构造函数的 prototype 值。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxfpy.html