国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JAVASCRIPT OBJECTS

taowen / 1068人閱讀

摘要:構(gòu)造函數(shù)第一種途徑是使用的構(gòu)造函數(shù),方式。一個(gè)構(gòu)造函數(shù)和其他函數(shù)一樣除了自身細(xì)節(jié)上有些許區(qū)別慣常的做法是將函數(shù)名首字母大寫以表示其存在目的是作為一個(gè)構(gòu)造函數(shù)。關(guān)鍵字的作用就是創(chuàng)建一個(gè)新對(duì)象,并將構(gòu)造函數(shù)內(nèi)的指向這個(gè)新創(chuàng)建的對(duì)象。

ECMAscript 說明文檔對(duì)這門語言的定義是“一門適于在宿主環(huán)境中執(zhí)行計(jì)算及操作計(jì)算對(duì)象的面向?qū)ο蟮木幊陶Z言”。簡單的說,JavaScript是一門面向?qū)ο螅∣O)的語言。

面向?qū)ο笾v究的是專注于對(duì)象本身——它們的結(jié)構(gòu),它們互相間是如何影響的。本文是@堂主 對(duì)《Pro JavaScript with Mootools》一書的第三章 Object 部分的翻譯,最早譯于 2012 年。因?yàn)槊嫦驅(qū)ο缶幊瘫旧硪呀?jīng)超出了本書的敘述范圍,所以我們?cè)诒菊滤劦闹皇?JavsScript 自身在面向?qū)ο蠓矫娴哪切┨攸c(diǎn)。

本篇譯文字?jǐn)?shù)約 3 萬字,各位看官如發(fā)現(xiàn)翻譯錯(cuò)誤或有優(yōu)化建議,歡迎留言指教,共同成長。另外,同樣的建議——非本土產(chǎn)技術(shù)類書籍,建議還是優(yōu)先閱讀英文原版。

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

所有面向?qū)ο蟮恼Z言在其核心都會(huì)對(duì)對(duì)象進(jìn)行處理,對(duì)象的創(chuàng)建及構(gòu)造的過程將大部分的面向?qū)ο笳Z言分為2個(gè)陣營:

基于類 (Classical or class-based) 的面向?qū)ο笳Z言采用類來創(chuàng)建對(duì)象。類是一個(gè)為創(chuàng)建對(duì)象提供藍(lán)本的特殊數(shù)據(jù)類型。在一個(gè)基于類的面向?qū)ο蟮恼Z言中,我們通過創(chuàng)建類來定義一個(gè)對(duì)象的結(jié)構(gòu),并通過創(chuàng)建該類的實(shí)例來創(chuàng)造這個(gè)對(duì)象本身。這一過程被稱為實(shí)例化 (instantiation)。

基于原型 (Prototypal or prototype-based) 的面向?qū)ο笳Z言沒有類的概念,它以其他的對(duì)象對(duì)藍(lán)本。在一個(gè)基于原型的語言中,prototype 是一個(gè)由你創(chuàng)建、體現(xiàn)著你期望的結(jié)構(gòu)的對(duì)象,這個(gè)對(duì)象之后會(huì)成為其他對(duì)象創(chuàng)建所參照的藍(lán)本。通過拷貝其本身 prototype 屬性來創(chuàng)建實(shí)例的方式被稱為克隆(cloning)。對(duì)一個(gè)純粹的原型語言而言,任何一個(gè)對(duì)象都能被作為創(chuàng)建其他對(duì)象的原型。

JavaScript 是一本基于原型的語言:這里沒有類的概念,所有對(duì)象都是由其他對(duì)象創(chuàng)建而來。不過,JavaScript 不是一門純粹的原型語言,在本章的后面我們會(huì)看到 JavaScript 還保留著一些基于類的殘存特征。如果你已經(jīng)對(duì)面向?qū)ο蟮恼Z言很熟悉了,你很可能會(huì)覺得 JavaScript 是奇異的,因?yàn)橄鄬?duì)你之前的那些面向?qū)ο蟮慕?jīng)驗(yàn),這門語言的怪異特質(zhì)是如此明顯。

哈哈,先別打退堂鼓:JavaScript,一門面向?qū)ο蟮恼Z言,因?yàn)榧鎮(zhèn)淞嘶陬惡驮偷奶卣鳎沟盟邆淞颂幚韽?fù)雜、龐大應(yīng)用的實(shí)力。

一門關(guān)于對(duì)象的語言 (A Language of Objects)

從本質(zhì)上講,一個(gè) JavaScript 的對(duì)象就是一些名值對(duì)(key-value pairs)的聚合體。相比于簡單的如字符串、數(shù)字等基本數(shù)據(jù)類型而言,JavaScript 對(duì)象是一種混合的復(fù)合數(shù)據(jù)類型。對(duì)象內(nèi)的每一個(gè)名值對(duì)被稱為一個(gè)屬性(property),key 被稱為屬性名(property name),value 被稱為屬性值(property value)

屬性名一向是字符串,而屬性值則可能是任何數(shù)據(jù)類型:字符串、數(shù)字、布爾值或者是復(fù)合型的數(shù)據(jù)類型如數(shù)組、函數(shù)或?qū)ο蟆1M管 JavaScript 并未將對(duì)象屬性值可承載的數(shù)據(jù)類型做任何區(qū)分,但我們還是習(xí)慣的將用函數(shù)類型作為值的屬性稱為方法(methods)以與其他值為非函數(shù)類型的屬性作區(qū)分。為了避免困惑,在后面的探討中我們采用如下的慣例:以函數(shù)為值的屬性稱之為“方法”,其他的統(tǒng)稱為“屬性”。如果我們所指的同時(shí)可能為一個(gè)對(duì)象的方法或?qū)傩裕俏覀儠?huì)稱它們?yōu)檫@個(gè)對(duì)象的成員(members)

注意:在面對(duì) JavaScript 是一門一等對(duì)象語言這個(gè)現(xiàn)實(shí)時(shí),屬性和方法間的區(qū)分會(huì)顯得不那么清晰。本章的觀點(diǎn)是:不論值是什么,一個(gè)對(duì)象內(nèi)的成員都是一個(gè)屬性,甚至是函數(shù)本身也可以被作為值來傳遞。

一個(gè)對(duì)象可以擁有多少屬性是沒有數(shù)量上的限制的,甚至一個(gè)對(duì)象可以擁有0個(gè)屬性(此時(shí)表示這是一個(gè)空對(duì)象)。依照其用途,一個(gè)對(duì)象可以在某些情況下被稱為是一個(gè)哈希(hash)、字典(dictionary) 或表(table),折射出其結(jié)構(gòu)是一組名值對(duì)。不過我們還是堅(jiān)持在討論時(shí)采用“對(duì)象”這一稱呼。

創(chuàng)建一個(gè)對(duì)象最簡單的辦法是使用對(duì)象字面量(object literal)

// 一個(gè)對(duì)象字面量
var person = {
    name : "Mark",
    age : 23
};

這里我們創(chuàng)建了一個(gè)具有2個(gè)屬性的新對(duì)象,一個(gè)鍵名是 name,另一個(gè)鍵名是 age,這個(gè)對(duì)象被存儲(chǔ)在 person 變量里——這為我們提供了一個(gè)有2個(gè)成員的 person 對(duì)象。注意雖然 key 是字符串但我們并將其包含在引號(hào)里,只要是非保留字的有效標(biāo)識(shí)符,在 JavaScript 中這就是容許的。對(duì)于下面的情況,我們需要用引號(hào)將 key 圍起來:

// 一個(gè)對(duì)象字面量
var person = {
    "name of the person" : "Mark",
    "age of the person" : 23
};

為了引用一個(gè)對(duì)象中的成員,我們可以使用點(diǎn)記法(dot notation),這可以使我們通過在屬性名標(biāo)識(shí)符之前置入一個(gè)句點(diǎn)來引用其對(duì)應(yīng)的屬性值;我們還可以使用括號(hào)記法(bracket notation),這個(gè)方法通過為字符串的屬性名標(biāo)識(shí)符圍上一個(gè)中括號(hào) [ ] 來達(dá)到同樣的引用屬性值的目的。

