在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 強(qiáng)大的原型和原型鏈
代碼復(fù)用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設(shè)計(jì)模式之狀態(tài)模式
JavaScript 核心(晉級(jí)高手必讀篇)
設(shè)計(jì)模式之建造者模式
JavaScript 與 DOM(上)——也適用于新手
設(shè)計(jì)模式之中介者模式
設(shè)計(jì)模式之裝飾者模式
設(shè)計(jì)模式之模板方法
設(shè)計(jì)模式之外觀模式
強(qiáng)大的原型和原型鏈
設(shè)計(jì)模式之構(gòu)造函數(shù)模式
揭秘命名函數(shù)表達(dá)式
深入理解J avaScript 系列(結(jié)局篇)
執(zhí)行上下文(Execution Contexts)
函數(shù)(Functions)
《你真懂 JavaScript 嗎?》答案詳解
設(shè)計(jì)模式之適配器模式
設(shè)計(jì)模式之組合模式
設(shè)計(jì)模式之命令模式
S.O.L.I.D 五大原則之單一職責(zé) SRP
編寫高質(zhì)量 JavaScript 代碼的基本要點(diǎn)
求值策略
閉包(Closures)
對(duì)象創(chuàng)建模式(上篇)
This? Yes,this!
設(shè)計(jì)模式之代理模式
變量對(duì)象(Variable Object)
S.O.L.I.D 五大原則之里氏替換原則 LSP
面向?qū)ο缶幊讨话憷碚?/span>
設(shè)計(jì)模式之單例模式
Function 模式(上篇)
S.O.L.I.D 五大原則之依賴倒置原則 DIP
設(shè)計(jì)模式之迭代器模式
立即調(diào)用的函數(shù)表達(dá)式
設(shè)計(jì)模式之享元模式
設(shè)計(jì)模式之原型模式
根本沒(méi)有“JSON 對(duì)象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實(shí)現(xiàn)
全面解析 Module 模式
對(duì)象創(chuàng)建模式(下篇)
設(shè)計(jì)模式之職責(zé)鏈模式
S.O.L.I.D 五大原則之開(kāi)閉原則 OCP
設(shè)計(jì)模式之橋接模式
設(shè)計(jì)模式之策略模式
設(shè)計(jì)模式之觀察者模式
代碼復(fù)用模式(推薦篇)
作用域鏈(Scope Chain)
Function 模式(下篇)
設(shè)計(jì)模式之工廠模式

強(qiáng)大的原型和原型鏈

前言

JavaScript 不包含傳統(tǒng)的類繼承模型,而是使用 prototypal 原型模型。

雖然這經(jīng)常被當(dāng)作是 JavaScript 的缺點(diǎn)被提及,其實(shí)基于原型的繼承模型比傳統(tǒng)的類繼承還要強(qiáng)大。實(shí)現(xiàn)傳統(tǒng)的類繼承模型是很簡(jiǎn)單,但是實(shí)現(xiàn) JavaScript 中的原型繼承則要困難的多。

由于 JavaScript 是唯一一個(gè)被廣泛使用的基于原型繼承的語(yǔ)言,所以理解兩種繼承模式的差異是需要一定時(shí)間的,今天我們就來(lái)了解一下原型和原型鏈。

原型

10 年前,我剛學(xué)習(xí) JavaScript 的時(shí)候,一般都是用如下方式來(lái)寫代碼:

        var decimalDigits = 2,
            tax = 5;
        function add(x, y) {
            return x + y;
        }
        function subtract(x, y) {
            return x - y;
        }
        //alert(add(1, 3));

通過(guò)執(zhí)行各個(gè) function 來(lái)得到結(jié)果,學(xué)習(xí)了原型之后,我們可以使用如下方式來(lái)美化一下代碼。

原型使用方式 1

在使用原型之前,我們需要先將代碼做一下小修改:

        var Calculator = function (decimalDigits, tax) {
            this.decimalDigits = decimalDigits;
            this.tax = tax;
        };

