JAVASCRIPT OBJECTS 詳解

原創不易,但願能關注下咱們,再順手點個贊~~

做者簡介:堂主,2006 年開始沉迷前端領域。工做歷經淘寶前端開發團隊、蘑菇街前端團隊,現爲政採雲前端團隊(ZooTeam)負責人,好煙很差酒,半個茶人。前端

堂主.png

ECMAscript 說明文檔對這門語言的定義是「一門適於在宿主環境中執行計算及操做計算對象的面向對象的編程語言」。簡單的說,JavaScript是一門面向對象(OO)的語言。web

面向對象講究的是專一於對象自己——它們的結構,它們互相間是如何影響的。本文是@堂主 對《Pro JavaScript with Mootools》一書的第三章 Object 部分的翻譯,最先譯於 2012 年。由於面向對象編程自己已經超出了本書的敘述範圍,因此咱們在本章所談的只是 JavsScript 自身在面向對象方面的那些特色。編程

本篇譯文字數約 3 萬字,各位看官如發現翻譯錯誤或有優化建議,歡迎留言指教,共同成長。另外,一樣的建議——非本土產技術類書籍,建議仍是優先閱讀英文原版。數組

JavaScript是基於原型的 JavaScript is Prototypal(-ish)

全部面向對象的語言在其核心都會對對象進行處理,對象的建立及構造的過程將大部分的面嚮對象語言分爲2個陣營:app

  1. 基於類 (Classical or class-based) 的面嚮對象語言採用類來建立對象。類是一個爲建立對象提供藍本的特殊數據類型。在一個基於類的面向對象的語言中,咱們經過建立類來定義一個對象的結構,並經過建立該類的實例來創造這個對象自己。這一過程被稱爲實例化 (instantiation)。編程語言

  2. 基於原型 (Prototypal or prototype-based) 的面嚮對象語言沒有類的概念,它以其餘的對象對藍本。在一個基於原型的語言中,prototype 是一個由你建立、體現着你指望的結構的對象,這個對象以後會成爲其餘對象建立所參照的藍本。經過拷貝其自己 prototype 屬性來建立實例的方式被成爲克隆(cloning)。對一個純粹的原型語言而言,任何一個對象都能被做爲建立其餘對象的原型。函數

JavaScript 是一本基於原型的語言:這裏沒有類的概念,全部對象都是由其餘對象建立而來。不過,JavaScript 不是一門純粹的原型語言,在本章的後面咱們會看到 JavaScript 還保留着一些基於類的殘存特徵。若是你已經對面向對象的語言很熟悉了,你極可能會以爲 JavaScript 是奇異的,由於相對你以前的那些面向對象的經驗,這門語言的怪異特質是如此明顯。post

哈哈,先別打退堂鼓:JavaScript,一門面向對象的語言,由於兼備了基於類和原型的特徵,使得它具有了處理複雜、龐大應用的實力。學習

一門關於對象的語言 (A Language of Objects)

從本質上講,一個 JavaScript 的對象就是一些名值對(key-value pairs)的聚合體。相比於簡單的如字符串、數字等基本數據類型而言,JavaScript 對象是一種混合的複合數據類型。對象內的每個名值對被稱爲一個屬性(property),key 被稱爲屬性名(property name),value 被稱爲屬性值(property value)優化

屬性名一貫是字符串,而屬性值則多是任何數據類型:字符串、數字、布爾值或者是複合型的數據類型如數組、函數或對象。儘管 JavaScript 並未將對象屬性值可承載的數據類型作任何區分,但咱們仍是習慣的將用函數類型做爲值的屬性稱爲方法(methods)以與其餘值爲非函數類型的屬性做區分。爲了不困惑,在後面的探討中咱們採用以下的慣例:以函數爲值的屬性稱之爲「方法」,其餘的統稱爲「屬性」。若是咱們所指的同時可能爲一個對象的方法或屬性,那咱們會稱它們爲這個對象的成員(members)

注意:在面對 JavaScript 是一門一等對象語言這個現實時,屬性和方法間的區分會顯得不那麼清晰。本章的觀點是:不論值是什麼,一個對象內的成員都是一個屬性,甚至是函數自己也能夠被做爲值來傳遞。

一個對象能夠擁有多少屬性是沒有數量上的限制的,甚至一個對象能夠擁有0個屬性(此時表示這是一個空對象)。依照其用途,一個對象能夠在某些狀況下被稱爲是一個哈希(hash)、字典(dictionary) 或表(table),折射出其結構是一組名值對。不過咱們仍是堅持在討論時採用「對象」這一稱呼。

建立一個對象最簡單的辦法是使用對象字面量(object literal)

// 一個對象字面量
var person = {
    name : 'Mark',
    age : 23
};
複製代碼

這裏咱們建立了一個具備2個屬性的新對象,一個鍵名是 name,另外一個鍵名是 age,這個對象被存儲在 person 變量裏——這爲咱們提供了一個有2個成員的 person 對象。注意雖然 key 是字符串但咱們並將其包含在引號裏,只要是非保留字的有效標識符,在 JavaScript 中這就是允許的。對於下面的狀況,咱們須要用引號將 key 圍起來:

// 一個對象字面量
var person = {
    'name of the person' : 'Mark',
    'age of the person' : 23
};
複製代碼

爲了引用一個對象中的成員,咱們可使用點記法(dot notation),這可使咱們經過在屬性名標識符以前置入一個句點來引用其對應的屬性值;咱們還可使用括號記法(bracket notation),這個方法經過爲字符串的屬性名標識符圍上一個中括號 [ ] 來達到一樣的引用屬性值的目的。

// 一個對象字面量
var person = {
    name : 'Mark',
    age : 23
};

// 點記法
console.log(person.name); // 'Mark'

// 括號記法
console.log(person['age']); // 23
複製代碼

實際上點記法是括號記法的快捷方式、語法糖(syntactic sugar),實際中大多數狀況下咱們都使用點記法。固然,點記法被限制在標識符是適當的情形下。在其餘狀況中,你須要使用括號記法。

var person = {
    'name of the person' : 'Mark',
    'age of the person' : 23
};

console.log(person['name of the person']); // 'Mark'
複製代碼

當你不是採用一個字符串 key 而是採用一個對象來引用的時候,也須要使用括號記法

var person = {
    name : 'Mark',
    age : 23
};

var key = 'name';

console.log(person[key]); // 'Mark'
複製代碼

訪問一個不存在的對象成員會返回 undefined。

var person = {};

console.log(person.name); // undefined
複製代碼

