你不知道的JS系列——深刻继承


不要由于没有掌声,就放弃了最初的理想
javascript

继承本质

软件中的继承是对真实世界继承的一种抽象。在面向对象语言中,提到继承,咱们先引出 这个概念。java

是什么?

是一种自定义的数据类型,每一个能够包含一组数据类型和操做数据的方法。字符串、数字就是数据类型,固然数据类型有不少,咱们能够有不一样的组合方式,这样组合出来的复杂数据类型,咱们就称之为,一般咱们还会向里面添加操做数据的方法。安全

面向对象语言中的 继承

面向对象语言中的 继承 ,本质上就是 复制 。子类将父类复制一份,那么父类上有的属性、方法,在子类上也就有了,咱们就说子类 继承 了父类。闭包

Javascript 中的 继承

很惋惜,在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

1. 显示混入

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

2.寄生继承

显式混入模式的一种变体,它既是显式的又是隐式的。

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 子类(对象)的定义,而后再用这个复合对象构建实例。

3.隐式混入

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 中。

4.原型继承

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

5.对象关联

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理解为谁是谁的原型。

每个有梦想的人都值得被尊敬!
相关文章
相关标签/搜索