原型
function Person(name) {
this.name = name;
}
var p1 = new Person('Leo');
var obj = new Object();
var fn = new Function();
// 對象的原型最終指向Object.prototype,Object.prototype的原型指向null
console.log( obj.__proto__ === Object.prototype ); // true 普通對象的原型
console.log( Person.prototype.__proto__ === Object.prototype ); // true 自定義構造函數原型的原型
console.log( Function.prototype.__proto__ === Object.prototype ); // true Function構造函數原型的原型
console.log( Object.prototype.__proto__ === null ); // true Object構造函數原型的原型
// 實例的原型指向創建該實例的構造函數的原型
console.log( p1.__proto__ === Person.prototype ); // true 實例對象的原型
// 函數的原型指向Function.prototype
console.log( fn.__proto__ === Function.prototype ); // true 普通函數的原型
console.log( Person.__proto__ === Function.prototype ); // true 自定義構造函數的原型
console.log( Function.__proto__ === Function.prototype ); // true Function構造函數的原型
console.log( Object.__proto__ === Function.prototype ); // true Object構造函數的原型
// 實例的constructor屬性,繼承自其原型
console.log( p1.hasOwnProperty('constructor') ); // false
console.log( obj.hasOwnProperty('constructor') ); // false
console.log( fn.hasOwnProperty('constructor') ); // false
// Function.prototype是一個函數
console.log( typeof Function.prototype ); // function
- prototype (原型)屬性
每個函數都有一個prototype屬性,這個屬性指向一個對象,這個對象就是原型對象。
原型對象的作用就是定義所有實例對象共享的屬性和方法。
實例對象可以看作從原型對象衍生出來的子對象,原型對象上的所有屬性和方法,都能被實例對象共享。
實例對象本身沒有某個屬性或方法時,會到原型對象去尋找該屬性或方法。
原型對象上的變動會立刻體現在所有實例對象上。
如果重寫整個原型,會切斷現有原型與任何之前已存在的對象實例之間的聯系,它們引用的依舊是之前的原型。
function Person(name) {
this.name = name;
};
Person.prototype.sayHi = function () {
console.log('Hi,'+ this.name);
}
var p1 = new Person('Leo');
// 先創建的實例對象,后修改原型,Person.prototype指向被新對象覆蓋
Person.prototype = { // 字面量相當于Object的實例
constructor: Person, //校正constructor屬性的指向,否則指向Object
getName: function () {
console.log(this.name);
}
}
var p2 = new Person('Tom');
// 重寫原型前創建的實例的原型依舊指向以前的原型
p1.sayHi(); // Hi,Leo
p1.getName(); // 報錯
// 重寫原型后創建的實例的原型依舊指向新的原型
p2.sayHi(); // 報錯
p2.getName(); // Tom
- constructor (構造函數)屬性
每個原型對象都會自動獲得一個constructor屬性,這個屬性默認指向prototype屬性所在函數(即構造函數)。
創建了自定義構造函數后,其原型對象默認只會取得constructor屬性,其他的方法則從Object繼承而來。
constructor屬性定義在原型對象上面,可以被所有實例對象繼承。
constructor屬性不是實例對象自身屬性,而是繼承自原型對象。
constructor屬性的作用,是分辨原型對象到底屬于哪個構造函數.
function Preson() {}
var p1 = new Preson();
console.log(p1.constructor); // function P() {}
console.log(p1.hasOwnProperty('constructor'));// false
console.log(p1.constructor === Preson); // true
console.log(p1.constructor === Function); // false
constructor屬性是一種原型對象與構造函數的關聯關系,修改原型對象時,一般同時校正constructor的指向。
[[Prototype]] 內部屬性
當用構造函數創建一個實例對象后,該實例對象包含一個[[Prototype]]內部屬性,指向構造函數的原型對象。
[[Prototype]]存在于實例對象和原型對象之間,而不是實例和構造函數之間。
[[Prototype]]內部屬性通過proto屬性來訪問。原型鏈
原型對象本身也是對象,也有自己原型對象,所以就形成了一條原型鏈(prototype chain)。
所有對象的對象原型終點可以可以上溯到Object.prototype。
Object.prototype對象的原型對象是沒有任何屬性和方法的null對象,null對象沒有原型對象。
讀取對象的某個屬性屬性時,先尋找對象本身的屬性,如果找不到就在原型鏈上找,直到Object.prototype。
如果對象自身和它的原型,都定義了一個同名屬性,那么優先讀取對象自身的屬性.instanceof 運算符
instanceof返回一個布爾值,表示左邊的實例對象是否為右邊的構造函數的實例。
instanceof的實質是檢查構造函數的原型對象是否在實例對象的原型鏈上。
instanceof對整個原型鏈上的對象都有效,因此同一個對象,可能會對多個構造函數都返回true。
JavaScript之中,只要是對象,就有對應的構造函數。因此,instanceof運算符可以判斷值的類型。
instanceof只適用于對象,不適用基本類型的值。
var d = new Date();
console.log(d instanceof Date); // true
console.log(d instanceof Object); // true
// 等同于
console.log(Date.prototype.isPrototypeOf(d)); // true
- in 運算符和 for…in 循環
in運算符返回一個布爾值,表示一個對象是否具有某個屬性。它不區分該屬性是對象自身的屬性,還是繼承的屬性。
'length' in Date // true
'toString' in Date // true
獲得對象的所有可枚舉屬性(不管是自身的還是繼承的),可以使用for...in循環。
for ( var name in object ) {
if ( object.hasOwnProperty(name) ) {
/* loop code */
}
}
- Object.prototype.hasOwnProperty()
對象實例的hasOwnProperty方法返回一個布爾值,用于判斷某個屬性定義在對象自身,還是定義在原型鏈上。
hasOwnProperty方法是JavaScript之中唯一一個處理對象屬性時,不會遍歷原型鏈的方法。
Date.hasOwnProperty('length'); // true
Date.hasOwnProperty('toString'); // false
- Object.getOwnPropertyNames()
Object.getOwnPropertyNames方法返回一個數組,成員是對象本身的所有屬性的鍵名,不包含繼承的屬性鍵名
Object.getOwnPropertyNames(String);
// ["length", "name", "arguments", "caller", "prototype", "fromCharCode", "fromCodePoint", "raw"]
- Object.prototype.isPrototypeOf()
對象實例isPrototypeOf方法,用來判斷一個對象是否是另一個對象的原型。
只有某個對象處在原型鏈上,isPrototypeOf都返回true。
Object.prototype處在原型鏈頂端,所以對各種實例都返回true,只有繼承null的對象除外。
Object.prototype.isPrototypeOf({});// true
Object.prototype.isPrototypeOf([]);// true
Object.prototype.isPrototypeOf(/xyz/);// true
Person.prototype.isPrototypeOf(p1);// true
Object.prototype.isPrototypeOf(Object.create(null));// false
Object.prototype.proto
proto方法可以獲取和改寫某個對象的原型對象。
proto方法應少用,而是使用標準方法Object.getPrototypeOf()和Object.setPrototypeOf()。Object.getPrototypeOf()
Object.getPrototypeOf方法接受一個對象,返回該對象的原型,是獲取原型對象的標準方法。
// 對象的原型是Object.prototype
console.log(Object.getPrototypeOf({}) === Object.prototype); // true
// 函數的原型是Function.prototype
function Person() {};
console.log(Object.getPrototypeOf(Person) === Function.prototype); // true
// 實例對象p1的原型是創建該實例的構造函數的原型
var p1 = new Person();
console.log(Object.getPrototypeOf(p1) === Person.prototype); // true
獲取原型對象方法的比較
// 獲取實例對象obj原型的三種方法:
var obj = new Object();
obj.__proto__; // 瀏覽器內部屬性,其他環境不可以部署
obj.constructor.prototype; // 手動修改原型對象時會失效,需要同時修改constructor屬性
Object.getPrototypeOf(obj)// 推薦
- Object.setPrototypeOf()
Object.setPrototypeOf方法可以為現有對象設置原型,返回一個新對象。
該方法接受兩個參數,第一個參數是現有對象,第二個參數是原型對象。
var a = { name: 'Leo'};
var b = Object.setPrototypeOf({}, a); // b對象本身為空,原型為a
console.log(a.isPrototypeOf(b)); // true a是b的原型
console.log(b.hasOwnProperty(name)); // false, b.name不是b自身的屬性
console.log(b.name); // Leo
// 等價于
var b = {__proto__: a};
console.log(b.name); // Leo
new命令通過構造函數新建實例對象,實質是將實例對象的原型,指向構造函數的prototype屬性,然后在實例對象上執行構造函數。
function Person(name, age) {
this.name = name;
this.age = age;
};
var p1 = new Person('Leo'); // 使用new新建構造函數
// 等價于
var p1 = Object.setPrototypeOf({}, Person.prototype);// 將實例對象的原型指向構造函數的prototype屬性
Person.call(p1, 'Leo', 26);// 在實例對象上執行構造函數,并傳入參數
console.log(p1.name); // Leo
console.log(p1.age); // 26
- Object.create()
Object.create方法用于從原型對象生成新的實例對象,可以替代new命令。
該方法接受一個對象為參數,返回一個新的對象,傳入的對象成為新對象的原型。必須提供原有對象,否則報錯。
var A = {
sayHi: function () {
console.log('hello');
}
};
var B = Object.create(A); // 在A的基礎上生成B,此時A就成為B的原型
B.sayHi(); // hello B繼承了A的所有屬性和方法
// 等同于
var A = function () {};
A.prototype.sayHi = function () {
console.log('hello');
}
B = new A();
B.sayHi() // hello
Object.create方法實質是新建一個構造函數F,讓F的prototype屬性指向作為原型的對象o,最后返回一個F的實例。
if (typeof Object.create !== 'function') {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
下面三種生成新對象的方式是等價的。
var o1 = Object.create({});
var o2 = Object.create(Object.prototype);
var o3 = new Object();
Object.create方法生成的新對象,動態的繼承了原型。在原型上的修改,會立刻反映在新對象上。
var A = {x: 1};
var B = Object.create(A);
A.x = 2;
console.log(B.x); // 2
Object.create方法還可以接受第二個參數,該參數是一個屬性描述對象,它所描述的對象屬性,會添加到新對象。
Object.create方法生成的對象,繼承了他的原型對象的構造函數。
function A() {}
var a = new A();
var b = Object.create(a); // b對象的原型是a,因此繼承了a的構造函數A
console.log(b.constructor === A); // true
console.log(b instanceof A); // true
參考資料:
《JavaScript高級程序設計》
《JavaScript 標準參考教程》