一、原型链
1、利用原型让一个引用类型继承另一个引用类型的属性和方法
每个实例都指向其构造函数的原型对象并继承其属性和方法,而一个原型也可能是另一个原型的实例
function SuperType () {
this.property = true
}
SuperType.prototype.getSuperValue = function () {
return this.property
}
function SubType () {
this.subproperty = false
}
SubType.prototype = new SuperType()//重写SubType的原型
SubType.prototype.getSubValue = function () {
return this.subproperty
}
const instance = new SubType()
console.log(instance.getSuperValue())//true
分析:instance是SubType函数的实例对象,指向SubType.prototype,继承了上面的属性和方法;SubType.prototype作为SuperType函数的实例对象,指向SuperType.prototype,继承了上面的属性和方法,因此instance也就继承了SuperType.prototype上的方法getSuperValue()
2、默认原型
所有对象的默认原型都是Object的实例,因此其指针__proto__都会指向Object.prototype,这也是原型链的顶端
console.log(Object.getPrototypeOf(SuperType.prototype) == Object.prototype)//true
console.log(Object.getPrototypeOf(SubType.prototype) == SuperType.prototype)//true
console.log(Object.getPrototypeOf(Object.prototype)) //null:Object.prototype-原型链的顶端,没有原型
3、给原型添加方法要在替换原型的语句之后
function SuperType () {
this.property = true
}
SuperType.prototype.getSuperValue = function () {
return this.property
}
function SubType () {
this.subproperty = false
}
SubType.prototype = new SuperType()
// 创建一个新方法
SubType.prototype.getSubValue = function () {
return this.subproperty
}
// 重写了原型对象上已有的方法,即屏蔽了原型上的方法
SubType.prototype.getSuperValue = function () {
return false
}
const instance = new SubType()
console.log(instance.getSuperValue())//false:调用的是实例方法
4、不使用字面量创建原型方法,使用字面量方式创建原型方法,相当于重写原型对象,原本的原型链已被切断
function SuperType () {
this.property = true
}
SuperType.prototype.getSuperValue = function () {
return this.property
}
function SubType () {
this.subproperty = false
}
SubType.prototype = new SuperType()
// 使用字面量方式创建原型方法
SubType.prototype = {
getSubValue: function () {
return this.subproperty
}
}
const instance = new SubType()
console.log(instance.getSuperValue())//Uncaught TypeError: instance.getSuperValue is not a function
5、原型链的问题
1)原本的实例属性变为了另一个对象的原型属性,继承自此原型属性的实例就可以改变其中的方法\引用类型值
function SuperType () {
this.colors = ['red', 'blue'] //引用类型
}
function SubType () {}
SubType.prototype = new SuperType()//原本SuperType对象的实例属性成为了SubType的原型属性,所有SubType的实例对象都可以改变并共享这些原型属性
const instance1 = new SubType()
const instance2 = new SubType()
instance1.colors.push('green')
console.log(instance1.colors)//["red", "blue", "green"]
console.log(instance2.colors)//["red", "blue", "green"]//instance2的colors属性也随之改变
2)无法向上层构造函数传递参数
二、借用构造函数(伪造对象/经典继承)
1、解决原型链的引用类型值属性共享的问题
function SuperType () {
this.colors = ['red', 'blue']
}
function SubType () {
SuperType.call(this)//调用了SuperType函数,传入自己的作用域
}
const instance1 = new SubType()
const instance2 = new SubType()
instance1.colors.push('green')
console.log(instance1.colors)//["red", "blue", "green"]
console.log(instance2.colors)//["red", "blue"]
分析:
1)原本SuperType()中this.colors中的this指向的是全局
2)SubType()调用SuperType()并通过call()传入自己的作用域,1)中的this也就指向SubType()
3)instance1、instance2作为SubType函数的实例,各自拥有SubType上的属性和方法,调用colors时,this指向的是实例自己,因此instance1修改colors属性,instance2不受影响
2、可传递参数
function SuperType (name) {
this.name = name
}
function SubType () {
SuperType.call(this, 'diana')//调用了SuperType函数,传入自己的作用域,并传入参数
this.age = 28
}
const instance1 = new SubType()
const instance2 = new SubType()
console.log(instance1.name)//diana
console.log(instance1.age)//28
3、借用构造函数的问题
借用构造函数,也存在构造函数模式的问题——函数复用
三、组合继承/伪经典继承
组合使用原型链继承和借用构造函数,使用原型链实现对原型属性和方法的继承,同时通过借用构造函数实现实例属性的分离
function SuperType (name) {
this.name = name
this.colors = ['red', 'blue']
}
SuperType.prototype.sayName = function () {
alert(this.name)
}
// 借用构造函数继承属性(不共享的)
function SubType (name, age) {
SuperType.call(this, name)
this.age = age
}
// 原型链继承公共方法
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType()//原本SubType.prototype的constructor指向SuperType.prototype,此处重写constructor属性指向
SubType.prototype.sayAge = function () {
alert(this.age)
}
const instance1 = new SubType('diana', 29)
instance1.colors.push('green')
console.log(instance1.colors)//["red", "blue", "green"]
instance1.sayName()//diana
instance1.sayAge()//29
const instance2 = new SubType('js', 0)
console.log(instance2.colors)//["red", "blue"]
instance2.sayName()//js
instance2.sayAge()//0
四、原型式继承
1、基于已有对象创建新对象,无需创建自定义类型
function object (o) {//传入一个对象参数
function F () {}//创建一个临时函数对象
F.prototype = o//临时函数对象的原型指向o
return new F()//返回这个对象的实例,也就是说返回的实例__proto__指向传入的参数对象,继承它的属性和方法
}
const Person = {
name: 'diana',
colors: ['red', 'blue']
}
const person1 = object(Person)
person1.name = 'a'
person1.colors.push('green')
console.log(person1.name)//a
console.log(person1.colors)//["red", "blue", "green"]
const person2 = object(Person)
person2.name = 'b'
person2.colors.push('black')
console.log(person2.name)//b
console.log(person2.colors)//["red", "blue", "green", "black"]
//但也存在原型链继承的问题
console.log(Person.name)//diana
console.log(Person.colors)//["red", "blue", "green", "black"]
2、ES5标准的Object.create()方法,接收2个参数:作为新对象的原型对象的对象,为新对象设置属性的对象
只传入第一个参数:
原型对象:
const Person = {
name: 'diana',
colors: ['red', 'blue']
}
只传入第一个参数:
const person1 = Object.create(Person)
person1.name = 'a'
person1.colors.push('green')
console.log(person1.name)//a
console.log(person1.colors)//["red", "blue", "green"]
console.log(Object.getOwnPropertyDescriptor(person1,'name').writable)//true
console.log(Object.getOwnPropertyDescriptor(Person,'name').writable)//true
const person2 = Object.create(Person)
person2.name = 'b'
person2.colors.push('black')
console.log(person2.name)//b
console.log(person2.colors)//["red", "blue", "green", "black"]
console.log(Person.name)//diana
console.log(Person.colors)//["red", "blue", "green", "black"]
再传入第二个参数:
const person3 = Object.create(Person, {
name: {
value: 'js'
},
age: {
value: 28
}
})
console.log(person3.name)//js
console.log(person3.age)//28
console.log(Person.name)//diana
console.log(Person.age)//undefined
console.log(Object.getOwnPropertyDescriptor(person3,'name').writable)//false:另外2个特性值也是false
console.log(Object.getOwnPropertyDescriptor(Person,'name').writable)//true:另外2个特性值也是true
注:使用字面量形式、Object.create()方式创建的对象,属性(如writable等)默认为true;当Object.create()传入第二个参数设置对象的属性时,默认为false
五、寄生式继承
创建一个用于封装继承过程的函数,最终返回对象
function object (o) {//传入一个对象参数
function F () {}//创建一个临时函数对象
F.prototype = o//临时函数对象的原型为o
return new F()//返回这个对象的实例,也就是说返回的实例__proto__指向传入的参数对象,继承它的属性和方法
}
function createAnother (original) {//创建一个函数封装继承
const clone = object (original)//clone继承自original
clone.sayHi = function () {//clone创建自己的方法sayHi()
alert('hi')
}
return clone
}
const person = {
name: 'diana',
colors: ['red', 'blue']
}
const anotherPerson = createAnother(person)
//anotherPerson继承person对象的属性和方法
console.log(anotherPerson.name)//diana
//此外还有一个sayHi方法
anotherPerson.sayHi()//hi
缺点:也存在构造函数模式的问题——函数复用
六、寄生组合式继承
1、常用的组合继承模式,会调用两次超类型函数,第二次调用会重写第一次时继承的超类型属性,解决这个问题的方法就是寄生组合式继承。
通过借用构造函数来继承属性,通过原型链的混成模式来继承方法
依旧使用原型式继承的函数:
function object (o) {//传入一个对象参数
function F () {}//创建一个临时函数对象
F.prototype = o//临时函数对象的原型为o
return new F()//返回这个对象的实例,也就是说返回的实例的原型对象是传入的对象o
}
原型链继承封装到函数内,后面调用此函数;使函数第一个参数对象作为第二个参数对象的实例,原型指向第二个参数的原型:
function inheritPrototype (subType, superType) {
const prototype = object (superType.prototype);//创建一个superType.prototype的实例
prototype.constructor = subType;//副本的constructor默认是指向superType的,这里重写指向subType
subType.prototype = prototype;//副本赋值给subType的原型
}
创建超类型和子类型函数对象:
function SuperType (name) {
this.name = name;
this.colors = ['red', 'blue'];
}
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);
};
const instance1 = new SubType('diana', 28)
instance1.sayName()//diana
instance1.sayAge()//28
2、优点:
- 子类型只调用了一次超类型构造函数,提高效率;
- 避免了在subType.prototype上创建不必要的多余的属性;
- 原型链能够保持不变;
- 能够正常使用instanceof和isPrototypeOf()。