// 一個(gè)對(duì)象字面量
var person = {
    name : "Mark",
    age : 23
};

// 點(diǎn)記法
console.log(person.name); // "Mark"

// 括號(hào)記法
console.log(person["age"]); // 23

實(shí)際上點(diǎn)記法是括號(hào)記法的快捷方式、語法糖(syntactic sugar),實(shí)際中大多數(shù)情況下我們都使用點(diǎn)記法。當(dāng)然,點(diǎn)記法被限制在標(biāo)識(shí)符是適當(dāng)?shù)那樾蜗隆T谄渌闆r中,你需要使用括號(hào)記法。

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

console.log(person["name of the person"]); // "Mark"

當(dāng)你不是采用一個(gè)字符串 key 而是采用一個(gè)對(duì)象來引用的時(shí)候,也需要使用括號(hào)記法

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

var key = "name";

console.log(person[key]); // "Mark"

訪問一個(gè)不存在的對(duì)象成員會(huì)返回 undefined。

var person = {};

console.log(person.name); // undefined

同時(shí)我們還可以在一個(gè)對(duì)象創(chuàng)建之后動(dòng)態(tài)的為其新增成員或改變某個(gè)成員的屬性值。

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

你可以通過為對(duì)象成員賦值為函數(shù)來創(chuàng)建方法。

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

你應(yīng)該會(huì)注意到我們?cè)诜椒ㄖ幸?person 對(duì)象的 name、age 屬性使用的是 this.name 和 this.age 的方式。回顧一下我們前一章討論過的部分,你會(huì)知道 this 關(guān)鍵字指的是包含方法等屬性的對(duì)象的本身,所以在本例中 this 指代的就是 person 對(duì)象。

對(duì)象的構(gòu)建模塊(The Buliding Blocks of Objects)

雖然對(duì)象字面量是一種創(chuàng)建對(duì)象的快捷方式,但它并不能完整的展示 JavaScript 面向?qū)ο蟮膬?yōu)勢(shì)。比如,如果你需要?jiǎng)?chuàng)建 30 個(gè) person 對(duì)象,那么對(duì)象字面量會(huì)是一種非常耗時(shí)的方式——為每一個(gè)對(duì)象都寫一個(gè)對(duì)象字面量是不切實(shí)際的。為了更有效率,我們需要為我們需要的對(duì)象創(chuàng)建一個(gè)藍(lán)本結(jié)構(gòu),并使用這個(gè)藍(lán)本來創(chuàng)造對(duì)象的實(shí)例。

在基于類的面向?qū)ο笳Z言中,我們可以為創(chuàng)建一個(gè)類來明確對(duì)象需要的結(jié)構(gòu);在基于原型的面向?qū)ο笳Z言中,我們可以簡化的創(chuàng)建一個(gè) Person 對(duì)象來提供這個(gè)結(jié)構(gòu),之后克隆這個(gè)對(duì)象來獲得我們需要的新對(duì)象。

構(gòu)造函數(shù)(Constructor Functions)

第一種途徑是使用 JavaScript 的構(gòu)造函數(shù)(constructor functions,or constructors)方式。對(duì)象字面量是對(duì)這種方式的一種簡化版。下面2個(gè)對(duì)象是等價(jià)的。

// 使用對(duì)象字面量
var personA = {
    name : "Mark",
    age : 23
};

// 使用構(gòu)造器
var personB = new Object();
personB.name = "Mark";
personB.age = 23;

Object 函數(shù)是我們的構(gòu)造器,采用 “var personB = new Object()” 方式和采用 “var personA = {}” 是等價(jià)的。采用 new Object(),我們創(chuàng)建了一個(gè)空對(duì)象,這個(gè)空對(duì)象被成為是 Object 的一個(gè)實(shí)例。

Object constructor 因其代表著JavaScript 的基礎(chǔ)對(duì)象而顯得與眾不同:所有的對(duì)象,不論這些對(duì)象是由哪個(gè) constructor 創(chuàng)建出來的,本質(zhì)上都是 Object 的實(shí)例。使用 instanceof 操作符可以判斷一個(gè)對(duì)象是否是一個(gè) constructor 的實(shí)例。

// 使用對(duì)象字面量
var personA = {};

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

// 檢測(cè)上面2個(gè)對(duì)象是否是Object的實(shí)例
conlose.log(personA instanceof Object) // true
conlose.log(personB instanceof Object) // true

每一個(gè)對(duì)象都有一個(gè)名字為 constructor 的特殊屬性,其是對(duì)創(chuàng)建該對(duì)象本身的 constructor 函數(shù)的引用。在我們上面的簡單例子中,constructor的屬性值是 Object constructor:

// 使用對(duì)象字面量
var personA = {};

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

// 檢測(cè)是否使用了Object的constructor
conlose.log(personA.constructor == Object) // true
conlose.log(personB.constructor == Object) // true

就像它的名字所示,constructor 函數(shù),顯然的,是一個(gè)函數(shù)。事實(shí)上,任何一個(gè) JavaScript 函數(shù)都能被用作構(gòu)造函數(shù)。這是JavaScript 對(duì)象處理方面的一個(gè)獨(dú)特的地方。不同于在對(duì)象實(shí)例化時(shí)創(chuàng)建一個(gè)新的構(gòu)造,Javascript 是依賴于現(xiàn)有的構(gòu)造。

當(dāng)然,你不必將你創(chuàng)造的所有函數(shù)都用作構(gòu)造函數(shù)。大部分情況下,你會(huì)為你的類創(chuàng)建一個(gè)專用于構(gòu)造目的的函數(shù)。一個(gè)構(gòu)造函數(shù)和其他函數(shù)一樣——除了自身細(xì)節(jié)上有些許區(qū)別——慣常的做法是將函數(shù)名首字母大寫以表示其存在目的是作為一個(gè)構(gòu)造函數(shù)。

// 一個(gè)person構(gòu)造函數(shù)
var Person = {};

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

// 以構(gòu)造器函數(shù)調(diào)用Person
var person = new Person();

console.log(typeof person); // "object"
console.log(person instanceof Person); // true
console.log(person.constructor == Person); // true

我們通過一個(gè)簡單的空函數(shù)來創(chuàng)建一個(gè)構(gòu)造器。當(dāng)Person函數(shù)被采用常規(guī)方式調(diào)用時(shí),它返回 undefined。當(dāng)我們?cè)谡{(diào)用之前加上一個(gè) new 關(guān)鍵字的時(shí)候,情況就變了:它返回了一個(gè)新對(duì)象。配合使用 new 關(guān)鍵字可以使一個(gè)函數(shù)被作為構(gòu)造器使用進(jìn)而產(chǎn)生一個(gè)對(duì)象的實(shí)例化。

在我們的例子中,new Person() 返回了一個(gè)空對(duì)象,這和使用 new Object() 的返回是一樣的。這里的區(qū)別是,返回的對(duì)象不單單是 Object 的實(shí)例,同時(shí)也是 Person 的實(shí)例,并且該對(duì)象的 constructor 屬性現(xiàn)在指向的是新的 Person 對(duì)象而非 Object 對(duì)象。不過返回的總歸還是一個(gè)空對(duì)象。

回顧一下上一章講到的,函數(shù)內(nèi)的 this 關(guān)鍵字指向的是一個(gè)對(duì)象。在這個(gè)關(guān)于我們的 Person 函數(shù)的例子中,當(dāng)它被作為平臺(tái)函數(shù)調(diào)用時(shí),引起被定義在全局作用域中,所以 this 關(guān)鍵字指向的對(duì)象是 global 對(duì)象。但當(dāng) Person 被作為一個(gè)構(gòu)造函數(shù)時(shí),情況就變了。this 關(guān)鍵字不再指向 global 對(duì)象,而是指向新創(chuàng)建出來的那個(gè)對(duì)象:

// 一個(gè)全局變量
var fruit = "banana";

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

// 被作為普通函數(shù)使用時(shí)
fruit(); // "banana"

// 被作為constructor使用時(shí)
new Person(); // undefinded

最后一行的代碼輸出的是 undefined,這是因?yàn)?this.fruit 不再指向一個(gè)已存在的變量標(biāo)識(shí)符。new 關(guān)鍵字的作用就是創(chuàng)建一個(gè)新對(duì)象,并將構(gòu)造函數(shù)內(nèi)的 this 指向這個(gè)新創(chuàng)建的對(duì)象。

