js--創(chuàng)建對象與對象繼承

創(chuàng)建對象

  1. 工廠模式:
    function createPerson(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
    alert(this.name);
    };
    return o;
    }
    var person2 = createPerson("Greg", 27, "Doctor");
  • 解析:雖然能創(chuàng)造對象,但是無法從constructor和instanceOf獲取更多關于構造函數(shù)的信息,因為這里生成的對象的constructor都指向Object,而instanceOf只有Object為true.
  1. 正常構造函數(shù)模式:
    function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
    alert(this.name);
    };
    }
    var person2 = new Person("Greg", 27, "Doctor");
  • 解析:函數(shù)里面沒有return語句,必須要用new才能創(chuàng)建對象,如果直接調用Person,則里面的this會指向window,造成的結果是會在window上生成全局變量。
  • 使用new可以生成新對象,具體步驟如下:
    1. 開辟內存,創(chuàng)建新對象
    2. 將構造函數(shù)的作用域賦給新對象(因此this指向這個新對象)
    3. 將新對象的proto指向構造函數(shù)默認的prototype指針指向的對象
    4. 執(zhí)行構造函數(shù)中的代碼(為這個新對象添加屬性)
    5. 返回新對象。
  1. 特殊比較——寄生構造函數(shù)模式:
    function Person(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
    alert(this.name);
    };
    return o;
    }

     var friend = new Person("Nicholas", 29, "Software Engineer");
     friend.sayName();  //"Nicholas"
    
  • 解析:這里想要強調的是,當使用new操作符來對一個函數(shù)進行操作時,系統(tǒng)默認會創(chuàng)建新對象,此時還要根據(jù)函數(shù)中是否存在return值,來判斷。如果沒有返回值,則返回系統(tǒng)后臺新創(chuàng)建的對象,如果有返回值而且是一個對象,則程序猿指定的返回值會代替新創(chuàng)建對象返回。
  • 所以,上面new之后返回給friend的是o,與工廠模式中一樣,不能依賴instanceof和constructor來確定對象的具體類型。
  1. 原型模式:
    function Person(){
    }

     Person.prototype.name = "Nicholas";
     Person.prototype.age = 29;
     Person.prototype.job = "Software Engineer";
     Person.prototype.sayName = function(){
         alert(this.name);
     };
     
     var person1 = new Person();
     var person2 = new Person();
     
     person1.name = "Greg";
     alert(person1.name);   //"Greg" – from instance
     alert(person2.name);   //"Nicholas" – from prototype
    

