不要由于没有掌声,就放弃了最初的理想
javascript
软件中的继承是对真实世界继承的一种抽象。在面向对象语言中,提到继承,咱们先引出 类 这个概念。java
类 是一种自定义的数据类型,每一个类能够包含一组数据类型和操做数据的方法。字符串、数字就是数据类型,固然数据类型有不少,咱们能够有不一样的组合方式,这样组合出来的复杂数据类型,咱们就称之为类,一般咱们还会向里面添加操做数据的方法。安全
面向对象语言中的 继承 ,本质上就是 复制 。子类将父类复制一份,那么父类上有的属性、方法,在子类上也就有了,咱们就说子类 继承 了父类。闭包
很惋惜,在Javascript中并无 类 这个概念,在这里万物皆对象,因此天然不存在 类复制 这一说法。那Javascript中的 继承 是怎样作到的呢?答案是 复制对象 和 委托关联 。固然最好的实现方式是 委托关联,就是利用原型链机制来达到所谓的 继承 。简单理解就是,有一个公有的对象,它上面存有属性和操做方法,别的对象均可以去它上面拿到想拿的东西,它在这里共享,这种模式就是Javascript中的 继承 本质。ide
固然上面只是简单分析,下面深刻原理。不过在此以前,由于本篇是讲 继承,咱们顺便也讲讲对应面向对象语言另外两大特征:封装 和 多态 在Javascript中的实现方式。仍是按 封装、继承、多态 的顺序讲吧,下面就一块儿进入丛林探险。 函数
顾名思义,封装 就是将数据密封起来,让外界访问不到,而后咱们对外只提供操做数据的方法。封装 的目的在于隐藏内部实现,保护数据安全,避免错误的修改数据。
在Javascript中,咱们利用 闭包 来实现数据的 封装 。举例以下:测试
function person(){
let obj = {
name: 'Tom',
age: 18
};
return {
get(){
return obj.name + '====' + obj.age
},
set(name, age){
obj.name = name;
obj.age = age;
}
}
}
var p = person();
p.get(); // "Tom====18"
p.set('Bob', 20);
p.get(); // "Bob====20"
复制代码
上面咱们没法直接访问obj
中的数据,对数据的访问只能经过返回的方法来进行读取和操做。固然闭包有闭包的好处,不过也不要滥用,要及时消除闭包。ui
这里不会讲 class、extends 这种语法糖,由于 class 的本质仍是对象(万物皆对象),咱们只讲模拟实现 继承 的五种方式。this
function mixin( sourceObj, targetObj ) { // 混入的方法
for (var key in sourceObj) { // 只会在不存在的状况下复制
if (!(key in targetObj)) {
targetObj[key] = sourceObj[key];
}
}
return targetObj;
}
var Animal = {
eyes: 2,
eat: function() {
console.log( "Animal love eat." );
}
};
var Cat = mixin( Animal, {
tail: 1, // 猫有1条尾巴
eat: function() {
Animal.eat.call(this);
console.log( "Cat also love eat." );
}
});
复制代码
须要注意的就是,这里的 复制 是 浅复制(浅拷贝),固然安全起见仍是进行 深拷贝 。spa
显式混入模式的一种变体,它既是显式的又是隐式的。
function Animal() {
this.eyes = 2;
}
Animal.prototype.eat = function() {
console.log( "Animal love eat." );
};
//“寄生类”Cat
function Cat() { // cat 也是一种 Animal
var cat = new Animal();
cat.tail = 1;
var animalEat = cat.eat;
cat.eat = function() {
animalEat.call( this );
console.log( "Cat also love eat." );
}
return cat;
}
var myCat = new Cat();
myCat.eat(); // Animal love eat. Cat also love eat.
复制代码
如你所见,首先咱们 复制 了一份 Animal 父类(对象)的定义,而后 混入 Cat 子类(对象)的定义,而后再用这个复合对象构建实例。
var Animal = {
eat: function() {
this.kind = 'Animal'
}
};
Animal.eat();
Animal.kind; // "Animal"
var Cat = {
eat: function() { // 隐式把 Animal 混入 Cat
Animal.eat.call(this);
}
};
Cat.eat();
Cat.kind; // "Animal"
复制代码
经过在方法调用中使用 Animal.eat.call(this)
,咱们把 Animal 的行为 混入 到了 Cat 中。
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function() {
return this.name;
};
function Bar(name,label) {
Foo.call( this, name );
this.label = label;
}
Bar.prototype = Object.create( Foo.prototype );
Bar.prototype.myLabel = function() {
return this.label;
};
var a = new Bar( "a", "obj a" );
a.myName(); // "a"
a.myLabel(); // "obj a"
复制代码
典型的“原型风格”,核心部分 Bar.prototype = Object.create( Foo.prototype )
, 即建立一个新的 Bar.prototype
对象并把它关联到 Foo. prototype
。
var Foo = {
init: function(who) {
this.me = who;
},
identify: function() {
return "I am " + this.me;
}
};
Bar = Object.create( Foo );
Bar.speak = function() {
console.log( "Hello, " + this.identify() + "." );
};
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
b1.speak();
b2.speak(); // Hello, I am b1. Hello, I am b2.
复制代码
咱们利用 [[Prototype]]
委托,实现了三个对象之间的关联,代码简洁易懂。
多种状态,指同一个实体同时具备多种形式。好比水有三种形态,液态、气态、固态。咱们简单理解就是子类都由父类派生,但它们的表现形式不一样。在代码中的具体体现就是子类方法的覆写,你们都懂的,这里就不作赘述了。
这里注意两个概念:
instanceof 运算符用于检测构造函数的 prototype 属性是否出如今某个实例对象的原型链上。
isPrototypeOf() 方法用于测试一个对象是否存在于另外一个对象的原型链上。
Tips: 千万不要按字面意思理解,将instanceof
理解为谁是谁的实例,将isPrototypeOf
理解为谁是谁的原型。