使用原型模式时,当给实例对象设置自己专属的属性的时候,该实例对象会忽略原型链中的该属性。但当原型链中的属性是引用类型值的时候,操作不当有可能会直接修改原型对象的属性!这会影响到所有使用该原型对象的实例对象!
大部分情况下,实例对象的多数方法是共有的,多数属性是私有的,所以属性在构造函数中设置,方法在原型中设置是合适的,构造函数与原型结合使用是通常的做法。
还有一些方法,无非是工厂模式与构造函数与原型模式的互相结合,在生成过程和 this 指向上做一些小变化。
class 方式:
见下面 ES6 class 部分,只是一个语法糖,本质上和构造函数并没有什么区别,但是继承的方式有一些区别。
proto与prototype
这两个到底是什么关系?搞清楚 实例对象 构造函数 原型对象 的三角关系,这两个属性的用法就自然清晰了,顺便说下 constructor。
构造函数创建的实例对象的 constructor 指向该构造函数(但实际上 constructor 是对应的原型对象上的一个属性!所以实例对象的 constructor 是继承来的,这一点要注意,如果利用原型链继承,constructor 将有可能指向原型对象的构造函数甚至更上层的构造函数,其他重写构造函数 prototype 的行为也会造成 constructor 指向问题,都需要重设 constructor),构造函数的 prototype 指向对应的原型对象,实例对象的 __proto__ 指对应的原型对象,__proto__是浏览器的实现,并没有出现在标准中,可以用 constructor.prototype 代替。考虑到 Object.create() 创建的对象,更安全的方法是 Object.getPrototpyeOf() 传入需要获取原型对象的实例对象。
我自己都感觉说的有点乱,但是他们就是这样的,上一张图,看看能不能帮你更深刻理解这三者关系。
继承与原型链
当访问一个对象的属性时,如果在对象本身找不到,就会去搜索对象的原型,原型的原型,知道原型链的尽头 null,那原型链是怎么链起来的?
把 实例对象 构造函数 原型对象 视为一个小组,上面说了三者互相之间的关系,构造函数是函数,可实例对象和原型对象可都是普通对象啊,这就出现了这样的情况:
这个小组的原型对象,等于另一个小组实例对象,而此小组的原型对象又可能是其他小组的实例对象,这样一个个的小组不就连接起来了么。举个例子:
function Super(){ this.val = 1; this.arr = [1]; } function Sub(){ // ... } Sub.prototype = new Super();
Sub 是一个小组 Super 是一个小组,Sub 的原型对象链接到了 Super 的实例对象。
基本上所有对象顺着原型链爬到头都是 Object.prototype , 而 Object.prototype 就没有原型对象,原型链就走到头了。
判断构造函数和原型对象是否存在于实例对象的原型链中:
实例对象 instanceof 构造函数,返回一个布尔值,原型对象.isPrototypeOf(实例对象),返回一个布尔值。
上面是最简单的继承方式了,但是有两个致命缺点:
所有 Sub 的实例对象都继承自同一个 Super 的实例对象,我想传参数到 Super 怎么办?
如果 Super 里有引用类型的值,比如上面例子中我给 Sub 的实例对象中的 arr 属性 push 一个值,岂不是牵一发动全身?
下面说一种最常用的组合继承模式,先举个例子:
function Super(value){ // 只在此处声明基本属性和引用属性 this.val = value; this.arr = [1]; } // 在此处声明函数 Super.prototype.fun1 = function(){}; Super.prototype.fun2 = function(){}; //Super.prototype.fun3... function Sub(value){ Super.call(this,value); // 核心 // ... } Sub.prototype = new Super(); // 核心
过程是这样的,在简单的原型链继承的基础上, Sub 的构造函数里运行 Super ,从而给 Sub 的每一个实例对象一份单独的属性,解决了上面两个问题,可以给 Super 传参数了,而且因为是独立的属性,不会因为误操作引用类型值而影响其他实例了。不过还有个小缺点: Sub 中调用的 Super 给每个 Sub 的实例对象一套新的属性,覆盖了继承的 Super 实例对象的属性,那被覆盖的的那套属性不就浪费了?岂不是白继承了?最严重的问题是 Super 被执行了两次,这不能忍(其实也没多大问题)。下面进行一下优化,把上面例子最后一行替换为:
Sub.prototype = Object.create(Super.prototype); // Object.create() 给原型链上添加一环,否则 Sub 和 Super 的原型就重叠了。 Sub.prototype.constructor = Sub;
到此为止,继承非常完美。
其他还有各路继承方式无非是在 简单原型链继承 --> 优化的组合继承 路程之间的一些思路或者封装。
通过 class 继承的方式:
通过 class 实现继承的过程与 ES5 完全相反,详细见下面 ES6 class的继承 部分。
对象的深度克隆
JavaScript的基础类型是值传递,而对象是引用传递,这导致一个问题:
克隆一个基础类型的变量的时候,克隆出来的的变量是和旧的变量完全独立的,只是值相同而已。