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

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

強大的原型和原型鏈

前言

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

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

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

原型

10 年前,我剛學習 JavaScript 的時候,一般都是用如下方式來寫代碼:

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

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

原型使用方式 1

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

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

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

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

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

原型使用方式 2

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

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

它的好處在前面的帖子里已經(jīng)知道了,就是可以封裝私有的 function,通過 return 的形式暴露出簡單的使用名稱,以達到 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 對象以后調(diào)用 add 方法來計算結(jié)果了。

再來一點

分步聲明

上述使用原型的時候,有一個限制就是一次性設(shè)置了原型對象,我們再來說一下如何分來設(shè)置原型的每個屬性吧。

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

首先,聲明了一個 BaseCalculator 對象,構(gòu)造函數(shù)里會初始化一個小數(shù)位數(shù)的屬性 decimalDigits,然后通過原型屬性設(shè)置 2 個 function,分別是 add(x,y)和 subtract(x,y),當然你也可以使用前面提到的2種方式的任何一種,我們的主要目的是看如何將 BaseCalculator 對象設(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)建完上述代碼以后,我們來開始:

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

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

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

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

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

通過將 BaseCalculator 的原型賦給 Calculator 的原型,這樣你在 Calculator 的實例上就訪問不到那個 decimalDigits 值了,如果你訪問如下代碼,那將會提升出錯。

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

重寫原型

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

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

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

原型鏈

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

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

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

屬性查找

當查找一個對象的屬性時,JavaScript 會向上遍歷原型鏈,直到找到給定名稱的屬性為止,到查找到達原型鏈的頂部 - 也就是 Object.prototype - 但是仍然沒有找到指定的屬性,就會返回 undefined,我們來看一個例子:

        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

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

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

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

hasOwnProperty 函數(shù)

hasOwnProperty 是 Object.prototype 的一個方法,它可是個好東西,他能判斷一個對象是否包含自定義屬性而不是原型鏈上的屬性,因為 hasOwnProperty 是 JavaScript 中唯一一個處理屬性但是不查找原型鏈的函數(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é)果,這在遍歷對象的屬性時會很有用。 沒有其它方法可以用來排除原型鏈上的屬性,而不是定義在對象自身上的屬性。

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

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

當檢查對象上某個屬性是否存在時,hasOwnProperty 是唯一可用的方法。同時在使用 for in loop 遍歷對象時,推薦總是使用 hasOwnProperty 方法,這將會避免原型對象擴展帶來的干擾,我們來看一下例子:

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

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

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

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

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

總結(jié)

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