同時咱們還能夠在一個對象建立以後動態的爲其新增成員或改變某個成員的屬性值。

var person = {name : 'Mark'};

person.name = 'Joseph';
console.log(person.name); // 'Joseph'

console.log(person.age); // undefined
person.age = 23;
console.log(person.age); // 23
複製代碼

你能夠經過爲對象成員賦值爲函數來建立方法。

var person = {
    name : 'Mark',
    age : 23,
    sayName : function() {
        console.log(this.name);
    }
};

console.log(typeof person.sayName); // 'function'
person.sayName(); // 'Mark'

person.sayAge = function() {
    console.log(this.age); // 23
};

console.log(typeof person.sayAge); // 'function'
person.sayAge(); // 23
複製代碼

你應該會注意到咱們在方法中引用 person 對象的 name、age 屬性使用的是 this.name 和 this.age 的方式。回顧一下咱們前一章討論過的部分,你會知道 this 關鍵字指的是包含方法等屬性的對象的自己,因此在本例中 this 指代的就是 person 對象。

對象的構建模塊(The Buliding Blocks of Objects)

雖然對象字面量是一種建立對象的快捷方式,但它並不能完整的展現 JavaScript 面向對象的優點。好比,若是你須要建立 30 個 person 對象,那麼對象字面量會是一種很是耗時的方式——爲每個對象都寫一個對象字面量是不切實際的。爲了更有效率,咱們須要爲咱們須要的對象建立一個藍本結構,並使用這個藍原本創造對象的實例。

在基於類的面嚮對象語言中,咱們能夠爲建立一個類來明確對象須要的結構;在基於原型的面嚮對象語言中,咱們能夠簡化的建立一個 Person 對象來提供這個結構,以後克隆這個對象來得到咱們須要的新對象。

構造函數(Constructor Functions)

第一種途徑是使用 JavaScript 的**構造函數(constructor functions,or constructors)**方式。對象字面量是對這種方式的一種簡化版。下面2個對象是等價的。

// 使用對象字面量
var personA = {
    name : 'Mark',
    age : 23
};

// 使用構造器
var personB = new Object();
personB.name = 'Mark';
personB.age = 23;
複製代碼

Object 函數是咱們的構造器,採用 「var personB = new Object()」 方式和採用 「var personA = {}」 是等價的。採用 new Object(),咱們建立了一個空對象,這個空對象被成爲是 Object 的一個實例。

Object constructor 因其表明着JavaScript 的基礎對象而顯得不同凡響:全部的對象,不論這些對象是由哪一個 constructor 建立出來的,本質上都是 Object 的實例。使用 instanceof 操做符能夠判斷一個對象是不是一個 constructor 的實例。

// 使用對象字面量
var personA = {};

// 使用構造器
var personB = new Object();

// 檢測上面2個對象是不是Object的實例
conlose.log(personA instanceof Object) // true
conlose.log(personB instanceof Object) // true
複製代碼

每個對象都有一個名字爲 constructor 的特殊屬性,其是對建立該對象自己的 constructor 函數的引用。在咱們上面的簡單例子中,constructor的屬性值是 Object constructor:

// 使用對象字面量
var personA = {};

// 使用構造器
var personB = new Object();

// 檢測是否使用了Object的constructor
conlose.log(personA.constructor == Object) // true
conlose.log(personB.constructor == Object) // true
複製代碼

就像它的名字所示,constructor 函數,顯然的,是一個函數。事實上,任何一個 JavaScript 函數都能被用做構造函數。這是JavaScript 對象處理方面的一個獨特的地方。不一樣於在對象實例化時建立一個新的構造,Javascript 是依賴於現有的構造。

固然,你沒必要將你創造的全部函數都用做構造函數。大部分狀況下,你會爲你的類建立一個專用於構造目的的函數。一個構造函數和其餘函數同樣——除了自身細節上有些許區別——慣常的作法是將函數名首字母大寫以表示其存在目的是做爲一個構造函數。

// 一個person構造函數
var Person = {};

// 以正規函數方式使用Person
var result = Person();
console.log(result); // undefined

// 以構造器函數調用Person
var person = new Person();

console.log(typeof person); // 'object'
console.log(person instanceof Person); // true
console.log(person.constructor == Person); // true
複製代碼

咱們經過一個簡單的空函數來建立一個構造器。當Person函數被採用常規方式調用時,它返回 undefined。當咱們在調用以前加上一個 new 關鍵字的時候,狀況就變了:它返回了一個新對象。配合使用 new 關鍵字可使一個函數被做爲構造器使用進而產生一個對象的實例化。

在咱們的例子中,new Person() 返回了一個空對象,這和使用 new Object() 的返回是同樣的。這裏的區別是,返回的對象不僅僅是 Object 的實例,同時也是 Person 的實例,而且該對象的 constructor 屬性如今指向的是新的 Person 對象而非 Object 對象。不過返回的總歸仍是一個空對象。

回顧一下上一章講到的,函數內的 this 關鍵字指向的是一個對象。在這個關於咱們的 Person 函數的例子中,當它被做爲平臺函數調用時,引發被定義在全局做用域中,因此 this 關鍵字指向的對象是 global 對象。但當 Person 被做爲一個構造函數時,狀況就變了。this 關鍵字再也不指向 global 對象,而是指向新建立出來的那個對象:

// 一個全局變量
var fruit = 'banana';

// 咱們的constructor
var Person = function() {
    console.log(this.fruit);
};

// 被做爲普通函數使用時
fruit(); // 'banana'

// 被做爲constructor使用時
new Person(); // undefinded
複製代碼

最後一行的代碼輸出的是 undefined,這是由於 this.fruit 再也不指向一個已存在的變量標識符。new 關鍵字的做用就是建立一個新對象,並將構造函數內的 this 指向這個新建立的對象。

在本章的開始部分,咱們遇到了一個使用對象字面量建立多個對象的問題——咱們須要一個方法來批量的建立對象的拷貝而非一個個的去敲代碼把它們全寫一遍。如今咱們知道構造函數能夠作到這一點,而且其內的 this 關鍵字指向的就是新建立的對象。

var Person = function(name, age) {
    this.name = name;
    this.age = age;
};

var mark = new Person('Mark', 23);
var joseph = new Person('Joseph', 22);
var andrew = new Person('Andrew', 21);

console.log(mark.name); // 'Mark'
console.log(joseph.age); // 22
console.log(andrew.name + ', ' + andrew.age); // 'Andrew, 21'
複製代碼

