JavaScript Fortnight-Diary:舊時代的妥協(xié)——創(chuàng)建對象與繼承

創(chuàng)建對象

工廠模式

        function createPerson(name){
            let o = new Object()
            o.name = name
            return o
        }
        let person1 = createPerson('lpj')
        let person2 = createPerson('hjy')

缺點:不能共享玷禽、對象標(biāo)識紊亂

  • 單純構(gòu)造函數(shù)模式
function Person(name){
            this.name = name;
            this.sayName = function(){console.log(this.name)}
        }
  • 確保實例被標(biāo)識為特定類型(通過constructor 但一般使用instanceof操作符)
  • person1 instanceof Object==person2 instance of Person==true

缺點:this.sayName = function(){}方法其實相當(dāng)于this.sayName = new function(){}也就是說方法和屬性等價了悔据,每個實例都有一個自己的方法
console.log(person1.sayName == person2.sayName)//false

原型模式

function Person(name){
            this.name = name;
        }
Person.prototype.sayName =  function(){console.log(this.name)}
Person.prototype.job = 'student'
  • 每個實例實際上都有[[prototype]]內(nèi)部特性(一個指向構(gòu)造器原型的指針) __ proto__實際上就用于訪問本實例的[[prototype]]而不是直接訪問外部
  • 任何自定義的構(gòu)造器函數(shù)的原型都只是單純的函數(shù)對象,由Object()構(gòu)造器創(chuàng)造翼闽,因此原型鏈到最后Person.prototype.__proto__.__proto__ ===null
  • 相關(guān)方法
    • console.log(Person.prototype.isPrototypeOf(person1)//true
      直接確認(rèn)原型
    • console.log(Object.getPrototypeOf(person1)== Person.prototype) console.log(Object.getPrototypeOf(person1).job)//student
      用于獲取實例[[Prototype]]的值
    • setPrototypeOf
      可以重寫實例proto所指向的原型枫匾,但具有性能問題
    • Object.create()
      也可以重寫實例指向的原型哑芹,性能更好花盐,在繼承時可能會用到
  • 遮蔽(shadow)與確認(rèn)
    實例如果有和原型上同名的屬性,會遮蔽原型上的拓挥,使其搜索不到唠梨,可以通過delete該屬性刪除從而恢復(fù)原型屬性的暴露
    hasOwnProperty用于確認(rèn)屬性在原型上還是實例上
    in用于確認(rèn)可枚舉是否屬性在原型在實例上
    Object.keys()返回所有可枚舉屬性
    Object.getOwnPropertyNames()返回全部屬性包括不可枚舉的
let person1 = new Person()
let person2 = new Person()
console.log(person1.hasOwnProperty("name"))//false 不在實例上
console.log("name" in person1)//true 在原型上
person1.name = 'sb'//遮蔽
console.log(person1.hasOwnProperty("name"))//true 在實例上
console.log("name" in person1)//true 在實例和原型上都有
delete person1.name//取消遮蔽 
console.log(person1.hasOwnProperty("name"))//false
  • 一些點
    • construcor默認(rèn)是不可枚舉屬性 要用Object.getOwnPropertyNames()才能返回
    • Object.keys()in在內(nèi)部的枚舉順序是不確定的,取決于引擎侥啤,其余的方法順序則是確定的
  • 原型修改與重寫
    • Person.prototype.any = 'something'可以直接添加原型的屬性当叭,并且只要添加后,任何情況下都可以訪問(原型的動態(tài)性)
    • 記住盖灸,實例只有指向原型的指針蚁鳖,沒有指向構(gòu)造器函數(shù)的指針
function Person(){}
let friend = new Person
Person.prototype = {
constructor :Person,//注意 如果不寫的話,這里將會從Person變成Object
name:'sb',
say(){console.log('sb')}
}
friend.say()//錯誤

friend指向的原型仍是最初的原型

  • 盡管可以修改原生對象原型(如Array糠雨、String的)才睹,但不建議這么做,應(yīng)使用es6的自定義類來繼承原生類型

缺點:

  • 弱化了構(gòu)造函數(shù)傳遞初始化參數(shù)的能力例如Person.prototype.job = 'student'會被所有實例繼承甘邀,這對函數(shù)(方法)來說還可以接受琅攘,但屬性往往不需要。
  • 共享了屬性松邪,導(dǎo)致如果你person1.job = 'teacher'的話,person2.job也會改變
    ↑這里是錯誤的,number和string都不會改變坞琴,這些屬性是獨立拷貝的,但如果是Array逗抑,顯然由于引用類型的原因剧辐,你push一個新元素進去,會導(dǎo)致共享的全都被修改
    ↑注意:“獨立拷貝是錯的”邮府,原型就是只有一份荧关,這里之所以看起來person2.job不變是因為添加了person1的實例屬性job,原型被遮蔽了褂傀,而person2未被遮蔽

總之忍啤,不能純粹使用原型模式,該寫構(gòu)造器里就寫構(gòu)造器里
↑對于num和str來說仙辟,寫在構(gòu)造器里可以解決這個問題同波,但對于Array等引用來說鳄梅,寫在構(gòu)造器里還是一樣的,必須采用盜用構(gòu)造器

↑草 寫在構(gòu)造器里還真不一樣 其實很顯然 因為寫在構(gòu)造器里就是new了新的

    function Animal(){
            this.foot = 4
            this.colors = ['white','black']
        }
        let cow = new Animal
        cow.colors.push("yellow")
        cow.foot = 2
        let cat = new Animal

        console.log(cow.foot+cow.colors)//2white,black,yellow
        console.log(cat.foot+cat.colors)//4white,balck

真正的缺點應(yīng)該稱作:原型包含引用值從而在所有實例間將會共享未檩,這個缺點將在繼承中顯著體現(xiàn)

繼承

為什么要繼承戴尸?
為了讓一個實例能得到多個引用類型(Person、Object等)的屬性和方法冤狡,提升面向?qū)ο蟮哪芰λ锩伞⑻岣呖勺x性、降低代碼復(fù)雜度

  • 基本繼承
        function Animal(){
            this.foot = 4
        }
        Animal.prototype.sayMyFoot = function(){
            console.log("my foot has "+this.foot)
        }
        function Cat(name){
            this.name = name
        }

        Cat.prototype = new Animal()//開始繼承

        Cat.prototype.sayMyName = function(){
            console.log("my name is "+this.name)
        }

        let mimi = new Cat("mimi")
        mimi.sayMyName()
        mimi.sayMyFoot()
