JavaScript 原型&原型鏈
更多JS總結(jié):https://github.com/dishui1238/Notes/tree/master/JS
1. 構(gòu)造函數(shù)
構(gòu)造函數(shù)本身就是一個(gè)函數(shù),與普通函數(shù)沒有任何區(qū)別,不過為了規(guī)范一般將其首字母大寫。構(gòu)造函數(shù)和普通函數(shù)的區(qū)別在于,使用 new 生成實(shí)例的函數(shù)就是構(gòu)造函數(shù),直接調(diào)用的就是普通函數(shù)。
2. prototype & __proto__
先看一個(gè)例子:
function Person(name, color) {
this.name = name;
this.color = color;
this.sayHi = () => {
console.log("hi~");
};
}
Person.prototype.job = "前端工程師";
const p1 = new Person("p1", "black");
const p2 = new Person("p2", "white");
console.log(p1);
console.log(p2);
console.log(p1.job);
console.log(p2.job);
console.log(Person.prototype === p1.__proto__); // true !構(gòu)造函數(shù)和實(shí)例原型的關(guān)系
console.log(Person.prototype === p2.__proto__); // true

在上面的代碼中,有兩個(gè)實(shí)例被創(chuàng)建,它們有自己的名字、顏色,但它們的 sayHi 方法是一樣的,而通過構(gòu)造函數(shù)創(chuàng)建實(shí)例的時(shí)候,每創(chuàng)建一個(gè)實(shí)例,都需要重新創(chuàng)建這個(gè)方法,再把它添加到新的實(shí)例中。這無疑造成了很大的浪費(fèi),既然實(shí)例的方法都是一樣的,為什么不把這個(gè)方法單獨(dú)放到一個(gè)地方,并讓所有的實(shí)例都可以訪問到呢。這里就需要用到原型(prototype):
這幾句話很重要!??!
-
所有函數(shù)對象都有一個(gè) prototype 屬性,屬性值是一個(gè)普通的對象。指向該函數(shù)的原型對象,這個(gè)對象正是調(diào)用該構(gòu)造函數(shù)而創(chuàng)建的實(shí)例的原型,也就是這個(gè)例子中的 p1 和 p2 的原型。
構(gòu)造函數(shù)和實(shí)例原型的關(guān)系圖:

-
每個(gè)實(shí)例對象(object)都有一個(gè)私有屬性(稱之為 __proto__ )指向創(chuàng)建它的構(gòu)造函數(shù)的原型對象(prototype )。
實(shí)例與實(shí)例原型的關(guān)系圖:
實(shí)例與實(shí)例原型的關(guān)系圖
既然實(shí)例對象和構(gòu)造函數(shù)都可以指向原型,那么原型是否有屬性指向構(gòu)造函數(shù)或者實(shí)例呢?
2. constructor
指向?qū)嵗故菦]有,因?yàn)橐粋€(gè)構(gòu)造函數(shù)可以生成多個(gè)實(shí)例,但是原型指向構(gòu)造函數(shù)倒是有的,這就要講到第三個(gè)屬性:constructor,每個(gè)原型都有一個(gè) constructor 屬性指向關(guān)聯(lián)的構(gòu)造函數(shù)。
實(shí)例原型與構(gòu)造函數(shù)的關(guān)系圖:

綜上,我們得出:
function Person() {}
var person = new Person();
console.log(person.__proto__ == Person.prototype); // true
console.log(Person.prototype.constructor == Person); // true
// 順便學(xué)習(xí)一個(gè)ES5的方法,可以獲得對象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype); // true
了解了構(gòu)造函數(shù)、實(shí)例原型、和實(shí)例之間的關(guān)系,接下來是實(shí)例和原型的關(guān)系。
3. 實(shí)例與原型
這句話很重要!?。?/strong>
當(dāng)讀取實(shí)例的屬性時(shí),如果找不到,就會查找與對象關(guān)聯(lián)的原型中的屬性,如果還查不到,就去找原型的原型,一直找到最頂層為止。
function Person() {}
Person.prototype.name = "Kevin";
var person = new Person();
person.name = "Daisy";
console.log(person.name); // Daisy
delete person.name;
console.log(person.name); // Kevin
在這個(gè)例子中,我們給實(shí)例對象 person 添加了 name 屬性,當(dāng)我們打印 person.name 的時(shí)候,結(jié)果自然為 Daisy。
但是當(dāng)我們刪除了 person 的 name 屬性時(shí),讀取 person.name,從 person 對象中找不到 name 屬性就會從 person 的原型也就是 person.__proto__ ,也就是 Person.prototype 中查找,幸運(yùn)的是我們找到了 name 屬性,結(jié)果為 Kevin。
但是萬一還沒有找到呢?原型的原型又是什么呢?
4. 原型的原型
在前面,我們已經(jīng)講了原型也是一個(gè)對象,既然是對象,我們就可以用最原始的方式創(chuàng)建它,那就是:
var obj = new Object();
obj.name = "Kevin";
console.log(obj.name); // Kevin
原型對象也有一個(gè)自己的原型對象( __proto__ ) ,層層向上直到一個(gè)對象的原型對象為 null。根據(jù)定義,null 沒有原型,并作為這個(gè)原型鏈中的最后一個(gè)環(huán)節(jié)。
其實(shí)原型對象就是通過 Object 構(gòu)造函數(shù)生成的,結(jié)合之前所講,實(shí)例的 __proto__ 指向構(gòu)造函數(shù)的 prototype ,所以我們再更新下關(guān)系圖:
原型的原型關(guān)系圖:

