构造函数、原型和实例之间的关系:每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个原型对象的指针。es6
原型链继承核心: 将父类的实例做为子类的原型。markdown
继承的本质就是复制,即重写原型对象,代之以一个新类型的实例,app
function Foo(name){
this.color = ['red','blue','black']
this.name = name
};
Foo.prototype.sayName = function () {
return ('my name is '+this.name)
}
function Car(age){
this.age = age
}
Car.prototype = new Foo('jack')
a = new Car(16)
b = new Car()
b.color.push('white') // 在Car的实例b的color属性上添加了一个white
console.log(a.name,a.age)
console.log(a.color,a.sayName())
复制代码
咱们在打印实例a的color属性意外的发现多出来一个白色,这是由于在原型链的继承中,引用类型会被全部的实例所共享,多个实例对引用类型的操做会被篡改。而不是单独属于本身的一份,dom
这也是原型链继承存在的问题:函数
问题1:原型中包含的引用类型属性将被全部实例共享;post
问题2:子类在实例化的时候不能给父类构造函数传参;性能
为此咱们想出来构造函数式继承方法优化
基本思想:this
借用构造函数的基本思想就是利用call或者apply把父类中经过this指定的属性和方法复制(借用)到子类建立的实例中。 由于this对象是在运行时基于函数的执行环境绑定的。也就是说,在全局中,this等于window,而当函数被做为某个对象的方法调用时,this等于那个对象。 call、apply 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。spa
不知道this的指向的话能够仔细看看这篇关于this的完整讲解
function SuperType(){
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.getColor = function () {
return this.colors
}
function SubType(){
// 继承了 SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
console.log(instance2.colors); //"red,blue,green"
instance2.getColor() // 报错 TypeError: instance2.getColor is not a function
复制代码
当咱们想要使用父类原型上的方法时报错,发现顺着原型链上去查找,找不到getColor这个方法, 这是由于子类的实例不能继承原型属性/方法
核心:使用父类的构造函数来加强子类实例,等因而复制父类的实例属性给子类(没用到原型)
缺点: 方法都在构造函数中定义, 只能继承父类的实例属性和方法,不能继承原型属性/方法, 没法实现函数复用,每一个子类都有父类实例函数的副本,影响性能
原理:使用原型链实现对原型属性和方法的继承,借用构造函数实现对实例属性的继承。 结合原型继承和构造函数的继承方法,对之进行优化,达到便可继承原型的方法,引用类型也不会被全部实例所共享。
function Person (name) {
this.name = name;
this.friends = ['小李','小红'];
};
Person.prototype.getName = function () {
return this.name;
};
function Parent (age) {
Person.call(this,'老明'); //第二次调用构造函数
this.age = age;
};
Parent.prototype = new Person('老明'); //这一步也很关键
var result = new Parent(24); // 第一次调用构造函数
console.log(result.name); //老明
result.friends.push("小智"); //
console.log(result.friends); //['小李','小红','小智']
console.log(result.getName()); //老明
console.log(result.age); //24
var result1 = new Parent(25); //经过借用构造函数都有本身的属性,经过原型享用公共的方法
console.log(result1.name); //老明
console.log(result1.friends); //['小李','小红']
复制代码
优势:
结合原型链和借用构造函数继承两种方法,取长补短。实现了函数复用,又可以保证每一个子类不会共享父类的引用类型属性。
缺点:
调用两次超类型构造函数:一次是在建立子类型原型的时候,另外一次是在子类型构造函数内部。
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype,{}); // 建立对象,建立父类原型的一个副本
prototype.constructor = subType; // 加强对象,弥补因重写原型而失去的默认的constructor 属性
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 instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);
instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]
复制代码
这个例子的高效率体如今它只调用了一次SuperType 构造函数,而且所以避免了在SubType.prototype 上建立没必要要的、多余的属性。于此同时,原型链还能保持不变; 所以,还可以正常使用instanceof 和isPrototypeOf() 这是最成熟的方法,也是如今库实现的方法
在 es6 中,官方给出了 class 关键字来实现面向对象风格的写法,但本质上是寄生组合式继承的语法糖。
class Person {
constructor(age) {
this.age_ = age;
}
sayAge() {
console.log(this.age_);
}
// 静态方法
static create() {
// 使用随机年龄建立并返回一个 Person 实例
return new Person(Math.floor(Math.random()*100));
}
}
// 继承普通构造函数
class Doctor extends Person {}
const doctor = new Doctor(32);
doctor.sayAge(); // 32
复制代码
1.ES5的继承实质上是先建立子类的实例对象,而后再将父类的方法添加到this上(Parent.apply(this)).
2.ES6的继承机制彻底不一样,实质上是先建立父类的实例对象this(因此必须先调用父类的super()方法),而后再用子类的构造函数修改this。
3.ES5的继承时经过原型或构造函数机制来实现。
4.ES6经过class关键字定义类,里面有构造方法,类之间经过extends关键字实现继承。