JavaScript六种非常经典的对象继承方式

85次阅读

共计 4830 个字符,预计需要花费 13 分钟才能阅读完成。

一、原型链继承
重点:利用原型让一个引用类型继承另外一个引用类型的属性和方法。构造函数,原型,实例之间的关系:每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};

function SubType(){
this.subproperty = false;
}
// 继承自 SuperType
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function (){
return this.subproperty;
};

var example = new SubType();
alert(example.getSuperValue());//true

使用原型创建对象会存在多个实例对引用类型的操作会被篡改的问题,在上面同样存在这个问题,如下:
function SuperType(){
this.colors = [“red”, “blue”, “green”];
}
function SubType(){}// 即使没有写,也不会影响结果

SubType.prototype = new SuperType();

var example1 = new SubType();
example1.colors.push(“black”);
alert(example1.colors); //”red,blue,green,black”

var example2 = new SubType();
alert(example.colors); //”red,blue,green,black”

两个实例对象 example1 和 example2 的 colors 属性指向相同,改变一个会影响另一个实例的属性。
缺点:①原型链继承多个实例的引用类型属性指向相同,一个实例修改了原型属性,另一个实例的原型属性也会被修改;②不能传递参数;③继承单一。
二、借用构造函数继承
重点:使用.call() 和.apply() 将父类构造函数引入子类函数,使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类。
function SuperType(name){
this.name = name;
this.colors = [“red”, “blue”, “green”];
}

function SubType(name, age){
// 继承自 SuperType
SuperType.call(this, name);
this.age = age;
}

var example1 = new SubType(“Mike”, 23);
example1.colors.push(“black”);
alert(example1.colors);//”red,blue,green,black”

var example2 = new SubType();
alert(example2.colors);//”red,blue,green”

alert(example1.name); // “Mike”
alert(example1.age); // 23

借用构造函数继承的重点就在于 SuperType.call(this, name),调用了 SuperType 构造函数,这样,SubType 的每个实例都会将 SuperType 中的属性复制一份。
缺点:①只能继承父类的实例属性和方法,不能继承原型属性 / 方法;②无法实现构造函数的复用,每个子类都有父类实例函数的副本,影响性能,代码会臃肿。
三、组合继承
重点:将原型链继承和构造函数继承这两种模式的优点组合在一起,通过调用父类构造,继承父类的属性并保留传参,然后通过将父类实例作为子类原型,实现函数复用。
其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。
function SuperType(name){
this.name = name;
this.colors = [“red”, “blue”, “green”];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};

function SubType(name, age){
// 继承属性
SuperType.call(this, name);
this.age = age;
}

// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};

var example1 = new SubType(“Mike”, 23);
example1.colors.push(“black”);
alert(example1.colors); //”red,blue,green,black”
example1.sayName(); //”Mike”;
example1.sayAge(); //23

var example2 = new SubType(“Jack”, 22);
alert(example2.colors); //”red,blue,green”
example2.sayName(); //”Jack”;
example2.sayAge(); //22
缺陷:父类中的实例属性和方法既存在于子类的实例中,又存在于子类的原型中,不过仅是内存占用,因此,在使用子类创建实例对象时,其原型中会存在两份相同的属性 / 方法。——- 这个方法是 javascript 中最常用的继承模式。
四、原型式继承
重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create() 就是这个原理, 直接将某个对象直接赋值给构造函数的原型。
function object(obj){
function O(){}
O.prototype = obj;
return new O();
}
object() 对传入其中的对象执行了一次浅复制,将 O 的原型直接指向传入的对象。
var person = {
name: “Mike”,
friends: [“Jack”, “Tom”, “Joes”]
};

var anotherPerson = object(person);
anotherPerson.name = “Greg”;
anotherPerson.friends.push(“Peter”);

var yetAnotherPerson = object(person);
yetAnotherPerson.name = “Linda”;
yetAnotherPerson.friends.push(“BoBo”);

alert(person.friends); //”Jack,Tom,Joes,Peter,BoBo”

ECMAScript5 通过新增 Object.create() 方法规范化了原型式继承,这个方法接收两个参数:一个用作新对象原型的对象和一个作为新对象定义额外属性的对象。
var person = {
name:”EvanChen”,
friends:[“Shelby”,”Court”,”Van”];
};
var anotherPerson = Object.create(person);
anotherPerson.name = “Greg”;
anotherPerson.friends.push(“Rob”);
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = “Linda”;
yetAnotherPerson.friends.push(“Barbie”);
console.log(person.friends);//”Shelby”,”Court”,”Van”,”Rob”,”Barbie”

缺点:①原型链继承多个实例的引用类型属性指向相同 (所有实例都会继承原型上的属性),存在篡改的可能;②无法传递参数, 无法实现复用。(新实例属性都是后面添加的)。
五、寄生式继承
重点:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回构造函数。(就像给原型式继承外面套了个壳子,然后 return 出来)
function createAnother(original){
varclone=object(original); // 过调用函数创建一个新对象
clone.sayHi = function(){ // 以某种方式增强这个对象
alert(“hi”);
};
return clone; // 返回对象
}
函数的主要作用是为构造函数新增属性和方法,以增强函数。
var person = {
name: “Nicholas”,
friends: [“Shelby”, “Court”, “Van”]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //”hi”
缺点:①原型链继承多个实例的引用类型属性指向相同,存在篡改的可能;②无法传递参数,没用到原型,无法复用。
六、寄生组合式继承
重点:通过借用构造函数传递参数和寄生模式实现继承属性,通过原型链的混成形式来继承方法,在函数中用 apply 或者 call 引入另一个构造函数,可传参。
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype); //Object.create 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}

// 父类初始化实例属性和原型属性
function SuperType(name){
this.name = name;
this.colors = [“red”, “blue”, “green”];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};

// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}

// 将父类原型指向子类
inheritPrototype(SubType, SuperType);

// 新增子类原型属性
SubType.prototype.sayAge = function(){
alert(this.age);
}

var example1 = new SubType(“abc”, 21);
var example2 = new SubType(“def”, 22);

example1.colors.push(“pink”); // [“red”, “blue”, “green”, “pink”]
example1.colors.push(“black”); // [“red”, “blue”, “green”, “black”]
寄生组合继承集合了前面几种继承优点,几乎避免了上面继承方式的所有缺陷,是执行效率最高也是应用面最广的。
缺点:实现的过程相对繁琐。
为什么要学习这些继承方式,明明可以直接继承为什么还要搞这么麻烦?主要是为了学习它们的思想,打下更好的基础,为以后阅读框架源码,或自己封装组件甚至框架大有益处。
时间有点匆忙,没有加上 ES6 的 extends,有空再补上。

正文完
 0