老生常谈:原型与原型链
首先,在 js 中每个对象都含有原型的引用,每个对象的原型也可以拥有一个原型,以此类推便形成了原型链。查找特定属性将会被委托到整个原型链上,当没有更多原型可以查找时,才会停止查找。
因此原型链在 ECMAScript 规范中作为实现继承的主要方式。但这种方式缺陷也十分明显:
- 实例共享引用类型,易造成使用修改的混乱
- 创建子类时无法传递参数。
使用构造函数
function Foo () {};
Foo.prototype.bar = function() {
return true;
};
const foo = new Foo();
复制代码
使用 new 操作符调用该函数,便是作为构造器进行调用,创建了新的分配对象并将及设置为函数的上下文(可通过 this 访问)new 返回的结果是这个新对象的引用。
缺陷
- 所有方法都在构造函数中定义,无法做到函数复用,且使用超类定义方法时,该方法对子类也不可见。
原型链与构造函数组合继承
主要思路:通过原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,请看以下代码:
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
console.log(this.name)
}
function SuperType(name, age) {
// 继承属性
SuperType.call(this, name);
// 先继承,后定义新的自定义属性
this.age = age
}
// 继承方法
SuperType.prototype = new SuperType();
// 先继承,后定义新的自定义属性
Object.defineProperty(
SubType.prototype,
"constructor", {
enumerable: false,
value: SubType
});
SubType.prototype.sayAge = function(){
console.log(this.age);
}
var instance1 = new SubType("james",9);
instance1.colors.push("black");
console.log(instance1.colors);
instance1.sayName();
instance1.sayAge();
复制代码
优点
- instanceOf() / isPrototypeOf() 可识别基于组合继承创建的对象。
缺点
- 调用两次超类构造函数:①创建子类原型时,②子类构造函数内部。子类最终包含超类对象的全部实例属性。
原型式继承
在不创建自定义类型的情况下,借助原型链,基于已有对象创建新对象:传入一个对象,返回一个原型对象为该对象的新对象。(so many 对象可你还是个单身狗。)
原理代码
function object(o) {
function F () {};
F.prototype = o;
return new F();
}
复制代码
实际可通过 Object.create() 方法规范原型式继承。
var person1 = {
name: 'a',
friends: ['b', 'c'],
}
var person2 = Object.create(person1, {
name: {
value: 'Luuu'
}
})
person2.friends.push('d')
console.log(person2.name) // luuu
var person3 = Object.create(person1)
person3.name = 'xx'
person3.friends.push('e')
console.log(person3.friends) // b, c, d, e
复制代码
优点
- 可基于一个对象实现继承,不必创建构造函数
缺点
- 与原型链缺点一致,所有实例会共享对象引用中属性值。
看到这里,已完成 ⅔ 的阅读。
寄生式继承
实现思路与工厂设计模式类似,先创建一个仅用于封装继承过程的函数,在该函数内部用某种方式增强对象,并返回。相当于原型式继承寄生于函数中,故如此命名。
function object(o) {
function F () {}
F.prototype = o
return new F()
}
function createFoo(o) {
// 通过调用函数创建一个新对象
var clone = object(o)
// 用某种方式增强对象
clone.hello = function () {
alert('hi!')
}
// 返回该对象
return clone
}
复制代码
寄生组合式继承
通过借用构造函数来继承属性;
通过原型链的形式继承方法;
思路是:为了指定子类原型,而调用超类构造函数。使用寄生式继承来获得超类原型,再讲结果指定给子类原型。
function object(o){
function F(){}
F.prototype = o;
return new F();
}
function intProto(sub, super){
//创建对象
var proto = object(super.prototype);
//增强对象
proto.constructor = sub;
//指定对象
sub.prototype = proto;
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name, age){
// 继承属性
SuperType.call(this, name);
this.age = age;
}
intProto(SubType, SuperType);
// 继承原型方法
SubType.prototype.sayAge = function(){
alert(this.age);
};
复制代码
使用关键字 class
ES6 引入的关键字 class,虽然提供了更优雅的实现继承的方式,本质仍是基于原型的实现的语法糖。
class Foo {
constructor(name) {
this.name = name;
}
swingSord() {
return true;
}
}
var foo = new Foo('luuu')