你會注意到這裏咱們對構造函數進行了一些修改使其能夠接受參數。這是由於構造函數和普通函數同樣,只不過其內部的 this 關鍵字指向的是新建立的對象。當 new Person 被執行的時候,一個新的對象被建立出來,而且 Person 函數被調用。在構造函數內部,參數 name、age 被設置爲同名對象屬性的值,以後這個對象被返回。

使用構造函數能夠很輕鬆的建立出和構造函數具備相似結構的新對象,而且不用費事的每次都爲新對象用字面量的方式書寫一遍結構。你能夠在編碼的開始階段就建立一個定義了基本結構的構造函數,這對你之後爲實例化的對象們增長新的屬性或方法早晚會有幫助。

var Person = function(name, age) {
    this.name = name;
    this.age = age;
    this.log = function() {
        console.log(this.name + ', ' + this.age);
    }
};

var mark = new Person('Mark', 23);
var joseph = new Person('Joseph', 22);
var andrew = new Person('Andrew', 21);

mark.log(); // 'Mark, 23'
joseph.log(); // 'Joseph, 22'
andrew.log(); // 'Andrew, 21'
複製代碼

這裏你會看到咱們在構造函數裏新增了一個 log 方法,該方法會將對象的 name 和 age 信息打印出來。這樣就避免了在對象實例化以後還要手工的爲沒一個對象增長 log 方法。

原型(Prototypes)

看起來彷佛構造函數已是關於 JavaScript 對象建立的終極知識點了,但請注意,還沒結束呢!咱們如今還只說了二分之一而已。若是咱們把本身侷限在僅僅使用構造函數的範圍,那麼很快就會遇到新問題。

問題之一就是代碼組織。在上一節的開頭,咱們想有一種簡單的方法能夠批量建立具備 name 和 age 屬性的 person 對象,而且指望同時具有 setName、getName、setAge、getAge 等方法。若是按照咱們如今的需求,沿用上一節的方式,最終咱們的代碼會變成下面這個樣子:

var Person = function(name, age) {

    // 屬性
    this.name = name;
    this.age = age;

    // 方法
    this.setName = function(name) {
        this.name = name;
    }

    this.getName = function() {
        return this.name;
    }

    this.setAge = function(age) {
        this.age = age;
    }

    this.getAge = function() {
        return this.age;
    }

};
複製代碼

如今咱們的 Person 構造器開始變得腫脹了——這還僅是包含了2個屬性和4個方法的時候!想一想若是你要建立一個很複雜的應用,那構造函數得變得多麼龐大!

另外一個問題是可擴展性。假設咱們有以下代碼:

// constructor.js
var Person = function(name, age) {
    this.name = name;
    this.age = age;
    this.log = function() {
        console.log(this.name + ', ' + this.age);
    }
};

// program.js
var mark = new Person('Mark', 23);
mark.log(); // 'Mark, 23'
複製代碼

如今Person是在外部引入的一個JS文件中定義的,咱們在這個頁面裏引入定義了 Person 構造函數的 constructor.js 文件,並實例化了一個 mark 對象。如今問題來了,由於咱們如今沒法修改構造函數自己,那該如何爲實例增長 setName、getName、setAge、getAge 等方法呢?

解決方案彷佛很簡單,既然不能經過修改構造函數來增長方法,那就直接給實例增長方法不就好了麼~很快隨着鍵盤的敲打,代碼變成了下面這個樣子。

// constructor.js
var Person = function(name, age) {
    this.name = name;
    this.age = age;
    this.log = function() {
        console.log(this.name + ', ' + this.age);
    }
};

// program.js
var mark = new Person('Mark', 23);
mark.log(); // 'Mark, 23'

mark.getName = function() {return this.name;}
mark.getAge = function() {return this.age;}

mark.getName(); // 'Mark'
mark.getAge(); // 23

var joseph = new Person('Joseph', 22);
mark.log(); // 'Joseph, 22'

// 下面的代碼會引發報錯
joseph.getName();
joseph.getAge();
複製代碼

雖然咱們成功的爲 mark 實例添加了須要的方法,但 joseph 實例並不能一樣得到這些方法。此時咱們遇到了和使用對象字面量同樣的問題:咱們必須爲每個對象的實例作一樣的設置才行,這顯然是不實用的。咱們須要一個更有「療效」的方法。

在本章的開頭咱們說過,Javascript 是一門基於原型的語言,基於原型的語言最重要的特徵就是建立對象是經過對一個目標對象的拷貝來實現,而非經過類。但咱們目前還未說起過拷貝,或者做爲原型的目標對象,咱們目前爲止看到的都是構造函數配合着new關鍵字。

咱們的線索就是new關鍵字。記住當咱們使用 new Object 時,new 關鍵字建立了一個新的對象,並將該對象做爲構造函數內this 關鍵字指向的對象。實際上,new 關鍵字並未建立一個新的對象:它只是拷貝了一個對象。這個被拷貝的對象不是別的,正是原型(prototype)

全部能被做爲構造函數使用的函數都有一個 prototype 屬性,這個屬性對象定義了你實例化對象的結構。當使用 new Object 時,一個對 Object.prototype 的拷貝被創造出來,這個拷貝就是新建立的那個實例對象。這是 Javascript 的另外一個有趣的特色:不一樣於其它的原型語言——對它們來講,任何對象都能做爲原型使用;但在Javascript中,卻有一個專爲做爲原型使用 prototype 對象存在。

注意:對 Javascript 而言,這是一種對其餘原型性語言的模仿:對其餘原型性語言而言,你能夠直接克隆一個對象來獲得新的對象,在 Javascript 中則是依賴克隆目標對象的 prototype 屬性。在本章的最後一節你會學到實現這一作法。

prototype 對象,和其餘對象同樣,對其內部可容納的成員沒有數量上的限制,對其增長一個成員基本上就是簡單的附加一個值而已。下面咱們對以前的 Person 函數進行一番改寫:

var Person = function(name, age) {
    this.name = name;
    this.age = age;
};

Person.prototype.log = function() {
    console.log(this.name + ', ' this.age);
}

var mark = new Person('Mark', 23);
mark.log(); // 'Mark, 23'
複製代碼

能夠看到,咱們將 log 方法的定義移出構造函數,經過 Person.prototype.log 的方式去定義,這樣咱們就能告訴解析器全部從 Person 構造函數實例化出來的對象都將具備 log 方法,因此最後一行的 mark.log() 會執行。剩餘的構造函數仍是保持原樣,咱們並未把 this.name 和 this.age 也放在 prototype 中去,由於咱們仍是但願在對象實例化之時就能初始化這些值。