然后,通過(guò)給 Calculator 對(duì)象的 prototype 屬性賦值對(duì)象字面量來(lái)設(shè)定 Calculator 對(duì)象的原型。

        Calculator.prototype = {
            add: function (x, y) {
                return x + y;
            },
            subtract: function (x, y) {
                return x - y;
            }
        };
        //alert((new Calculator()).add(1, 3));

這樣,我們就可以 new Calculator 對(duì)象以后,就可以調(diào)用 add 方法來(lái)計(jì)算結(jié)果了。

原型使用方式 2

第二種方式是,在賦值原型 prototype 的時(shí)候使用 function 立即執(zhí)行的表達(dá)式來(lái)賦值,即如下格式:

Calculator.prototype = function () { } ();

它的好處在前面的帖子里已經(jīng)知道了,就是可以封裝私有的 function,通過(guò) return 的形式暴露出簡(jiǎn)單的使用名稱,以達(dá)到 public/private 的效果,修改后的代碼如下:

 Calculator.prototype = function () {
            add = function (x, y) {
                return x + y;
            },
            subtract = function (x, y) {
                return x - y;
            }
            return {
                add: add,
                subtract: subtract
            }
        } ();
        //alert((new Calculator()).add(11, 3));

同樣的方式,我們可以 new Calculator 對(duì)象以后調(diào)用 add 方法來(lái)計(jì)算結(jié)果了。

再來(lái)一點(diǎn)

分步聲明

上述使用原型的時(shí)候,有一個(gè)限制就是一次性設(shè)置了原型對(duì)象,我們?cè)賮?lái)說(shuō)一下如何分來(lái)設(shè)置原型的每個(gè)屬性吧。

var BaseCalculator = function () {
    //為每個(gè)實(shí)例都聲明一個(gè)小數(shù)位數(shù)
    this.decimalDigits = 2;
};      
//使用原型給BaseCalculator擴(kuò)展2個(gè)對(duì)象方法
BaseCalculator.prototype.add = function (x, y) {
    return x + y;
};
BaseCalculator.prototype.subtract = function (x, y) {
    return x - y;
};

首先,聲明了一個(gè) BaseCalculator 對(duì)象,構(gòu)造函數(shù)里會(huì)初始化一個(gè)小數(shù)位數(shù)的屬性 decimalDigits,然后通過(guò)原型屬性設(shè)置 2 個(gè) function,分別是 add(x,y)和 subtract(x,y),當(dāng)然你也可以使用前面提到的2種方式的任何一種,我們的主要目的是看如何將 BaseCalculator 對(duì)象設(shè)置到真正的 Calculator 的原型上。

var BaseCalculator = function() {
    this.decimalDigits = 2;
};
BaseCalculator.prototype = {
    add: function(x, y) {
        return x + y;
    },
    subtract: function(x, y) {
        return x - y;
    }
};

創(chuàng)建完上述代碼以后,我們來(lái)開(kāi)始:

var Calculator = function () {
    //為每個(gè)實(shí)例都聲明一個(gè)稅收數(shù)字
    this.tax = 5;
};       
Calculator.prototype = new BaseCalculator();

我們可以看到 Calculator 的原型是指向到 BaseCalculator 的一個(gè)實(shí)例上,目的是讓 Calculator 集成它的 add(x,y)和 subtract(x,y)這 2 個(gè) function,還有一點(diǎn)要說(shuō)的是,由于它的原型是 BaseCalculator 的一個(gè)實(shí)例,所以不管你創(chuàng)建多少個(gè) Calculator 對(duì)象實(shí)例,他們的原型指向的都是同一個(gè)實(shí)例。

var calc = new Calculator();
alert(calc.add(1, 1));
//BaseCalculator 里聲明的decimalDigits屬性,在 Calculator里是可以訪問(wèn)到的
alert(calc.decimalDigits); 