在本章的開始部分,我們遇到了一個(gè)使用對(duì)象字面量創(chuàng)建多個(gè)對(duì)象的問題——我們需要一個(gè)方法來批量的創(chuàng)建對(duì)象的拷貝而非一個(gè)個(gè)的去敲代碼把它們?nèi)珜懸槐椤,F(xiàn)在我們知道構(gòu)造函數(shù)可以做到這一點(diǎn),并且其內(nèi)的 this 關(guān)鍵字指向的就是新創(chuàng)建的對(duì)象。

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"

你會(huì)注意到這里我們對(duì)構(gòu)造函數(shù)進(jìn)行了一些修改使其可以接受參數(shù)。這是因?yàn)闃?gòu)造函數(shù)和普通函數(shù)一樣,只不過其內(nèi)部的 this 關(guān)鍵字指向的是新創(chuàng)建的對(duì)象。當(dāng) new Person 被執(zhí)行的時(shí)候,一個(gè)新的對(duì)象被創(chuàng)建出來,并且 Person 函數(shù)被調(diào)用。在構(gòu)造函數(shù)內(nèi)部,參數(shù) name、age 被設(shè)置為同名對(duì)象屬性的值,之后這個(gè)對(duì)象被返回。

使用構(gòu)造函數(shù)可以很輕松的創(chuàng)建出和構(gòu)造函數(shù)具有類似結(jié)構(gòu)的新對(duì)象,并且不用費(fèi)事的每次都為新對(duì)象用字面量的方式書寫一遍結(jié)構(gòu)。你可以在編碼的開始階段就創(chuàng)建一個(gè)定義了基本結(jié)構(gòu)的構(gòu)造函數(shù),這對(duì)你以后為實(shí)例化的對(duì)象們?cè)黾有碌膶傩曰蚍椒ㄟt早會(huì)有幫助。

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"

這里你會(huì)看到我們?cè)跇?gòu)造函數(shù)里新增了一個(gè) log 方法,該方法會(huì)將對(duì)象的 name 和 age 信息打印出來。這樣就避免了在對(duì)象實(shí)例化之后還要手工的為每一個(gè)對(duì)象增加 log 方法。

原型(Prototypes)

看起來似乎構(gòu)造函數(shù)已經(jīng)是關(guān)于 JavaScript 對(duì)象創(chuàng)建的終極知識(shí)點(diǎn)了,但請(qǐng)注意,還沒結(jié)束呢!我們現(xiàn)在還只說了二分之一而已。如果我們把自己局限在僅僅使用構(gòu)造函數(shù)的范圍,那么很快就會(huì)遇到新問題。

問題之一就是代碼組織。在上一節(jié)的開頭,我們想有一種簡單的方法可以批量創(chuàng)建具有 name 和 age 屬性的 person 對(duì)象,并且期望同時(shí)具備 setName、getName、setAge、getAge 等方法。如果按照我們現(xiàn)在的需求,沿用上一節(jié)的方式,最終我們的代碼會(huì)變成下面這個(gè)樣子:

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;
    }

};

現(xiàn)在我們的 Person 構(gòu)造器開始變得腫脹了——這還僅是包含了2個(gè)屬性和4個(gè)方法的時(shí)候!想想如果你要?jiǎng)?chuàng)建一個(gè)很復(fù)雜的應(yīng)用,那構(gòu)造函數(shù)得變得多么龐大!

另一個(gè)問題是可擴(kuò)展性。假設(shè)我們有如下代碼:

// 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"

現(xiàn)在Person是在外部引入的一個(gè)JS文件中定義的,我們?cè)谶@個(gè)頁面里引入定義了 Person 構(gòu)造函數(shù)的 constructor.js 文件,并實(shí)例化了一個(gè) mark 對(duì)象。現(xiàn)在問題來了,因?yàn)槲覀儸F(xiàn)在無法修改構(gòu)造函數(shù)本身,那該如何為實(shí)例增加 setName、getName、setAge、getAge 等方法呢?

解決方案似乎很簡單,既然不能通過修改構(gòu)造函數(shù)來增加方法,那就直接給實(shí)例增加方法不就行了么~很快隨著鍵盤的敲打,代碼變成了下面這個(gè)樣子。

// 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"

// 下面的代碼會(huì)引起報(bào)錯(cuò)
joseph.getName();
joseph.getAge();

雖然我們成功的為 mark 實(shí)例添加了需要的方法,但 joseph 實(shí)例并不能同樣獲得這些方法。此時(shí)我們遇到了和使用對(duì)象字面量一樣的問題:我們必須為每一個(gè)對(duì)象的實(shí)例做同樣的設(shè)置才行,這顯然是不實(shí)用的。我們需要一個(gè)更有“療效”的方法。

在本章的開頭我們說過,Javascript 是一門基于原型的語言,基于原型的語言最重要的特征就是創(chuàng)建對(duì)象是通過對(duì)一個(gè)目標(biāo)對(duì)象的拷貝來實(shí)現(xiàn),而非通過類。但我們目前還未提及過拷貝,或者作為原型的目標(biāo)對(duì)象,我們目前為止看到的都是構(gòu)造函數(shù)配合著new關(guān)鍵字。

我們的線索就是new關(guān)鍵字。記住當(dāng)我們使用 new Object 時(shí),new 關(guān)鍵字創(chuàng)建了一個(gè)新的對(duì)象,并將該對(duì)象作為構(gòu)造函數(shù)內(nèi)this 關(guān)鍵字指向的對(duì)象。實(shí)際上,new 關(guān)鍵字并未創(chuàng)建一個(gè)新的對(duì)象:它只是拷貝了一個(gè)對(duì)象。這個(gè)被拷貝的對(duì)象不是別的,正是原型(prototype)

所有能被作為構(gòu)造函數(shù)使用的函數(shù)都有一個(gè) prototype 屬性,這個(gè)屬性對(duì)象定義了你實(shí)例化對(duì)象的結(jié)構(gòu)。當(dāng)使用 new Object 時(shí),一個(gè)對(duì) Object.prototype 的拷貝被創(chuàng)造出來,這個(gè)拷貝就是新創(chuàng)建的那個(gè)實(shí)例對(duì)象。這是 Javascript 的另一個(gè)有趣的特點(diǎn):不同于其它的原型語言——對(duì)它們來說,任何對(duì)象都能作為原型使用;但在Javascript中,卻有一個(gè)專為作為原型使用 prototype 對(duì)象存在。

注意:對(duì) Javascript 而言,這是一種對(duì)其他原型性語言的模仿:對(duì)其他原型性語言而言,你可以直接克隆一個(gè)對(duì)象來得到新的對(duì)象,在 Javascript 中則是依賴克隆目標(biāo)對(duì)象的 prototype 屬性。在本章的最后一節(jié)你會(huì)學(xué)到實(shí)現(xiàn)這一做法。

prototype 對(duì)象,和其他對(duì)象一樣,對(duì)其內(nèi)部可容納的成員沒有數(shù)量上的限制,對(duì)其增加一個(gè)成員基本上就是簡單的附加一個(gè)值而已。下面我們對(duì)之前的 Person 函數(shù)進(jìn)行一番改寫:

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 方法的定義移出構(gòu)造函數(shù),通過 Person.prototype.log 的方式去定義,這樣我們就能告訴解析器所有從 Person 構(gòu)造函數(shù)實(shí)例化出來的對(duì)象都將具有 log 方法,所以最后一行的 mark.log() 會(huì)執(zhí)行。剩余的構(gòu)造函數(shù)還是保持原樣,我們并未把 this.name 和 this.age 也放在 prototype 中去,因?yàn)槲覀冞€是希望在對(duì)象實(shí)例化之時(shí)就能初始化這些值。

