[TOC]
## prototype對象
### 構造函數的缺點
JavaScript通過構造函數生成新對象,因此構造函數可以視為對象的模板。實例對象的屬性和方法,可以定義在構造函數內部。
~~~
function Animal (name) {
this.name = name;
this.color = 'white';
}
var cat1 = new Animal('大毛');
cat1.name // '大毛'
cat1.color // 'white'
~~~
上面代碼的Animal函數是一個構造函數,函數內部定義了name屬性和color屬性,所有實例對象都會生成這兩個屬性。
但是,這樣做是對系統資源的浪費,因為同一個構造函數的對象實例之間,無法共享屬性。
### prototype屬性的作用
在JavaScript語言中,每一個對象都有一個對應的原型對象,被稱為prototype對象。定義在原型對象上的所有屬性和方法,都能被派生對象繼承。這就是JavaScript繼承機制的基本設計。
除了這種方法,JavaScript還提供了另一種定義實例對象的方法。我們知道,構造函數是一個函數,同時也是一個對象,也有自己的屬性和方法,其中有一個prototype屬性指向另一個對象,一般稱為prototype對象。該對象非常特別,只要定義在它上面的屬性和方法,能被所有實例對象共享。也就是說,構造函數生成實例對象時,自動為實例對象分配了一個prototype屬性。
~~~
function Animal (name) {
this.name = name;
}
Animal.prototype.color = "white";
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');
cat1.color // 'white'
cat2.color // 'white'
~~~
上面代碼對構造函數Animal的prototype對象,添加了一個color屬性。結果,實例對象cat1和cat2都帶有該屬性。
更特別的是,只要修改prototype對象,變動就立刻會體現在實例對象。
~~~
Animal.prototype.color = "yellow";
cat1.color // 'yellow'
cat2.color // 'yellow'
~~~
上面代碼將prototype對象的color屬性的值改為yellow,兩個實例對象的color屬性的值立刻就跟著變了。這是因為實例對象其實沒有color屬性,都是讀取prototype對象的color屬性。也就是說,當實例對象本身沒有某個屬性或方法的時候,它會到構造函數的prototype對象去尋找該屬性或方法。這就是prototype對象的特殊之處。
如果實例對象自身就有某個屬性或方法,它就不會再去prototype對象尋找這個屬性或方法。
~~~
cat1.color = 'black';
cat2.color // 'yellow'
Animal.prototype.color // "yellow";
~~~
上面代碼將實例對象cat1的color屬性改為black,就使得它不用再去prototype對象讀取color屬性,后者的值依然為yellow。
總而言之,prototype對象的作用,就是定義所有實例對象共享的屬性和方法,所以它也被稱為實例對象的原型,而實例對象可以視作從prototype對象衍生出來的。
~~~
Animal.prototype.walk = function () {
console.log(this.name + ' is walking.');
};
~~~
上面代碼在Animal.protype對象上面定義了一個walk方法,這個方法將可以在所有Animal實例對象上面調用。
### 原型鏈
由于JavaScript的所有對象都有構造函數,而所有構造函數都有prototype屬性(其實是所有函數都有prototype屬性),所以所有對象都有自己的prototype原型對象。
因此,一個對象的屬性和方法,有可能是定義它自身上面,也有可能定義在它的原型對象上面(就像上面代碼中的walk方法)。由于原型本身也是對象,又有自己的原型,所以形成了一條原型鏈(prototype chain)。比如,a對象是b對象的原型,b對象是c對象的原型,以此類推。因為追根溯源,最源頭的對象都是從Object構造函數生成(使用new Object()命令),所以如果一層層地上溯,所有對象的原型最終都可以上溯到Object.prototype。那么,Object.prototype有沒有原型呢?回答可以是有,也可以是沒有,因為Object.prototype的原型是沒有任何屬性和方法的null。
~~~
Object.getPrototypeOf(Object.prototype)
// null
~~~
上面代碼表示Object.prototype對象的原型是null,由于null沒有任何屬性,所以原型鏈到此為止。
“原型鏈”的作用在于,當讀取對象的某個屬性時,JavaScript引擎先尋找對象本身的屬性,如果找不到,就到它的原型去找,如果還是找不到,就到原型的原型去找。以此類推,如果直到最頂層的Object.prototype還是找不到,則返回undefined。
舉例來說,如果讓某個函數的prototype屬性指向一個數組,就意味著該函數可以用作數組的構造函數,因為它生成的實例對象都可以通過prototype屬性調用數組方法。
~~~
function MyArray (){}
MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;
var mine = new MyArray();
mine.push(1, 2, 3);
mine.length // 3
mine instanceof Array // true
~~~
上面代碼的mine是MyArray的實例對象,由于MyArray的prototype屬性指向一個數組,使得mine可以調用數組方法(這些方法其實定義在數組的prototype對象上面)。至于最后那行instanceof表達式,我們知道instanceof運算符用來比較一個對象是否為某個構造函數的實例,最后一行表示mine為Array的實例。
~~~
mine instanceof Array
// 等同于
(Array === MyArray.prototype.constructor) ||
(Array === Array.prototype.constructor) ||
(Array === Object.prototype.constructor )
~~~
上面代碼說明了instanceof運算符的實質,它依次與實例對象的所有原型對象的constructor屬性(關于該屬性的介紹,請看下一節)進行比較,只要有一個符合就返回true,否則返回false。
### constructor屬性
prototype對象有一個constructor屬性,默認指向prototype對象所在的構造函數。
~~~
function P() {}
P.prototype.constructor === P
// true
~~~
由于constructor屬性定義在prototype對象上面,意味著可以被所有實例對象繼承。
~~~
function P() {}
var p = new P();
p.constructor
// function P() {}
p.constructor === P.prototype.constructor
// true
p.hasOwnProperty('constructor')
// false
~~~
上面代碼表示p是構造函數P的實例對象,但是p自身沒有contructor屬性,該屬性其實是讀取原型鏈上面的`P.prototype.constructor`屬性。
constructor屬性的作用是分辨prototype對象到底定義在哪個構造函數上面。
~~~
function F(){};
var f = new F();
f.constructor === F // true
f.constructor === RegExp // false
~~~
上面代碼表示,使用constructor屬性,確定變量f的構造函數是F,而不是RegExp。
## Object.getPrototypeOf方法
Object.getPrototypeOf方法返回一個對象的原型。
~~~
// 空對象的原型是Object.prototype
Object.getPrototypeOf({}) === Object.prototype
// true
// 函數的原型是Function.prototype
function f() {}
Object.getPrototypeOf(f) === Function.prototype
// true
// 假定F為構造函數,f為F的實例對象
// 那么,f的原型是F.prototype
var f = new F();
Object.getPrototypeOf(f) === F.prototype
// true
~~~
## Object.create方法
Object.create方法用于生成新的對象,可以替代new命令。它接受一個原型對象作為參數,返回一個新對象,后者完全繼承前者的屬性。
~~~
var o1 = { p: 1 };
var o2 = Object.create(o1);
o2.p // 1
~~~
上面代碼中,o1是o2的原型對象,o2繼承了o1的屬性。
Object.create方法基本等同于下面的代碼,如果老式瀏覽器不支持Object.create方法,可以用下面代碼自己部署。
~~~
if (typeof Object.create !== "function") {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
~~~
上面代碼表示,Object.create方法實質是新建一個構造函數F,然后讓F的prototype屬性指向作為原型的對象o,最后返回一個F的實例,從而實現讓實例繼承o的屬性。
下面三種方式生成的新對象是等價的。
~~~
var o1 = Object.create({})
var o2 = Object.create(Object.prototype)
var o3 = new Object();
~~~
如果想要生成一個不繼承任何屬性(比如toString和valueOf方法)的對象,可以將Object.create的參數設為null。
~~~
var o = Object.create(null);
o.valueOf()
// TypeError: Object [object Object] has no method 'valueOf'
~~~
上面代碼表示,如果對象o的原型是null,它就不具備一些定義在Object.prototype對象上面的屬性,比如valueOf方法。
使用Object.create方法的時候,必須提供對象原型,否則會報錯。
~~~
Object.create()
// TypeError: Object prototype may only be an Object or null
~~~
Object.create方法生成的新對象,動態繼承了原型。在原型上添加或修改任何方法,會立刻反映在新對象之上。
~~~
var o1 = { p: 1 };
var o2 = Object.create(o1);
o1.p = 2;
o2.p
// 2
~~~
上面代碼表示,修改對象原型會影響到新生成的對象。
除了對象的原型,Object.create方法還可以接受第二個參數,表示描述屬性的attributes對象,跟用在Object.defineProperties方法的格式是一樣的。它所描述的對象屬性,會添加到新對象。
~~~
var o = Object.create(Object.prototype, {
p1: { value: 123, enumerable: true },
p2: { value: "abc", enumerable: true }
});
o.p1 // 123
o.p2 // "abc"
~~~
由于Object.create方法不使用構造函數,所以不能用instanceof運算符判斷,對象是哪一個構造函數的實例。這時,可以使用下面的isPrototypeOf方法,判讀原型是哪一個對象。
## isPrototypeOf方法
isPrototypeOf方法用來判斷一個對象是否是另一個對象的原型。
~~~
var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);
o2.isPrototypeOf(o3) // true
o1.isPrototypeOf(o3) // true
~~~
上面代碼表明,只要某個對象處在原型鏈上,isProtypeOf都返回true。
- 第一章 導論
- 1.1 前言
- 1.2 為什么學習JavaScript?
- 1.3 JavaScript的歷史
- 第二章 基本語法
- 2.1 語法概述
- 2.2 數值
- 2.3 字符串
- 2.4 對象
- 2.5 數組
- 2.6 函數
- 2.7 運算符
- 2.8 數據類型轉換
- 2.9 錯誤處理機制
- 2.10 JavaScript 編程風格
- 第三章 標準庫
- 3.1 Object對象
- 3.2 Array 對象
- 3.3 包裝對象和Boolean對象
- 3.4 Number對象
- 3.5 String對象
- 3.6 Math對象
- 3.7 Date對象
- 3.8 RegExp對象
- 3.9 JSON對象
- 3.10 ArrayBuffer:類型化數組
- 第四章 面向對象編程
- 4.1 概述
- 4.2 封裝
- 4.3 繼承
- 4.4 模塊化編程
- 第五章 DOM
- 5.1 Node節點
- 5.2 document節點
- 5.3 Element對象
- 5.4 Text節點和DocumentFragment節點
- 5.5 Event對象
- 5.6 CSS操作
- 5.7 Mutation Observer
- 第六章 瀏覽器對象
- 6.1 瀏覽器的JavaScript引擎
- 6.2 定時器
- 6.3 window對象
- 6.4 history對象
- 6.5 Ajax
- 6.6 同域限制和window.postMessage方法
- 6.7 Web Storage:瀏覽器端數據儲存機制
- 6.8 IndexedDB:瀏覽器端數據庫
- 6.9 Web Notifications API
- 6.10 Performance API
- 6.11 移動設備API
- 第七章 HTML網頁的API
- 7.1 HTML網頁元素
- 7.2 Canvas API
- 7.3 SVG 圖像
- 7.4 表單
- 7.5 文件和二進制數據的操作
- 7.6 Web Worker
- 7.7 SSE:服務器發送事件
- 7.8 Page Visibility API
- 7.9 Fullscreen API:全屏操作
- 7.10 Web Speech
- 7.11 requestAnimationFrame
- 7.12 WebSocket
- 7.13 WebRTC
- 7.14 Web Components
- 第八章 開發工具
- 8.1 console對象
- 8.2 PhantomJS
- 8.3 Bower:客戶端庫管理工具
- 8.4 Grunt:任務自動管理工具
- 8.5 Gulp:任務自動管理工具
- 8.6 Browserify:瀏覽器加載Node.js模塊
- 8.7 RequireJS和AMD規范
- 8.8 Source Map
- 8.9 JavaScript 程序測試
- 第九章 JavaScript高級語法
- 9.1 Promise對象
- 9.2 有限狀態機
- 9.3 MVC框架與Backbone.js
- 9.4 嚴格模式
- 9.5 ECMAScript 6 介紹
- 附錄
- 10.1 JavaScript API列表
- 草稿一:函數庫
- 11.1 Underscore.js
- 11.2 Modernizr
- 11.3 Datejs
- 11.4 D3.js
- 11.5 設計模式
- 11.6 排序算法
- 草稿二:jQuery
- 12.1 jQuery概述
- 12.2 jQuery工具方法
- 12.3 jQuery插件開發
- 12.4 jQuery.Deferred對象
- 12.5 如何做到 jQuery-free?
- 草稿三:Node.js
- 13.1 Node.js 概述
- 13.2 CommonJS規范
- 13.3 package.json文件
- 13.4 npm模塊管理器
- 13.5 fs 模塊
- 13.6 Path模塊
- 13.7 process對象
- 13.8 Buffer對象
- 13.9 Events模塊
- 13.10 stream接口
- 13.11 Child Process模塊
- 13.12 Http模塊
- 13.13 assert 模塊
- 13.14 Cluster模塊
- 13.15 os模塊
- 13.16 Net模塊和DNS模塊
- 13.17 Express框架
- 13.18 Koa 框架