目录
1. 前言
2. 原型链
3. 继承方式
a. 原型链继承
b. 盗用构造函数(经典继承)
c. 组合继承(伪经典继承)
d. 原型式继承
e. 寄生式继承
f. 寄生式组合继承
JavaScript的面向对象世界里,是基于原型链继承的,比如调用对象instance的print方法。
如果了解数据结构的同学,立马知道这是一个单向链表结构。本文梳理一下原型链、构造函数、原型链继承知识点。
示例代码定义了构造函数Person(是一个函数对象),并使用new操作符示例化Person,得到示例对象instance。
function Person(name) {
this.name = name;
}
const instance = new Person('xiao ming');
原型链和构造函数的指向关系,如图所示:
对于实例化对象instance、函数对象Person、函数对象Function、函数对象Object,原型链的结构(指向关系)为:
实例化对象instance、函数对象Person、函数对象Function、函数对象Object,此四个对象均有一个指向构造函数的原型对象的隐式属性__proto__(PS:此处构造函数是指,实例化instance、Person、Function、Object这4个对象的各个构造函数)
和一个指向构造函数的属性constructor
函数对象Person、Function、Object同时也是作为构造函数的函数对象(简称构造函数对象),会比非构造函数对象多出一个属性:原型对象prototype,并且原型对象prototype的constructor属性指回构造函数对象。
通过修改构造函数的原型对象,将父构造函数的实例化对象赋值给prototype
function Animal() {
this.animalName = 'animal';
}
Animal.prototype.getAnimalName = function() {
return this.animalName;
}
function Cat() {
this.catName = 'mimi';
}
// 通过修改构造函数对象Cat的原型对象prototype(设置为Animal的一个示例对象),实现原型链继承
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// 并在原型对象prototype新增加一个方法getCatName方法
Cat.prototype.getCatName = function() {
return this.catName;
}
let cat = new Cat();
console.log(cat.getAnimalName());
构成的原型链:cat -> Cat.prototype(Animal的实例化对象) -> Animal.prototype -> Object.prototype -> null
如果一个实例对象的原型链中出现过相应的构造函数,使用instanceof运算符,会返回true。
存在两个问题:
将父构造函数当成普通函数执行,仅修改父构造函数this指向
function Animal() {
this.animalName = 'animal';
}
Animal.prototype.getAnimalName = function() {
return this.animalName;
}
function Cat() {
// 盗用构造函数:父构造函数属性不会被子构造函数实例化对象共享了,并且可以传参给父构造函数
Animal.call(this, arg1, arg2, ...);
this.catName = 'mimi';
}
// 并在原型对象prototype新增加一个方法getCatName方法
Cat.prototype.getCatName = function() {
return this.catName;
}
let cat = new Cat();
console.log(cat.getAnimalName());
构成的原型链:cat -> Cat.prototype -> Object.prototype -> null
存在问题:
组合原型链继承和盗用构造函数(经典继承)。
function Animal() {
this.animalName = 'animal';
}
Animal.prototype.getAnimalName = function() {
return this.animalName;
}
function Cat() {
// 盗用构造函数:父构造函数属性不会被子构造函数实例化对象共享了,并且可以传参给父构造函数
// 继承属性,第二次调用
Animal.call(this, arg1, arg2, ...);
this.catName = 'mimi';
}
// 通过修改构造函数对象Cat的原型对象prototype(设置为Animal的一个示例对象),实现原型链继承
// 继承方法,第一次调用
Cat.prototype = new Animal();
// 更改构造函数指向
Cat.prototype.constructor = Cat;
// 并在原型对象prototype新增加一个方法getCatName方法
Cat.prototype.getCatName = function() {
return this.catName;
}
let cat = new Cat();
console.log(cat.getAnimalName());
构成的原型链:cat(拥有了Animal的属性) -> Cat.prototype(Animal的实例化对象) -> Animal.prototype -> Object.prototype -> null
存在问题:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
let person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
let anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
let yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
构成的原型链:
anotherPerson -> peson(F.prototype) -> Object.prototype -> null
yetAnotherPerson -> peson(F.prototype) -> Object.prototype -> null
object生成的对象共享传入的对象的属性/方法
Object.create(o, {name: { value: 'gg' }})不传第二项参数时,就是原型式继承。
适用场景:不需要创建构造函数,但需要对象之间需要共享数据和方法
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function create(original) {
let clone = object(original);
// 比原型式继承多了给实例对象新增方法
clone.say = function() {
console.log('hello');
};
return clone;
}
let person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
let anotherPerson = create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
anotherPerson.say();
比原型式继承多了给实例对象新增方法。
“盗用构造函数”无法继承父构造函数的原型对象上的方法/属性,”组合式继承“为解决“盗用构造函数”的问题,调用了两次父构造函数,子构造函数的原型对象多出了父构造函数的属性。
“寄生组合式继承”避免使用父构造函数实例化一个对象作为子构造函数的原型,而是设置一个空的构造函数F,将父构造函数的原型对象赋值给空的构造函数F,并返回new F()实例化对象作为子构造函数的原型。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritProtype(Cat, Animal) {
Cat.prototype = object(Animal.prototype);
// 构造函数的原型对象的contructor属性,需要指回构造函数本身
Cat.prototype.constructor = Cat;
}
function Animal() {
this.animalName = 'animal';
}
Animal.prototype.getAnimalName = function() {
return this.animalName;
}
function Cat() {
// 盗用构造函数:父构造函数属性不会被子构造函数实例化对象共享了,并且可以传参给父构造函数
// 继承属性,第二次调用
Animal.call(this, arg1, arg2, ...);
this.catName = 'mimi';
}
inheritProtype(Cat, Animal);
// 并在原型对象prototype新增加一个方法getCatName方法
Cat.prototype.getCatName = function() {
return this.catName;
}
let cat = new Cat();
console.log(cat.getAnimalName());
原型链结构:cat -> Cat.prototype -> F.prototype(Animal.prototype) -> Object.prototype -> null
注:以上,如有不合理之处,还请帮忙指出,大家一起交流学习~