上面的代碼,運(yùn)行以后,我們可以看到因?yàn)?Calculato r的原型是指向 BaseCalculator 的實(shí)例上的,所以可以訪問(wèn)他的 decimalDigits 屬性值,那如果我不想讓 Calculator 訪問(wèn) BaseCalculator 的構(gòu)造函數(shù)里聲明的屬性值,那怎么辦呢?這么辦:

var Calculator = function () {
    this.tax= 5;
};
Calculator.prototype = BaseCalculator.prototype;

通過(guò)將 BaseCalculator 的原型賦給 Calculator 的原型,這樣你在 Calculator 的實(shí)例上就訪問(wèn)不到那個(gè) decimalDigits 值了,如果你訪問(wèn)如下代碼,那將會(huì)提升出錯(cuò)。

var calc = new Calculator();
alert(calc.add(1, 1));
alert(calc.decimalDigits);

重寫原型

在使用第三方 JS 類庫(kù)的時(shí)候,往往有時(shí)候他們定義的原型方法是不能滿足我們的需要,但是又離不開(kāi)這個(gè)類庫(kù),所以這時(shí)候我們就需要重寫他們的原型中的一個(gè)或者多個(gè)屬性或 function,我們可以通過(guò)繼續(xù)聲明的同樣的 add 代碼的形式來(lái)達(dá)到覆蓋重寫前面的 add 功能,代碼如下:

//覆蓋前面Calculator的add() function 
Calculator.prototype.add = function (x, y) {
    return x + y + this.tax;
};
var calc = new Calculator();
alert(calc.add(1, 1));

這樣,我們計(jì)算得出的結(jié)果就比原來(lái)多出了一個(gè) tax 的值,但是有一點(diǎn)需要注意:那就是重寫的代碼需要放在最后,這樣才能覆蓋前面的代碼。

原型鏈

在將原型鏈之前,我們先上一段代碼:

function Foo() {
    this.value = 42;
}
Foo.prototype = {
    method: function() {}
};
function Bar() {}
// 設(shè)置Bar的prototype屬性為Foo的實(shí)例對(duì)象
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
// 修正Bar.prototype.constructor為Bar本身
Bar.prototype.constructor = Bar;
var test = new Bar() // 創(chuàng)建Bar的一個(gè)新實(shí)例
// 原型鏈
test [Bar的實(shí)例]
    Bar.prototype [Foo的實(shí)例] 
        { foo: 'Hello World' }
        Foo.prototype
            {method: ...};
            Object.prototype
                {toString: ... /* etc. */};

上面的例子中,test 對(duì)象從 Bar.prototype 和 Foo.prototype 繼承下來(lái);因此,它能訪問(wèn) Foo 的原型方法 method。同時(shí),它也能夠訪問(wèn)那個(gè)定義在原型上的 Foo 實(shí)例屬性 value。需要注意的是 new Bar() 不會(huì)創(chuàng)造出一個(gè)新的 Foo 實(shí)例,而是重復(fù)使用它原型上的那個(gè)實(shí)例;因此,所有的 Bar 實(shí)例都會(huì)共享相同的 value 屬性。

屬性查找

當(dāng)查找一個(gè)對(duì)象的屬性時(shí),JavaScript 會(huì)向上遍歷原型鏈,直到找到給定名稱的屬性為止,到查找到達(dá)原型鏈的頂部 - 也就是 Object.prototype - 但是仍然沒(méi)有找到指定的屬性,就會(huì)返回 undefined,我們來(lái)看一個(gè)例子:

        function foo() {
            this.add = function (x, y) {
                return x + y;
            }
        }
        foo.prototype.add = function (x, y) {
            return x + y + 10;
        }
        Object.prototype.subtract = function (x, y) {
            return x - y;
        }
        var f = new foo();
        alert(f.add(1, 2)); //結(jié)果是3,而不是13
        alert(f.subtract(1, 2)); //結(jié)果是-1

