JavaScript中的原型繼承基礎(chǔ)學習教程

字號:


    這篇文章主要介紹了JavaScript中的原型繼承基礎(chǔ)學習教程,基于原型prototype的繼承是JavaScript中實現(xiàn)面向?qū)ο笾械睦^承特性的基本手段,需要的朋友可以參考下
    大多數(shù)編程語言中,都有類和對象,一個類可以繼承其他類。
    在JavaScript中,繼承是基于原型的(prototype-based),這意味著JavaScript中沒有類,取而代之的是一個對象繼承另一個對象。:)
    1. 繼承, the proto
    在JavaScript中,當一個對象rabbit繼承另一了對象animal時,這意味著rabbit對象中將會有一個特殊的屬性:rabbit.__proto__ = animal;
    當訪問rabbit對象時,如果解釋器在rabbit中不能找到屬性,那么它會順著__proto__鏈往上在animal對象中尋找
    栗子中的__proto__屬性僅在Chrome和FireFox中可以訪問,請看一個栗子:
    var animal = { eats: true }
    var rabbit = { jumps: true }
    rabbit.__proto__ = animal // inherit
    alert(rabbit.eats) // true
    eats屬性是從animal對象中訪問的。
    如果在rabbit對象中已經(jīng)發(fā)現(xiàn)了屬性,那么就不會去檢查proto屬性啦。
    再來一個栗子,當子類中也有eats屬性時,父類中的就不會訪問了。
    var animal = { eats: true }
    var fedUpRabbit = { eats: false}
    fedUpRabbit.__proto__ = animal 
    alert(fedUpRabbit.eats) // false
    你也可以在animal中添加一個函數(shù),那么在rabbit中也可以訪問了。
    var animal = {
     eat: function() {
      alert( "I'm full" )
      this.full = true
     }
    }
    var rabbit = {
     jump: function() { /* something */ }
    }
    rabbit.__proto__ = animal 
    (1)rabbit.eat():
    rabbit.eat()函數(shù)以如下兩步執(zhí)行:
    首先,解釋器查找rabbit.eat,rabbit中沒有eat函數(shù),那么它就順著rabbit.__proto__往上找,在animal中找到了。
    函數(shù)以this = rabbit運行。this值與__proto__屬性完全無關(guān)。
    因此,this.full = true在rabbit中:
    看看這里我們有什么新發(fā)現(xiàn),一個對象調(diào)用了父類函數(shù),但是this還是指向?qū)ο蟊旧?,這就是繼承。
    被__proto__引用的對象稱作是原型(prototype),animal是rabbit的原型(譯者注:這就是rabbit的__proto__屬性引用了animal 的prototype屬性)
    (2)讀時查找,不是寫時
    當讀取一個對象時,比如this.prop,解釋器會在它的原型中查找屬性。
    當設置一個屬性值時,比如this.prop = value,那么就沒有理由去查找了,這個屬性(prop)會被直接添加到這個對象中(這里是this)。delete obj.prop也類似,它只刪除對象本身的屬性,原型中的屬性保持原封不動。
    (3)關(guān)于proto
    如果你在閱讀指南,這里我們叫的__proto__,在指南中表示為[[Prototype]]。雙方括號是很重要的,因為有另一個屬性叫做prototype。
    2. Object.create, Object.getPrototypeOf
    __proto__是一個非標準的屬性,由Chrome/FireFox提供訪問,在其他的瀏覽器中保持不可見。
    所有的現(xiàn)代瀏覽器除了Opera(IE > 9)支持兩個標準的函數(shù)來處理原型問題:
    Object.ceate(prop[,props])
    用給定了proto創(chuàng)建一個空對象:
    var animal = { eats: true }
    rabbit = Object.create(animal)
    alert(rabbit.eats) // true
    上面代碼創(chuàng)建了一個空rabbit對象,并且原型設置為animal
    rabbit對象創(chuàng)建好以后,我們可以往里添加屬性了:
    var animal = { eats: true }
    rabbit = Object.create(animal)
    rabbit.jumps = true
    Object.creat函數(shù)的第二個參數(shù)props是可選的,它允許像新對象設置屬性。這里就省略了,因為我們關(guān)系的繼承。
    (1)Object.getPrototypeOf(obj)
    返回obj.__proto__的值。這個函數(shù)是標準的,可以在不能直接訪問__proto__屬性的瀏覽器中使用了。
    var animal = {
     eats: true
    }
    rabbit = Object.create(animal)
    alert( Object.getPrototypeOf(rabbit) === animal ) // true
    現(xiàn)代瀏覽器允許讀取__proto__屬性值,但是不能設置。
    3. The prototype
    有一些好的跨瀏覽器的方式設置__proto__屬性,這將會使用構(gòu)造器函數(shù)(constructor functions)。記??!任何函數(shù)創(chuàng)建一個對象都是通過new關(guān)鍵字的。
    一個栗子:
    function Rabbit(name) {
     this.name = name
    }
    var rabbit = new Rabbit('John')
    alert(rabbit.name) // John
    new操作將原型的屬性設置到rabbit對象的的__proto__屬性中了。
    讓我們來看看它的原理,例如,new Rabbit 對象,而Rabbit是繼承animal 的。
    var animal = { eats: true }
    function Rabbit(name) {
     this.name = name
    }
    Rabbit.prototype = animal
    var rabbit = new Rabbit('John')
    alert( rabbit.eats ) // true, because rabbit.__proto__ == animal
    Rabbit.prototype = animal 字面量意味著:對所有由new Rabbit創(chuàng)建的對象設__proto__ = animal
    4. 跨瀏覽器 Object.create(proto)
    Object.create(prop)函數(shù)功能的強大的,因為它允許從給定的對象直接繼承。它可以由如下代碼模擬:
    function inherit(proto) {
     function F() {}
     F.prototype = proto
     return new F
    }
    inherit(animal) 與Object.create(animal)是完全等同的,返回一個空的對象,并且object.__proto__ = animal。
    一個栗子:
    var animal = { eats: true }
    var rabbit = inherit(animal)
    alert(rabbit.eats) // true
    alert(rabbit.hasOwnProperty('eats')) // false, from prototype
    來看一下它的原理是什么:
    function inherit(proto) {
     function F() {}   // (1)
     F.prototype = proto // (2)
     return new F()   // (3)
    }
    (1) 創(chuàng)建了一個新函數(shù),函數(shù)沒有向this設置任何屬性,以此`new F` 會創(chuàng)建一個空對象。
    (2) `F.prototype`被設置為proto
    (3) `new` F創(chuàng)建了一個空對象,對象的`__proto__ = F.prototype` 
    (4) Bingo! 我們得到了一個繼承`proto`的空對象
    這個函數(shù)廣泛適用于各種庫和框架之中。
    你的函數(shù)接受了一個帶有options 的對象
    /* options contains menu settings: width, height etc */
    function Menu(options) {
     // ...
    }
    你想設置某些options
    function Menu(options) {
     options.width = options.width || 300 // set default value
     // ...
    }
    。。。但是改變參數(shù)值可能會產(chǎn)生一些錯誤的結(jié)果,因為options可能會在外部代碼中使用。一個解決辦法就是克隆options對象,復制所有的屬性到一個新的對象中,在新對象中修改,
    怎樣用繼承來解決這個問題呢? P.S. options可以添加設設置,但是不能被刪除。
    Solution
    你可以繼承options,并且在它的子類的中修改或者添加新的屬性。
    function inherit(proto) {
     function F() {}
     F.prototype = proto
     return new F
    }
    function Menu(options) {
     var opts = inherit(options)
     opts.width = opts.width || 300
     // ...
    }
    所有的操作只在子對象中有效,當Menu方法結(jié)束時,外部代碼仍然可以使用沒有修改的過的options對象。delete操作在這里非常重要,如果width是一個prototype中的屬性,delete opts.width不會產(chǎn)生任何作用
    5. hasOwnProperty
    所有的對象都有hasOwnProperty函數(shù),它可以用來檢測一個屬性是否對象自身還是屬于原型
    一個栗子:
    function Rabbit(name) {
     this.name = name
    }
    Rabbit.prototype = { eats: true }
    var rabbit = new Rabbit('John')
    alert( rabbit.hasOwnProperty('eats') ) // false, in prototype
    alert( rabbit.hasOwnProperty('name') ) // true, in object
    6. Looping with/without inherited properties
    for..in循環(huán)輸出一個對象的所有屬性,包括自身的和原型的。
    function Rabbit(name) {
     this.name = name
    }
    Rabbit.prototype = { eats: true }
    var rabbit = new Rabbit('John')
    for(var p in rabbit) {
     alert (p + " = " + rabbit[p]) // outputs both "name" and "eats"
    }
    用hasOwnProperty可以過濾得到屬于對象自己的屬性:
    function Rabbit(name) {
     this.name = name
    }
    Rabbit.prototype = { eats: true }
    var rabbit = new Rabbit('John')
    for(var p in rabbit) {
     if (!rabbit.hasOwnProperty(p)) continue // filter out "eats"
     alert (p + " = " + rabbit[p]) // outputs only "name"
    }
    7. Summary
    JavaScript是通過一個特殊的屬性proto來實現(xiàn)繼承的
    當訪問一個對象的屬性時,如果解釋器不能在對象中找到,它就會去對象的原型中繼續(xù)尋找 對函數(shù)屬性來說,this指向這個對象,而不是它的原型。
    賦值obj.prop = value, 刪除delete obj.prop
    管理proto:
    Chrome和FireFox可以直接訪問對象的__proto__屬性,大多數(shù)現(xiàn)代瀏覽器支持用Object.getPrototypeOf(obj)只讀訪問。
    Object.create(proto) 可以用給定的proto生成空的子對象,或者通過如下代碼達到相同的功能:
    function inherit(proto) {
       function F() {}   
       F.prototype = proto
       return new F()  
      }
    其他方法:
    for..in循環(huán)輸出一個對象的所有屬性(包括自身的和原型的)和對象的原型鏈。
    如果一個屬性prop屬于對象obj那么obj.hasOwnProperty(prop)返回true,否則返回false。