8.js的原型、原型链、new关键字、继承问题
原型
js
function Person() {}
- Person是一个构造函数,每个构造函数都会生成一个prototype属性,指向一个空对象,这个空对象就是原型。每一个实例对象都会从原型继承属性和方法。
- 原型对象中有一个属性constructor,它指向构造函数
显/隐式原型
- 显式原型:每个函数都有一个prototype,即显式原型
- 隐式原型:每个实例(new 构造函数)都有一个[[prototype]](_proto_),即隐式原型
- 隐式原型的值(_proto_)为对应构造函数显式原型(prototype)的值
原型链
js
function Person(){}
// 等价于
const Person = new Function()
Person.prototype
是一个对象,对象可以理解为Object的实例,所以prototype
的__proto__
属性指向Object.prototype
继承
继承的目的就是重复使用另外一个对象的属性和方法
原型链继承
让一个构造函数的原型是另一个类型的实例,那么这个构造函数new出来的实例就具有该实例的属性。 当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
js
function Parent() {
this.isshow = true
this.info = {
name: 'likewei',
age: 28
}
}
Parent.prototype.getInfo = function() {
console.log(this.info);
console.log(this.isShow);
}
function Child(){}
Child.prototype = new Parent()
let c = new Child();
c.getInfo()
问题
- 问题一: 原型中包含引用值时会在所有实例间共享js
function superType() { this.colors = ["red", "blue", "green"]; } function subType() {} subType.prototype = new superType(); let instance1 = new subType() instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black" let instance2 = new SubType(); console.log(instance2.colors); // "red,blue,green,black"
- 问题二: 子类型实例化的时候不能给父类型的构造函数传参
借用构造函数继承(对象伪装、经典继承)
为了解决原型包含引用值导致的继承问题,盗用构造函数(对象伪装、经典继承)开始流行。 基本思路是: 在子类构造函数中调用父类构造函数 因为函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()或call()方法以新创建的对象为上下文执行构造函数
js
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
// 继承 SuperType
SuperType.call(this);
}
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green"
问题
必须在构造函数中定义方法,通过盗用构造函数继承的方法本质上都变成了实例自己的方法,不是公共的方法,因此失去了复用性。
子类不能访问父类原型上的方法
jsfunction Person(eyes) { this.eyes = eyes this.getEyes = function() { return this.eyes } } Person.prototype.returnEyes = function() { return this.eyes } function YellowRace() { Person.call(this, 'yellow') } const hjy = new YellowRace() console.log(hjy.getEyes()) // yellow console.log(hjy.returnEyes()) // TypeError: hjy.returnEyes is not a function
组合继承
组合继承结合了原型链继承和盗用构造函数继承,综合了他们两者的优点,取其精华,去其糟粕
js
function Father() {
this.colors = ['white', 'black', 'blue']
}
Father.prototype.getColor = function() {
return this.colors
}
function Son() {
Father.call(this)
}
Son.prototype = new Father()
const s1 = new Son()
s1.colors.push('green')
console.log(s1.colors) // ['white', 'black', 'blue', 'green']
const s2 = new Son()
console.log(s2.colors) // ['white', 'black', 'blue']
s2.getColor() // ['white', 'black', 'blue']
问题
调用了两次构造函数,浪费性能
原型式继承 - 工厂模式(Object.create)
核心代码:
js
function object(f) {
function F {}
F.prototype = f
return new F()
}
上述代码相当于精简版Object.create
问题
- 原对象中引用类型的属性会被新对象共享
寄生式继承
在原型式继承的基础上增强对象(给对象挂载新属性、方法)
js
function extend(o) {
let clone = Object.create(o);
clone.age = 20
clone.sayHi = function(){
console.log('Hello')
}
return clone
}
问题
- 函数难重用
寄生组合式继承(最佳)
是组合继承(原型链、盗用构造函数)和寄生式继承的结合
解决了组合继承中,构造函数调用两次的问题(不通过调用父类构造函数给子类赋值(不使用Son.prototype = new Father()
),而使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型)
js
// 实现寄生式组合继承的核心代码,接受两个参数(子类/父类构造函数)
// 第一步: 创建父类原型的一个副本
// 第二步: 给返回的prototype对象设置constructor属性,解决重写原型导致默认constructor丢失的问题
// 第三步: 将新创建的对象赋值给子类型的原型
function inheritPrototype(subType, superType) {
let 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继承
本质是原型继承