//實際上mimi還繼承了所有Object()原型的方法        console.log(mimi.__proto__.__proto__.__proto__.__proto__===null)//ture

注意:Cat.prototype如果使用字面量重寫的話筒溃,會導(dǎo)致原型鏈斷開马篮,因為構(gòu)造器是Object了而不是Animal

Cat.prototype = {
            constructor:Cat,
            //constructor:Animal, constructor就是Cat啊 別亂來
            sayMyName : function(){
            console.log("my name is "+this.name)
        }
        }
        Cat.prototype.__proto__ = Animal.prototype//有了這句就能修復(fù)原型鏈

注意:別糾結(jié)沾乘,知道原型鏈斷開就好怜奖。這里修復(fù)后只能繼承sayMyFoot,如果要繼承foot的話還要call才行

原型鏈的問題

  • 正如前面結(jié)尾所說的翅阵,原型包含引用值從而在所有實例間將會共享歪玲,而這里(通過new繼承) 相當(dāng)于給Cat.prototype添加了一個foot屬性,這是num就沒問題掷匠,如果是Array等引用值的話滥崩,就會產(chǎn)生問題
    mimi.colors===["white","black"]
    mimi.colors.push("yellow")
    miao.colors===["white","black","yellow"]//受影響
    mimi.foot = 2
    miao.foot===4//不受影響
  • 子類型在實例化時不能給父類型的構(gòu)造器傳參 Cat.prototype = new Animal()你傳個p參對吧
  • 解決辦法:盜用構(gòu)造函數(shù)
    利用call把Animal的this指向Cat(即call里面的第一個參數(shù)this) 這樣相當(dāng)于把Animal的初始化代碼都偷到Cat上執(zhí)行了,相當(dāng)于沒有兩層new了讹语,該放到構(gòu)造器里的屬性就放進了Cat構(gòu)造器里而非跑到prototype里