有了 prototype 這個(gè)利器,我們就可以對(duì)開頭的代碼進(jìn)行重構(gòu),并使其變得更具可維護(hù)性:

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;
    }

}

現(xiàn)在好多了,再也沒有那么多的東西擁擠在構(gòu)造函數(shù)內(nèi)了。而且以后一旦需要增加新的方法,只需要按照給 prototype 增加即可,而不用去重新整理構(gòu)造函數(shù)。

我們?cè)?jīng)有的另一個(gè)問題(第一個(gè)是快捷創(chuàng)建多個(gè)實(shí)例對(duì)象,見上面)是在無法修改構(gòu)造函數(shù)的情況下給實(shí)例成員添加新的方法,現(xiàn)在隨著我們打通了一個(gè)通往構(gòu)造函數(shù)的大門(prototype屬性),我們可以輕松的在不通過構(gòu)造函數(shù)的情況下為實(shí)例對(duì)象添加方法。

// 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"

在前面我們已經(jīng)看到了一些簡單的動(dòng)態(tài)豐富 prototype 的例子。一個(gè)函數(shù)對(duì)象,以構(gòu)造函數(shù)來確定其形式,并可通過Mootools 的 Function.implement 函數(shù)為其增加新的方法。所有 Javascript 函數(shù)其實(shí)都是 Function 對(duì)象的實(shí)例,F(xiàn)unction.implement 實(shí)際上就是通過修改 Function.prototype 對(duì)象來實(shí)現(xiàn)的。雖然我們并不能直接操作 Function 的構(gòu)造函數(shù)——一個(gè)由解析器提供的內(nèi)置構(gòu)造——但我們依然可以通過 Function.prototype 來為 Function 對(duì)象增加新的方法。對(duì)原生方法類型的增益我們將會(huì)在后面“衍生與原生”(Types and Natives)一節(jié)中進(jìn)行討論。

繼承(Inheritance)

為了更高的理解 Javascript 是一門基于原型的語言,我們需要區(qū)分原型與實(shí)例之間的區(qū)別。原型(prototype)是一個(gè)對(duì)象,它就像一個(gè)藍(lán)本,用來定義我們需要的對(duì)象結(jié)構(gòu)。通過對(duì)原型的拷貝,我們可以創(chuàng)造出一個(gè)該原型的實(shí)例(instance)

// 動(dòng)物的構(gòu)造器
var Animal = function(name) {
    this.name = name;
};

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

// 動(dòng)物的實(shí)例
var cat = new Animal("Cat");
cat.walk(); // "Cat is walking"

上面的代碼中,構(gòu)造函數(shù) Animal 和它的 prototype 一起定義了 Animal 對(duì)象的結(jié)構(gòu),cat 對(duì)象是 Animal 的一個(gè)實(shí)例。當(dāng)我們執(zhí)行 new Animal() 語句,一個(gè) Animal.prototype 的拷貝就被創(chuàng)建,我們稱這個(gè)拷貝為一個(gè)實(shí)例(instance)。Animal.prototype 是一個(gè)只有一個(gè)成員的對(duì)象,這個(gè)唯一的成員是 walk 方法。自然,所有 Animal 的實(shí)例都會(huì)自動(dòng)擁有 walk 這個(gè)方法。

那么,當(dāng)我們?cè)谝粋€(gè)實(shí)例已經(jīng)被創(chuàng)建之后再去修改 Animal.prototype ,會(huì)發(fā)生什么呢?

// 動(dòng)物的構(gòu)造器
var Animal = function(name) {
    this.name = name;
};

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

// 動(dòng)物的實(shí)例
var cat = new Animal("Cat");
cat.walk(); // "Cat is walking"

// 難道動(dòng)物不應(yīng)該擁有吃(eat)這個(gè)方法嗎?
console.log(typeof cat.eat); // undefined --> 沒有 TT

// 給動(dòng)物增加一個(gè)“吃”的方法
Animal.prototype.eat = function() {
    console.log(this.name + " is eating.");
};

console.log(typeof cat.eat); // "function"
cat.eat(); // "Cat is eating"

嘿,現(xiàn)在這發(fā)生的事有點(diǎn)意思哈?在我們創(chuàng)建好 cat 實(shí)例時(shí)候,檢測(cè) eat 方法顯示的是 undefined。在我們給 Animal.prototype 對(duì)象新增了一個(gè) eat 方法之后,cat 實(shí)例就擁有了吃的能力!實(shí)際上,cat 的“吃”的能力就是我們給 Animal.prototype 增加的那個(gè)函數(shù)。

看起來,似乎是不論我們什么時(shí)候給原型增加新的方法,這都會(huì)自動(dòng)觸發(fā)全部的實(shí)例進(jìn)行一次更新。但記住當(dāng)我們新創(chuàng)建一個(gè)對(duì)象,那么這個(gè)新的操作就會(huì)創(chuàng)建一個(gè)新的原型拷貝。當(dāng)我們創(chuàng)建 cat 時(shí),原型還僅擁有一個(gè)方法。如果這是一個(gè)純粹的拷貝,那就不應(yīng)該擁有我們之后才設(shè)置的 eat 方法。畢竟,當(dāng)你復(fù)印了一份文檔,之后在源文檔上又寫上一句 “天朝人民最幸福”,你不能指望那份復(fù)印的文檔上也立即出現(xiàn)同樣的字句,不是嗎?

或者是解析器知道什么時(shí)候 prototype 新增了成員并自動(dòng)給全部的實(shí)例都增加上這個(gè)方法?也許是當(dāng)我們給原型增加了 eat 這個(gè)方法后,解析器便立刻給全部的 Animal 實(shí)例增加上了這個(gè)方法?對(duì)于這一點(diǎn)的驗(yàn)證是很簡單的:我們可以先給實(shí)例設(shè)置一個(gè) eat 的方法,之后再給原型增加 eat 方法。如果上面的猜測(cè)是對(duì)的,那么后增加的原型的 eat 方法會(huì)覆蓋掉較早給 Animal 實(shí)例多帶帶設(shè)置的那個(gè) eat 方法。

// 動(dòng)物的構(gòu)造器
var Animal = function(name) {
    this.name = name;
};

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

// 動(dòng)物的實(shí)例
var cat = new Animal("Cat");
cat.walk(); // "Cat is walking"

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

// 給動(dòng)物增加一個(gè)“吃”的方法
Animal.prototype.eat = function() {
    console.log(this.name + " is eating.");
};

cat.eat(); // "Meow. Cat is eating."

很明顯,前面的猜測(cè)是錯(cuò)誤的。Javascript 解析器不會(huì)更新實(shí)例。那真實(shí)的情況到底是什么呢?

所有的對(duì)象都有一個(gè)叫做 proto 的內(nèi)置屬性,該屬性指向該對(duì)象的原型。解析器利用該屬性將對(duì)象“鏈接”到它對(duì)應(yīng)的原型上。雖然在使用 new 關(guān)鍵字的時(shí)候確實(shí)是創(chuàng)建了一個(gè)原型的拷貝,且這個(gè)拷貝看起來確實(shí)很像原型本身,但它實(shí)際上卻是一個(gè)“淺拷貝”。真相是,當(dāng)這個(gè)實(shí)例被創(chuàng)建時(shí),它實(shí)際上只是一個(gè)空對(duì)象,這個(gè)空對(duì)象的 proto 屬性指向了其構(gòu)造函數(shù)的 prototype 對(duì)象。