有了 prototype 這個利器,咱們就能夠對開頭的代碼進行重構,並使其變得更具可維護性:

var Person = function(name, age) {
    this.name = name;
    this.age = age;
};

Person.prototype.setName = function(name) {
    this.name = name;
};

Person.prototype.getName = function() {
    return this.name;
};

Person.prototype.setAge = function(age) {
    this.age = age;
};

Person.prototype.getAge = function() {
    return this.age;
};
複製代碼

上面這段代碼還能夠像下面這樣合併着來寫:

var Person = function(name, age) {
    this.name = name;
    this.age = age;
};

Person.prototype = {

    setName : function(name) {
        this.name = name;
    },

    getName : function() {
        return this.name;
    },

    setAge : function(age) {
        this.age = age;
    },

    getAge : function() {
        return this.age;
    }

}
複製代碼

如今好多了,再也沒有那麼多的東西擁擠在構造函數內了。並且之後一旦須要增長新的方法,只須要按照給 prototype 增長便可,而不用去從新整理構造函數。

咱們曾經有的另外一個問題(第一個是快捷建立多個實例對象,見上面)是在沒法修改構造函數的狀況下給實例成員添加新的方法,如今隨着咱們打通了一個通往構造函數的大門(prototype屬性),咱們能夠輕鬆的在不經過構造函數的狀況下爲實例對象添加方法。

// person.js
var Person = function(name, age) {
    this.name = name;
    this.age = age;
};

// program.js
Person.prototype.log = function() {
    console.log(this.name + ', ' + this.age);
};

var mark = new Person('Mark', 23);
mark.log(); // 'Mark, 23'

var joseph = new Person('Joseph', 22);
joseph.log(); // 'Joseph, 22'
複製代碼

在前面咱們已經看到了一些簡單的動態豐富 prototype 的例子。一個函數對象,以構造函數來肯定其形式,並可經過Mootools 的 Function.implement 函數爲其增長新的方法。全部 Javascript 函數其實都是 Function 對象的實例,Function.implement 實際上就是經過修改 Function.prototype 對象來實現的。雖然咱們並不能直接操做 Function 的構造函數——一個由解析器提供的內置構造——但咱們依然能夠經過 Function.prototype 來爲 Function 對象增長新的方法。對原生方法類型的增益咱們將會在後面「衍生與原生」(Types and Natives)一節中進行討論。

繼承(Inheritance)

爲了更高的理解 Javascript 是一門基於原型的語言,咱們須要區分原型與實例之間的區別。原型(prototype)是一個對象,它就像一個藍本,用來定義咱們須要的對象結構。經過對原型的拷貝,咱們能夠創造出一個該原型的實例(instance)

// 動物的構造器
var Animal = function(name) {
    this.name = name;
};

// 動物的原型
Animal.prototype.walk = function() {
    console.log(this.name + ' is walking.');
};

// 動物的實例
var cat = new Animal('Cat');
cat.walk(); // 'Cat is walking'
複製代碼

上面的代碼中,構造函數 Animal 和它的 prototype 一塊兒定義了 Animal 對象的結構,cat 對象是 Animal 的一個實例。當咱們執行 new Animal() 語句,一個 Animal.prototype 的拷貝就被建立,咱們稱這個拷貝爲一個實例(instance)。Animal.prototype 是一個只有一個成員的對象,這個惟一的成員是 walk 方法。天然,全部 Animal 的實例都會自動擁有 walk 這個方法。

那麼,當咱們在一個實例已經被建立以後再去修改 Animal.prototype ,會發生什麼呢?

// 動物的構造器
var Animal = function(name) {
    this.name = name;
};

// 動物的原型
Animal.prototype.walk = function() {
    console.log(this.name + ' is walking.');
};

// 動物的實例
var cat = new Animal('Cat');
cat.walk(); // 'Cat is walking'

// 難道動物不該該擁有吃(eat)這個方法嗎?
console.log(typeof cat.eat); // undefined --> 沒有 TT

// 給動物增長一個「吃」的方法
Animal.prototype.eat = function() {
    console.log(this.name + ' is eating.');
};

console.log(typeof cat.eat); // 'function'
cat.eat(); // 'Cat is eating'
複製代碼

嘿,如今這發生的事有點意思哈?在咱們建立好 cat 實例時候,檢測 eat 方法顯示的是 undefined。在咱們給 Animal.prototype 對象新增了一個 eat 方法以後,cat 實例就擁有了吃的能力!實際上,cat 的「吃」的能力就是咱們給 Animal.prototype 增長的那個函數。

看起來,彷佛是不論咱們何時給原型增長新的方法,這都會自動觸發所有的實例進行一次更新。但記住當咱們新建立一個對象,那麼這個新的操做就會建立一個新的原型拷貝。當咱們建立 cat 時,原型還僅擁有一個方法。若是這是一個純粹的拷貝,那就不該該擁有咱們以後才設置的 eat 方法。畢竟,當你複印了一份文檔,以後在源文檔上又寫上一句 「天朝人民最幸福」,你不能期望那份複印的文檔上也當即出現一樣的字句,不是嗎?

或者是解析器知道何時 prototype 新增了成員並自動給所有的實例都增長上這個方法?也許是當咱們給原型增長了 eat 這個方法後,解析器便馬上給所有的 Animal 實例增長上了這個方法?對於這一點的驗證是很簡單的:咱們能夠先給實例設置一個 eat 的方法,以後再給原型增長 eat 方法。若是上面的猜想是對的,那麼後增長的原型的 eat 方法會覆蓋掉較早給 Animal 實例單獨設置的那個 eat 方法。

// 動物的構造器
var Animal = function(name) {
    this.name = name;
};

// 動物的原型
Animal.prototype.walk = function() {
    console.log(this.name + ' is walking.');
};

// 動物的實例
var cat = new Animal('Cat');
cat.walk(); // 'Cat is walking'

// 給cat增長一個eat的方法
cat.eat = function() {
    console.log('Meow. Cat is eating.');
};

// 給動物增長一個「吃」的方法
Animal.prototype.eat = function() {
    console.log(this.name + ' is eating.');
};

cat.eat(); // 'Meow. Cat is eating.'
複製代碼

很明顯,前面的猜想是錯誤的。Javascript 解析器不會更新實例。那真實的狀況究竟是什麼呢?

全部的對象都有一個叫作 proto 的內置屬性,該屬性指向該對象的原型。解析器利用該屬性將對象「連接」到它對應的原型上。雖然在使用 new 關鍵字的時候確實是建立了一個原型的拷貝,且這個拷貝看起來確實很像原型自己,但它實際上倒是一個「淺拷貝」。真相是,當這個實例被建立時,它實際上只是一個空對象,這個空對象的 proto 屬性指向了其構造函數的 prototype 對象。

