继承是面向对象编程中经常讨论的话题。面向对象语言支持两种继承:接口继承和实现继承。
- 接口继承:只继承方法的签名。
- 实现继承:继承实际的方法。
js中用到的是实现继承,主要是通过原型链实现。
原型链:【了解】
- 原型:所有的JavaScript对象至少继承于一个对象,被继承的对象被称为原型。
- 原型链:多个原型之间有继承关系,组成的链条叫做原型链。
- 构造函数、原型、实例的关系:每个构造函数都有一个原型对象
prototype
,原型对象上有一个属性指回构造函数constructor
,实例有一个内部指针指向原型__proto__
。
获取对象原型
方法一:Object.getPrototypeOf()
方法返回指定对象的原型。
方法二:实例化对象.__proto__
Object.getPrototypeOf(object)
- 参数
obj:要返回其原型的对象 - 返回值
给定对象的原型,如果没有继承属性,则返回null
设置或修改对象原型
方法一:Object.create()
创建对象的时候指定原型.
Object.create(proto, propertiesObject)
:
- 参数:
proto:新创建对象的原型对象
propertiesObject:可选 - 返回值
一个新对象,带着指定的原型对象的属性。
方法二:Objcet.setPrototypeOf()
Objcet.setPrototypeOf(obj, prototype)
- 参数:
obj:设置其原型对象
prototype:对象的新原型(对象或null) - 过程:
如果对象的原型被修改成不可扩展(通过Object.isExtensible()
)的,则抛出TypeError异常。
如果prototype新原型参数不是对象或努力了,则什么也不做。
否则,该方法修改对象的原型。
通过此方法设置原型比较消耗资源。
方法三:Reflect.setPrototypeOf()
Reflect.setPrototypeOf(target, prototype)
- 参数:
target:设置其原型对象
prototype:对象的新原型(对象或null) - 返回值:
返回一个boolean类型表明是否原型已经设置成功。 - 过程:
如果参数target不是Object,或者prototype既不是对象,也不是null,则抛出TypeError异常。
Object原型与属性相关的方法
方法 | 作用 | 参数 | 返回值 |
---|---|---|---|
obj.hasOwnProperty(prop) | 判断属性是否在某个对象自身上 {不含原型链} | prop:要检测的属性 | true:有 false:无 |
prop in obj | 判断属性是否在某个对象上 {含原型链} | true:有 false:无 |
|
Object.getPrototypeOf(obj) | 获取实例obj的原型对象 | obj:实例化对象 | 返回原型对象 |
prototypeObj.isPrototypeOf(obj) | 测试一个对象是否在另一个对象的原型链上 | object:在该对象的原型链上搜寻 | true:在 false:不 |
instanceof操作符 | 检测构造函数的prototype属性是否会出现在某个实例对象的原型链上 | 操作符,不是函数 | true:在 false:不在 |
obj.propertyIsEnumerable(prop) | 判断属性名是否可枚举 | prop需要测试的属性 | true:枚举 false:非枚举 |
原型链
function SuperType() {
this.property = true;
}
// 在原型上添加方法
SuperType.prototype.getProperty = function() {
return this.property;
}
function SubType() {
this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
let a = new SubType();
console.log(a.getProperty()); // true
- 默认原型:默认情况下,所有引用类型都继承自Object,这个是通过原型链实现的。
- 原型与继承的关系:
1、通过instanceof
操作符进行判断实例的原型链上是否有相应的构造函数。
2、调用原型中的提供的方法isPrototypeOf()
方法,判断该原型是否是此实例的原型。
3、操作原型时,必须在原型赋值之后。
原型和原型链的缺点:
1、原型或原型链中包含引用类型的值时会在全部实例中共享,任意一个实例操作共享属性时,会影响其他属性。
2、子类型实例化的时候,不能给父类传递参数
盗用构造函数【了解】
原理:在子类构造函数中使用apply()、call()调用父类的构造函数。解决原型链中的问题。
示例
function SuperType(name) {
this.name = name;
this.colors = ["blue", "yellow", "white"];
}
function SubType(name) {
SuperType.call(this, name)
this.age = 18;
}
let example1 = new SubType("张三");
example1.colors.push("red")
let example2 = new SubType("李四");
console.log(example1.name) // 张三
console.log(example1.colors) // ["blue", "yellow", "white", "red"]
console.log(example2.name) // 李四
console.log(example2.colors) // ["blue", "yellow", "white"]
SuperType.call(this, name)
盗用构造函数的实现。
盗用构造函数的缺点:
1、和构造函数类型,必须在构造函数中定义方法,且函数不能重用。
2、创建的对象没有原型链,子类也不能访问父类原型上定义的方法。
组合继承【佳】
思想:
将原型链和盗用构造函数两个的优点进行结合:使用原型链继承原型上的属性和方法,通过盗用构造函数继承实例属性。
示例
function SuperType(name) {
this.name = name;
this.colors = ["blue", "yellow", "white"];
}
SuperType.prototype.sayName = function() {
console.log(this.name)
}
function SubType(name, age) {
// 通过盗用改造函数继承属性
SuperType.call(this, name)
this.age = age;
}
// 通过原型链继承方法
SubType.prototype = new SuperType()
SubType.prototype.sayAge = function() {
console.log(this.age)
}
Object.defineProperty(SubType.prototype, "constructor", {
value: SubType,
enumerable: false,
})
let example1 = new SubType("张三", 18);
example1.colors.push("red")
console.log(example1.colors); // ["blue", "yellow", "white", "red"]
example1.sayName(); // 张三
example1.sayAge(); // 18
console.log(example1 instanceof SuperType) // true
let example2 = new SubType("李四", 19);
console.log(example2.colors); // ["blue", "yellow", "white"]
example2.sayName(); // 李四
example2.sayAge(); // 19
console.log(SuperType.prototype.isPrototypeOf(example2)) // true
组合继承的原型链如下:
组合继承的优点:
- 组合继承弥补了JavaScript中原型链和盗用构造函数的不足,是JavaScript中使用最多的继承模式。
- 组合继承保留了
instanceof
操作符和isPrototypeOf()
方法,识别合成对象的能力。
组合继承的缺点:效率的问题。父类的构造函数始终会调用两次:一次是创建子类原型时[原型链],一次是调用子类构造函数时[盗用构造函数]。
原型式继承【了解】
思想
创建临时的构造函数,把传入的对象赋值给临时构造函数的原型,然后返回这个临时构造函数创建的对象。此过程中相当于对传入的对象进行一次浅复制。
示例
function object(obj) {
function F() {
}
F.prototype = obj;
return new F();
}
let person = {
name: "张三",
age: 18,
friends: ["1", "2", "3", "4"]
}
let person2 = object(person);
person2.name = "李四";
person2.age = 19;
person.friends.push("5");
console.log(person2.friends) // ["1", "2", "3", "4", "5"]
- ES2015增加了
Object.create(原型对象[, 额外属性及其属性描述符])
此方法,将原型式继承概念化。
适用场景
1、不需要单独创建构造函数,但仍然需要在对象将共享信息。
缺点:与原型模式一样,无法传递参数;共享属性时,多个实例之间会受到影响。
寄生式继承【了解】
与原型式继承相似。
思想
寄生于构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
示例
function object(obj) {
function F() {
}
F.prototype = obj;
return new F();
}
function createAnother(original) {
let clone = object(original);
clone.sayHi = function() {
console.log("Hi")
}
return clone;
}
let person = {
name: "张三",
age: 18,
friends: ["1", "2", "3", "4"]
}
let person2 = createAnother(person);
person2.name = "李四";
person2.age = 19;
person.friends.push("5");
person2.sayHi(); // Hi
console.log(person2.friends) // ["1", "2", "3", "4", "5"]
适用场景
1、主要关注对象,不在乎类型和构造函数的场景。
缺点:
1、与原型模式一样,无法传递参数;共享属性时,多个实例之间会受到影响。
2、创建的函数难以复用。
寄生式组合继承 【最佳】
思路
解决组合模式中效率的问题,即父类的构造函数被调用了两次。
寄生式组合继承通过盗用构造函数继承属性,使用混合式原型链继承方法。
设置原型时,要对原型的constructor进行写回,同时保证constructor是不可枚举的【通过Object.defineProperty()
方法实现】。
实例
function SuperType(name) {
this.name = name;
this.colors = ["blue", "yellow", "white"];
}
SuperType.prototype.sayName = function() {
console.log(this.name)
}
function SubType(name, age) {
// 通过盗用构造函数继承属性
SuperType.call(this, name)
this.age = age;
}
function object(obj) {
function F() {
}
F.prototype = obj;
return new F();
}
function inhertPrototype(child, father) {
let proto = object(father.prototype);
Object.defineProperty(proto , "constructor", {
value: child,
enumerable: false,
});
child.prototype = proto ;
}
// 通过原型链继承方法
inhertPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
console.log(this.age)
}
let example1 = new SubType("张三", 18);
example1.colors.push("red")
console.log(example1.colors); // ["blue", "yellow", "white", "red"]
example1.sayName(); // 张三
example1.sayAge(); // 18
console.log(example1 instanceof SuperType) // true
let example2 = new SubType("李四", 19);
console.log(example2.colors); // ["blue", "yellow", "white"]
example2.sayName(); // 李四
example2.sayAge(); // 19
console.log(SuperType.prototype.isPrototypeOf(example2)) // true
寄生式组合继承只调用了一次SuperType的构造函数,避免了SubType.prototype中的不必要的也用不到的属性。
寄生式组合继承 VS 组合继承
寄生式组合继承。组合继承综合了。其中主要的区别就是寄生式继承和原型继承这两种方式。同时instanceof
操作符和isPrototypeOf()
方法正常有效。
寄生式组合继承 | 组合继承 | |
---|---|---|
思想 | 寄生式继承和盗用构造函数继承两种方式 | 原型继承和盗用构造函数继承两种方式 |
主要区别 | 直接到构造函数的原型上,将拿到的原型作为目标原型 | 通过构造函数创建一个对象,将这个对象作为目标原型 |
构造函数的使用 | 原型的构造函数使用了一次 1、盗用构造函数的时候 |
原型的构造函数使用了两次 1、盗用构造函数的时候 2、设置原型的时候 |
现象 | 原型上没有多余的、不用的属性 | 原型上有多余的属性 |
效率 | 高 | 低 |
类 | 更加符合类 | 稍微符合类 |