你可能會(huì)問:“等等,既然這個(gè)新的實(shí)例是一個(gè)空對(duì)象,那為什么它還會(huì)像其來源的原型那樣具有屬性和方法呢”?其實(shí)這就是 proto 屬性的作用。實(shí)例對(duì)象通過 proto 屬性鏈接到它的原型,這樣它原型上的屬性和方法也能被其實(shí)例對(duì)象訪問到。在我們的例子中,cat 對(duì)象本身被沒有 walk 的方法。當(dāng)解析器讀取到 cat.walk() 語句時(shí),它首先檢測(cè) cat 對(duì)象自身的prototype 對(duì)象中有無 walk 這個(gè)方法成員,如果沒有,就通過 cat 的 proto 屬性上溯到其原型的 prototype 中去尋找 walk 方法。而正好在這里解析器找到了它需要的方法,于是我們的 cat 就能執(zhí)行“走”的動(dòng)作了。

這也能解釋為什么上面的代碼中最后 log 出的信息是“Meow. Cat is eating.”,因?yàn)槲覀兘o實(shí)例對(duì)象 cat 的 prototype 屬性對(duì)象增加了 eat 這個(gè)方法成員,于是解析器先在這里找到了它需要的 “eat 方法,進(jìn)而 cat 的原型 prototype 中的 eat 方法就不會(huì)起作用了。

一個(gè)實(shí)例對(duì)象的成員(屬性啊方法啊神馬的)來自于它的原型(而非是針對(duì)這個(gè)實(shí)例對(duì)象多帶帶設(shè)置),被稱為繼承(inheritance)。對(duì)所有對(duì)象,你都能使用 hasOwnProperty 方法來檢測(cè)某個(gè)成員是不是隸屬于它。

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

這里,我們對(duì) cat 使用 .hasOwnProperty(walk) 檢測(cè),返回為true,這是因?yàn)槲覀円呀?jīng)對(duì) cat 多帶帶設(shè)置了一個(gè)它自己的 walk 方法。對(duì)應(yīng)的,因?yàn)?dog 對(duì)象并未被賦以一個(gè)多帶帶的 walk 方法,所以檢測(cè)結(jié)果為 false。另外,如果對(duì) cat 采用 .hasOwnProperty(hasOwnProperty),返回的同樣會(huì)是 false。這是因?yàn)?hasOwnProperty 實(shí)際上是 Obiect 對(duì)象的方法,而 cat 對(duì)象由 Object 處繼承而來。

現(xiàn)在有一個(gè)家伙需要我們好好的去考慮一下:this。在構(gòu)造函數(shù)內(nèi)的 this,其永遠(yuǎn)指向構(gòu)造函數(shù)的實(shí)例化對(duì)象而非構(gòu)造函數(shù)的 prototype 對(duì)象。但是在原型內(nèi)定義的函數(shù)則遵循另一個(gè)法則:如果該方法是直接的由原型方式來調(diào)用,則該方法內(nèi)的 this 指向的是這個(gè)原型對(duì)象本身;如果該方法由這個(gè)原型的實(shí)例化對(duì)象來引用,則方法內(nèi)的 this 關(guān)鍵字就會(huì)指向這個(gè)實(shí)例化對(duì)象。

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

Animal.prototype.name = "Animal";

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

// 直接使用原型方法來調(diào)用“getName”
Animal.prototype.getName(); // 返回 "Animal"

var cat = new Animal("Cat");
cat.getName(); // 返回 "Cat"

這里我們對(duì)代碼進(jìn)行了一些小的修改,以便 Animal.prototype 可以有其自己的 name 屬性。當(dāng)我們直接用原型方式調(diào)用 getName 時(shí),返回的是 Animal.prototype 的 name 屬性。但當(dāng)我們通過實(shí)例化對(duì)象去執(zhí)行 cat.getName() 時(shí),返回的就是 cat 的 name 屬性。

原型和實(shí)例是不同的對(duì)象,它們之間唯一的聯(lián)系是:針對(duì)原型做的修改會(huì)反射到所有該原型的實(shí)例對(duì)象,但對(duì)某具體實(shí)例對(duì)象的修改卻只對(duì)該實(shí)例對(duì)象本身起作用。

記住在 Javascript 中同時(shí)存在著基本數(shù)據(jù)類型和復(fù)合數(shù)據(jù)類型。如字符串、數(shù)字以及布爾值等都屬于基本數(shù)據(jù)類型:當(dāng)它們被作為參數(shù)傳遞給函數(shù)或被賦值于一個(gè)變量時(shí),被使用的都是它們的拷貝。而像數(shù)組、函數(shù)、對(duì)象這樣的復(fù)合數(shù)據(jù)類,被使用的則是它們的引用

// 創(chuàng)建一個(gè)對(duì)象
var object = {name : "Mark"};

// 把這個(gè)對(duì)象“拷貝”給另一個(gè)變量
var copy = object;

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

// 更改copy對(duì)象的name值
copy.name = "Joseph";

console.log(object.name); // "Joseph"
console.log(copy.name); // "Joseph"

當(dāng) var copy = object 被執(zhí)行時(shí),沒有新的對(duì)象被創(chuàng)建出來。copy 變量其實(shí)只是指向了 object 所指向的同一個(gè)對(duì)象。object 和 copy 現(xiàn)在都是指向同一個(gè)對(duì)象,自然從 copy 處對(duì)其指向?qū)ο笞龅母膭?dòng),object 也會(huì)得到反射。

對(duì)象可以擁有復(fù)合數(shù)據(jù)類型的成員,對(duì)象自身的 prototype 也同樣如此。所以便出現(xiàn)了下面這個(gè)需要被注意的問題:當(dāng)給一個(gè)指向復(fù)合數(shù)據(jù)類型的原型增加新的成員時(shí),因?yàn)樗性撛偷膶?shí)例對(duì)象也都指向該原型本身,所以對(duì)原型的改動(dòng)也會(huì)被繼承。

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"

因?yàn)槲覀兊?cat 和 shark 對(duì)象都沒有自己的 data 屬性,所以它們從 Animal.prototype 處繼承而來,所以 cat.data 和 shark.data 都指向了 Animal.prototype 中定義的 data 對(duì)象,對(duì)任何一個(gè)實(shí)例的 data 對(duì)象的更改都會(huì)引起我們不希望看到的行為。

最簡單的解決辦法就是將 data 屬性從 Animal.prototype 中移除并在每個(gè)實(shí)例對(duì)象中多帶帶定義它們。通過構(gòu)造函數(shù)來實(shí)現(xiàn)這一點(diǎn)是很簡單的。

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"

因?yàn)榇藭r(shí)構(gòu)造函數(shù)內(nèi)的 this 關(guān)鍵字在此處是指向?qū)嵗瘜?duì)象的,所以 this.data 也就為每一個(gè)對(duì)象多帶帶賦予了一個(gè) data 屬性,且不會(huì)影響到構(gòu)造函數(shù)的原型。進(jìn)而會(huì)看到,最后的輸出結(jié)果也正是我需要的那樣。

原型鏈(The Prototype Chain)

在 Javascript 中,Object 是基礎(chǔ)對(duì)象模型。其他對(duì)象不論是具備如何不同的構(gòu)造,都是會(huì)從 Object 對(duì)象處獲得繼承。下面的代碼足夠幫助我們來理解這一點(diǎn):

var object = new Object();

console.log(object instanceof Object); // true

因?yàn)槲覀兪前凑?Object 的構(gòu)造函數(shù)來創(chuàng)建的 object 對(duì)象,所以我們可以說 object 對(duì)象的內(nèi)部屬性 proto 指向的就是 Object 的 prototype 屬性。現(xiàn)在,再來看下面這段代碼。

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"

因?yàn)槭褂?new Animal() 的緣故,所以我們知道 cat 實(shí)際上是一個(gè) Animal 的實(shí)例。而且我們還知道所有對(duì)象都有一個(gè)繼承自 Object 的 hasOwnProperty 屬性。于是我們就要問了,既然 object 對(duì)象的 proto 屬性現(xiàn)在指向的是 Animal 的原型,那這里又是怎么做到的 object 能在未涉及 Object 構(gòu)造函數(shù)的情況下還能同時(shí)從 Animal 和 Object 獲得繼承呢?