function Animal(foot){
            this.foot = foot
            this.colors = ['white','black']
        }
        Animal.prototype.sayMyFoot = function(){
            console.log("my foot has "+this.foot)
        }

        function Cat(name,foot){
            this.name = name
            Animal.call(this,foot)
        }

        //Cat.prototype = new Animal()//開始繼承
        
        Cat.prototype.sayMyName = function(){
            console.log("my name is "+this.name)
        }       let mimi = new Cat("mimi",4)
        console.log(mimi.colors)

        mimi.colors.push("yellow")

        let miao = new Cat("miao",4)
        console.log(miao.colors)//不被影響
        console.log(miao.foot)//傳參成功
        miao.sayMyFoot()//報錯!

如cb所說:Cat.prototype.__proto__ = Animal.prototype等價于Objcet.setPrototypeOf(Cat.prototype, Animal.prototype)但我覺得后者會有性能問題
還可以Cat.prototype= Object.create(Animal.prototype)//(注意不是__proto__)這個方法自己就可以重新使[[prototype]]指過去但要補充一行Cat.prototype.constructor = Cat//因為是函數(shù)特有的默認(rèn)屬性

什么钙皮?這都有缺點?
是的

  • 必須在構(gòu)造函數(shù)中定義方法顽决,this.name = name Animal.call(this,foot)因此函數(shù)不能重用
  • 更致命的是短条,子類實例也訪問不到父類(Animal、Object)原型上的方法
  • 組合式繼承
    既然訪問不到方法才菠,那就用原型鏈繼承方法茸时,用盜用來繼承屬性
    這樣既可以把方法定義在初始原型上實現(xiàn) 重用,又可以讓每個實例擁有自己的屬性
    完整代碼如下:
    function Animal(foot){
            this.foot = foot
            this.colors = ['white','black']
        }
        Animal.prototype.sayMyFoot = function(){
            console.log("my foot has "+this.foot)
        }
        
        //盜用構(gòu)造器繼承屬性
        function Cat(name,foot){
            this.name = name
            Animal.call(this,foot)
        }
        //第三種繼承:組合繼承
        //在盜用的基礎(chǔ)上赋访,使用__proto__來進行原型鏈繼承而不使用new
        
        Cat.prototype= new Animal //不要用 盡管它是書里的方式
        
        Cat.prototype.sayMyName = function(){
            console.log("my name is "+this.name)
        }
        //你也可以選擇覆寫
/*      Cat.prototype = {
            //constructor:Animal, constructor就是Cat啊 別亂來
            sayMyName : function(){
            console.log("my name is "+this.name)
        }
        }
        Cat.prototype.__proto__ = Animal.prototype*/

        let mimi = new Cat("mimi",4)
        console.log(mimi.colors)

        mimi.colors.push("yellow")

        let miao = new Cat("miao",4)
        console.log(miao.colors)//不被影響
        console.log(miao.foot)//傳參成功
        miao.sayMyFoot()//成功繼承方法

注意:這里書里沙雕了可都,就硬要把Animal屬性全部繼承給Cat,從而使用Cat.prototype= new Animal蚓耽,但其實沒必要渠牲,反而會帶來很多副作用

  • 存在效率問題,在Cat.prototype= new Animal調(diào)用了一次Animal
    Animal.call(this,foot)又調(diào)了一次
  • 由于上述問題步悠,Cat.prototype的foot和colors屬性和實例上的foot和colors是兩個不同的重復(fù)
    console.log(Cat.prototype.colors===mimi.colors)//false
    解決方法
  • Cat.prototype.__proto__ = Animal.prototype
    • 區(qū)別是Cat.prototype搜索不到foot和colors 要能搜索到就必須new
    • 缺點是失去Object.create添加屬性的特性以及寄生方式DIY方法的能力
  • Object.setPrototypeOf(Cat.prototype, Animal.prototype)
    跟上面一樣签杈,但是還會有性能問題
  • 標(biāo)準(zhǔn)方法: Object.create(Animal.prototype)然后設(shè)置constructor
    即寄生式組合繼承
  • 原型式繼承:為寄生式打基礎(chǔ)
    解決了重復(fù)調(diào)用父類構(gòu)造函數(shù)的問題(直接利用了原型而不用構(gòu)造器)
    (直接用__ proto__重定位也是可以解決這個問題的)
    試想一種函數(shù)
        function object(o){
            function F(){}
            F.prototype = o
            return new F()
        }
        let person = {
            name : "lpj",
            girlFriends: ["hjy","cst"]
        }
        let anotherPerson = object(person)
        anotherPerson.name = "sb"http://這里會添加name到實例上而不是原型上
        //anotherPerson.__proto__.name = "sb"
        anotherPerson.girlFriends.push("someone")//這里會直接訪問原型的girlFriends
        let yetAnotherPerson = object(person)
        yetAnotherPerson.name = "KKP"

        console.log(anotherPerson)//name is sb
        console.log(yetAnotherPerson)//name is KKP
        console.log(anotherPerson.__proto__)//Array has 3
        console.log(yetAnotherPerson.__proto__)//Array has 3