你可能會問:「等等,既然這個新的實例是一個空對象,那爲何它還會像其來源的原型那樣具備屬性和方法呢」?其實這就是 proto 屬性的做用。實例對象經過 proto 屬性連接到它的原型,這樣它原型上的屬性和方法也能被其實例對象訪問到。在咱們的例子中,cat 對象自己被沒有 walk 的方法。當解析器讀取到 cat.walk() 語句時,它首先檢測 cat 對象自身的prototype 對象中有無 walk 這個方法成員,若是沒有,就經過 cat 的 proto 屬性上溯到其原型的 prototype 中去尋找 walk 方法。而正好在這裏解析器找到了它須要的方法,因而咱們的 cat 就能執行「走」的動做了。

這也能解釋爲何上面的代碼中最後 log 出的信息是「Meow. Cat is eating.」,由於咱們給實例對象 cat 的 prototype 屬性對象增長了 eat 這個方法成員,因而解析器先在這裏找到了它須要的 「eat 方法,進而 cat 的原型 prototype 中的 eat 方法就不會起做用了。

一個實例對象的成員(屬性啊方法啊神馬的)來自於它的原型(而非是針對這個實例對象單獨設置),被稱爲繼承(inheritance)。對全部對象,你都能使用 hasOwnProperty 方法來檢測某個成員是否是隸屬於它。

var Animal = function() {};
Animal.prototype.walk = function() {};

var dog = new Animal();

var cat = new Animal();
cat.walk = function() {};

console.log(cat.hasOwnProperty('walk')); // true
console.log(dog.hasOwnProperty('walk')); // false
複製代碼

這裏,咱們對 cat 使用 .hasOwnProperty(walk) 檢測,返回爲true,這是由於咱們已經對 cat 單獨設置了一個它本身的 walk 方法。對應的,由於 dog 對象並未被賦以一個單獨的 walk 方法,因此檢測結果爲 false。另外,若是對 cat 採用 .hasOwnProperty(hasOwnProperty),返回的一樣會是 false。這是由於 hasOwnProperty 其實是 Obiect 對象的方法,而 cat 對象由 Object 處繼承而來。

如今有一個傢伙須要咱們好好的去考慮一下:this。在構造函數內的 this,其永遠指向構造函數的實例化對象而非構造函數的 prototype 對象。可是在原型內定義的函數則遵循另外一個法則:若是該方法是直接的由原型方式來調用,則該方法內的 this 指向的是這個原型對象自己;若是該方法由這個原型的實例化對象來引用,則方法內的 this 關鍵字就會指向這個實例化對象。

var Animal = function(name) {
    this.name = name;
};

Animal.prototype.name = 'Animal';

Animal.prototype.getName = function() {
    return this.name;
};

// 直接使用原型方法來調用「getName」
Animal.prototype.getName(); // 返回 'Animal'

var cat = new Animal('Cat');
cat.getName(); // 返回 'Cat'
複製代碼

這裏咱們對代碼進行了一些小的修改,以便 Animal.prototype 能夠有其本身的 name 屬性。當咱們直接用原型方式調用 getName 時,返回的是 Animal.prototype 的 name 屬性。但當咱們經過實例化對象去執行 cat.getName() 時,返回的就是 cat 的 name 屬性。

原型和實例是不一樣的對象,它們之間惟一的聯繫是:針對原型作的修改會反射到全部該原型的實例對象,但對某具體實例對象的修改卻只對該實例對象自己起做用。

記住在 Javascript 中同時存在着基本數據類型和複合數據類型。如字符串、數字以及布爾值等都屬於基本數據類型:當它們被做爲參數傳遞給函數或被賦值於一個變量時,被使用的都是它們的拷貝。而像數組、函數、對象這樣的複合數據類,被使用的則是它們的引用

// 建立一個對象
var object = {name : 'Mark'};

// 把這個對象「拷貝」給另外一個變量
var copy = object;

console.log(object.name); // 'Mark'
console.log(copy.name); // 'Mark'

// 更改copy對象的name值
copy.name = 'Joseph';

console.log(object.name); // 'Joseph'
console.log(copy.name); // 'Joseph'
複製代碼

當 var copy = object 被執行時,沒有新的對象被建立出來。copy 變量其實只是指向了 object 所指向的同一個對象。object 和 copy 如今都是指向同一個對象,天然從 copy 處對其指向對象作的改動,object 也會獲得反射。

對象能夠擁有複合數據類型的成員,對象自身的 prototype 也一樣如此。因此便出現了下面這個須要被注意的問題:當給一個指向複合數據類型的原型增長新的成員時,由於全部該原型的實例對象也都指向該原型自己,因此對原型的改動也會被繼承。

var Animal = function() {};

Animal.prototype.data = {
    name : 'animal',
    type : 'unknow'
};

Animal.prototype.setData = function(name, type) {
    this.data.name = name;
    this.data.type = type;
};

Animal.prototype.getData = function() {
    console.log(this.data.name + ': ' + this.data.type);
};

var cat = new Animal();
cat.setData('Cat', 'Mammal');
cat.getData(); // 'Cat: Mammal'

var shark = new Animal();
shark.setData('Shark', 'Fish');
shark.getData(); // 'Shark: Fish'

cat.getData(); // 'Shark: Fish'
複製代碼

由於咱們的 cat 和 shark 對象都沒有本身的 data 屬性,因此它們從 Animal.prototype 處繼承而來,因此 cat.data 和 shark.data 都指向了 Animal.prototype 中定義的 data 對象,對任何一個實例的 data 對象的更改都會引發咱們不但願看到的行爲。

最簡單的解決辦法就是將 data 屬性從 Animal.prototype 中移除並在每一個實例對象中單獨定義它們。經過構造函數來實現這一點是很簡單的。

var Animal = function() {
    this.data = {
        name : 'animal',
        type : 'unknow'
    };
};

Animal.prototype.setData = function(name, type) {
    this.data.name = name;
    this.data.type = type;
};

Animal.prototype.getData = function() {
    console.log(this.data.name + ': ' + this.data.type);
};

var cat = new Animal();
cat.setData('Cat', 'Mammal');
cat.getData(); // 'Cat: Mammal'

var shark = new Animal();
shark.setData('Shark', 'Fish');
shark.getData(); // 'Shark: Fish'

cat.getData(); // 'Cat: Mammal'
複製代碼