答案就在原型之間。默認(rèn)情況下,構(gòu)造函數(shù)的 prototype 對(duì)象是一個(gè)不含任何方法只含有其構(gòu)造函數(shù)中設(shè)置的屬性的基本對(duì)象。這聽起來很熟悉不是嗎?這和我們使用 new Object() 創(chuàng)造出來的對(duì)象是一樣的!實(shí)際上我們的代碼還可以像下面這樣來寫。

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"

現(xiàn)在就已經(jīng)很清晰了,Animal.prototype 由 Object.prototype 處繼承而來。對(duì)于一個(gè)實(shí)例而言,除了會(huì)從它自身的 prototype 對(duì)象繼承之外,還會(huì)從 它原型的原型的 prototype 對(duì)象處繼承。

感到費(fèi)解?那就通過對(duì)上面的代碼進(jìn)行分析來加強(qiáng)一下對(duì)這點(diǎn)的理解。我們的 cat 對(duì)象是由 Animal 對(duì)象實(shí)例化而來,所以 cat 會(huì)繼承 Animal.prototype 的屬性和方法。而 Animal.prototype 是由 Object 實(shí)例化而來,所以 Animal.prototype 會(huì)繼承 Object.prototype 的屬性和方法。進(jìn)而 cat對(duì)象 會(huì)同時(shí)繼承 Animal.prototype 和 Object.prototype 的屬性和方法,所以我們說 cat 是間接繼承(indirectly inherits)了 Object.prototype 對(duì)象。

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

注意:所有對(duì)象原型鏈的終點(diǎn)都是 Object.prototype,且 Object 的 proto 屬性不指向任何一個(gè)對(duì)象——否則原型鏈就會(huì)變得沒有邊界而導(dǎo)致基于原型鏈的上溯流程變得無法終止。Object.prototype 對(duì)象本身非由任何構(gòu)造函數(shù)產(chǎn)生,而是由解析器內(nèi)置的方法創(chuàng)建,這使得 Object.prototype 成為唯一一個(gè)不是由 Object 實(shí)例化而來的對(duì)象。

沿著一個(gè)對(duì)象的原型鏈查找屬性或方法的行為我們稱之為遍歷(traversal)。當(dāng)解析器遇到 cat.hasOwnProperty 語句時(shí),解析器首先在當(dāng)前對(duì)象的 prototype 對(duì)象中查找相關(guān)方法。如果沒有,則順序的在原型鏈上下一個(gè)對(duì)象—— Animal.prototype 上查找。還是沒有,則繼續(xù)在下一個(gè)對(duì)象的 prototype 上查找,以此類推。一旦解析器找到了它要的方法,解析器就會(huì)使用當(dāng)前找到的這個(gè)方法,其在原型鏈上的遍歷也會(huì)停止。如果解析器在整個(gè)原型鏈上都找不到它需要的方法,它就會(huì)返回 undefined。在我們的例子中,解析器最后在 Object.prototype 對(duì)象上找到了 hasOwnProperty 方法。

一個(gè)對(duì)象總是屬于至少一個(gè)構(gòu)造函數(shù)的實(shí)例:不論是使用對(duì)象字面量還是對(duì)象構(gòu)造函數(shù)創(chuàng)造出來的對(duì)象,總都屬于 Object 的實(shí)例。對(duì)那些非直接由 Object 構(gòu)造函數(shù)創(chuàng)造出來的對(duì)象而言,它們既是直接創(chuàng)建它們的構(gòu)造函數(shù)的實(shí)例,同時(shí)還是它們?cè)玩溕纤?prototype 對(duì)象對(duì)應(yīng)的構(gòu)造函數(shù)的實(shí)例。

有考量的原型鏈(Deliberate Chains)

一旦我們要?jiǎng)?chuàng)建更為復(fù)雜的對(duì)象,原型鏈就會(huì)變得非常有用。比如我們現(xiàn)在要?jiǎng)?chuàng)建一個(gè) Animal 對(duì)象:所有的動(dòng)物都有名字(name),所有的動(dòng)物還要能夠吃東西(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"

目前為止一切都還好。不過現(xiàn)在需要?jiǎng)游飩兡馨l(fā)出聲音,于是我們需要增加新的方法。顯然,這些動(dòng)物發(fā)出的聲音應(yīng)該是不一樣的:貓咪的叫聲是“meow”,小鳥的叫聲是“tweet”。我們可以為每一個(gè)動(dòng)物實(shí)例多帶帶設(shè)置發(fā)聲的方法,但顯然在面對(duì)一個(gè)需要?jiǎng)?chuàng)造多個(gè)貓咪和小鳥的需求面前,這種做法是不合事宜的。我們似乎還可以通過為 Animal.prototype 增加方法來達(dá)到貓咪和小鳥等實(shí)例都具備發(fā)聲的能力,但這還是在浪費(fèi)精力:因?yàn)樨堖洳粫?huì)發(fā)出“tweet”的聲音,小鳥也不會(huì)“meow”的叫。

那我們?yōu)槊總€(gè)實(shí)例對(duì)象自身的構(gòu)造函數(shù)多帶帶設(shè)置方法行不行呢?我們可以制造出 Cat、Bird 的構(gòu)造器并為其分別設(shè)置不同的發(fā)聲方式。而“吃”的能力則還是從 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 構(gòu)造函數(shù),并且基于它新建了另外兩個(gè)更具體的構(gòu)造函數(shù)——Cat 和 Bird。之后我們分別為 Cat 和 Bird 設(shè)置了它們自己的發(fā)聲方式。這樣,我們最終的實(shí)例對(duì)象貓咪和小鳥就都能發(fā)出它們各自不同的叫聲了。

在基于類的程序語言中,這種直接繼承了其實(shí)例化來源的類的特征,且更具針對(duì)性的分支被稱為子類(subclassing)。Javascript,則是一門基于原型的語言,并沒有類的概念,就其本質(zhì)而言,我們唯一所做的就是創(chuàng)造了一個(gè)有考量的原型鏈(deliberate prototype chain)。這里之所以用“有考量”這個(gè)詞,是因?yàn)槲覀冿@然是有意的設(shè)計(jì)了哪些對(duì)象應(yīng)該出現(xiàn)在我們的實(shí)例原型鏈上。

原型鏈上的成員數(shù)量沒有限制,你還可以通過豐富原型鏈上的對(duì)象來滿足更有針對(duì)性的需求。

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

這里我們創(chuàng)造了一個(gè)名為 Persian(波斯貓) 的 Cat 分支。你會(huì)注意到這里我們?cè)O(shè)置了一個(gè) Persian.prototype.meow 的方法,這個(gè)方法在 Persian 的實(shí)例中會(huì)覆蓋掉 Cat.prototype.meow。如果你檢查一下,會(huì)發(fā)現(xiàn) king 對(duì)象分別是 Animal、Cat 和 Persian 的實(shí)例,這也說明了我們?cè)玩湹脑O(shè)計(jì)是正確的。

原型鏈真正的威力在于繼承與原型鏈遍歷的結(jié)合。因?yàn)樵玩溕纤械?prototype 對(duì)象都是鏈起來的,所以原型鏈上某一點(diǎn)的改變會(huì)立即反射到它所指向的其他成員對(duì)象。如果我們給 Animal.prototype 新增一個(gè)方法,那么所有 Animal 的實(shí)例都會(huì)新增加上這個(gè)方法。這位我們批量的為對(duì)象擴(kuò)充方法提供了簡易快捷的方式。

如果你的程序正變得愈加龐大,那么有考量的原型鏈會(huì)幫助你的代碼更具結(jié)構(gòu)性。不同于把所有的代碼都塞進(jìn)一個(gè) prototype 對(duì)象中,你可以創(chuàng)建多重的具備良好設(shè)計(jì)的 prototype 對(duì)象,這對(duì)減少代碼量、提升代碼的可維護(hù)性都很有好處。

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

