什么是原型链?
原型链,简单理解就是原型组成的链,对象的__proto__是它的原型,而原型也是一个对象,也有__proto__属性,原型的__proto__又是原型的原型,就这样可以一直通过__proto_向上找,这就是原型链。
理解原型链只需要理解三个角色与三个等式
三个角色:
constructor 、prototype、实例
三个等式:
constructor.prototype == prototype
prototype.constructor == constructor
实例.__ proto __ == prototype
function B(){
}
let b = new B();
我们来看一下这段代码的原型链是怎样的?
我们可以看到__proto__的终点最终指向null;一切构造函数的原型都指向Function.prototype,包括Function自身。
继承
原型链继承
通过指定子类构造函数的原型为父类的实例实现继承。
function Animal() {
this.name = "animal";
this.friends =[];
}
Animal.prototype.addFriend=function (name){
this.friends.push(name);
}
Animal.prototype.sayHello = function (){
this.friends.forEach((item)=>{
console.log(item + "hello");
})
}
function Cat() {
this.type = "1";
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat; //把构造函数指向改回来
let cat = new Cat();
let cat2 = new Cat();
cat.addFriend("Bob");
cat.addFriend("Jack");
cat2.addFriend("Tom");
cat.sayHello(); //Bob hello Jack hello Tom hello
cat2.sayHello(); //Bob hello Jack hello Tom hello
缺点:
1、当父类中有引用类型的属性时,所有实例都会共享同一个属性,其中一个实例修改该属性,会影响其他实例。
2、在创建子类实例时,不能向父类构造函数传递参数。
3、子类构造函数指向是错误的,需要手动修改回来。
构造函数继承
在子类构造函数中指定上下文调用父类构造函数。相较于原型链继承,这种方式可以在子类构造函数中向父类构造函数传递参数。
function Animal(name) {
this.name = name;
}
Animal.prototype.sayName=function (){
console.log("My name is " + this.name);
}
function Dog(name) {
Animal.call(this,name);
}
let dog = new Dog("A");
console.log(dog.name);
dog.sayName(); //Uncaught TypeError: dog.sayName is not a function
缺点:
1、只能继承父类构造函数上的私有属性和方法,无法继承原型上的属性和方法。
2、子类实例上也会生成父类构造函数的私有方法的副本,无法实现函数复用。
组合继承
顾名思义,就是将原型链继承和构造函数继承组合在一起,发挥两者的长处。
function SuperType(name){
this.name = name;
this.color=["red","blue"];
}
SuperType.prototype.sayName = function (){
console.log("My name is " + 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 (){
console.log("My age is " + this.age);
}
var xm = new SubType("xiaoming",24);
xm.sayName();
xm.sayAge();
console.log(xm.color);
缺点:
1、会执行两次父类的构造函数
2、子类的原型对象上会有冗余的属性(如上代码中的name,color)
原型式继承
不使用构造函数,基于一个已有对象使用Object.create()指定已有对象为原型创建新对象。
var person = {
name:"Bob",
friends:["Jack","Tom"]
}
var otherPeron = Object.create(person);
otherPeron.name = "Jim";
otherPeron.friends.push("David");
console.log(person.friends);
缺点:
1、和原型链继承相同,当有需要继承的对象引用类型的属性时,所有实例都会共享同一个属性,其中一个实例修改
该属性,会影响其他实例。
寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
function createOther(o) {
var clone = Object(o);
clone.sayHi = function (){
console.log('Hi!');
}
return clone;
}
var person = {
name:"Bob",
friends:["Jack","Tom"]
}
var otherPerson = createOther(person);
otherPerson.name = "Jack";
console.log(otherPerson.friends);
otherPerson.sayHi();
缺点:
1、无法实现函数复用。
寄生组合式继承
通过构造函数来继承属性,通过原型链来继承方法。
function Person(name){
this.friends =[];
this.name = name;
}
Person.prototype.sayName = function (){
console.log("My name is " + this.name);
}
function Student(name) {
Person.call(this,name);//调用父类构造函数,继承属性
}
//改变子类的原型指向,继承父类方法
function changeProto(superType,subType){
var prototype = Object(superType.prototype);//复制一份父类原型
prototype.constructor = subType;//修正构造函数指向
subType.prototype = prototype;//指定子类原型
}
changeProto(Person,Student);
Student.prototype.sayScore = function (){
console.log(this.score);
}
var student1 = new Student("Bob");
student1.score = 95;
student1.sayName();
student1.sayScore();
在组合继承的基础加入了寄生式继承,不用再生成父类实例,而是直接使用父类原型对象继承父类方法,避免了调用
两次父类构造函数,在子类原型对象上生成冗余属性的问题,也能实现方法复用,子类实例自己管理一份属性,不会
互相影响,可以说是目前最优最完善的继承方式了。