完全弄懂JavaScript原型和原型鏈問題

JavaScript中的原型和原型鏈問題,一直是困擾新手乃至於廣大前端工程師的重要問題,的確,相對於普通語法來講,它會更加難以理解,在平常開發過程當中也不常見。可是……它的重要性是不言而喻的。下面咱們就來探究一下。javascript

進入主題前端

1.三個重要屬性

要理解原型問題,先要了解如下三個屬性。java

  • prototype
  • __protp__
  • constructor

進入逐一講解階段:面試

1.1 prototype

JavaScript中的每一個構造函數都有prototype對象。全部實例對象須要共享的屬性和方法都放在這個對象裏。而那些不須要共享的,就放在構造函數中。使用過程當中,不須要咱們手動聲明一個prototype屬性。有了它,咱們就能夠將全部須要共享的方法提取到一處,避免冗餘。經過一個小栗子來看下前端工程師

// 定義一個動物的構造函數。
function Animal(name) {
  // 構造函數中存放的是不須要共享的方法和屬性
  this.name = name; // 定義每一個動物的名字
}

// 將每一個動物須要共享的方法和屬性放到一塊
Animal.prototype.eat = function () {
  console.log('吃東西')
}
// 輸出本身的名字
Animal.prototype.sayName = function () {
  console.log(this.name)
}

let dog = new Animal('狗'); // 實例化一個狗的對象
let cat = new Animal('貓'); // 實例化一個貓的對象
dog.sayName(); // 狗
cat.sayName(); // 貓

dog.eat();
cat.eat();

這裏,咱們經過Animal實例化的dogcat類,都沒有聲明sayName函數,可是它們都有這個函數能夠執行。而實例對象一旦建立,將自動引用prototype對象的屬性和方法。函數

相信你已經get到了這個知識點,別急,繼續看下面這種狀況。this

// 再次定義Animal。
function Animal() {}
Animal.prototype.eat = function () {
  console.log('吃東西')
}

let dog = new Animal();
dog.eat = function () {
  console.log('我只吃骨頭!')
}

dog.eat(); // 我只吃骨頭

注意:prototype

上述例子說明:只有在dogcat上找不到eat方法的時候纔會向Animal查找,若是能查找到,則不會執行Animal的方法。這種狀況也叫作方法的重寫code

上面的例子相信你已經看的很明白了,不明白也問題不大,下面的講解咱們還會經過實際的問題來介紹。接下來看第二個重要的屬性。對象

看的累了就休息會兒吧,下面的內容更精彩…………

1.2 __proto__

不少人容易將 prototype__proto__(雙下劃線) 混淆,由於它們之間的指向有點兒繞。不過,一通則百通😝,等你真正理解了,就會發現,其實挺簡單的。

在不少狀況下,__proto__能夠理解爲 構造器的原型,這是爲何呢?接着看小栗子吧。

function Animal () {}

let dog = new Animal();

// 重點來了…………
console.log(dog.__proto__ === Animal.prototype) // true

能夠看到,dog.__proto__Animal.prototype是徹底相等的。好了,基本使用看完了,咱們來探究下__proto__的指向問題吧。

1.2.1 __proto__的指向問題

可能你們不太理解,爲何__proto__還有指向。這裏須要作個說明,經過不一樣方式建立出來的對象,它的__proto__指向是不一樣的。

1.字面量方式建立對象

這個你們比較容易理解,平常工做中咱們也是使用的比較多。

let dog = {}
console.log(dog.__proto__) // Object {} --> Object的原型

// 驗證是否相等
console.log(dog.__proto__ === Object.prototype) // true

這裏由於直接給dog賦值爲了全局的對象,因此__proto__指向了Object.prototype😎。

2.構造器方式建立

話很少說,直接上栗子。

function Animal() {}
let dog = new Animal()

console.log(dog.__proto__) // A {} --> A的原型

// 驗證
console.log(dog.__proto__ === Animal.prototype) // true

這是由於dogAnimal的一個實例,因此dog__proto__指向了Animal.prototype

3.Object.create 方式建立
let Animal = {
  c: 1 // 爲了區分,咱們這裏加一個自定義屬性。
}
let dog = Object.create(Animal)

console.log(dog.__proto__) // Animal {c: 1}

//驗證
console.log(dog.__proto__ === Animal) // true

注意:

Object.create()方法建立一個新對象,使用現有的對象來提供新建立的對象的__proto__。也就是說,這裏直接將Animal做爲了dog__proto__

1.3 constructor

這個屬性沒必要多說,指的就是構造函數,每一個對象都會有一個constructor屬性來指向本身的構造函數

function Animal() {} // 繼續使用這個使用了不少次的Animal構造函數

console.log(Animal === Animal.prototype.constructor) // true

這裏的Animal.prototype.constructor 就指向了 Animal 函數。

重點來了👇👇👇👇👇👇👇

2.__proto__、prototype和constructor的關係

關係太複雜,只能經過圖解來講明瞭。

<center><img src="./1.jpeg"></center>

3.原型鏈

知道了三者的概念,瞭解了它們的用途,接下來想弄懂原型鏈就會變得簡單了。原型鏈就是無數個上圖的串聯。咱們仍是經過圖解的方式來看。

<center><img src="./5.jpeg"></center>

圖太長了,不過千萬別被圖嚇到喲。原型鏈就是從實例對象開始,經過 __proto__來向上查找,直到找到null爲止。

附加項:

看到這裏,你們可能會有一個疑問。每一個構造函數都有 prototype對象, 那麼這個對象到底指向到了哪兒?

其實,每一個prototype對象的最終指向都是Object.prototype。爲何這麼說,是由於每一個構造函數在建立的時候會分配一起空間來存儲須要共享的方法和屬性,因此 prototype 是經過對象建立來的,那麼它的最終指向確定也是Object.prototype。老規矩,咱們來經過栗子來驗證一下。

function dog(){}
function cat(){}
function monkey(){}

console.log(dog.prototype.__proto__ === Object.prototype) // true
console.log(cat.prototype.__proto__ === Object.prototype) // true
console.log(monkey.prototype.__proto__ === Object.prototype) // true

4.面試題理解

4.1 請查看下列程序,會輸出什麼
function F() {}
Object.prototype.a = function () {}
Function.prototype.b = function () {}
var f = new F()
F.a()
F.b()
f.a()
f.b()

解析:

F.a F.b f.a 執行沒問題,由於即便在構造函數總找不到這兩個函數,可是經過原型鏈查找到。

  • F.a 函數的查找順序是 F構造函數自己 --> Function.prototype --> Function.prototype.__proto__
  • F.b 函數的查找順序是 F構造函數自己 --> Function.prototype
  • f.a 函數的查找順序是 f自己 --> f._proto__(Function.prototype) --> f.__proto__._proto__(Object.prototype)

f.b 則會出現執行出錯的狀況

緣由是這樣的:

查找順序和f.a相同,可是,在f.__proto__._proto__,也就是Object.prototype上查找不到 b 函數,最終致使查找失敗,出現錯誤。

聰明的你確定學會了,接下來挑戰一下吧!!!

function User() {}
User.prototype.hello = function () {}

let u1 = new User()
let u2 = new User()

console.log(u1.hello === u2.hello)
console.log(User.prototype.constructor)
console.log(User.prototype === Function.prototype)
console.log(User.__proto__ === Function.prototype)
console.log(User.prototype === Function.__proto__)
console.log(u1.__proto__ === u2.__proto__)
console.log(u1.__proto__ === User.__proto__)
console.log(Function.__proto__ === Object.__proto__)
console.log(Function.prototype.__proto__ === Object.prototype.__proto__)
console.log(Function.prototype.__proto__ === Object.prototype)
相關文章
相關標籤/搜索