現(xiàn)在你應(yīng)該已經(jīng)意識(shí)到 Javascript 的面向?qū)ο箫L(fēng)情有其獨(dú)到的范式。Javascript 所謂的“基于原型的程序語言”很大程度上是僅限于名義上的。Javascript 中有著本應(yīng)是在基于類的語言中才會(huì)出現(xiàn)的構(gòu)造函數(shù)和 new 關(guān)鍵字的組合,同時(shí)將從原型——這個(gè)顯著的原型式語言的特征——處繼承來的東西作為其用以實(shí)現(xiàn)針對(duì)性 prototype 對(duì)象的依據(jù),而這些更具針對(duì)性的 prototype 對(duì)象,則是那么的類似類式語言中的子類。這門語言在對(duì)象機(jī)制實(shí)現(xiàn)方面的設(shè)計(jì)一定程度上受到了當(dāng)時(shí)程序語言潮流的影響:在這門語言被創(chuàng)建的那個(gè)時(shí)代,基于類的程序語言處于正統(tǒng)的標(biāo)準(zhǔn)地位。所以,最終的決定就是為這門新語言賦予一些同類式語言相似的特征。

盡管如此,Javascript 依然是一門靈活的語言。雖然我們不能改變?cè)谄浜诵闹卸x的對(duì)象的實(shí)現(xiàn)機(jī)制,但我們依然可使用現(xiàn)有手段令這門語言散發(fā)出更純粹的原型式風(fēng)格(當(dāng)然我們?cè)谙乱徽轮袝?huì)看到另一種流派——如何使這門語言在實(shí)際中更具備類式風(fēng)格)。

在我們現(xiàn)在所討論的簡化原型的范疇內(nèi),讓我們把視線從 Javascript 本身那具備復(fù)合性特征的原型上先移開,只先關(guān)注對(duì)象本身。不同于先創(chuàng)建一個(gè)構(gòu)造函數(shù)之后再設(shè)置其 prototype,我們使用真的對(duì)象作為原型來創(chuàng)建新的對(duì)象,并將其prototype屬性“克隆”到新創(chuàng)建的對(duì)象身上。為了更明確的說明我們要做的,這里先舉一個(gè)例子,這個(gè)例子來自另一個(gè)純粹的原型式程序語言 IO:

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

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

myCat := Cat clone

雖然這不是一本關(guān)于 IO 語言的書,但我們還是從基礎(chǔ)講起。同 Javascript 一樣,IO 中的基礎(chǔ)對(duì)象也是 Object。不過,這里的 Object 并不是一個(gè)構(gòu)造器(厄,一個(gè)函數(shù)),而是一個(gè)真正的對(duì)象。在我們代碼的開始部分,我們創(chuàng)造了一個(gè)新的對(duì)象—— Animal,這個(gè)新對(duì)象由源對(duì)象 Object 處克隆而來。因?yàn)樵?IO 語言中,空格用來訪問屬性,所以 Object clone 語句的含義就是“使用 Object 的 clone 方法并執(zhí)行它”。之后我們?yōu)?Animal 的 name 屬性設(shè)置了一個(gè)字符型的值,通過克隆 Animal 創(chuàng)建了一個(gè)名為 Cat 的新對(duì)象,同時(shí)我也為這個(gè) Cat 對(duì)象設(shè)置了 name 屬性,最后我們克隆 Cat 得到一個(gè) myCat 對(duì)象。

我們可以在 Javascript 中實(shí)現(xiàn)類似的事:

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 處克隆而來的,這些都是純粹的對(duì)象而非構(gòu)造器。但在我們的 Javascript 的例子中,最終的 myCat 對(duì)象則是由Cat、Animal、Object 等對(duì)象的 prototype 屬性繼承而來,Cat、Animal、Object 等也都是函數(shù)而非對(duì)象。換句話說。IO 沒有構(gòu)造函數(shù)的概念,一切都是直接從對(duì)象克隆而來。但 Javascript 卻有構(gòu)造函數(shù),且克隆的是 prototype。

如果我們能控制內(nèi)部屬性 proto,那么我們就能在 Javascript 中實(shí)現(xiàn)和 IO 一樣特性。 例如,假如我們有一個(gè) Animal 對(duì)象和一個(gè) Cat 對(duì)象,我們可以改變 Cat 對(duì)象的 proto屬性使之直接鏈向 Animal 對(duì)象(而非鏈向 Animal 的 prototype 對(duì)象)本身,這樣 Cat 就能直接繼承 Animal 對(duì)象。

因?yàn)?proto 屬性是內(nèi)置屬性不能直接修改它,但一些 Javascript 解析器卻引入了一個(gè)和其類似的名為 proto 的屬性。一個(gè)對(duì)象的 proto 屬性被用作更改其內(nèi)置的 proto 屬性,以使其可以直接鏈向其他對(duì)象。

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."

這里不存在構(gòu)造函數(shù),Animal 和 Cat 對(duì)象直接由字面量創(chuàng)建。通過 Cat.__proto__ = Animal 語句我們告訴解析器 Cat 的 proto 屬性直接指向 Animal 對(duì)象。最后 myCat 對(duì)象都直接從 Cat 和 Animal 處得到繼承,在 myCat 的原型鏈上也不存在任何為 prototype 的對(duì)象。這個(gè)簡化的原型模型不包含任何的構(gòu)造器或原型屬性,而是替代的將真實(shí)的對(duì)象本身放置其原型鏈上。

類似的,你可以使用 Object.create 方法來達(dá)到同樣的效果,這個(gè)新函數(shù)目前已經(jīng)被 ECMAScript 5 正式引入。它只接受一個(gè)參數(shù),該參數(shù)為一個(gè)對(duì)象,其執(zhí)行的結(jié)果是創(chuàng)建一個(gè)空對(duì)象,而這個(gè)對(duì)象的 proto 屬性將被指向作為參數(shù)傳入的那個(gè)對(duì)象。

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 方法很相像,實(shí)際上,它們實(shí)現(xiàn)的也是同一件事。我們可以使用 Object.create 方法非常高仿的實(shí)現(xiàn) IO 語言的那個(gè)片段:

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

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

myCat = Object.create(Cat);

不幸的是,雖然上面的兩種方式都很美妙,但它們卻不能兼容所有平臺(tái)。__proto__ 屬性目前還不屬于正式的 ECMAScript 規(guī)范,所以并不是所有的解析器都對(duì)其提供支持。而 Object.create() 方法,雖然是規(guī)范中的一員,但該規(guī)范卻是指 ECMAScript 5。因該規(guī)范是2009年才頒布的,所以目前也不是所有解析器都能提供完整的支持。如果你希望寫出具有更好兼容性的代碼(尤其是 web app 程序),就尤其要記住這2種方式都不是通用方案。

現(xiàn)在有一種方案可以使較為古老的解析器也能支持 Object.create 方法。就是記住 Javascript 對(duì)象通過引用來起作用,如果你將一個(gè)對(duì)象存儲(chǔ)在變量 x 中,之后操作 y = x,那么 y 和 x 將同時(shí)指向同一個(gè)對(duì)象。同時(shí),一個(gè)函數(shù)的 prototype 屬性也是一個(gè)對(duì)象,而這個(gè)對(duì)象的初始值可以很輕易的通過被分配給一個(gè)新的對(duì)象值來覆蓋:

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"

這段代碼現(xiàn)在看來應(yīng)該有些眼熟了吧。我們首先創(chuàng)建了一個(gè)有著2個(gè)成員(一個(gè)name 屬性、一個(gè) eat 方法)的 Animal 對(duì)象,之后我們創(chuàng)建了一個(gè)名為 AnimalProto 的“跳板級(jí)”構(gòu)造函數(shù),并將它的 prototype 屬性設(shè)置為 Animal 對(duì)象。因?yàn)橐玫木壒剩珹nimalProto.prototype 屬性 和 Animal 現(xiàn)在都指向了同一個(gè)對(duì)象。這就意味著,當(dāng)我們創(chuàng)建了 cat 實(shí)例時(shí),它實(shí)際上是直接繼承自 Animal 對(duì)象 —— 這就像是使用 Object.create 方法創(chuàng)造出來的一樣。

