- 经典继承或伪造对象(借用构造函数)
思想:通过使用 apply() 和 call() 方法在(将来)新创建的对象上执行构造函数,从而解决原型中包含引用类型值所带来问题。
我们实际上是在(未来将要)新创建的 Student实例的环境下调用了 Person构造函数。这样一来,就会在新 Student对象上执行 Person() 函数中定义的所有对象初始化代码。结果,Student的每个实例就都会具有自己的name属性的副本了。
function Person(){
this.name=["zw","zz","ww"];
}
function Student(){
//继承了Person
Person.call(this);
}
var instance1=new Student();
instance1.name.push("qq");
console.log(instance1.name);//["zw", "zz", "ww", "qq"]
var instance2=new Student();
console.log(instance2.name);//["zw", "zz", "ww"]
借用构造函数可以在子类型构造函数中向超类型构造函数传递参数:
function Person(name){
this.name=name;
}
function Student(){
//继承了Person
Person.call(this,"zw");
this.age=20;
}
var instance=new Student();
console.log(instance.name);//zw
console.log(instance.age);//20
但是借用构造函数有一个很大的缺点:就是解决不了函数复用。
2.组合继承(伪经典继承)
思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。
function Person(sex) {
this.sex = sex;
this.name = ["zw", "zz", "ww"];
}
Person.prototype.getSex=function(){
console.log(this.sex);
};
function Student(sex, age) {
//继承属性
Person.call(this,sex);
this.age = age;
}
//继承方法
Student.prototype=new Person();
Student.prototype.constructor = Student;
Student.prototype.getAge=function(){
console.log(this.age);
};
var instance1 = new Student("女",20);
instance1.name.push("qq");
console.log(instance1.name); //["zw", "zz", "ww", "qq"]
instance1.getSex();//女
instance1.getAge();//20
var instance2 = new Student("男",23);
console.log(instance2.name);//["zw", "zz", "ww"]
instance2.getSex();//男
instance2.getAge();//23
Person构造函数定义了两个属性:sex和name 。Person的原型定义了一个方法getSex。Student构造函数在调用Person狗咱函数是传入了sex参数,然后又定义了它自己的属性age。然后,将Person的实例复制给Student的原型,然后又在该新原型上定义了方法getAge()。这样一来,就可以让两个不同的student实例既分别拥有自己属性--包括name属性,又可以使用相同的方法了。
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为 JavaScript 中最常用的继
承模式。而且, instanceof 和 isPrototypeOf() 也能够用于识别基于组合继承创建的对象。
3.原型式继承
思想:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
object()函数
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
在 object() 函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var student = {
name: "zw",
course: ["软件工程", "计算机网络"]
};
var anotherStudent = object(student);
anotherStudent.name = "zz";
anotherStudent.course.push("JavaScript程序设计");
console.log(anotherStudent.course);//["软件工程", "计算机网络", "JavaScript程序设计"]
var otheranotherStudent = object(student);
otheranotherStudent.name = "ww";
otheranotherStudent.course.push("C语言程序设计");
console.log(otheranotherStudent.course);//["软件工程", "计算机网络", "JavaScript程序设计", "C语言程序设计"]
console.log(student.course);// ["软件工程", "计算机网络", "JavaScript程序设计", "C语言程序设计"]
ES5中新增了Object.create()方法规范了原型式继承。这个方法接受两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与object方法的行为相同。
var student = {
name: "zw",
course: ["软件工程", "计算机网络"]
};
var anotherStudent = Object.create(student);
anotherStudent.name = "zz";
anotherStudent.course.push("JavaScript程序设计");
console.log(anotherStudent.course);//["软件工程", "计算机网络", "JavaScript程序设计"]
var otheranotherStudent = Object.create(student);
otheranotherStudent.name = "ww";
otheranotherStudent.course.push("C语言程序设计");
console.log(otheranotherStudent.course);//["软件工程", "计算机网络", "JavaScript程序设计", "C语言程序设计"]
console.log(student.course);// ["软件工程", "计算机网络", "JavaScript程序设计", "C语言程序设计"]
Object.create() 方法的第二个参数与 Object.defineProperties() 方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。
var student = {
name: "zw",
course: ["软件工程", "计算机网络"]
};
var anotherStudent = Object.create(student,{name:{value:"zz"}});
console.log(anotherStudent.name);//zz
原型式继承中包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。
4.寄生式继承
思路:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function createAnother(original) {
var clone = object(original);
clone.sayHi = function () {
console.log("hello");
};
return clone;
}
var student = {
name: "zw",
course: ["软件工程", "计算机网络"]
};
var anotherStudent = createAnother(student);
anotherStudent.sayHi(); //hello
console.log(anotherStudent.name); //zw
这个例子中的代码基于student返回了一个新对象—— anotherStudent。新对象不仅具有student的所有属性和方法,而且还有自己的 sayHi() 方法。
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点与构造函数模式类似。
5.寄生组合式继承
组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。没错,子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。再来看一看下面组合继承的例子。
function Person(sex) {
this.sex = sex;
this.name = ["zw", "zz", "ww"];
}
Person.prototype.getSex = function () {
console.log(this.sex);
};
function Student(sex, age) {
//继承属性
Person.call(this, sex);//第二次调用Person()
this.age = age;
}
//继承方法
Student.prototype = new Person();//第一次调用Person()
Student.prototype.constructor = Student;
Student.prototype.getAge = function () {
console.log(this.age);
};
在第一次调用Person构造函数时,Student.prototype会得到两个属性:name和sex;它们都是Person的实例属性,只不过现在位于Student的原型中。当调用Student构造函数时,又会调用一次Person构造函数,这一次有着新对象上创建了实例属性name和sex。于是,这两个属性就屏蔽了原型种的两个同名属性。
寄生组合式继承思想:即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
寄生组合式继承的基本模式如下所示。
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
这个示例中的 inheritPrototype() 函数实现了寄生组合式继承的最简单形式。这个函数接收两个参数:子类型构造函数和超类型构造函数。在函数内部,第一步是创建超类型原型的一个副本。第二步是为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性。最后一步,将新创建的对象(即副本)赋值给子类型的原型。
function Person(sex) {
this.sex = sex;
this.name = ["zw", "zz", "ww"];
}
Person.prototype.getSex = function () {
console.log(this.sex);
};
function Student(sex, age) {
//继承属性
Person.call(this, sex);
this.age = age;
};
//继承方法
inheritPrototype(Student, Person);
Student.prototype.getAge = function () {
console.log(this.age);
};
这个例子的高效率体现在它只调用了一次Person构造函数,并且因此避免了在Student.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和 isPrototypeOf() 。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。