JavaScript中實現繼承的幾種方式

最近讀到了《JavaScript高級程序設計》中的第六章,在繼承的這一小節,瞭解到了在Javascript中實現繼承的幾種方式和優缺點,記錄一下。

繼承是指一個對象直接使用另一個對象的屬性和方法

JacaScript原生不支持繼承,Javascript將原型鏈作爲實現繼承的主要方式。那麼,到底什麼是原型鏈?簡單回顧一下構造函數、原型和實例的關係。每個構造函數都有一個prototype指向原型對象,每個原型對象都有一個指針constructor指回構造函數,每一個實例都有一個指向原型對象的內部指針,在現代瀏覽器中就是__proto__。在使用原型鏈實現的繼承中,實例的__proto__指向的不是一個原型而是另一個原型的實例,這個實例自己的__proto__不是指向自己的原型對象,而又是另外一個類的實例,那麼這樣層層遞進,就構成了實例和原型的鏈條,這就是所謂的原型鏈的基本概念。

  • 默認的原型

    所有的引用類型都繼承了Object,這個繼承也是通過原型鏈實現的,所有函數的默認原型都是Object的實例,因此默認原型都會包含一個內部指針指向(__proto__)指向Object.prototype。這也是爲什麼所有的自定義類型都會繼承tostring()valueOf()等默認方法的原因了。

  • 確定原型和實例的關係

    • instanceof
    • isPrototypeOf()
  • 謹慎的定義方法

    • 爲原型添加方法的代碼一定要放在替換原型的語句之後。
    • 通過原型鏈實現繼承的時候,不能使用對象字面量創建原型方法,因爲這會重寫原型鏈
  • 原型鏈的問題

    • 原型鏈繼承會導致實例之間互相修改原型引用類型屬性的值。
    • 在創建子類型的實例的時候,無法向超類型的構造函數傳遞參數
    function SuperType(){
        this.colors = ["red","blue","green"];
    }
    function SubType(){}
    //繼承了SuperType
    SubType.prototype = new SuperType();
    var instance1 = new SubType();
    instance1.colors.push("black");
    var instance2 = new SubType();
    console.log(instance2.colors);//red,blue,green,black
借用構造函數實現繼承

先看下面的代碼

    function SuperType(){
        this.name = name;
    }
    function SubType(){
        //繼承了SuperType,同時傳遞了參數
        SuperType.call(this,"Xiaoxiao");
        //實例屬性
        this.age = 22;
    }
    var instance = new SubType();
    console.log(instance.name);//"Xiaoxiao"
    console.log(instance.age);//22

存在的問題

  1. 方法在構造函數中定義,因此函數複用就無從談起
  2. 在超類中定義的方法,對於子類型而言也是不可見的,所以所有的類型只能使用構造函數模式。
最常用的方法-組合繼承
function SuperType(name){
    this.name = name;
    this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
}
function SubType(name,age){
    //繼承屬性
    //call在在這改變了函數上下文,函數作爲構造函數使用時this指代創建的對象,相當於初始化了三個一個屬性
    SuperType.call(this,name);
    this.age = age;
}
//繼承方法
SubType.prototype = new SuperType();//也會經常使用 SubType.prototype = Object.create(SuperType.prorotypr);
SubType.constructor.prototype = SubType;
SubType.prorotype.sayAge = function(){
    alert(this.age);
}

var instance1 = new SubType("Xiaoxiao",21);
instance1.colors.push("black");

var instance2 = new SubType("xx",22);
console.log(instance2.colors);//red,blue,green

優點:組合繼承避免了原型鏈和借用構造函數的缺陷,融合了他們的優點,是最常使用的方式。

原型式繼承

ECMAScript5通過Object.create()方法實現了這一繼承方式。第一個參數是父對象,第二個參數是擴展的屬性。
當只傳遞一個參數時,Object.create()的行爲和如下object方法類似:

function object(){
    var args = arguments;
    var proto = args[0];
    function F(){};
    F.prototype = proto;
    return new F();
}

當有兩個參數時,Object.create的polyfill如下

if(typeof Object.create != "function"){
    Object.create = function(){
        function F();
        var hasOwn = Object.hasOwnProperty;
        return function(){
            var args = arguments,proto = args[0];
            if(typeof proto != "object") return;
            F.protoytpe = proto;
            var obj = new F();
            F.prototype = null;//每次釋放掉
            if(args.length > 1){
                var properties = Object(args[1]);
                for(var key in properties){
                    if(hasOwn.call(properties,key)){
                        obj[pro] = properties[key];
                    }
                }
            }
            return obj;
        }
    }
}

瀏覽器支持:IE9+ FF4+ Safari5+ Opear12+ 和 Chrome

在沒有必要興師動衆的創建構造函數,而只是想讓一個對象和另一個對象保持類似的情況下,原型式繼承是完全可以勝任的,但是,包含引用類型的值得屬性使用都會共享響應的值,就像使用原型模式一樣。

最有效的方法-組合寄生式繼承

前面的組合繼承是JavaScript裏面最常用的繼承模式,不過,他也有自己的不足。組合繼承最大的問題就是無論什麼情況下,都會調用兩次超類構造函數。一次是在創建子類型原型的時候,另一次是在子類型狗仔函數內部。

function SuperType(name){
    this.name = name;
    this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
}
function SubType(name,age){
    //在創造實例時會執行第二次調用,這時實例上有age,name,colors屬性
    SuperType.call(this,name);
    this.age = age;
}
//初始化SubType時會執行第一次調用,這時SubType原型上會有name和colors屬性
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
}

所謂的組合寄生式繼承,就是通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。

基本的模式如下

function inheritPrototype(subType,superType){
    var prototype = Object(superType.prototypr);
    prototype.constructor = subType;
    subType.prototype = prototypr;
}

避免了在SubType.prototype上創建不必要的,多餘的屬性。

發佈了33 篇原創文章 · 獲贊 19 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章