借用 構(gòu)造器簡單地實現(xiàn)了對象之間的信息共享,不用專門去寫構(gòu)造器函數(shù)了
其實是對person進行淺拷貝

Objcet.create()第二個參數(shù)還允許添加新的屬性到新的實例上贤徒,注意同名會產(chǎn)生遮蔽

  • 更進一步:寄生式繼承
    寄生式繼承的概念是比較廣的芹壕,不僅用于繼承

顧名思義汇四,用Object.create()新建一個對象,然后增強它 再返回

  function createAnother(original){
  let clone = Object.create(original)
  clone.sayHi = function(){console.log('hi')}
  return clone
}
  let person = {
name : "lpj"
}
  let anotherPerson = createAnother(person)
  anotherPerson.sayHi() 

注意:這會使得createAnother類型的函數(shù)的復(fù)用性大幅下降

  • 來個大雜燴:寄生式組合繼承
    利用create()把Animal的原型淺復(fù)制出來踢涌,增強一個constructor屬性通孽,賦給Cat的原型就完事了
        function Animal(foot){
            foot = 4
            this.foot = foot
            this.colors = ['white','black']
        }
        Animal.prototype.sayMyFoot = function(){
            console.log("my foot has "+this.foot)
        }
        
        //繼承屬性
        function Cat(name,foot){
            this.name = name
            Animal.call(this,foot)
        }

        //非標(biāo)準(zhǔn)最佳方式:Cat.prototype.__proto__ = Animal.prototype 缺點是失去Object.create添加屬性的特性以及寄生方式DIY方法的能力
        //Cat.prototype= new Animal 原方式
        function inheritPrototype(child,father){
            let prototype = Object.create(father.prototype)
            prototype.constructor = child
            child.prototype = prototype
        }
        inheritPrototype(Cat, Animal)

        Cat.prototype.sayMyName = function(){
            console.log("my name is "+this.name)
        }

到這里,實現(xiàn)了方法共享睁壁、屬性私有(偽)背苦、傳參、且只調(diào)用了一次構(gòu)造器潘明、不破壞任何原型鏈行剂、且在必要時可以添加新方法和屬性

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市钳降,隨后出現(xiàn)的幾起案子厚宰,更是在濱河造成了極大的恐慌,老刑警劉巖遂填,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铲觉,死亡現(xiàn)場離奇詭異,居然都是意外死亡吓坚,警方通過查閱死者的電腦和手機撵幽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來礁击,“玉大人盐杂,你說我怎么就攤上這事《吡” “怎么了链烈?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長更耻。 經(jīng)常有香客問我测垛,道長,這世上最難降的妖魔是什么秧均? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任食侮,我火速辦了婚禮,結(jié)果婚禮上目胡,老公的妹妹穿的比我還像新娘锯七。我一直安慰自己,他們只是感情好誉己,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布眉尸。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪噪猾。 梳的紋絲不亂的頭發(fā)上霉祸,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機與錄音袱蜡,去河邊找鬼丝蹭。 笑死,一個胖子當(dāng)著我的面吹牛坪蚁,可吹牛的內(nèi)容都是我干的奔穿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼敏晤,長吁一口氣:“原來是場噩夢啊……” “哼贱田!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嘴脾,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤男摧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后统阿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彩倚,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡筹我,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年扶平,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蔬蕊。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡结澄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出岸夯,到底是詐尸還是另有隱情麻献,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布猜扮,位于F島的核電站勉吻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏旅赢。R本人自食惡果不足惜齿桃,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望煮盼。 院中可真熱鬧短纵,春花似錦、人聲如沸僵控。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悠就,卻和暖如春千绪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梗脾。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工翘紊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人藐唠。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓帆疟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宇立。 傳聞我的和親對象是個殘疾皇子踪宠,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容