JavaScript 原型&原型鏈

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
輸出結(jié)果圖

在上面的代碼中,有兩個(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)

這幾句話很重要!??!

  1. 所有函數(shù)對象都有一個(gè) prototype 屬性,屬性值是一個(gè)普通的對象。指向該函數(shù)的原型對象,這個(gè)對象正是調(diào)用該構(gòu)造函數(shù)而創(chuàng)建的實(shí)例的原型,也就是這個(gè)例子中的 p1 和 p2 的原型。

    構(gòu)造函數(shù)和實(shí)例原型的關(guān)系圖:

構(gòu)造函數(shù)和實(shí)例原型的關(guān)系圖[圖片上傳中...(7d5ee838e2f1aa9a7e50a8f3b819d98.png-e6232b-1610094966156-0)]
  1. 每個(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)系圖:


實(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)系圖:


原型的原型關(guān)系圖

5. 原型鏈

那 Object.prototype 的原型呢?null

console.log(Object.prototype.__proto__ === null); // true

所以查找屬性的時(shí)候查到 Object.prototype 就可以停止查找了。

原型鏈?zhǔn)疽鈭D:


原型鏈?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)典原型圖相等圖:

經(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()


參考文章:

冴羽的博客

https://juejin.cn/post/6844903567375990791

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容