原型链
原型链是ECMAScript中重要的概念,理解原型链我们需要理解这三者之间的关系,即构造函数、构造函数的原型对象和调用构造函数生成的对象实例之间的关系;

如上图,我们可以看出一个构造函数有一个prototype属性指向它的原型对象,原型对象有一个constructor属性指向构造函数,构造函数的实例有一个__proto__属性指向原型对象;我们可以在控制台进行验证:

原型链的形成就是将一个对象构造函数的prototype赋值为需要继承对象的实例,所有对象最终继承Object;我们通过一个例子来具体说明一下:
function A () {};
A.prototype.sayhi = () => {
// ...
}
function B () {};
B.prototype = new A();
var b = new B();
b.sayhi();我们将A的实例赋值给B的原型B就将继承A原型上的所有属性和方法;因为所有对象都继承Object,所以A或者B可以使用Object提供的一些方法,如hasOwnProperty、toString等;我们可以画一个B继承A,A继承Object的关系图,如下:
由上,我们可以总结原型链继承的原理,让一个对象继承另一个对象的属性和方法,即让一个对象的原型作为另一个对象的实例,此时这个对象的原型就继承了另一个原型的属性和方法,当生成第一个对象实例时,这个实例对象就继承了其原型和另一个对象的属性和方法,举个栗子,我们要访问b对象的sayhi方法,现在b实例上找,没找到就在B.prototype上找,B.prototype是A的实例,A实例上也没有sayhi方法,就去A.prototype上找,找到了就返回或执行该方法,这种查找就是原型链查找,查找的这个链路就叫做原型链;b继承B.prototype,B.prototype继承A.prototype,A.prototype继承Object.prototype,这种继承就叫做原型链继承;
因为原型链的查找方式是依次网上查找,如果sayhi在b实例中找到了,就不会再往上查找了,这叫做属性屏蔽;
这里需要注意,我们可以往B.prototype上加属性或者方法,如:
B.prototype.dd = () => {console.log('123')}b.dd();// 12B.prototype.constructor === A; // true但是如果我们用字面量方式给B原型添加方法,就会断开继承:
B.prototype = {
dd () {
return "dd";
}
}
B.prototype.constructor === A; // false
B.prototype.constructor === Object; // false原型链跟上一篇讲的原型构造对象存在一样的缺点,即当对象继承的属性包括引用类型值时,此时所有的实例都会继承这个引用类型的属性,因此其中一个属性修改它那它的修改将会反射到所有的实例上,其次就是子类型实例不能向超类型构造函数传递参数。
借用构造函数
这种方法即在子类构造函数中调用超类型构造函数,通过call()或apply()来传递实例对象,如下:
function A () {
this.friends: ['liu', 'chen', 'lin']
}
function B () {
A.call(this);
}
var b1 = new B();
var b2 = new B();
b1.friends.push('zhang');
b2.friends; // "liu", "chen", "lin"从上面代码我们可以看出b1的操作并不会影响b2的friends,这是因为在B中传递的是不同的实例对象,而且我们也可以向超类型构造函数传递参数,如:
function A (name) {
this.name = name;
}
function B (age, name) {
A.call(this, name);
this.age = age;
}
var b = new B('leizi', 22);
b.name; // "leizi"
b.age; // 22借用构造函数其实就是调用超类型构造函数在子类型函数里生成超类型中有的属性,它解决了原型链继承的这两个问题,但是我们定义方法只能在函数内部定义,而每一个实例实现相同功能的函数其实又是不同的函数,这就失去了函数复用的意义。
原型链构造函数组合式继承(组合继承)
融合了原型链继承和构造函数继承,如下:
function A (name) {
this.name = name;
}
A.prototype.sayName = function () {
alert(this.name);
}
function B (age, name) {
A.call(this, name);
this.age = age;
}
B.prototype = new A();
var b = new B('leizi', 22);此时b实例就可以调用A原型上的sayName()方法了;这个方法看似很好,融合了原型链和构造函数继承的优点,但是,这个方法它调用了两次A构造函数,一次是new A()生成A的实例,一次是在B内部A.call()调用A;
原型式继承
function object (o) {
function F() {};
F.prototype = o;
return new F();
}
var o = {
name = "chenlei",
age = 22;
};
var f = new F();
f.name; //"chenlei"
f.age; // 22ECMAScript新增Object.create()方法来实现原型式继承,它接收两个参数,第一个为需要继承的基对象,第二个是要额外或者修改的参数,第二个参数格式与Object.defineProperties()方法的第二个参数格式一样,即:
var o = {
name = "chenlei",
age = 22;
};
var f = Object.create(o, {
name: {
value: "leizi"
}
});这种方式的继承跟原型链继承一样存在引用类型值时修改将会反射到所有继承的实例中。
ES6 class类的继承
extends
class A {};
class B extends A {};在ES5中我们知道不能给内置对象添加自定义方法,所以我们采用寄生式构造函数的方式来创建一个对象,它拥有自定义的方法和内置对象的所有方法,但在ES6的Class中我们可以直接让子类extends内置对象,如我们之前的例子:
function MyArray () {
var arr = new Array();
arr.push.apply(arr, arguments);
arr.toPipedString = function () {
return this.join('|');
};
return arr;
};
var myarr = new MyArray('red', 'green', 'black');
myarr.toPipedString(); // "red|green|black"这个例子是构造一个有特殊方法的数组,因为我们不能直接在Array上新增方法,我们用Class来改写:
class MyArray extends Array {
constructor () {
super();
this.arr = [...arguments];
}
toPipedString () {
return this.arr.join('|');
}
}
var myarr = new MyArray('red', 'green', 'black');
myarr.toPipedString(); // "red|green|black在class类中,super是指超类型,它有两种使用方式,如下:
class A {
constructor (x, y) {
this.x = x;
this.y = y;
}
add () {
console.log(this.x + this.y);
}
}
class B extends A {
constructor (x, y, z) {
super(x, y);
this.z = z;
this.p = super.add();
}
}
var b = new B(10, 12);
b.x; // 10
b.y; // 12
b.p; // 22如上,当super作为构造函数时指向的是类A的constructor,当super作为对象时,在普通方法中使用则是指向父类的原型,当在静态方法中使用时指向父类,即上面的super.add()实际上等于A.prototype.add();
ES6规定必须在子类的constructor中用一次super,且内部的this使用必须在super之后,比如这样将报错:
class B extends A {
constructor (x, y, z) {
this.z = z; //TypeError
super(x, y);
this.p = super.add();
}
}这是因为Class是先创建父类实例对象this,然后子类的构造函数再修改this,子类自身是没有this对象的,所以先于super()使用this会报错;ES5的继承机制是先创建子类型的this对象,再将父类属性和方法添加到子类型的this上;
判断一个类是否是另一个类的子类可以用Object.getPrototypeOf()方法:
Object.getPrototypeOf(B) === A; // true在class中有__proto__和prototype两条继承线,子类__proto__指向父类,prototype的__proto__指向父类的prototype:
B.__proto__ === A; //true
B.prototype.__proto__ === A.prototype; // true