一、建構函式、原型物件、例項物件的關係 及延伸
function Foo(){} //Foo為建構函式
Foo.prototype.name = 'Feidian'; //Foo.prototype即原型物件
var f1 = new Foo() //f1 為 例項物件
複製程式碼
要想了解Foo、Foo.prototype、f1 之間的關係,我們就要理解 prototype、__proto __、constructor 。
1、prototype
函式都有 prototype屬性(即原型物件):該原型物件儲存著所有例項物件的 公共屬性和方法。
function Foo(){}
Foo.prototype.name = 'Feidian';
var f1 = new Foo();
console.log(f1.name); //'Feidian'
複製程式碼
f1是Foo函式new出來的例項,訪問 f1.name,實際訪問的 是Foo.prototype中的name屬性。
注: 基本上所有函式都有 prototype 屬性,除了下面例子:
let fun = Function.prototype.bind()
2、__proto __
每個js物件(除了 null )都有 __proto__屬性(指標): __proto__指向著創造該js物件的建構函式 的prototype.
function Foo(){}
Foo.prototype.name = 'Feidian';
var f1 = new Foo();
console.log(f1.__proto__ === Foo.prototype); // true
複製程式碼
f1通過__proto__,指向著Foo的原型物件。
3、constructor
原型物件都有 constructor屬性(指標):它指向著該原型物件的擁有者。
function Foo(){}
Foo.prototype.name = 'Feidian';
var f1 = new Foo();
console.log(Foo.prototype.constructor === Foo) // true
複製程式碼
Foo.prototype的constructor,指向著建構函式Foo
(1)注意:
若完全重寫prototype,則要重指向constructor
console.log(Foo.prototype.constructor); //ƒ Foo(){}
Foo.prototype = {}; //重寫prototype
console.log(Foo.prototype.constructor); //ƒ Object() { [native code] }
複製程式碼
改:
console.log(Foo.prototype.constructor); //ƒ Foo(){}
Foo.prototype = {constructor:Foo} //重寫,讓constructor重新指向Foo
console.log(Foo.prototype.constructor); //ƒ Foo(){}
複製程式碼
(2)注意:
例項物件呼叫constructor,實際上呼叫的是原型物件的constructor。
console.log(f1.constructor === Foo.prototype.constructor); //true
複製程式碼
4、繼續擴充關係
(1)Foo.prototype.__proto __指向哪裡?
我們知道:原型物件是一個物件,物件是由Object建構函式創造的
;因而得知Foo.prototype是由建構函式Object創造的。
前面又提到:__proto__指向著創造該js物件的建構函式 的prototype。所以Foo.prototype.__proto __ 指向著Object.prototype
再根據前面講的prototype、constructor,可以將圖更新為:
(2)Object.prototype.__proto __指向哪裡?
我們用 (1) 的理論推測Object.prototype.__proto __的指向,發現按上面理論講它應該指向它自己,但實際並不是。
console.log(Object.prototype.__proto__ === null) // true
複製程式碼
通過上面程式碼,我們發現Object.prototype.__proto __指向為null
。意味著Object.prototype沒有原型。
所有物件都可以通過原型鏈最終找到 Object.prototype ,雖然 Object.prototype 也是一個物件,但是這個物件卻不是 Object 創造的,而是引擎自己建立了 Object.prototype
。
(3)Object.__proto __指向哪裡?
我們知道:Object()是一個建構函式,函式都是由new Function()生成的
;因而Object()也是由建構函式Function創造的。
前面又提到:__proto__指向著創造該js物件的建構函式 的prototype。所以Object.__proto__ 指向著Function.prototype
再根據前面講的prototype、constructor,可以將圖更新為:
(4)Function.__proto __指向哪兒?Function.prototype.__proto __又指向哪兒?
console.log(Function.__proto__ === Function.prototype); //true
console.log(Function.prototype.__proto__ === Object.prototype); //true
複製程式碼
通過上面程式碼,我們發現:
Function.__proto __ 指向 Function.prototype
。難道Function自己創造了自己?Function.prototype.__proto__ 指向 Object.prototype
。用之前理論分析沒毛病,但具體原因不是我們分析的那樣。往後看?
如果你在瀏覽器將Function.prototype物件
列印出來,會發現這個物件其實是一個函式
:
我們知道函式都是通過 new Function() 生成的,難道Function.prototype 是通過 new Function() 產生的?答案也是否定的。Function.prototype也是引擎自己建立的
。
首先引擎建立了 Object.prototype ,然後建立了 Function.prototype ,並且通過 __proto __ 將兩者聯絡了起來。並且是:先有的 Function.prototype ,之後才有的 function Function()。
通過上面,我們就知道了為什麼 let fun = Function.prototype.bind() 沒有 prototype 屬性。 因為 Function.prototype 是引擎建立出來的物件,引擎認為不需要給這個物件新增 prototype 屬性。
對於為什麼 Function.__proto __ 會等於 Function.prototype ,有這麼種理解是:其他所有的建構函式都可以通過原型鏈找到 Function.prototype ,並且 function Function() 本質也是一個函式,為了不產生混亂就將 function Function() 的 __proto __ 聯絡到了 Function.prototype 上。
再更新一下圖:
(5)Foo.__proto __指向哪裡?
我們知道:Foo是一個建構函式,函式都是由new Function()生成的
;因而Foo也是由建構函式Function創造的。
前面又提到:__proto__指向著創造該js物件的建構函式 的prototype。所以Foo.__proto__ 指向著Function.prototype
小結:
- Object 是所有物件的爸爸,所有物件都可以通過 __proto __ 找到它
- Function 是所有函式的爸爸,所有函式都可以通過 __proto __ 找到它
- Function.prototype 和 Object.prototype 是由引擎來建立的。其中Function.prototype是個函式.
- 除了上面兩個,其他物件都是被構造器new出來的。
二、原型鏈及操作
1、什麼是原型鏈?
簡單點理解,就是由若干__proto__連起來形成的鏈條,就是原型鏈。
拿上面例子來說: 就是一條原型鏈; (f1能使用Foo.prototype和Object.prototype裡的屬性和方法,即實現物件導向程式語言的繼承關係) 也是一條原型鏈;2、原型鏈的操作
function Foo(){}
Foo.prototype.name = 'Feidian';
var f1 = new Foo();
f1.name = 'bty';
console.log(f1.name); //'bty'
delete f1.name;
console.log(f1.name); //'Feidian'
console.log(f1.toString()); //'[object Object]'
複製程式碼
上面例子的原型鏈如下:
下面我們看看執行後五句,發生了什麼:- f1.name = 'bty'; 先在f1物件上查詢有沒有name屬性,發現沒有,則直接在f1物件上新增name屬性,並賦值為’bty’ ; 若發現f1上有name屬性,則直接修改f1的name的值。
- console.log(f1.name); 先在f1物件上查詢有沒有name屬性,發現有,則輸出 'bty'
- delete f1.name; 刪除f1的name屬性。 若f1沒有name屬性,delete f1.name後,也不會對Foo原型上的屬性造成影響。
- console.log(f1.name); 先在f1物件上查詢有沒有name屬性,發現沒有;再到看看Foo.prototype上有沒有,發現有,則輸出 'Feidian'
- console.log(f1.toString()); 先在f1物件上找有沒有toString()方法,沒有;再看看Foo.prototype上有沒有,還有沒;再沿著原型鏈到Object.prototype上找,發現有,則呼叫該方法。