由於此時構造函數內的 this 關鍵字在此處是指向實例化對象的,因此 this.data 也就爲每個對象單獨賦予了一個 data 屬性,且不會影響到構造函數的原型。進而會看到,最後的輸出結果也正是我須要的那樣。

原型鏈(The Prototype Chain)

在 Javascript 中,Object 是基礎對象模型。其餘對象不管是具有如何不一樣的構造,都是會從 Object 對象處得到繼承。下面的代碼足夠幫助咱們來理解這一點:

var object = new Object();

console.log(object instanceof Object); // true
複製代碼

由於咱們是按照 Object 的構造函數來建立的 object 對象,因此咱們能夠說 object 對象的內部屬性 proto 指向的就是 Object 的 prototype 屬性。如今,再來看下面這段代碼。

var Animal = function()
複製代碼

{};

var cat = new Animal();

console.log(cat instanceof Animal); // true
console.log(cat instanceof Object); // true
console.log(typeof cat.hasOwnProperty()); // 'function'
複製代碼

由於使用 new Animal() 的緣故,因此咱們知道 cat 其實是一個 Animal 的實例。並且咱們還知道全部對象都有一個繼承自 Object 的 hasOwnProperty 屬性。因而咱們就要問了,既然 object 對象的 proto 屬性如今指向的是 Animal 的原型,那這裏又是怎麼作到的 object 能在未涉及 Object 構造函數的狀況下還能同時從 Animal 和 Object 得到繼承呢?

答案就在原型之間。默認狀況下,構造函數的 prototype 對象是一個不含任何方法只含有其構造函數中設置的屬性的基本對象。這聽起來很熟悉不是嗎?這和咱們使用 new Object() 創造出來的對象是同樣的!實際上咱們的代碼還能夠像下面這樣來寫。

var Animal = function() {};

Animal.prototype = new Object();

var cat = new Animal();

console.log(cat instanceof Animal); // true
console.log(cat instanceof Object); // true
console.log(typeof cat.hasOwnProperty()); // 'function'
複製代碼

如今就已經很清晰了,Animal.prototype 由 Object.prototype 處繼承而來。對於一個實例而言,除了會從它自身的 prototype 對象繼承以外,還會從 它原型的原型的 prototype 對象處繼承。

感到費解?那就經過對上面的代碼進行分析來增強一下對這點的理解。咱們的 cat 對象是由 Animal 對象實例化而來,因此 cat 會繼承 Animal.prototype 的屬性和方法。而 Animal.prototype 是由 Object 實例化而來,因此 Animal.prototype 會繼承 Object.prototype 的屬性和方法。進而 cat對象 會同時繼承 Animal.prototype 和 Object.prototype 的屬性和方法,因此咱們說 cat 是**間接繼承(indirectly inherits)**了 Object.prototype 對象。

咱們的 cat 對象的 proto 屬性指向了 Animal.prototype 對象;而 Animal 的 proto 屬性則指向 Object.prototype 對象。這種 prototype 原型之間持續的鏈向被稱爲原型鏈(prototype chain)。進而咱們說 cat 對象的原型鏈展度爲從其自身一直到 Object.prototype。

注意:全部對象原型鏈的終點都是 Object.prototype,且 Object 的 proto 屬性不指向任何一個對象——不然原型鏈就會變得沒有邊界而致使基於原型鏈的上溯流程變得沒法終止。Object.prototype 對象自己非由任何構造函數產生,而是由解析器內置的方法建立,這使得 Object.prototype 成爲惟一一個不是由 Object 實例化而來的對象。

沿着一個對象的原型鏈查找屬性或方法的行爲咱們稱之爲遍歷(traversal)。當解析器遇到 cat.hasOwnProperty 語句時,解析器首先在當前對象的 prototype 對象中查找相關方法。若是沒有,則順序的在原型鏈上下一個對象—— Animal.prototype 上查找。仍是沒有,則繼續在下一個對象的 prototype 上查找,以此類推。一旦解析器找到了它要的方法,解析器就會使用當前找到的這個方法,其在原型鏈上的遍歷也會中止。若是解析器在整個原型鏈上都找不到它須要的方法,它就會返回 undefined。在咱們的例子中,解析器最後在 Object.prototype 對象上找到了 hasOwnProperty 方法。

一個對象老是屬於至少一個構造函數的實例:不管是使用對象字面量仍是對象構造函數創造出來的對象,總都屬於 Object 的實例。對那些非直接由 Object 構造函數創造出來的對象而言,它們既是直接建立它們的構造函數的實例,同時仍是它們原型鏈上全部 prototype 對象對應的構造函數的實例。

有考量的原型鏈(Deliberate Chains)

一旦咱們要建立更爲複雜的對象,原型鏈就會變得很是有用。好比咱們如今要建立一個 Animal 對象:全部的動物都有名字(name),全部的動物還要可以吃東西(eat)來活下去。OK,下面是咱們的代碼:

var Animal = function(name) {
    this.name = name;
};

Animal.prototype.eat = function() {
    console.log('The ' + this.name + ' is eating.');
};

var cat = new Animal('cat');
cat.eat(); // 'The cat is eating'

var bird = new Animal('bird');
bird.eat(); // 'The bird is eating'
複製代碼

目前爲止一切都還好。不過如今須要動物們能發出聲音,因而咱們須要增長新的方法。顯然,這些動物發出的聲音應該是不同的:貓咪的叫聲是「meow」,小鳥的叫聲是「tweet」。咱們能夠爲每個動物實例單獨設置發聲的方法,但顯然在面對一個須要創造多個貓咪和小鳥的需求面前,這種作法是不合事宜的。咱們彷佛還能夠經過爲 Animal.prototype 增長方法來達到貓咪和小鳥等實例都具有發聲的能力,但這仍是在浪費精力:由於貓咪不會發出「tweet」的聲音,小鳥也不會「meow」的叫。

那咱們爲每一個實例對象自身的構造函數單獨設置方法行不行呢?咱們能夠製造出 Cat、Bird 的構造器併爲其分別設置不一樣的發聲方式。而「吃」的能力則仍是從 Animal.prototype 那繼承而來:

var Animal = function(name) {
    this.name = name;
};

Animal.prototype.eat = function() {
    console.log('The ' + this.name + ' is eating.');
};

var Cat = function() {};

Cat.prototype = new Animal('cat');

Cat.prototype.meow = function() {
    console.log('Meow!');
};

var Bird = function() {};

Bird.prototype = new Animal('bird');

Bird.prototype.tweet = function() {
    console.log('Tweet!');
};

