但在 JavaScript 中,由于继承的两者都是对象,而 JavaScript 的对象又具有运行期动态添加属性等特性,所以,如果修改原型上的属性,是会同步到继承该原型的子对象上的。
function A() {} A.prototype.num = 1; var a = new A(); var b = new A(); a.num; //输出1 b.num; //输出1,因为都是继承的 A.prototype A.prototype.num = 5; a.num; //输出5,原型的属性动态的变化可同步到子对象上 b.__proto__.num = 0; a.num; //输出0,因为可通过b对象获取原型对象,对原型的操作会同步到子对象上以上代码,首先定义了一个构造函数A,通过它创建了两个新的子对象a,b,这两个子对象都继承自A.prototype,所以当访问 a.num 时会输出 1。
然后动态修改 A.prototype 对象的 num 属性,将其改成5,这时会发现,子对象 a 和 b 访问 num 时都输出 5 了,也就是说对原型对象的动态修改属性可同步到它的子对象上。
而子对象又可以通过 __proto__ 属性或者符合默认关系下 constructor.prototype 来获取原型对象,之后对原型对象的操作也可影响到所有继承该原型的子对象。
这点就是 JavaScript 与 Java 这种有类机制语言的很大不同之处。
另外,对原型对象的修改之所以可以同步到子对象上,其实是因为原型链的原理。a,b对象虽然继承自 A.prototype,但其实它们两内部中并没有 num 这个属性,而当访问 num 属性时,在它们内部没找到时,会去沿着原型链中寻找,所以原型对象的属性发生变化时才会影响到子对象。
清楚这点原理后,应该就能理解,有些文章说,原型对象的属性只有读操作会同步到子对象上,写操作无效的原因了吧。
看个例子:
function A() {} A.prototype.num = 1; var a = new A(); var b = new A(); a.num = 5; b.num; //输出1上面说过,虽然 a 对象继承自 A.prototype,但其实 a 对象内部并没有 num 属性,使用 a.num 时其实会去原型链上寻找这个 num 属性是否存在。
现在,执行了 a.num = 5,因为 a 对象内部没有这个 num 属性,所以这行代码作用等效于动态给 a 对象添加了 num 属性,那这个属性自然也就只属于 a 对象,自然不会对 b 对象造成任何影响,b.num 还是去b的原型链上寻找 num。
改变继承关系Java 中,类是继承结构一旦编写完毕,在运行期间是不可改变的了。
但在 JavaScript 中,由于对象的属性是可运行期间动态添加、修改的,所以在运行期间是可改变对象的继承结构的。
有两种不同的场景,一是修改构造函数的 prototype 属性,二是修改对象的 __proto__ 属性。
修改构造函数 prototype对象的创建大部分都是通过构造函数,所以,在构造函数创建这个对象时,它的继承关系就确定了。
看个例子:
var B = []; //定义一个数组对象 function A() {} //定义构造函数 var a = new A(); //创建一个对象,该对象继承自 A.prototype a.__proto__.constructor.name; //应该输出什么默认不手动破坏原型链的话,构造函数、原型两者间是相关关联的关系,所以通过实例对象 a 的原型 __proto__ 访问与它关联的构造函数,输出函数名,这里就应该是 “A”。
这也是之前讲过,可用来获取对象的标识—构造函数名的方法,但有前提,就是构造函数、原型、实例对象三者关系满足默认的关联关系。
那么,如果这个时候再手动修改 A 的 prototype 属性呢?
举个例子,在上面代码基础上,继续执行下述代码:
var C = A.prototype; //先将 A.prototype 保存下来 A.prototype = B; //手动修改A.prototype var b = new A(); b.__proto__.constructor.name; //应该输出什么 a.__proto__.constructor.name; //应该输出什么手动修改了构造函数的 prototype 属性,然后又新创建了 b 对象,那么此时 a 对象和 b 对象都是通过构造函数 A 创建的。
但 a 对象创建时是继承自 A.prototype,这是一个继承自 Object.prototype 的空对象,后续手动修改了构造函数 A 的 prototype,会让 a 对象的继承关系自动跟随着发生变化吗?
我们看一下输出,a 对象仍旧是之前的继承结构,它的原型链并没有因为构造函数的 prototype 发生变化而跟随着变化。