原型式继承
原型式继承的object方法本质上是对参数对象的一个浅复制。
ECMAScript 5通过增加Object.create()方法将原型式继承的概念规范化了。这个方法接收两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个可选)。在只有一个参数时,Object.create()与这里的函数A方法效果相同。
function object(o){
function F(){};
F.prototype = o;
return new F();
}
var person = {
name:"zs",
friends:["ls","ww"]
};
var person1 = object(person);
person1.name = "xm";
person1.friends.push("xz");
var person2 = object(person);
person2.name = "ll";
person2.friends.push("lx");
console.log("person1:" + person1.name);
console.log("person2:" + person2.name)
console.log("person1 friends:" + person1.friends);
console.log("person2 friends:" + person2.friends);
console.log("all friends:" + person.friends);
优点:不需要单独创建构造函数。
缺点:子类创建实例时不能向父类传递参数,属性中包含的引用值始终会在相关对象间共享。
原型链继承
将父类的实例作为子类的原型
// 人类类型
function Person(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 学生类型
function Student(score) {
this.score = score;
}
// 老师类型
function Teacher(salary) {
this.salary = salary;
}
// 原型对象,可以将自己的属性和方法继承给将来的实例对象使用
Student.prototype = new Person("zs",18,"男");
Student.prototype.constructor = Student;
// 生成一个实例
var s1 = new Student(89);
var s2 = new Student(100);
console.dir(s1);
console.dir(s2);
优点:写法简单容易理解。
缺点:父类的引用属性会被子类实例共享,子类创建实例时不能向父类传递参数。
借用构造函数继承
使用call()或apply()方法将父类对象的构造函数绑定在子对象上。
// 人类类型
function Person(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 学生类型
function Student(name,age,sex,score) {
// 直接对父类型的构造函数进行一个普通调用
// Person 普通调用过程中,内部的 this 指向的是 window
// 可以通过 call 方法更改Person 内部的 this
Person.call(this,name,age,sex);
this.score = score;
}
// 老师类型
function Teacher(name,age,sex,salary) {
Person.call(this,name,age,sex);
this.salary = salary;
}
// 创建学生的实例对象
var s1 = new Student("zs",18,"男",56);
var s2 = new Student("ls",19,"男",93);
console.dir(s1);
console.dir(s2);
优点:解决了不能向父类传参的问题。
缺点:父类的方法子类不可见,不能复用。
组合继承
将原型链和借用构造函数的组合到一块,兼具了二者的优点。
// 组合继承:属性在构造函数内部继承,方法通过原型继承
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function () {
console.log("你好");
}
// 生成一个子类型
function Teacher(name,age,salary) {
// 继承父类的属性
Person.call(this,name,age);
this.salary = salary;
}
// 方法继承,通过原型对象继承
Teacher.prototype = new Person();
Teacher.prototype.constructor = Teacher;
// 生成老师的一个实例
var t1 = new Teacher("ww",28,10000);
console.dir(t1);
console.log(t1.name);
t1.sayHi();
优点:解决了原型链继承和借用构造函数继承的影响。
缺点:无论在什么情况下都会在创建子类原型和子类构造函数内部调用两次父类构造函数
寄生式继承
寄生式继承:寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
function object(o){
function F(){}
F.prototype = o
return new F()
}
function createAnother(o){
var clone = object(o)
clone.sayHi = function(){
console.log("hello")
}
return clone
}
var person = {
name: "zs",
friends: ["ls", "ww", "xm"]
};
var p = createAnother(person);
p.sayHi()
优点:写法简单,不需要单独创建构造函数。
缺点:通过寄生式继承给对象添加函数会导致函数难以重用。
寄生组合式继承
寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
function object(o){
function F(){}
F.prototype = o
return new F()
}
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}
function SuperType(name){
this.name = name
this.colors = ["red", "blue", "green"]
}
SuperType.prototype.sayName = function(){
console.log(this.name)
};
function SubType(name, age){
SuperType.call(this, name)
this.age = age
}
inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function(){
console.log(this.age)
}
优点:高效率只调用一次父构造函数,并且因此避免了在子原型上面创建不必要,多余的属性。与此同时,原型链还能保持不变;
缺点是:代码复杂
ES6 Class
本质上,ES6继承是一种语法糖。而ES6先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
class Person {
constructor (name, age) {
this.name = name;
this.age = age;
}
sayHi () {
console.log("hello")
}
}
class Student extends Person {
constructor (name,age,number) {
super(name,age)
this.number = number
}
hello () {
super.sayHi()
}
}
const s1 = new Student("zs",18,101)
console.log(s1.name);
console.log(s1.age);
console.log(s1.number);
s1.hello();
优点:语法简单易懂操作方便。
缺点:兼容问题,并不是所有浏览器都支持class关键字。