var cat = new Cat();
cat.eat(); // 'The cat is eating'
cat.meow(); // 'Meow!'

var bird = new Bird();
bird.eat(); // 'The bird is eating'
bird.tweet(); // 'Tweet!'
複製代碼

能夠看到,咱們保留了原有的 Animal 構造函數,而且基於它新建了另外兩個更具體的構造函數——Cat 和 Bird。以後咱們分別爲 Cat 和 Bird 設置了它們本身的發聲方式。這樣,咱們最終的實例對象貓咪和小鳥就都能發出它們各自不一樣的叫聲了。

在基於類的程序語言中,這種直接繼承了其實例化來源的類的特徵,且更具針對性的分支被稱爲子類(subclassing)。Javascript,則是一門基於原型的語言,並無類的概念,就其本質而言,咱們惟一所作的就是創造了一個有考量的原型鏈(deliberate prototype chain)。這裏之因此用「有考量」這個詞,是由於咱們顯然是有意的設計了哪些對象應該出如今咱們的實例原型鏈上。

原型鏈上的成員數量沒有限制,你還能夠經過豐富原型鏈上的對象來知足更有針對性的需求。

var Animal = function(name) {
    this.name = name;
};

Animal.prototype.eat = function() {
    console.log('The ' + this.name + ' is eating.');
};

var Cat = function() {};

Cat.prototype = new Animal('cat');

Cat.prototype.meow = function() {
    console.log('Meow!');
};

var Persian = function() {
   this.name = 'persian cat';
};

Persian.prototype = new Cat();

Persian.prototype.meow = function() {
    console.log('Meow...');
};

Persian.prototype.setColor = function() {
    this.color = color;
};

Persian.prototype.getColor = function() {
    return this.color;
};

var king = new Persian();
king.setColor('black');
king.getColor(); // 'black'
king.eat(); // 'The persian cat is eating'
king.meow(); // 'Meow...'

console.log(king instanceof Animal); // true
console.log(king instanceof Cat); // true
console.log(king instanceof Persian); // true
複製代碼

這裏咱們創造了一個名爲 Persian(波斯貓) 的 Cat 分支。你會注意到這裏咱們設置了一個 Persian.prototype.meow 的方法,這個方法在 Persian 的實例中會覆蓋掉 Cat.prototype.meow。若是你檢查一下,會發現 king 對象分別是 Animal、Cat 和 Persian 的實例,這也說明了咱們原型鏈的設計是正確的。

原型鏈真正的威力在於繼承與原型鏈遍歷的結合。由於原型鏈上全部的 prototype 對象都是鏈起來的,因此原型鏈上某一點的改變會當即反射到它所指向的其餘成員對象。若是咱們給 Animal.prototype 新增一個方法,那麼全部 Animal 的實例都會新增長上這個方法。這位咱們批量的爲對象擴充方法提供了簡易快捷的方式。

若是你的程序正變得越發龐大,那麼有考量的原型鏈會幫助你的代碼更具結構性。不一樣於把全部的代碼都塞進一個 prototype 對象中,你能夠建立多重的具有良好設計的 prototype 對象,這對減小代碼量、提高代碼的可維護性都頗有好處。

簡化原型的編程(Simplified Prototypal Programming)

如今你應該已經意識到 Javascript 的面向對象風情有其獨到的範式。Javascript 所謂的「基於原型的程序語言」很大程度上是僅限於名義上的。Javascript 中有着本應是在基於類的語言中才會出現的構造函數和 new 關鍵字的組合,同時將從原型——這個顯著的原型式語言的特徵——處繼承來的東西做爲其用以實現針對性 prototype 對象的依據,而這些更具針對性的 prototype 對象,則是那麼的相似類式語言中的子類。這門語言在對象機制實現方面的設計必定程度上受到了當時程序語言潮流的影響:在這門語言被建立的那個時代,基於類的程序語言處於正統的標準地位。因此,最終的決定就是爲這門新語言賦予一些同類式語言類似的特徵。

儘管如此,Javascript 依然是一門靈活的語言。雖然咱們不能改變在其核心中定義的對象的實現機制,但咱們依然可以使用現有手段令這門語言散發出更純粹的原型式風格(固然咱們在下一章中會看到另外一種流派——如何使這門語言在實際中更具有類式風格)。

在咱們如今所討論的簡化原型的範疇內,讓咱們把視線從 Javascript 自己那具有複合性特徵的原型上先移開,只先關注對象自己。不一樣於先建立一個構造函數以後再設置其 prototype,咱們使用真的對象做爲原型來建立新的對象,並將其prototype屬性「克隆」到新建立的對象身上。爲了更明確的說明咱們要作的,這裏先舉一個例子,這個例子來自另外一個純粹的原型式程序語言 IO:

Animal := Object clone
Animal name := "animal"

Cat := Animal clone
Cat name := "cat"

myCat := Cat clone
複製代碼

雖然這不是一本關於 IO 語言的書,但咱們仍是從基礎講起。同 Javascript 同樣,IO 中的基礎對象也是 Object。不過,這裏的 Object 並非一個構造器(厄,一個函數),而是一個真正的對象。在咱們代碼的開始部分,咱們創造了一個新的對象—— Animal,這個新對象由源對象 Object 處克隆而來。由於在 IO 語言中,空格用來訪問屬性,因此 Object clone 語句的含義就是「使用 Object 的 clone 方法並執行它」。以後咱們爲 Animal 的 name 屬性設置了一個字符型的值,經過克隆 Animal 建立了一個名爲 Cat 的新對象,同時我也爲這個 Cat 對象設置了 name 屬性,最後咱們克隆 Cat 獲得一個 myCat 對象。

咱們能夠在 Javascript 中實現相似的事:

var Animal = function() {};
Animal.prototype = new Object();
Animal.prototype.name = 'animal';

var Cat = function() {};
Cat.prototype = new Object();
Cat.prototype.name = 'cat';

var myCat = new Cat();
複製代碼

很像,但卻不徹底同樣。在 IO 的例子中,最終的 myCat 是直接由 Cat、Animal、Object 處克隆而來的,這些都是純粹的對象而非構造器。但在咱們的 Javascript 的例子中,最終的 myCat 對象則是由Cat、Animal、Object 等對象的 prototype 屬性繼承而來,Cat、Animal、Object 等也都是函數而非對象。換句話說。IO 沒有構造函數的概念,一切都是直接從對象克隆而來。但 Javascript 卻有構造函數,且克隆的是 prototype。

