qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請訪問 http://qaseven.github.io/

          JavaScript原型和繼承

           那么原型有什么用呢?

            先了解下new運算符,如下:

          var a1 = new A;
          var a2 = new A;

            這是通過構(gòu)造函數(shù)來創(chuàng)建對象的方式,那么創(chuàng)建對象為什么要這樣創(chuàng)建而不是直接var a1 = {};呢?這就涉及new的具體步驟了,這里的new操作可以分成三步(以a1的創(chuàng)建為例):

            1、新建一個對象并賦值給變量a1:var a1 = {};

            2、把這個對象的[[Prototype]]屬性指向函數(shù)A的原型對象:a1.[[Prototype]] = A.prototype

            3、調(diào)用函數(shù)A,同時把this指向1中創(chuàng)建的對象a1,對對象進(jìn)行初始化:A.apply(a1,arguments)

            其結(jié)構(gòu)圖示如下:

            從圖中看到,無論是對象a1還是a2,都有一個屬性保存了對函數(shù)A的原型對象的引用,對于這些對象來說,一些公用的方法可以在函數(shù)的原型中找到,節(jié)省了內(nèi)存空間。

            四、原型鏈

            了解了new運算符以及原型的作用之后,一起來看看什么是[[Prototype]]?以及對象如何沿著這個引用來進(jìn)行屬性的查找?

            在js的世界里,每個對象默認(rèn)都有一個[[Prototype]]屬性,其保存著的地址就構(gòu)成了對象的原型鏈,它是由js編譯器在對象 被創(chuàng)建 的時候自動添加的,其取值由new運算符的右側(cè)參數(shù)決定:當(dāng)我們var object1 = {};的時候,object1的[[Prototype]]就指向Object構(gòu)造函數(shù)的原型對象,因為var object1 = {};實質(zhì)上等于var object = new Object();(原因可參照上述對new A的分析過程)。

            對象在查找某個屬性的時候,會首先遍歷自身的屬性,如果沒有則會繼續(xù)查找[[Prototype]]引用的對象,如果再沒有則繼續(xù)查找[[Prototype]].[[Prototype]]引用的對象,依次類推,直到[[Prototype]].….[[Prototype]]為undefined(Object的[[Prototype]]就是undefined)

            如上圖所示:

          //我們想要獲取a1.fGetName
            alert(a1.fGetName);//輸出undefined
            //1、遍歷a1對象本身
            //結(jié)果a1對象本身沒有fGetName屬性
            //2、找到a1的[[Prototype]],也就是其對應(yīng)的對象A.prototype,同時進(jìn)行遍歷
            //結(jié)果A.prototype也沒有這個屬性
            //3、找到A.prototype對象的[[Prototype]],指向其對應(yīng)的對象Object.prototype
            //結(jié)果Object.prototype也沒有fGetNam

            簡單說就是通過對象的[[Prototype]]保存對另一個對象的引用,通過這個引用往上進(jìn)行屬性的查找,這就是原型鏈。前幾天看了《再談js面向?qū)ο缶幊獭罚?dāng)時就請教哈大神,發(fā)現(xiàn)文章有的地方可能會造成誤導(dǎo)(或者說和ECMA有出入),后來自己翻一翻ECMA,總算找到“標(biāo)準(zhǔn)”的理解……

            本文適合初學(xué)者,特別是對構(gòu)造函數(shù)、原型和原型鏈概念比較模糊的,大牛請路過,好了,讓我們一步步來看看 js 的原型(鏈)到底有多神秘……

            一、函數(shù)創(chuàng)建過程

            在了解原型鏈之前我們先來看看一個函數(shù)在創(chuàng)建過程中做了哪些事情,舉一個空函數(shù)的例子:

          function A() {};

            當(dāng)我們在代碼里面聲明這么一個空函數(shù),js解析的本質(zhì)是(膚淺理解有待深入):

            1、創(chuàng)建一個對象(有constructor屬性及[[Prototype]]屬性),根據(jù)ECMA,其中[[Prototype]]屬性不可見、不可枚舉

            2、創(chuàng)建一個函數(shù)(有name、prototype屬性),再通過prototype屬性 引用 剛才創(chuàng)建的對象

            3、創(chuàng)建變量A,同時把函數(shù)的 引用 賦值給變量A

            如下圖所示:

          (注意圖中都是“ 引用 ”類型)

            每個函數(shù)的創(chuàng)建都經(jīng)歷上述過程。

            二、構(gòu)造函數(shù)

            那么什么是構(gòu)造函數(shù)呢?

            按照ECMA的定義

            Constructor is a function that creates and initializes the newly created object.

            構(gòu)造函數(shù)是用來新建同時初始化一個新對象的函數(shù)。

            什么樣的函數(shù)可以用來創(chuàng)建同時初始化新對象呢?答案是:任何一個函數(shù),包括空函數(shù)。

            所以,結(jié)論是:任何一個函數(shù)都可以是構(gòu)造函數(shù)。

            三、原型

            根據(jù)前面空函數(shù)的創(chuàng)建圖示,我們知道每個函數(shù)在創(chuàng)建的時候都自動添加了prototype屬性,這就是函數(shù)的原型,從圖中可知其實質(zhì)就是對一個對象的引用(這個對象暫且取名原型對象)。

            我們可以對函數(shù)的原型對象進(jìn)行操作,和普通的對象無異!一起來證實一下。

            圍繞剛才創(chuàng)建的空函數(shù),這次給空函數(shù)增加一些代碼:

          functionA() {
           this.width = 10;
           this.data = [1,2,3];
           this.key ="this is A";
           }
           A._objectNum = 0;//定義A的屬性
           A.prototype.say =function(){//給A的原型對象添加屬性
           alert("hello world")
           }

            第7~9行代碼就是給函數(shù)的原型對象增加一個say屬性并引用一個匿名函數(shù),根據(jù)“函數(shù)創(chuàng)建”過程,圖解如下:

          (灰色背景就是在空函數(shù)基礎(chǔ)上增加的屬性)

            簡單說原型就是函數(shù)的一個屬性,在函數(shù)的創(chuàng)建過程中由js編譯器自動添加。

           五、繼承

            有了原型鏈的概念,就可以進(jìn)行繼承。

          function B() {};

            這個時候產(chǎn)生了B的原型B.prototype

            原型本身就是一個Object對象,我們可以看看里面放著哪些數(shù)據(jù)

            B.prototype 實際上就是 {constructor : B , [[Prototype]] : Object.prototype}

            因為prototype本身是一個Object對象的實例,所以其原型鏈指向的是Object的原型

          B.prototype = A.prototype;//相當(dāng)于把B的prototype指向了A的prototype;這樣只是繼承了A的prototype方法,A中的自定義方法則不繼承
          B.prototype.thisisb = "this is constructor B";//這樣也會改變a的prototype

            但是我們只想把B的原型鏈指向A,如何實現(xiàn)?

            第一種是通過改變原型鏈引用地址

          B.prototype.__proto__ = A.prototype;

            ECMA中并沒有__proto__這個方法,這個是ff、chrome等js解釋器添加的,等同于EMCA的[[Prototype]],這不是標(biāo)準(zhǔn)方法,那么如何運用標(biāo)準(zhǔn)方法呢?

            我們知道new操作的時候,實際上只是把實例對象的原型鏈指向了構(gòu)造函數(shù)的prototype地址塊,那么我們可以這樣操作

          B.prototype = new A();

            這樣產(chǎn)生的結(jié)果是:

            產(chǎn)生一個A的實例,同時賦值給B的原型,也即B.prototype 相當(dāng)于對象 {width :10 , data : [1,2,3] , key : "this is A" , [[Prototype]] : A.prototype}

            這樣就把A的原型通過B.prototype.[[Prototype]]這個對象屬性保存起來,構(gòu)成了原型的鏈接

            但是注意,這樣B產(chǎn)生的對象的構(gòu)造函數(shù)發(fā)生了改變,因為在B中沒有constructor屬性,只能從原型鏈找到A.prototype,讀出constructor:A

          var b = new B;
          console.log(b.constructor);//output A

            所以我們還要人為設(shè)回B本身

          B.prototype.constructor = B;
            //現(xiàn)在B的原型就變成了{(lán)width :10 , data : [1,2,3] , key : "this is A" , [[Prototype]] : A.prototype , constructor : B}
            console.log(b.constructor);//output B
            //同時B直接通過原型繼承了A的自定義屬性width和name
            console.log(b.data);//output [1,2,3]
            //這樣的壞處就是
            b.data.push(4);//直接改變了prototype的data數(shù)組(引用)
            var c = new B;
            alert(c.data);//output [1,2,3,4]
            //其實我們想要的只是原型鏈,A的自定義屬性我們想在B中進(jìn)行定義(而不是在prototype)
            //該如何進(jìn)行繼承?
            //既然我們不想要A中自定義的屬性,那么可以想辦法把其過濾掉
            //可以新建一個空函數(shù)
            function F(){}
            //把空函數(shù)的原型指向構(gòu)造函數(shù)A的原型
            F.prototype = A.prototype;
            //這個時候再通過new操作把B.prototype的原型鏈指向F的原型
            B.prototype = new F;
            //這個時候B的原型變成了{(lán)[[Prototype]] : F.prototype}
            //這里F.prototype其實只是一個地址的引用
            //但是由B創(chuàng)建的實例其constructor指向了A,所以這里要顯示設(shè)置一下B.prototype的constructor屬性
            B.prototype.constructor = B;
            //這個時候B的原型變成了{(lán)constructor : B , [[Prototype]] : F.prototype}

            圖示如下,其中紅色部分代表原型鏈:

            作為初學(xué)者淺陋的理解,本文目的在于更具象地去理解js的面向?qū)ο螅杪┲幷堉刚?/p>

          posted on 2012-06-04 10:17 順其自然EVO 閱讀(154) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          <2012年6月>
          272829303112
          3456789
          10111213141516
          17181920212223
          24252627282930
          1234567

          導(dǎo)航

          統(tǒng)計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 东阿县| 遂平县| 临洮县| 丰镇市| 盐城市| 禄丰县| 云浮市| 克拉玛依市| 教育| 白山市| 孟州市| 余庆县| 大姚县| 宁陕县| 额敏县| 明星| 平陆县| 清苑县| 武乡县| 淮南市| 太仓市| 屏东市| 新乡市| 临沂市| 大港区| 皮山县| 英德市| 东海县| 昌都县| 庆安县| 仲巴县| 霸州市| 灌南县| 武汉市| 合江县| 航空| 土默特左旗| 吴堡县| 蒲城县| 海口市| 丹凤县|