Javascript原型介紹
原型及原型鏈
原型基礎概念
function Person () {
this.name = 'John';
}
var person = new Person();
Person.prototype.say = function() {
console.log('Hello,' + this.name);
};
person.say();//Hello,John
上述代碼非常簡單,Person原型對象定義了公共的say方法,雖然此舉在構造實例之後出現,但因為原型方法在調用之前已經聲明,當此實例本身沒有此say方法時候,會在自身原型上查找到此方法。
原型鏈
function Foo() { this.value = 42; } Foo.prototype = { method: function() {} }; function Bar() {} // 設置Bar的prototype屬性為Foo的實例對象 Bar.prototype = new Foo(); Bar.prototype.foo = 'Hello World'; // 修正Bar.prototype.constructor為Bar本身 Bar.prototype.constructor = Bar; var test = new Bar() // 創建Bar的一個新實例 // 原型鏈 test [Bar的實例] Bar.prototype [Foo的實例] { foo: 'Hello World' } Foo.prototype {method: ...}; Object.prototype {toString: ... /* etc. */};
上面的例子中,test 對象從 Bar.prototype 繼承下來;因此,它能訪問 Bar的原型方法,同時Bar.prototypeFoo為Foo的實例對象,能夠訪問Foo的原型方法 method。它也能夠訪問Foo 實例屬性 value。需要註意的是 new Bar() 不會創造出一個新的 Foo 實例,而是重復使用它原型上的那個實例;因此,所有的 Bar 實例都會共享相同的 value 屬性。
屬性查找
當查找一個對象的屬性時,JavaScript 會向上遍歷原型鏈,直到找到給定名稱的屬性為止,到查找到達原型鏈的頂部 - 也就是 Object.prototype - 但是仍然沒有找到指定的屬性,就會返回 undefined,我們來看一個例子:
function foo() {
this.add = function (x, y) {
return x + y;
}
}
foo.prototype.add = function (x, y) {
return x + y + 10;
}
Object.prototype.subtract = function (x, y) {
return x - y;
}
var f = new foo();
alert(f.add(1, 2)); //結果是3,而不是13
alert(f.subtract(1, 2)); //結果是-1
通過代碼運行,我們發現subtract是安裝我們所說的向上查找來得到結果的,但是add方式有點小不同,這也是我想強調的,就是屬性在查找的時候是先查找自身的屬性,如果沒有再查找原型,再沒有,再往上走,一直插到Object的原型上,所以在某種層面上說,用 for in語句遍歷屬性的時候,效率也是個問題。
還有一點我們需要註意的是,js中基礎構造器的prototype是不可改寫的, 不可刪除, 不可見的;
Object.getOwnPropertyDescriptor(Number, 'prototype');
// Object {value: Number, writable: false, enumerable: false, configurable: false};
hasOwnProperty函數:
hasOwnProperty是Object.prototype的一個方法,它可是個好東西,他能判斷一個對象是否包含自定義屬性而不是原型鏈上的屬性,
// 修改Object.prototype
Object.prototype.bar = 1;
var foo = {goo: undefined};
foo.bar; // 1
'bar' in foo; // true
foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true
使用 hasOwnProperty
可以給出正確和期望的結果,這在遍歷對象的屬性時會很有用。
對象在查找屬性時, 首先從自身查找, 查不到在原型鏈上查找, 層層向上一旦查到就返回, 直到查到 Object.protype
還查不到就返回undefined。
大家可以體會一下下面的結果, 建議動手畫一下js中幾種構造器和函數類型的原型鏈, 徹底理解他們之間的關系。
Function.toString === Object.toString // true
Function.prototype.toString === Object.toString // true
Function.prototype.__proto__ === Object.prototype // ture
Function.prototype.toString === Object.prototype.toString // false
當檢查對象上某個屬性是否存在時,hasOwnProperty 比較推薦的方法。同時在使用 for in loop 遍歷對象時,推薦總是使用 hasOwnProperty 方法,這將會避免原型對象擴展帶來的幹擾,我們來看一下例子:
// 修改 Object.prototype
Object.prototype.bar = 1;
var foo = {moo: 2};
for(var i in foo) {
console.log(i); // 輸出兩個屬性:bar 和 moo
}
我們沒辦法改變 for in
語句的行為,所以想過濾結果可以使用 hasOwnProperty
方法,代碼如下:
// foo 變量是上例中的
for(var i in foo) {
if (foo.hasOwnProperty(i)) {
console.log(i);
}
}
這個版本的代碼是唯一正確的寫法。由於我們使用了 hasOwnProperty,所以這次只輸出 moo。如果不使用 hasOwnProperty,
也可以使用Object.key()來獲取目標的屬性和方法列表,得到的將是一個數組, 裏面的屬性是目標對象上的, 不含其原型上和其自身不可枚舉的屬性, 若要想得到更細致的結果可以使用 Object.getOwnPropertyNames()
配合 hasOwnProperty()
使用。
總結:推薦使用 hasOwnProperty,不要對代碼運行的環境做任何假設,不要假設原生對象是否已經被擴展了。
總結
原型極大地豐富了我們的開發代碼,但是在平時使用的過程中請一定要註意上述提到的一些註意事項。
- 對象屬性的查找規則, 原型鏈上屬性之間的屏蔽。
- 深入理解
hasOwnProperty()
、for in
機制、Object.keys()
。 in
操作符具有遍歷到原型鏈頂端的特性,還能夠從對象和原型鏈上不可枚舉的屬性拿到true
, 以至於我們for in
遍歷的時候要註意原型鏈上的方法。Object.getOwnPropertyDescriptor()
可以幫助我們更細致的了解對象上的屬性。- 也涉及到了
Object.defineProperty()
方法, 可用來非常細致的定義對象上的某個屬性, 接受三個參數, 對象(object), 屬性名(string), 屬性描述器(object), 另外Object.defineProperties()
也是相似的,只是它接受2個參數, 要被定義屬性的對象(object), 屬性描述集合props(object), 該方法可以一次定義多個屬性。
Javascript原型介紹