若是咱們能控制內部屬性 proto,那麼咱們就能在 Javascript 中實現和 IO 同樣特性。 例如,假如咱們有一個 Animal 對象和一個 Cat 對象,咱們能夠改變 Cat 對象的 proto屬性使之直接鏈向 Animal 對象(而非鏈向 Animal 的 prototype 對象)自己,這樣 Cat 就能直接繼承 Animal 對象。

由於 proto 屬性是內置屬性不能直接修改它,但一些 Javascript 解析器卻引入了一個和其相似的名爲 proto 的屬性。一個對象的 proto 屬性被用做更改其內置的 proto 屬性,以使其能夠直接鏈向其餘對象。

var Animal = {
    name : 'animal',
    eat : function() {
        console.log('The ' + this.name + ' is eating.')
    }
};

var Cat = {name : 'cat'};
Cat.__proto__ = Animal;

var myCat = {};
myCat.__proto__ = Cat;

myCat.eat(); // 'The cat is eating.'
複製代碼

這裏不存在構造函數,Animal 和 Cat 對象直接由字面量建立。經過 Cat.proto = Animal 語句咱們告訴解析器 Cat 的 proto 屬性直接指向 Animal 對象。最後 myCat 對象都直接從 Cat 和 Animal 處獲得繼承,在 myCat 的原型鏈上也不存在任何爲 prototype 的對象。這個簡化的原型模型不包含任何的構造器或原型屬性,而是替代的將真實的對象自己放置其原型鏈上。

相似的,你可使用 Object.create 方法來達到一樣的效果,這個新函數目前已經被 ECMAScript 5 正式引入。它只接受一個參數,該參數爲一個對象,其執行的結果是建立一個空對象,而這個對象的 proto 屬性將被指向做爲參數傳入的那個對象。

var Animal = {
    name : 'animal',
    eat : function() {
        console.log('The ' + this.name + ' is eating.')
    }
};

var Cat = Object.create(Animal);
Cat.name = 'cat';

var myCat = Object.create(Cat);
myCat.eat(); // 'The cat is eating.'
複製代碼

注意這裏的 Object.create 方法和 IO 裏的 clone 方法很想象,實際上,它們實現的也是同一件事。咱們可使用 Object.create 方法很是高仿的實現 IO 語言的那個片斷:

var Animal = Object.create({});
Animal.name = 'animal';

var Cat = Object.create(Animal);
Cat.name = 'cat';

myCat = Object.create(Cat);
複製代碼

不幸的是,雖然上面的兩種方式都很美妙,但它們卻不能兼容全部平臺。proto 屬性目前還不屬於正式的 ECMAScript 規範,因此並非全部的解析器都對其提供支持。而 Object.create() 方法,雖然是規範中的一員,但該規範倒是指 ECMAScript 5。因該規範是2009年才頒佈的,因此目前也不是全部解析器都能提供完整的支持。若是你但願寫出具備更好兼容性的代碼(尤爲是 web app 程序),就尤爲要記住這2種方式都不是通用方案。

如今有一種方案可使較爲古老的解析器也能支持 Object.create 方法。就是記住 Javascript 對象經過引用來起做用,若是你將一個對象存儲在變量 x 中,以後操做 y = x,那麼 y 和 x 將同時指向同一個對象。同時,一個函數的 prototype 屬性也是一個對象,而這個對象的初始值能夠很輕易的經過被分配給一個新的對象值來覆蓋:

var Animal = {
    name : 'animal',
    eat : function() {
        console.log('The ' + this.name + ' is eating.')
    }
};

var AnimalProto = function() {};
AnimalProto.prototype = Animal;

var Cat = new AnimalProto();
console.log(typeof cat.purr); // 'undefinded'

Animal.purr = function() {};
console.log(typeof cat.purr); // 'function'
複製代碼

這段代碼如今看來應該有些眼熟了吧。咱們首先建立了一個有着2個成員(一個name 屬性、一個 eat 方法)的 Animal 對象,以後咱們建立了一個名爲 AnimalProto 的「跳板級」構造函數,並將它的 prototype 屬性設置爲 Animal 對象。由於引用的緣故,AnimalProto.prototype 屬性 和 Animal 如今都指向了同一個對象。這就意味着,當咱們建立了 cat 實例時,它其實是直接繼承自 Animal 對象 —— 這就像是使用 Object.create 方法創造出來的同樣。

採用這個點子,咱們能夠模擬出 Javascript 解析器所不支持的 Object.create 方法。

if (!Object.create) Object.create = function(proto) {
    var Intermediate = function() {};
    Intermediate.prototype = proto;
    return new Intermediate();
};

var Animal = {
    name : 'animal',
    eat : function() {
        console.log('The ' + this.name + ' is eating.')
    }
};

var Cat = Object.create(Animal);
console.log(typeof cat.purr); // 'undefinded'

Animal.purr = function() {};
console.log(typeof cat.purr); // 'function'
複製代碼

最開始,咱們使用一個 IF 語句來判斷當前解析器是否支持 Object.create 方法。若是支持,則直接執行下面的語句,若是不支持,就模擬一個該方法:它首先創造一個名爲 Intermediate 的構造器,以後將該構造器的 prototype 屬性指向做爲參數傳入的那個對象。最後該函數返回一個 Intermediate.prototype 的實例。由於這裏咱們使用的方法都是當下解析器所支持的,因此咱們能夠說這個模擬的 Object.create 方法是具有普適性的。

總結(The Wrap Up)

在這一章,咱們詳細的討論了有關 Javascript 對象機制的全部話題,並展現了它和其餘語言之間的區別。雖然它是一門基於原型的語言,但由於其自身的一些獨特性,使其其實是兼具類式和原型式語言的特徵。咱們看到了如何使用字面量和構造器的 prototype 屬性來新建對象。咱們還展現了繼承的奧祕、Javascript 原型鏈上的遍歷是如何工做的。最後咱們還實踐了一個將 Javascript 自己的原型混雜性隱藏起來的簡便原型式模型。

由於 Javascript 的核心是一門面向對象的語言,因此在這裏所寫的針對該點的知識,會在咱們開發複雜應用時候提供莫大的幫助。雖然面向對象自己已經超越了本書所要講述的範圍,但我依然但願我在這裏所提供的信息,能夠爲你在該話題上的深刻學習提供一點幫助。

招賢納士(Recruitment)

招人,前端,隸屬政採雲前端大團隊(ZooTeam),50 餘個小夥伴正等你加入一塊兒浪 [壞笑] 若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5年工做時間3年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手參與一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com

推薦閱讀(Recommend)

JAVASCRIPT FUNCTIONS 詳解

相關文章
相關標籤/搜索