JavaScript中prototype用法
轉自: http://blog.csdn.net/jasonzds/article/details/53706958
1 概述
大部分物件導向的程式語言,都是以類class作為物件的基礎語法,js語言不是如此,它的物件導向程式設計基於‘原型物件’。
首先說說建構函式的缺點:
js通過建構函式生成新的物件,因此建構函式可以視為獨享的模版。例項物件的屬性和方法,可以定義在建構函式內部
概述
建構函式的缺點
JavaScript通過建構函式生成新物件,因此建構函式可以視為物件的模板。例項物件的屬性和方法,可以定義在建構函式內部。
function Cat (name, color) {
this.name =name;
this.color =color;
}
var cat1 = new Cat('大毛', '白色');
cat1.name // '大毛'
cat1.color // '白色'
上面程式碼的Cat函式是一個建構函式,函式內部定義了name屬性和color屬性,所有例項物件都會生成這兩個屬性。但是,這樣做是對系統資源的浪費,因為同一個建構函式的物件例項之間,無法共享屬性。
function Cat(name, color) {
this.name =name;
this.color =color;
this.meow =function () {
console.log('mew, mew, mew...');
};
}
var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');
cat1.meow === cat2.meow
// false
上面程式碼中,cat1和cat2是同一個建構函式的例項。但是,它們的meow方法是不一樣的,就是說每新建一個例項,就會新建一個meow方法。這既沒有必要,又浪費系統資源,因為所有meow方法都是同樣的行為,完全應該共享。
prototype屬性的作用
JavaScript的每個物件都繼承另一個物件,後者稱為“原型”(prototype)物件。只有null除外,它沒有自己的原型物件。
原型物件上的所有屬性和方法,都能被派生物件共享。這就是JavaScript繼承機制的基本設計。
通過建構函式生成例項物件時,會自動為例項物件分配原型物件。每一個建構函式都有一個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物件,就是例項物件cat1和cat2的原型物件。在原型物件上新增一個color屬性。結果,例項物件都能讀取該屬性。
原型物件的屬性不是例項物件自身的屬性。只要修改原型物件,變動就立刻會體現在所有例項物件上。
Animal.prototype.color = 'yellow';
cat1.color // "yellow"
cat2.color // "yellow"
上面程式碼中,原型物件的color屬性的值變為yellow,兩個例項物件的color屬性立刻跟著變了。這是因為例項物件其實沒有color屬性,都是讀取原型物件的color屬性。也就是說,當例項物件本身沒有某個屬性或方法的時候,它會到建構函式的prototype屬性指向的物件,去尋找該屬性或方法。這就是原型物件的特殊之處。
如果例項物件自身就有某個屬性或方法,它就不會再去原型物件尋找這個屬性或方法。
cat1.color = 'black';
cat2.color // 'yellow'
Animal.prototype.color // "yellow";
上面程式碼中,例項物件cat1的color屬性改為black,就使得它不再去原型物件讀取color屬性,後者的值依然為yellow。
總結一下,原型物件的作用,就是定義所有例項物件共享的屬性和方法。這也是它被稱為原型物件的含義,而例項物件可以視作從原型物件衍生出來的子物件。
Animal.prototype.walk = function () {
console.log(this.name + ' is walking');
};
上面程式碼中,Animal.prototype物件上面定義了一個walk方法,這個方法將可以在所有Animal例項物件上面呼叫。
由於JavaScript的所有物件都有建構函式,而所有建構函式都有prototype屬性(其實是所有函式都有prototype屬性),所以所有物件都有自己的原型物件。
原型鏈
物件的屬性和方法,有可能是定義在自身,也有可能是定義在它的原型物件。由於原型本身也是物件,又有自己的原型,所以形成了一條原型鏈(prototype chain)。比如,a物件是b物件的原型,b物件是c物件的原型,以此類推。
如果一層層地上溯,所有物件的原型最終都可以上溯到Object.prototype,即Object建構函式的prototype屬性指向的那個物件。那麼,Object.prototype物件有沒有它的原型呢?回答可以是有的,就是沒有任何屬性和方法的null物件,而null物件沒有自己的原型。
Object.getPrototypeOf(Object.prototype)
// null
上面程式碼表示,Object.prototype物件的原型是null,由於null沒有任何屬性,所以原型鏈到此為止。
“原型鏈”的作用是,讀取物件的某個屬性時,JavaScript引擎先尋找物件本身的屬性,如果找不到,就到它的原型去找,如果還是找不到,就到原型的原型去找。如果直到最頂層的Object.prototype還是找不到,則返回undefined。
如果物件自身和它的原型,都定義了一個同名屬性,那麼優先讀取物件自身的屬性,這叫做“覆蓋”(overiding)。
需要注意的是,一級級向上,在原型鏈尋找某個屬性,對效能是有影響的。所尋找的屬性在越上層的原型物件,對效能的影響越大。如果尋找某個不存在的屬性,將會遍歷整個原型鏈。
舉例來說,如果讓某個函式的prototype屬性指向一個陣列,就意味著該函式可以當作陣列的建構函式,因為它生成的例項物件都可以通過prototype屬性呼叫陣列方法。
var MyArray = function () {};
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的例項。
下面的程式碼可以找出,某個屬性到底是原型鏈上哪個物件自身的屬性。
function getDefiningObject(obj, propKey) {
while (obj&& !{}.hasOwnProperty.call(obj, propKey)) {
obj =Object.getPrototypeOf(obj);
}
return obj;
}
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屬性的作用,是分辨原型物件到底屬於哪個建構函式。
function F() {};
var f = new F();
f.constructor === F // true
f.constructor === RegExp // false
上面程式碼表示,使用constructor屬性,確定例項物件f的建構函式是F,而不是RegExp。
有了constructor屬性,就可以從例項新建另一個例項。
function Constr() {}
var x = new Constr();
var y = new x.constructor();
y instanceof Constr // true
上面程式碼中,x是建構函式Constr的例項,可以從x.constructor間接呼叫建構函式。
這使得在例項方法中,呼叫自身的建構函式成為可能。
Constr.prototype.createCopy = function () {
return newthis.constructor();
};
這也提供了繼承模式的一種實現。
function Super() {}
function Sub() {
Sub.superclass.constructor.call(this);
}
Sub.superclass = new Super();
上面程式碼中,Super和Sub都是建構函式,在Sub內部的this上呼叫Super,就會形成Sub繼承Super的效果。
由於constructor屬性是一種原型物件與建構函式的關聯關係,所以修改原型物件的時候,務必要小心。
function A() {}
var a = new A();
a instanceof A // true
function B() {}
A.prototype = B.prototype;
a instanceof A // false
上面程式碼中,a是A的例項。修改了A.prototype以後,constructor屬性的指向就變了,導致instanceof運算子失真。
所以,修改原型物件時,一般要同時校正constructor屬性的指向。
// 避免這種寫法
C.prototype = {
method1:function (...) { ... },
// ...
};
// 較好的寫法
C.prototype = {
constructor:C,
method1:function (...) { ... },
// ...
};
// 好的寫法
C.prototype.method1 = function (...) { ... };
上面程式碼中,避免完全覆蓋掉原來的prototype屬性,要麼將constructor屬性重新指向原來的建構函式,要麼只在原型物件上新增方法,這樣可以保證instanceof運算子不會失真。
此外,通過name屬性,可以從例項得到建構函式的名稱。
function Foo() {}
var f = new Foo();
f.constructor.name// "Foo"
2 instanceof運算子
instanceof運算子返回一個布林值,表示指定物件是否為某個建構函式的例項。
var v = new Vehicle();
v instanceof Vehicle // true
上面程式碼中,物件v是建構函式Vehicle的例項,所以返回true。
instanceof運算子的左邊是例項物件,右邊是建構函式。它的運算實質是檢查右邊構建函式的原型物件,是否在左邊物件的原型鏈上。因此,下面兩種寫法是等價的。
v instanceof Vehicle
// 等同於
Vehicle.prototype.isPrototypeOf(v)
由於instanceof對整個原型鏈上的物件都有效,因此同一個例項物件,可能會對多個建構函式都返回true。
var d = new Date();
d instanceof Date // true
d instanceof Object // true
上面程式碼中,d同時是Date和Object的例項,因此對這兩個建構函式都返回true。
instanceof的原理是檢查原型鏈,對於那些不存在原型鏈的物件,就無法判斷。
Object.create(null) instanceof Object // false
上面程式碼中,Object.create(null)返回的新物件的原型是null,即不存在原型,因此instanceof就認為該物件不是Object的例項。
除了上面這種繼承null的特殊情況,JavaScript之中,只要是物件,就有對應的建構函式。因此,instanceof運算子的一個用處,是判斷值的型別。
var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true
上面程式碼中,instanceof運算子判斷,變數x是陣列,變數y是物件。
注意,instanceof運算子只能用於物件,不適用原始型別的值。
var s = 'hello';
s instanceof String // false
上面程式碼中,字串不是String物件的例項(因為字串不是物件),所以返回false。
此外,undefined和null不是物件,所以instanceOf運算子總是返回false。
undefined instanceof Object // false
null instanceof Object // false
利用instanceof運算子,還可以巧妙地解決,呼叫建構函式時,忘了加new命令的問題。
function Fubar (foo, bar) {
if (thisinstanceof Fubar) {
this._foo =foo;
this._bar =bar;
}
else {
return newFubar(foo, bar);
}
}
上面程式碼使用instanceof運算子,在函式體內部判斷this關鍵字是否為建構函式Fubar的例項。如果不是,就表明忘了加new命令。
3 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.prototype
var f = new F();
Object.getPrototypeOf(f) === F.prototype
// true
4 Object.setPrototypeOf()
Object.setPrototypeOf方法可以為現有物件設定原型,返回一個新物件。
Object.setPrototypeOf方法接受兩個引數,第一個是現有物件,第二個是原型物件。
var a = {x: 1};
var b = Object.setPrototypeOf({}, a);
// 等同於
// var b = {__proto__: a};
b.x // 1
上面程式碼中,b物件是Object.setPrototypeOf方法返回的一個新物件。該物件本身為空、原型為a物件,所以b物件可以拿到a物件的所有屬性和方法。b物件本身並沒有x屬性,但是JavaScript引擎找到它的原型物件a,然後讀取a的x屬性。
new命令通過建構函式新建例項物件,實質就是將例項物件的原型,指向建構函式的prototype屬性,然後在例項物件上執行建構函式。
var F = function () {
this.foo ='bar';
};
var f = new F();
// 等同於
var f = Object.setPrototypeOf({}, F.prototype);
F.call(f);
5 Object.create()
Object.create方法用於從原型物件生成新的例項物件,可以替代new命令。
它接受一個物件作為引數,返回一個新物件,後者完全繼承前者的屬性,即原有物件成為新物件的原型。
var A = {
print: function() {
console.log('hello');
}
};
var B = Object.create(A);
B.print() // hello
B.print === A.print // true
上面程式碼中,Object.create方法在A的基礎上生成了B。此時,A就成了B的原型,B就繼承了A的所有屬性和方法。這段程式碼等同於下面的程式碼。
var A = function () {};
A.prototype = {
print: function() {
console.log('hello');
}
};
var B = new A();
B.print === A.prototype.print // true
實際上,Object.create方法可以用下面的程式碼代替。如果老式瀏覽器不支援Object.create方法,可以就用這段程式碼自己部署。
if (typeof Object.create !== 'function') {
Object.create= function (o) {
function F(){}
F.prototype= o;
return newF();
};
}
上面程式碼表示,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 Objector null
object.create方法生成的新物件,動態繼承了原型。在原型上新增或修改任何方法,會立刻反映在新物件之上。
var o1 = { p: 1 };
var o2 = Object.create(o1);
o1.p = 2;
o2.p
// 2
上面程式碼表示,修改物件原型會影響到新生成的物件。
除了物件的原型,Object.create方法還可以接受第二個引數。該引數是一個屬性描述物件,它所描述的物件屬性,會新增到新物件。
var o = Object.create({}, {
p1: { value:123, enumerable: true },
p2: { value:'abc', enumerable: true }
});
// 等同於
var o = Object.create({});
o.p1 = 123;
o.p2 = 'abc';
Object.create方法生成的物件,繼承了它的原型物件的建構函式。
function A() {}
var a = new A();
var b = Object.create(a);
b.constructor === A // true
b instanceof A // true
上面程式碼中,b物件的原型是a物件,因此繼承了a物件的建構函式A。
6 Object.prototype.isPrototypeOf()
物件例項的isPrototypeOf方法,用來判斷一個物件是否是另一個物件的原型。
var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);
o2.isPrototypeOf(o3) // true
o1.isPrototypeOf(o3) // true
上面程式碼表明,只要某個物件處在原型鏈上,isPrototypeOf都返回true。
Object.prototype.isPrototypeOf({}) // true
Object.prototype.isPrototypeOf([]) // true
Object.prototype.isPrototypeOf(/xyz/) // true
Object.prototype.isPrototypeOf(Object.create(null)) //false
上面程式碼中,由於Object.prototype處於原型鏈的最頂端,所以對各種例項都返回true,只有繼承null的物件除外。
7 Object.prototype.__prop__
__proto__屬性(前後各兩個下劃線)可以改寫某個物件的原型物件。
var obj = {};
var p = {};
obj.__proto__ = p;
Object.getPrototypeOf(obj) === p // true
上面程式碼通過__proto__屬性,將p物件設為obj物件的原型。
根據語言標準,__proto__屬性只有瀏覽器才需要部署,其他環境可以沒有這個屬性,而且前後的兩根下劃線,表示它本質是一個內部屬性,不應該對使用者暴露。因此,應該儘量少用這個屬性,而是用Object.getPrototypeof()(讀取)和Object.setPrototypeOf()(設定),進行原型物件的讀寫操作。
原型鏈可以用__proto__很直觀地表示。
var A = {
name: '張三'
};
var B = {
name: '李四'
};
var proto = {
print: function() {
console.log(this.name);
}
};
A.__proto__ = proto;
B.__proto__ = proto;
A.print() // 張三
B.print() // 李四
上面程式碼中,A物件和B物件的原型都是proto物件,它們都共享proto物件的print方法。也就是說,A和B的print方法,都是在呼叫proto物件的print方法。
A.print === B.print // true
A.print === proto.print // true
B.print === proto.print // true
可以使用Object.getPrototypeOf方法,檢查瀏覽器是否支援__proto__屬性,老式瀏覽器不支援這個屬性。
Object.getPrototypeOf({ __proto__: null }) === null
上面程式碼將一個物件的__proto__屬性設為null,然後使用Object.getPrototypeOf方法獲取這個物件的原型,判斷是否等於null。如果當前環境支援__proto__屬性,兩者的比較結果應該是true。
8 獲取原型物件方法的比較
如前所述,__proto__屬性指向當前物件的原型物件,即建構函式的prototype屬性。
var obj = new Object();
obj.__proto__ === Object.prototype
// true
obj.__proto__ === obj.constructor.prototype
// true
上面程式碼首先新建了一個物件obj,它的__proto__屬性,指向建構函式(Object或obj.constructor)的prototype屬性。所以,兩者比較以後,返回true。
因此,獲取例項物件obj的原型物件,有三種方法。
• obj.__proto__
• obj.constructor.prototype
• Object.getPrototypeOf(obj)
上面三種方法之中,前兩種都不是很可靠。最新的ES6標準規定,__proto__屬性只有瀏覽器才需要部署,其他環境可以不部署。而obj.constructor.prototype在手動改變原型物件時,可能會失效。
var P = function () {};
var p = new P();
var C = function () {};
C.prototype = p;
var c = new C();
c.constructor.prototype === p // false
上面程式碼中,C建構函式的原型物件被改成了p,結果c.constructor.prototype就失真了。所以,在改變原型物件時,一般要同時設定constructor屬性。
C.prototype = p;
C.prototype.constructor = C;
c.constructor.prototype === p // true
所以,推薦使用第三種Object.getPrototypeOf方法,獲取原型物件。
var o = new Object();
Object.getPrototypeOf(o) === Object.prototype
// true
相關文章
- JavaScript prototype原型用法JavaScript原型
- JavaScript prototypeJavaScript
- 理解 JavaScript 中的 Function.prototype.bindJavaScriptFunction
- 理解JavaScript中的Function.prototype.bindJavaScriptFunction
- javascript中各類的prototype屬性JavaScript
- JavaScript prototype 原型JavaScript原型
- JavaScript:原型(prototype)JavaScript原型
- javascript中的prototype和__proto__的理解JavaScript
- JavaScript prototype屬性JavaScript
- JavaScript中的setInterval用法JavaScript
- JavaScript中document的用法JavaScript
- C#中的this擴充套件方法與javascript中的prototype方法C#套件JavaScript
- Javascript - prototype、__proto__、constructorJavaScriptStruct
- Javascript篇之Prototype的原型JavaScript原型
- javascript基礎(原型(prototype))(十七)JavaScript原型
- javascript prototype介紹的文章JavaScript
- 關於JavaScript中arguments的用法JavaScript
- JavaScript:prototype屬性使用方法JavaScript
- Function.prototype.bind()方法用法簡單介紹Function
- 深入瞭解JavaScript中基於原型(prototype)的繼承機制JavaScript原型繼承
- 全面瞭解 Javascript Prototype Chain 原型鏈JavaScriptAI原型
- JavaScript this用法JavaScript
- JavaScript用法JavaScript
- javascript __proto___ prototype和Function原始碼狂想JavaScriptFunction原始碼
- Object.prototype.__proto__, [[prototype]] 和 prototypeObject
- JavaScript delete用法JavaScriptdelete
- javascript hasOwnProperty() 用法JavaScript
- Javascript 的 this 用法JavaScript
- Javascript Promise用法JavaScriptPromise
- JavaScript中解決jQuery和Prototype.js同時引入衝突問題JavaScriptjQueryJS
- JavaScript 逗號(,)用法JavaScript
- 02JavaScript用法JavaScript
- JavaScript 逗號用法JavaScript
- JavaScript with 語句用法JavaScript
- javascript中的資料型別及其常見用法JavaScript資料型別
- 前端 JavaScript 中 JSON.stringify() 的基本用法前端JavaScriptJSON
- javascript中call()、apply()、bind()的用法終於理解JavaScriptAPP
- JavaScript中apply、call、bind的區別與用法JavaScriptAPP