5. 原型鏈
那 Object.prototype 的原型呢?null
console.log(Object.prototype.__proto__ === null); // true
所以查找屬性的時(shí)候查到 Object.prototype 就可以停止查找了。
原型鏈?zhǔn)疽鈭D:

圖中由相互關(guān)聯(lián)的原型組成的鏈狀結(jié)構(gòu)就是原型鏈,也就是藍(lán)色的這條線。
原型鏈的實(shí)際上是指隱式原型鏈
JavaScript 對象有一個(gè)指向一個(gè)原型對象的鏈。當(dāng)試圖訪問一個(gè)對象的屬性時(shí),它不僅僅在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個(gè)名字匹配的屬性或到達(dá)原型鏈的末尾,如果沒有,返回 undefined。
6. Object 和 Function
首先,Object 、Function 是 JS 自帶的函數(shù)對象。
console.log(typeof Object); // function
console.log(typeof Function); // function
console.log(typeof Function.prototype); // object
console.log(Function.__proto__ === Function.prototype); // true
這兩句話很重要?。?!
對于函數(shù)對象的原型:除了 Object 的原型對象(Object.prototype)的__proto__指向 null,其他內(nèi)置函數(shù)對象的原型對象(例如:Array.prototype)和自定義構(gòu)造函數(shù)原型的__proto__都指向 Object.prototype, 因?yàn)樵蛯ο蟊旧硎瞧胀▽ο?屬于 Object 的實(shí)例,可以通過 new Object() 產(chǎn)生)。
對于函數(shù)對象:所有函數(shù)對象的 __proto__ 都是一樣的,函數(shù)都可以通過 new Function()產(chǎn)生,所有函數(shù)的 __proto__ 指向創(chuàng)建它的構(gòu)造函數(shù)的原型,即 Function.prototype.
Function = new Function(),F(xiàn)unction 自己創(chuàng)造自己,所以 Function 的 __proto__ 指向 Function.prototype.(個(gè)人理解)

經(jīng)典原型圖相等圖:

7. instanceof
function Foo() {}
var f1 = new Foo();
console.log(f1 instanceof Foo); // true
console.log(f1 instanceof Object); // true
console.log(Object instanceof Function); // true
console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Function instanceof Object); // true
function Foo() {}
console.log(Object instanceof Foo); // false
表達(dá)式:A instanceof B (A 為實(shí)例對象,B 為構(gòu)造函數(shù)對象)
如果 B 函數(shù)的顯示原型對象在 A 對象的原型鏈上,返回 true,否則返回 false
8. 測試一下
問題一:輸出結(jié)果是什么?
function A() {}
A.prototype.n = 1;
var b = new A();
A.prototype = {
n: 2,
m: 3,
};
var c = new A();
console.log(b.n, b.m, c.n, c.m);
答案:1, undefined, 2, 3
function A() {}
A.prototype.n = 1;
var b = new A();
A.prototype.n = 2;
A.prototype.m = 3;
var c = new A();
console.log(b.n, b.m, c.n, c.m);
答案:2, 3, 2, 3
問題二:
function Foo() {}
Object.prototype.a = function () {
console.log("a()");
};
Function.prototype.b = function () {
console.log("b()");
};
var foo = new Foo();
foo.a();
foo.b();
Foo.a();
Foo.b();
答案:
a()
Uncaught TypeError: foo.b is not a function // 報(bào)錯(cuò)后面的結(jié)果不輸出,但是是有值的
a()
b()
參考文章:
