[學習筆記] JavaScript 原型

今天研究了一下js的原型,把本身的理解寫到這裏,若有不正確的地方,還望指出,在此先謝過啦~javascript

什麼是原型?

原型是一個對象。全部對象都有原型。任何一個對象也均可以成爲其餘對象的原型。
每一個原型都有一個 constructor 屬性指向其構造函數。html

怎麼訪問原型?

一個對象的原型被對象內部的 [[Prototype]] 屬性所持有。
一句話就是,全部對象都有原型,其原型是該對象的內部屬性。那麼有哪些方法能夠訪問到這個內部屬性呢?java

  • ECMAScript 5 增長了方法 Object.getPrototypeOf() ,用於返回對象 [[Prototype]] 的值。IE 9+、Firefox 3.5+、Safari 5+、Opera 12+ 和 Chrome 都實現了該方法。
  • 除了IE,其餘瀏覽器都支持非標準的訪問器 __proto__
  • 若是這二者都不起做用,咱們就須要經過對象的構造函數找它的原型屬性了。
var a = {};
Object.getPrototypeOf(a);
a.__proto__;
a.constructor.prototype;

函數的 prototype 屬性

不知親剛剛有沒有注意到訪問 [[Prototype]] 的最後一個方法 a.constructor.prototype; ,直接使用了構造函數的 prototype屬性。git

在這裏要多說兩句,js中函數也是對象,因此函數也有原型,其原型也和其餘對象同樣,能夠經過 Object.getPrototypeOf()__proto__ 訪問。可是建立對象時,咱們每每要使用構造函數 (constructor) 的原型,爲了用起來方便,就給構造函數添加了 prototype 屬性,用於直接訪問其原型。因爲全部的函數均可以成爲構造函數,因此就造就了函數有那麼一點點特(you)殊(shi)—— 一個函數能夠經過其 prototype 屬性直接訪問其原型,或者說 一個函數的 prototype 屬性指向其原型對象瀏覽器

若是僅僅只是由於一個實例而使用原型是沒有多大意義的,這和直接添加屬性到這個實例是同樣的。原型真正魅力體如今多個實例共用一個原型,原型對象的屬性一旦定義,就能夠被多個引用它的實例所繼承,這種操做在性能和維護方面其意義是不言自明的。閉包

這也是構造函數存在的緣由,構造函數提供了一種方便的跨瀏覽器機制,這種機制容許在建立實例時爲實例提供一個通用的原型。函數

原型語法

在使用原型前,先寫一下構造函數部分。性能

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

分步聲明

分開設置原型的每一個屬性。this

Calculator.prototype.add = function (x, y) {
    return x + y;
};

Calculator.prototype.subtract = function (x, y) {
    return x - y;
};

這樣,在new Calculator對象之後,就能夠調用add方法來計算結果了。
此時, Calculator.prototypeconstructor 屬性指向 Calculator。即:
Calculator.prototype.constructor = Calculator.net

更簡單的原型語法

經過給Calculator的prototype屬性賦值對象字面量來設定Calculator對象的原型。

Calculator.prototype = {
    add: function (x, y) {
        return x + y;
    },

    subtract: function (x, y) {
        return x - y;
    }
};

上面的代碼中,將 Calculator.prototype 賦值爲一個以對象字面量形式建立的新對象。最終結果相同,可是,此時 constructor 屬性再也不指向 Calculator ,即 Calculator.prototype.constructor != Calculator

每建立一個函數,就會同時建立它的 prototype 對象,這個對象會自動得到 constructor 屬性。而咱們這裏使用的語法,本質上徹底重寫了默認的 prototype 對象,所以 constructor 屬性也就變成了新對象的 constructor 屬性 —— 指向 Object 構造函數。

若是 constructor 的值很重要,能夠向下面同樣設定:

Calculator.prototype = {

    constructor: Calculator, // 修復constructor 屬性

    add: function (x, y) {
        return x + y;
    },

    subtract: function (x, y) {
        return x - y;
    }
};

其餘方式

使用function當即執行的表達式來爲prototype賦值,格式以下:

Calculator.prototype = function () {
    var add = function (x, y) {
        return x + y;
    },

    var subtract = function (x, y) {
        return x - y;
    }

    return {
        add: add,
        subtract: subtract
    }
} ();

原型的動態性

var friend = new Person();

Person.prototype.sayHi = function () {
    alert("hi");
};

friend.sayHi(); // "hi" (沒有問題)

在以上代碼中,即便 friend 是在添加新方法以前建立的,但它仍然能夠訪問這個新方法。其緣由能夠歸結爲實例與原型之間的鬆散鏈接關係。當咱們調用 friend.sayHi() 時,會首先在實例中搜索名爲 sayHi 的屬性,在沒有找到的狀況下,會繼續搜索原型。由於實例實例與原型之間鏈接是一個指針,而非副本,所以能夠在原型中找到新的 sayHi 屬性並返回保存在那裏的函數。

能夠隨時爲原型添加屬性和方法,而且修改能當即在全部對象實例中反映出來,但若是重寫了原型對象,那結果就不同了……

function Person () {
}

var friend = new Person();

Person.prototype = {
    constructor: Person,
    name: "Nicholas",
    age: 29,
    job: "software Engineer",
    sayHi: function () {
        alert("hi");
    }
};

friend.sayHi(); // error

在這個例子中,先建立了 friend 實例,而後又重寫其原型對象。在調用 friend.sayHi() 時發生錯誤,由於friend 指向的原型中不包含以該名字命名的屬性。

重寫原型對象切斷了現有原型與任何以前已經存在的對象實例之間的聯繫,這些實例仍然引用的是最初的原型。

一切都是對象

這部分與原型沒什麼大關係,只是看到了覺有幫助,就插到這裏了,莫要見怪 :)

固然,也不是全部的都是對象,值類型就不是對象。

舉個不太容易理解的例子,函數做爲對象時,其屬性仍是函數的例子。

var fn = function () {
    alert(100);
};

fn.a = 10;
fn.b = function () {
    alert(123);
};
fn.c = {
    name: "福布斯",
    year: 1968
};

上段代碼中,函數就做爲對象被賦值了a、b、c三個屬性,第二個屬性值就是函數,這個有用嗎?
能夠看看jQuery源碼。

在jQuery源碼中,「jQuery」或者「$」,這個變量實際上是一個函數,不信能夠用 typeof 驗證一下。

console.log(typeof $);  // function
console.log($.trim(" ABC "));

驗明正身!的確是個函數。而常用的 $.trim() 也是個函數。

很明顯,這就是在 $ 或者 jQuery 函數上加了一個 trim 屬性,屬性值是函數,做用是截取先後空格。要習慣的把js中的一切看做對象,只要是對象,就是屬性的集合,屬性是鍵值對的形式。

參考資料

相關文章
相關標籤/搜索