首页 > 代码库 > 浅谈JS中的继承

浅谈JS中的继承

  •  JavaScript本身是一种神马语言:

提到继承,我们常常会联想到C#、java等面向对象的高级语言(当然还有C++),因为存在类的概念使得这些语言在实际的使用中抽象成为一个对象,即面向对象。JavaScript这门语言本身就是作为浏览器脚本语言的弱语言,伴随着没有类的概念,JavaScript就成为了一种基于对象的语言而不是面向对象的语言,面向对象就会存在继承,那么基于对象的JavaScript是如何继承的,老生常谈一下。

  • JavaScript的4种继承方式:

(1)原型继承

function Parent() {
    this.name = "dqhan";
    this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘];
    this.action = function () {
        console.log(‘Iamdqhan‘);
    };
}

function Child() {
  //do someting
}
Child.prototype = new Parent();   //mark

var child1 = new Child();
var child2 = new Child();

 原型继承是最基础的继承方式,核心就是重写子类原型,是父类实例对象充当子类原型,

 优点:通俗易懂

 缺点:(1)原型中的引用类型共享(2)无法向父类构造函数传参

child1.arr.push(‘houhou‘);
console.log(child1.arr); // ["haha", "heihei", "hehe", "houhou"]
console.log(child2.arr); // ["haha", "heihei", "hehe", "houhou"]

原因就是操作child1中的arr时,首先回到child1下查找arr,当没有时会在child1的原型里查找,此时arr为child1原型中的属性,由于child2与child1的原型同为Parent的实例,此时更改arr值时,child2也会变,即原型共享

那么如果现在更改属性name呢?

child1.name = "newdqhan";
console.log(child2.name); // "dqhan"

这又是为什么呢?因为child1.name会直接在child下重新定义一个name属性,这个name并不是原型上的属性,也就不会更改原型,如图。

技术分享      技术分享

(2)借用构造函数实现继承

function Parent(value) {
    this.name = "dqhan";
    this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘];
    this.value = http://www.mamicode.com/value;"value1");
var child2 = new Child("value2");

child1.action === child2.action //false

  看一下结果

技术分享  技术分享

利用Call的方式将Child方法中的this传入Parent方法中,然后重新定义一遍Parent中的属性及方法

Call方法我们这个换一种写法:

function Child(value) {
    this.tempParent = Parent;
    this.tempParent(value);
    delete this.tempParent
}

  在Child函数中引用Parent构造函数并执行,此时就会在Child中定义了一遍Parent中的属性及方法。看到了这里我相信你一定会产生一个疑惑就是为什么不在Child中直接执行一次Parent函数呢。

  看一下结果

技术分享

并没有达到理想的结果,这是为啥呢?因为在Child中不引用Parent,直接调用,Parent中的this默认为Window对象(严格开发模式下为Undefined),目的就是为了传递this。

    function Parent() {
        this.name = "dqhan";        //this 为 Window 对象
        this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘];
        this.action = function () {
            console.log(‘Iamdqhan‘);
        };
    }
    function Child(){
        Parent();
    }
    var child = new Child();
    function Parent() {
     "use strict"; this.name = "dqhan"; //this 为 undefined this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘]; this.action = function () { console.log(‘Iamdqhan‘); }; } function Child(){ Parent(); } var child = new Child();

优点:摒弃了原型,避免了原型共享;解决了向父类构造函数传参的问题。

缺点:没生成一个新对象,都会重新定义一次function,严重影响内存。

(3)组合继承:

这种继承方式相比第二种将父类方法中的函数定义在了原型内

function Parent() {
    this.name = "dqhan";
    this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘];
}
Parent.prototype.action = function () {
    console.log(‘Iamdqhan‘);
};
function Child() {
    Parent.call(this);
}
Child.prototype = new Parent(); var child1 = new Child("value1"); var child2 = new Child("value2"); child1.action === child2.action // true

结果:

技术分享     技术分享

优点:这种方式成功的避免了重复定义function的尴尬情况,同时解决了原型共享的问题。

缺点:如果有两个子类继承父类,但是父类的属性有一个子类不用,怎么搞?这个是没法避免的,而且父类的属性全部在子类的原型上,很不美观。

(4)寄生组合继承:

 为了扣掉组合继承中原型中不需要的属性,看到为了满足这一点,可不可以介样:

function Parent() {
    this.name = "dqhan";
    this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘];
}
Parent.prototype.action = function () {
    console.log(‘Iamdqhan‘);
};
function Child() {
    Parent.call(this);
};
Child.prototype = Parent.prototype;

  结果:

技术分享  技术分享

 乍一看好像对,实际Child中的__proto__为Object,并不是Parent,已经背离了Child继承Parent的目的。

改进:

function Parent() {
    this.name = "dqhan";
    this.arr = [‘haha‘, ‘heihei‘, ‘hehe‘];
}
Parent.prototype.action = function () {
    console.log(‘Iamdqhan‘);
};

function Child() {
    Parent.call(this);
};


function initObject(obj) {
    var
        F = function () { };
    F.prototype = obj;
    return new F();
}

//Child.prototype = Parent.prototype;
var newPrototype = initObject(Parent.prototype);
newPrototype.constructor = Child;
Child.prototype = newPrototype;


var child1 = new Child("value1");
var child2 = new Child("value2");

  结果:

技术分享   技术分享

 Child的__proto__已经为Parent,而且去掉了原型上我们不要的属性只留下父类在原型上定义的方法,核心目的就是为了更改原型链的设置。

 

浅谈JS中的继承