對象繼承

  1. 原型繼承:
    function SuperType(){
    this.property = true;
    }

     SuperType.prototype.getSuperValue = function(){
         return this.property;
     };
     
     function SubType(){
         this.subproperty = false;
     }
     
     //inherit from SuperType
     SubType.prototype = new SuperType();
     
     SubType.prototype.getSubValue = function (){
         return this.subproperty;
     };
     
     var instance = new SubType();
     alert(instance.getSuperValue());   //true
    
     alert(instance instanceof Object);      //true
     alert(instance instanceof SuperType);   //true
     alert(instance instanceof SubType);     //true
    
     alert(Object.prototype.isPrototypeOf(instance));    //true
     alert(SuperType.prototype.isPrototypeOf(instance)); //true
     alert(SubType.prototype.isPrototypeOf(instance));   //true
    
  • 缺點:對象實例共享所有繼承的屬性和方法。
  1. 借用構造函數(shù):
    function SuperType(){
    this.colors = ["red", "blue", "green"];
    }

     function SubType(){  
         //inherit from SuperType
         SuperType.call(this);
     }
    
     var instance1 = new SubType();
     instance1.colors.push("black");
     alert(instance1.colors);    //"red,blue,green,black"
     
     var instance2 = new SubType();
     alert(instance2.colors);    //"red,blue,green"
    
  • 解析:這里實際上是在(未來將要)新創(chuàng)建的SubType實例的環(huán)境下調用了SuperType構造函數(shù),這樣就會在新SubType對象上執(zhí)行SuperType()函數(shù)中定義的所有對象初始化代碼,這樣每個SubType實例都會具有自己的colors屬性。
  • 解決了上一方法中所有對象共享同一實例的缺點,每個對象都會調用SubType(),因此也都有自己的環(huán)境,會形成自己的實例屬性。
  1. 組合繼承(原型鏈+借用構造函數(shù))

     function SuperType(name){
         this.name = name;
         this.colors = ["red", "blue", "green"];
     }
     
     SuperType.prototype.sayName = function(){
         alert(this.name);
     };
    
     function SubType(name, age){  
         SuperType.call(this, name);  //第二次調用超類構造函數(shù)
         
         this.age = age;
     }
    
     SubType.prototype = new SuperType();    //第一次調用超類構造函數(shù)
      SubType.prototype.constructor = SubType;
     SubType.prototype.sayAge = function(){
         alert(this.age);
     };
     
     var instance1 = new SubType("Nicholas", 29);
     instance1.colors.push("black");
     alert(instance1.colors);  //"red,blue,green,black"
     instance1.sayName();      //"Nicholas";
     instance1.sayAge();       //29
    
     var instance2 = new SubType("Greg", 27);
     alert(instance2.colors);  //"red,blue,green"
     instance2.sayName();      //"Greg";
     instance2.sayAge();       //27
    
  • 解析:這種方法通過使用原型鏈實現(xiàn)對原型屬性和方法的繼承(需共享的數(shù)據(jù)),通過借用構造函數(shù)來實現(xiàn)對實例屬性的繼承(需要每個實例獨立的數(shù)據(jù))。
  • 最終,子類會包含超類的全部實例屬性。子類的原型與超類的原型會分開,是不同的對象,更改不會相互影響,但通過子類原型的proto可以取得超類的原型。
  • 組合繼承最大的問題:無論什么情況下都會兩次調用超類構造函數(shù),一次是在創(chuàng)建子類原型的時候,第二次是在子類構造函數(shù)內部call。
  • instanceof和isPrototypeOf()也能夠用于識別基于組合繼承創(chuàng)建的對象。
  1. 原型式繼承:
    function object(o){
    function F(){};
    F.prototype = o;
    return new F();
    }
  • 這意味著被傳入的對象o成為了新對象的原型,新對象的proto直接指向o,并且當新對象對實例中不存在但原型上存在的屬性操作時,對象o也會被隨之改變。
  • 原型式繼承還可以使用ES5中的Object.create(),如下:
    Square.prototype = Object.create(Rect.prototype,{
    constructor:{
    configure:true,
    enumerable:true,
    writable:true,
    value:Square
    }
    })
  • 解析:這樣就實現(xiàn)了Square繼承Rectangle的操作,還免去了生成中間構造函數(shù)F()的操作。
  1. 原型式繼承進階版——寄生式繼承
    function createAnother(original){
    var clone = object(original); //這里沿用上面的object()函數(shù)
    clone.sayHi = function(){
    alert("hi");
    }
    return clone;
    }
    var person = {
    name: "json",
    age: 23
    }
    var newPerson = createAnother(person);
    newPerson.sayHi(); //"hi"
  • 解析:其實就是在上面基礎上,增加了可以對繼承后的對象添加實例方法或實例屬性,然后這里繼承以后,newPerson的proto指向person,兩者修改其一,另一都會受到影響。
  1. 寄生組合式繼承:
  • 完整版代碼:
    function object(o){
    function F(){}
    F.prototype = o;
    return new F();
    }

            function inheritPrototype(subType, superType){
                var prototype = object(superType.prototype);   //返回一個新對象,新對象的__proto__指向superType.prototype            
                subType.prototype = prototype;                 //修改子類的原型,之后,subType的實例的__proto__會指向這個原型,而這個原型的__proto__會指向父類的原型superType.prototype,由此實現(xiàn)繼承
                subType.prototype.constructor = subType;   //修正constructor
            }
                                    
            function SuperType(name){
                this.name = name;
                this.colors = ["red", "blue", "green"];
            }
            
            SuperType.prototype.sayName = function(){
                alert(this.name);
            };
    
            function SubType(name, age){  
                SuperType.call(this, name);
                this.age = age;
            }
    
            inheritPrototype(SubType, SuperType);
            
            SubType.prototype.sayAge = function(){
                alert(this.age);
            };
            
            var instance1 = new SubType("Nicholas", 29);
            instance1.colors.push("black");
            alert(instance1.colors);  //"red,blue,green,black"
            instance1.sayName();      //"Nicholas";
            instance1.sayAge();       //29
           
            var instance2 = new SubType("Greg", 27);
            alert(instance2.colors);  //"red,blue,green"
            instance2.sayName();      //"Greg";
            instance2.sayAge();       //27
    
  • 解析: 與組合繼承相比的高效率在于,只調用一次SuperType構造函數(shù),并且避免了在SuperType.prototype上面創(chuàng)建不必要的多余屬性。與此同時,原型鏈保持不變,能夠用instanceof和isPrototypeOf()來判斷。是引用類型最理想的繼承范式。

  • 這里說只調用一次SuperType構造函數(shù),被省略掉那那一次是new SuperType(),因為這里如果直接new一個出來的話,雖然可以得到一個proto指向SuperType.prototype對象的對象,但是會存在冗余的SuperType實例屬性,而本模式中的做法是,另用一個空函數(shù),使它的prototype也指向SuperType.prototype,這樣除了實例屬性為空以外,其他都與SuperType()一樣,此時new一個新函數(shù)的實例對象,就能取得一個proto指向SuperType.prototype對象的對象,最后將subType的prototype指向這個新new的實例對象,此時subType.prototype.proto = SuperType.prototype, 就實現(xiàn)了subType.prototype到SuperType.prototype的鏈接,形成了繼承鏈,而subType.prototype里也沒有冗余的值為空的SubperType的實例屬性,Perfect?。?!

  • 代碼注釋:

    • var prototype = object(superType.prototype); //返回一個新對象,新對象的proto指向superType.prototype
    • subType.prototype = prototype; //修改子類的原型,之后,subType的實例的proto會指向這個原型,而這個原型的proto會指向父類的原型superType.prototype,由此實現(xiàn)繼承

補充:寄生組合式繼承的圖解:

111 (1).png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,202評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,742評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,580評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,297評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,688評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,875評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,438評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,183評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,384評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,612評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,022評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,297評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,093評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,330評論 2 377

推薦閱讀更多精彩內容