JS中的prototype和__proto__
在開始之前要明確一點,“在JS裏,萬物皆對象”,方法(Function)是對象,方法的原型(Function.prototype)也是對象。因此,它們都會具有對象共有的特點。
一、prototype和__proto__分別是什麽?
prototype(顯式原型)是對象的一個屬性(每個對象都有一個prototype屬性),這個屬性是一個指針,指向一個對象,通過它可以向對象添加屬性和方法。
__proto__(隱式原型)是對象的一個內置屬性,是JS內部使用尋找原型鏈的屬性,也就是說,當我們訪問obj這個對象中的x屬性時,如果在obj中找不到,那麽就會沿著__proto__依次向上查找。
註意:用chrome和FF都可以訪問到對象的__proto__屬性,IE不可以。所以我們建議避免使用__proto__,因為它存在兼容性問題。
二者關系:一個對象的隱式原型指向構造該對象的構造函數的原型
二、創建對象實例的new做了些什麽?
var Person = function(){};
Person.prototype.sayName = function() {
alert("My Name is Jacky");
};
Person.prototype.age = 27;
var p = new Person();
console.log(p.age); // 27
p.sayName(); // My Name is Jacky
這段代碼大家都能看懂,但是有人思考過這個問題嗎:我們並沒有顯式設置p這個實例的age屬性,為什麽p.age可以打印出27?是什麽幫我們悄悄做了處理?答案就是__proto__。
當執行 var p = new Person() 時 ,悄然發生了以下事情:
var p={};
p.__proto__ = Person.prototype; // 一個對象的隱式原型指向構造該對象的構造函數的原型
Person.call(p);
為了證明關鍵的第二步代碼的正確性,我們執行:
alert(p.__proto__ === Person.prototype); // true
有這層等價關系存在,就可以保證實例化對象p可以找到原型鏈上的屬性。
p是一個引用指向Person的實例化對象,我們在Person的原型上定義了一個sayName方法和age屬性,當我們執行p.age時,會先在this的內部查找(也就是構造函數內部),如果沒有找到,就沿著原型鏈向上追溯。
這裏的向上追溯是怎麽向上的呢?就是通過__proto__屬性來鏈接到原型(也就是Person.prototype)進行查找。最終在原型上找到了age屬性。
三、一個更復雜的示例
var Person = function() {};
Person.prototype.say = function() {
console.log("Person say");
}
Person.prototype.salary = 50000;
var Programmer = function() {};
Programmer.prototype = new Person();
Programmer.prototype.writeCode = function() {
console.log("Programmer writes code");
};
Programmer.prototype.salary = 500;
var p = new Programmer();
p.say(); // Person say
p.writeCode(); // Programmer writes code
console.log(p.salary); // 500
大家可以先自己思考一下,在new Programmer()時悄然發生了什麽?
------------------------------------思考分割線------------------------------------------
在var p = new Programmer()時發生了以下事情:
var p = {};
p.__proto__ = Programmer.prototype;
p.__proto__ = new Person();
p.__proto__.__proto__ = Pserson.prototype;
Person.call(p.__proto__);
Programmer.call(p);
當我們調用 p.say() 時,p 中是沒有 say 屬性,於是到 p 的 __proto__ 屬性中去找,也就是 Programmer.prototype,此時 Programmer.prototype 是等於 new Person(),但 new Person() 中也沒有 say 屬性,於是又到 new Person().__proto__ 中找,此時 new Person().__proto__ 等於 Pserson.prototype 的,我們定義了 Person.prototype.say=function(){}; 所以,p 在 Person.prototype 中就找到了這個方法。
四、一張粗糙的理解圖
1.構造函數Foo()
構造函數的原型屬性Foo.prototype指向了原型對象,在原型對象裏有共有的方法,所有構造函數聲明的實例(這裏是f1,f2)都可以共享這個方法。
2.原型對象Foo.prototype
Foo.prototype保存著實例共享的方法,有一個指針constructor指回構造函數。
3.實例
f1和f2是Foo這個對象的兩個實例,這兩個對象也有屬性__proto__,指向構造函數的原型對象,這樣就可以訪問原型對象的所有方法。
另外:
構造函數Foo()除了是方法,也是對象(萬物皆對象),它也有__proto__屬性,指向誰呢?指向它的構造函數的原型對象,而函數的構造函數是Function,因此Foo.__proto__ = Function.prototype。
Function(), Object()也是一樣的道理。Function是由Object構造出來的,所以Function的__proto__指向了Object.prototype
最後,Object.prototype的__proto__屬性指向null。
JS中的prototype和__proto__