采用這個(gè)點(diǎn)子,我們可以模擬出 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"

最開始,我們使用一個(gè) IF 語句來判斷當(dāng)前解析器是否支持 Object.create 方法。如果支持,則直接執(zhí)行下面的語句,如果不支持,就模擬一個(gè)該方法:它首先創(chuàng)造一個(gè)名為 Intermediate 的構(gòu)造器,之后將該構(gòu)造器的 prototype 屬性指向作為參數(shù)傳入的那個(gè)對(duì)象。最后該函數(shù)返回一個(gè) Intermediate.prototype 的實(shí)例。因?yàn)檫@里我們使用的方法都是當(dāng)下解析器所支持的,所以我們可以說這個(gè)模擬的 Object.create 方法是具備普適性的。

總結(jié)(The Wrap Up)

在這一章,我們?cè)敿?xì)的討論了有關(guān) Javascript 對(duì)象機(jī)制的所有話題,并展示了它和其他語言之間的區(qū)別。雖然它是一門基于原型的語言,但因?yàn)槠渥陨淼囊恍┆?dú)特性,使其實(shí)際上是兼具類式和原型式語言的特征。我們看到了如何使用字面量和構(gòu)造器的 prototype 屬性來新建對(duì)象。我們還展示了繼承的奧秘、Javascript 原型鏈上的遍歷是如何工作的。最后我們還實(shí)踐了一個(gè)將 Javascript 本身的原型混雜性隱藏起來的簡便原型式模型。

因?yàn)?Javascript 的核心是一門面向?qū)ο蟮恼Z言,所以在這里所寫的針對(duì)該點(diǎn)的知識(shí),會(huì)在我們開發(fā)復(fù)雜應(yīng)用時(shí)候提供莫大的幫助。雖然面向?qū)ο蟊旧硪呀?jīng)超越了本書所要講述的范圍,但我依然希望我在這里所提供的信息,可以為你在該話題上的深入學(xué)習(xí)提供一點(diǎn)幫助。

招賢納士(Recruitment)

招人,前端,隸屬政采云前端大團(tuán)隊(duì)(ZooTeam),50 余個(gè)小伙伴正等你加入一起浪~ 如果你想改變一直被事折騰,希望開始能折騰事;如果你想改變一直被告誡需要多些想法,卻無從破局;如果你想改變你有能力去做成那個(gè)結(jié)果,卻不需要你;如果你想改變你想做成的事需要一個(gè)團(tuán)隊(duì)去支撐,但沒你帶人的位置;如果你想改變既定的節(jié)奏,將會(huì)是“5年工作時(shí)間3年工作經(jīng)驗(yàn)”;如果你想改變本來悟性不錯(cuò),但總是有那一層窗戶紙的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望參與到隨著業(yè)務(wù)騰飛的過程,親手參與一個(gè)有著深入的業(yè)務(wù)理解、完善的技術(shù)體系、技術(shù)創(chuàng)造價(jià)值、影響力外溢的前端團(tuán)隊(duì)的成長歷程,我覺得我們?cè)摿牧摹H魏螘r(shí)間,等著你寫點(diǎn)什么,發(fā)給 ZooTeam@cai-inc.com

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/110040.html

相關(guān)文章

  • ES5特性 - ECMAScript特性 - Javascript核心

    摘要:特性本文將簡單列舉的核心特性。獲取自有屬性名列表。以給丁對(duì)象為創(chuàng)建新的對(duì)象并返回。將對(duì)象的每個(gè)自有自有屬性做如下操作屬性的特性置為屬性的特性置為同時(shí),該對(duì)象將不可擴(kuò)展。檢查對(duì)象是否是位于給定對(duì)象的原型鏈上。 原文: http://pij.robinqu.me/JavaScript_Core/ECMAScript/es5.html 源代碼: https://github....

    Half 評(píng)論0 收藏0
  • JavaScript 特殊對(duì)象 Array-Like Objects 詳解

    摘要:很簡單,不是數(shù)組,但是有屬性,且屬性值為非負(fù)類型即可。至于屬性的值,給出了一個(gè)上限值,其實(shí)是感謝同學(xué)指出,因?yàn)檫@是中能精確表示的最大數(shù)字。如何將函數(shù)的實(shí)際參數(shù)轉(zhuǎn)換成數(shù)組 這篇文章拖了有兩周,今天來跟大家聊聊 JavaScript 中一類特殊的對(duì)象 -> Array-Like Objects。 (本文節(jié)選自 underscore 源碼解讀系列文章,完整版請(qǐng)關(guān)注 https://githu...

    zhaofeihao 評(píng)論0 收藏0
  • MongoEngine 查詢(翻譯)

    摘要:數(shù)據(jù)庫查詢對(duì)象有一個(gè)屬性,用來訪問在數(shù)據(jù)庫中跟這個(gè)類有關(guān)的對(duì)象。使用方法在數(shù)據(jù)不存在的時(shí)候會(huì)返回默認(rèn)查詢默認(rèn)情況下,的屬性返回一個(gè)一個(gè)對(duì)象,它并沒有進(jìn)行任何篩選和過濾,它返回的是所有的數(shù)據(jù)對(duì)象。它返回結(jié)果是函數(shù)的返回值。 數(shù)據(jù)庫查詢 Document 對(duì)象有一個(gè) objects 屬性,用來訪問在數(shù)據(jù)庫中跟這個(gè)類有關(guān)的對(duì)象。這個(gè) objects 屬性其實(shí)是一個(gè)QuerySetManage...

    int64 評(píng)論0 收藏0
  • MongoEngine 查詢(翻譯)

    摘要:數(shù)據(jù)庫查詢對(duì)象有一個(gè)屬性,用來訪問在數(shù)據(jù)庫中跟這個(gè)類有關(guān)的對(duì)象。使用方法在數(shù)據(jù)不存在的時(shí)候會(huì)返回默認(rèn)查詢默認(rèn)情況下,的屬性返回一個(gè)一個(gè)對(duì)象,它并沒有進(jìn)行任何篩選和過濾,它返回的是所有的數(shù)據(jù)對(duì)象。它返回結(jié)果是函數(shù)的返回值。 數(shù)據(jù)庫查詢 Document 對(duì)象有一個(gè) objects 屬性,用來訪問在數(shù)據(jù)庫中跟這個(gè)類有關(guān)的對(duì)象。這個(gè) objects 屬性其實(shí)是一個(gè)QuerySetManage...

    didikee 評(píng)論0 收藏0
  • Javascript Objects - Javascript語法基礎(chǔ) - Javascript核心

    摘要:創(chuàng)建對(duì)象對(duì)象直接量構(gòu)造函數(shù)原型繼承類繼承對(duì)象擁有自有屬性和繼承屬性。遍歷順序是以廣度優(yōu)先遍歷所以使用便可以判斷是否是對(duì)象自有的屬性。可執(zhí)行對(duì)象通過如下方法可以創(chuàng)建一個(gè)可執(zhí)行對(duì)象既可以當(dāng)作對(duì)象來使用有原型鏈,也可以當(dāng)作函數(shù)來直接調(diào)用 原文: http://pij.robinqu.me/Javascript_Core/Javascript_Basics/Objects.html ...

    wzyplus 評(píng)論0 收藏0
  • 重學(xué)前端學(xué)習(xí)筆記(九)--JavaScript中的對(duì)象分類

    摘要:固有對(duì)象由標(biāo)準(zhǔn)規(guī)定,隨著運(yùn)行時(shí)創(chuàng)建而自動(dòng)創(chuàng)建的對(duì)象實(shí)例。普通對(duì)象由語法構(gòu)造器或者關(guān)鍵字定義類創(chuàng)建的對(duì)象,它能夠被原型繼承。 筆記說明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開的一個(gè)專欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過程的一些要點(diǎn)筆記以及感悟,完整的可以加入winter的專欄學(xué)習(xí)【原文有winter的語音】,如有侵權(quán)請(qǐng)聯(lián)系我,郵箱:ka...

    ShowerSun 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<