通過(guò)代碼運(yùn)行,我們發(fā)現(xiàn) subtract 是安裝我們所說(shuō)的向上查找來(lái)得到結(jié)果的,但是 add 方式有點(diǎn)小不同,這也是我想強(qiáng)調(diào)的,就是屬性在查找的時(shí)候是先查找自身的屬性,如果沒(méi)有再查找原型,再?zèng)]有,再往上走,一直插到 Object 的原型上,所以在某種層面上說(shuō),用 for in 語(yǔ)句遍歷屬性的時(shí)候,效率也是個(gè)問(wèn)題。

還有一點(diǎn)我們需要注意的是,我們可以賦值任何類型的對(duì)象到原型上,但是不能賦值原子類型的值,比如如下代碼是無(wú)效的:

function Foo() {}
Foo.prototype = 1; // 無(wú)效

hasOwnProperty 函數(shù)

hasOwnProperty 是 Object.prototype 的一個(gè)方法,它可是個(gè)好東西,他能判斷一個(gè)對(duì)象是否包含自定義屬性而不是原型鏈上的屬性,因?yàn)?hasOwnProperty 是 JavaScript 中唯一一個(gè)處理屬性但是不查找原型鏈的函數(shù)。

// 修改Object.prototype
Object.prototype.bar = 1; 
var foo = {goo: undefined};
foo.bar; // 1
'bar' in foo; // true
foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true

只有 hasOwnProperty 可以給出正確和期望的結(jié)果,這在遍歷對(duì)象的屬性時(shí)會(huì)很有用。 沒(méi)有其它方法可以用來(lái)排除原型鏈上的屬性,而不是定義在對(duì)象自身上的屬性。

但有個(gè)惡心的地方是:JavaScript 不會(huì)保護(hù) hasOwnProperty 被非法占用,因此如果一個(gè)對(duì)象碰巧存在這個(gè)屬性,就需要使用外部的 hasOwnProperty 函數(shù)來(lái)獲取正確的結(jié)果。

var foo = {
    hasOwnProperty: function() {
        return false;
    },
    bar: 'Here be dragons'
};
foo.hasOwnProperty('bar'); // 總是返回 false
// 使用{}對(duì)象的 hasOwnProperty,并將其上下為設(shè)置為foo
{}.hasOwnProperty.call(foo, 'bar'); // true

當(dāng)檢查對(duì)象上某個(gè)屬性是否存在時(shí),hasOwnProperty 是唯一可用的方法。同時(shí)在使用 for in loop 遍歷對(duì)象時(shí),推薦總是使用 hasOwnProperty 方法,這將會(huì)避免原型對(duì)象擴(kuò)展帶來(lái)的干擾,我們來(lái)看一下例子:

// 修改 Object.prototype
Object.prototype.bar = 1;
var foo = {moo: 2};
for(var i in foo) {
    console.log(i); // 輸出兩個(gè)屬性:bar 和 moo
}

我們沒(méi)辦法改變 for in 語(yǔ)句的行為,所以想過(guò)濾結(jié)果就只能使用 hasOwnProperty 方法,代碼如下:

// foo 變量是上例中的
for(var i in foo) {
    if (foo.hasOwnProperty(i)) {
        console.log(i);
    }
}

這個(gè)版本的代碼是唯一正確的寫法。由于我們使用了 hasOwnProperty,所以這次只輸出 moo。如果不使用 hasOwnProperty,則這段代碼在原生對(duì)象原型(比如 Object.prototype)被擴(kuò)展時(shí)可能會(huì)出錯(cuò)。

總結(jié):推薦使用 hasOwnProperty,不要對(duì)代碼運(yùn)行的環(huán)境做任何假設(shè),不要假設(shè)原生對(duì)象是否已經(jīng)被擴(kuò)展了。

總結(jié)

原型極大地豐富了我們的開(kāi)發(fā)代碼,但是在平時(shí)使用的過(guò)程中一定要注意上述提到的一些注意事項(xiàng)。