这是最后的最后了,我会顺便总结一下各种继承方式的学习和理解。(老板要求什么的,管他呢)
这是一种将原型链和借用构造函数的技术结合起来的一种继承模式。不是假合体,是真合体!
核心思想是:
很像之前说过的组合使用构造函数模式和原型模式。
// 父类构造函数
function Food(name) {
this.name = name;
this.colors = ["red", "blue"];
}
// 父类原型对象的方法
Food.prototype.sayName = function() {
console.log("我是" + this.name);
};
// 子类构造函数
function Fruit(name, place) {
// 在构造函数里面调用父类搞糟函数,实现属性继承
Food.call(this, name);
this.place = place;
}
// 将父类的实例赋值给子类的原型对象,实现方法继承
Fruit.prototype = new Food();
// 添加子类原型对象的方法
Fruit.prototype.sayPlace = function() {
console.log(this.place);
};
var food1 = new Fruit("苹果", "非洲");
food1.colors.push("black");
console.log(food1.colors); // 返回 [ 'red', 'blue', ' black' ]
food1.sayName(); // 返回 我是苹果
food1.sayPlace(); // 返回 非洲
var food2 = new Fruit("香蕉", "亚洲");
food2.colors.push("yellow");
console.log(food2.colors); // 返回 [ 'red', 'blue', 'yellow' ]
food2.sayName(); // 返回 我是香蕉
food2.sayPlace(); // 返回 亚洲
name
和 colors
)和超类构造函数的原型对象的方法( sayName
)都能够被继承,并且对于引用类型的值也不会出现相互影响的情况,而子类构造函数的属性(place)和子类构造函数的原型对象的方法( sayPlace
)也能够很好的使用,不会被覆盖,他们相互共享又相互独立。call
方式,将父类的属性放到子类的构造函数里面,也就是借用构造函数模式。constructor
属性,因为原型对象被重写了,constructor
就丢失了// 。。。。。。。。
// 子类构造函数
function Fruit(name, place) {
// 在构造函数里面调用父类搞糟函数,实现属性继承
Food.call(this, name); // 第二次调用父类构造函数
this.place = place;
}
// 将父类的实例赋值给子类的原型对象,实现方法继承
Fruit.prototype = new Food(); // 第一次调用父类构造函数
Fruit.prototype.constrcutor=Fruit;//因重写原型而失去constructor属性,所以要对constrcutor重新赋值
// 添加子类原型对象的方法
Fruit.prototype.sayPlace = function() {
console.log(this.place);
};
// 。。。。。。。
在一般情况下,这是我们在 javascript 程序开发设计中比较常用的继承模式了。
基于以上原因,我们需要引入寄生组合式继承来解决它的存在的问题,实现完美的继承。但是在了解它之前,需要先了解寄生式继承,而了解寄生式继承之前,需要了解原型式继承,他们是一个接一个的推导出来的。
核心思想是借助原型可以基于已有的对象创建新对象,同时不必因此创建自定义类型。
// 原型式继承的关键-复制
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var food1 = {
name: "苹果",
colors: ["red", "blue"]
};
// 继承
var food2 = object(food1);
food2.name = "香蕉";
food2.colors.push("black");
//。。。。。。无限增殖
console.log(food1.name); // 返回 苹果
console.log(food2.name); // 返回 香蕉
console.log(food1.colors); // 返回 [ 'red', 'blue', 'black' ]
console.log(food2.colors); // 返回 [ 'red', 'blue', 'black' ]
Object.create()
:Object.create()
方法会创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
。
Object.create()
是es5新增的,用来规范原型式继承。
如果单纯使用的话,效果跟之前的差别不大,参考下面例子:
var food1 = {
name: "苹果",
colors: ["red", "blue"]
};
var food2 = Object.create(food1);
food2.name = "香蕉";
food2.colors.push("black");
console.log(food1.name); // 返回 苹果
console.log(food2.name); // 返回 香蕉
console.log(food1.colors); // 返回 [ 'red', 'blue', 'black' ]
console.log(food2.colors); // 返回 [ 'red', 'blue', 'black' ]
如果注意使用它的第二个参数的话,差别就不一样了:
var food1 = {
name: "苹果",
colors: ["red", "blue"]
};
var food2 = Object.create(food1, {
name: { value: "香蕉" },
colors: { // !!!!!
value: ["red", "blue", "black"]
}
});
console.log(food1.name); // 返回 苹果
console.log(food2.name); // 返回 香江
console.log(food1.colors); // 返回 [ 'red', 'blue' ] !!!!!
console.log(food2.colors); // 返回 [ 'red', 'blue', 'black' ]
可以看到引用类型的数值不会被共享,实现了很好的继承效果。
出现这个情况主要是因为如果使用 push 的话,还是操作同一个内存指针,使用
Object.create
的话,会重新添加到新创建对象的可枚举属性,不是同一个内存指针了。
参考 mdn 里面的介绍,会发现一些更有价值的东西,可以用 Object.create
实现类式继承:
// Shape - 父类(superclass)
function Shape() {
this.x = 0;
this.y = 0;
}
// 父类的方法
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
// Rectangle - 子类(subclass)
function Rectangle() {
Shape.call(this); // call super constructor.
}
// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
var rect = new Rectangle();
console.log(rect instanceof Rectangle); // true
console.log(rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'
Object.create
会将参数里的对象添加到它返回的新对象的原型对象里面去,这样首先生成了一个新对象,并且该对象的原型对象是参数里的值,即Shape.prototype
,新对象是临时的,暂时看不到,这个临时的新对象里面就包含了父类原型对象。Object.create
返回的新对象放到子类的原型对象里面,这样子类就拥有了父类的原型对象,也就实现了方法的继承。constructor
,是为了重新指定子类的构造函数名字,这样子类实例对象就可以查看到他的构造函数是谁,证明是某个实例来自于哪一个构造函数,这样代码和结构都会清晰。call
实现。还有更屌炸飞的东西,如果你希望能继承到多个对象,则可以使用混入的方式。
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function() {
// do a thing
};
Object.assign
会把 OtherSuperClass
原型上的函数拷贝到 MyClass
原型上,使 MyClass
的所有实例都可用 OtherSuperClass
的方法。Object.assign
是在 ES2015
引入的,且可用 polyfilled
。要支持旧浏览器的话,可用使用jQuery.extend()
或者 _.assign()
。与时俱进,红宝书《javascript 高级程序设计第三版》 也并不是无敌的,当然,一下子知识量太大,我们吸收不了,所以这里不展开细说。
在引入寄生组合式继承之前,需要了解什么是寄生式继承。
寄生式继承的思路跟寄生构造函数模式和工厂模式很类似,核心思想是创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真得是它做了所有工作一样返回对象。
感觉像是原型式继承的升级版!
// 原型式继承的关键-复制
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function createFood(original) {
var clone = object(original);
clone.sayName = function(name) {
console.log(name);
};
return clone;
}
var food1 = {
name: "苹果"
};
var food2 = createFood(food1);
console.log(food2.name); // 返回苹果
food2.sayName("香蕉"); // 返回香蕉
这是一种比较简单的实现继承的方式,在不考虑自定义类型和构造函数的情况下,也算是一种有用的模式。
终于到了主角了。
寄生组合式继承的核心思想是:
好复杂的解释,先看看代码吧:
// object 函数可以用 Object.create 来代替。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
// 这里是关键
function inheritPrototype(subType, superType) {
// ①将超类原型放到一个临时的对象里面(创建超类型圆形的副本)
var prototype = object(superType.prototype);
// ②重新指定这个临时对象的constructor 为 子类构造函数
prototype.constructor = subType;
// ③将这个临时对象赋值给子类的原型对象
subType.prototype = prototype;
}
function Food(name) {
this.name = name;
this.colors = ["red", "blue"];
}
Food.prototype.sayName = function() {
console.log(this.name);
};
function Fruit(name, place) {
Food.call(this, name);
this.place = place;
}
inheritPrototype(Fruit, Food);
Fruit.prototype.sayPlace = function() {
console.log(this.place);
};
var food1 = new Fruit("苹果", "非洲");
var food2 = new Fruit("香蕉", "亚洲");
console.log(food1.sayName()); // 返回 苹果
console.log(food1.sayPlace()); // 返回 非洲
food1.colors.push("black");
console.log(food1.colors); // 返回 [ 'red', 'blue', 'black' ]
console.log(food2.colors); // 返回 [ 'red', 'blue' ]
console.log(food1 instanceof Fruit); // 返回 true
console.log(food1 instanceof Food); // 返回 true
console.log(Fruit.prototype.isPrototypeOf(food1)); // 返回 true
console.log(Food.prototype.isPrototypeOf(food1)); // 返回 true
object
函数可以用Object.create
来代替。
借助这个图理解一下,这种继承模式拆开来看就是寄生式(复制)+组合式(原型链+构造函数)
图片来自https://www.jianshu.com/p/0045cd01e0be
几乎涵盖了所有 javascript 的继承模式了:
图片来自:https://zhuanlan.zhihu.com/p/41656666
有几点是我觉得可以总结一下,前人栽树,后人乘凉: