1. 程式人生 > >2020年了,別再重複學習原型了

2020年了,別再重複學習原型了

# 前置 原型是 JavaScript 巧妙的設計,它非常容易理解。都 2020 年了,看完這篇希望你以後不需要再重複學習 JavaScript 原型了。如有不當之處,懇請指點一二! # 單詞 下面是相關單詞及其翻譯,牢牢記住它們就成功一半了。 - constructor 構造器 - proto & prototype 原型;雛形;最初形態 # constructor ```js function Drink() {} const a = new Drink() console.log(a.constructor) // ƒ Drink() {} ``` a 是由函式 Drink 構造而來的。 # prototype 簡單寫點程式碼,後面全是 `console.log`。 ```js function f() { this.a = 1 this.b = 2 } const o = new f() f.prototype.b = 3 f.prototype.c = 4 ``` o 是 `new f()` 返回的結果,不妨回顧一下當執行 `new f()` 時, new 操作符對函式 f 做了些什麼事。 1. 建立一個空物件(即{}): `var obj = Object.create(null)`。 2. 連結該物件(即設定該物件的建構函式)到另一個物件(本函式), 這個空物件繼承其原型: `obj.__proto__ = f.prototype`。 3. 使用指定的引數呼叫函式 f,`new Foo` 等同於 `new Foo()`,也就是 f 不帶任何引數呼叫的情況; 將步驟 1 建立的物件作為 this 的上下文(將 this 繫結到新建立的物件 | f 函式中的 this 的指標替換成 obj) ,`f.call(obj)`。 4. 如果該函式沒有顯式地在函式中寫 return,則返回 this。 > 對於一個函式,如果不使用 `new` 操作它,它只是一個正常的函式;使用 `new` 操作符僅僅改變了它的 this 指向且在函式內部隱式地建立了一個物件,然後再稱之為 “建構函式”。僅此而已。 如果你對第三步中的操作有困惑,看幾個簡單的例子:
設定建構函式 ```js function f() { this.a = 1 this.b = 2 } f() console.log(f.constructor) //ƒ Function() { [native code] } ``` ```js function f() { this.a = 1 this.b = 2 } const o = new f() console.log(o.constructor) // ƒ f() { // this.a = 1 // this.b = 2 // } ```
this指標替換 ```js function f() { console.log(this) // window this.a = 1 this.b = 2 } f() ``` ```js function f() { console.log(this) // f {} this.a = 1 this.b = 2 console.log(this) // f {a: 1, b: 2} } new f() ```
什麼是call? ```js const drink = { name: 'Coca Cola', color: 'black', price: '3.5', intro: function () { console.log(`名稱:${this.name},顏色:${this.color},價格:${this.price}`) }, } const computer = { name: 'Orange Juice', color: 'orange', price: '4', } drink.intro.call(computer) //名稱:Orange Juice,顏色:orange,價格:4 ```
確保上面的內容你能十分清晰,否則不要進行下面的內容。 ```js console.log(o.b) // 2 ``` o 的值是通過 `new f()` 得到的物件,this 指向這個物件,函式中給 this 添加了屬性 b 為其賦值為 2,並將他返回。所以 這裡打印出 2。`f.prototype` 是無法被訪問到的,這種情況還被稱之為 property shadowing ---**屬性遮蔽**。 ```js console.log(o.c) // 4 console.log(o.__proto__.c) // 4 console.log(o.__proto__ === f.prototype) // true ``` 函式中並沒有給 this 新增 c 屬性併為其賦值 4,但是列印 o.c 返回 4。通過上文你已經知道 constructor 是幹什麼的了: ```js console.log(o.constructor.prototype.b) // 3 ``` o 是由函式 f 構造的,`o.constructor` 返回函式 f,所以`o.constructor.prototype === f.prototype`, `f.prototype` 返回什麼呢?上面初始程式碼中直接寫好的,現在可以翻上去看看 `f.prototype`,所以 `o.constructor.prototype.b` 返回 3。查詢物件上的屬性就是先找自身再通過 `__proto__` 一層一層往上找的: - 如果自身有該屬性直接獲取它的值; - 如果自身有且其構造器的 `prototype` 上也有,**屬性遮蔽**不會忘了吧; - 如果一直沿著 `__proto__` 找但沒找到,會返回 `undefined`;為什麼呢? ```js console.log({}.constructor) // ƒ Object() { [native code] } console.log({}.__proto__ === Object.prototype) // true console.log(Object.prototype.__proto__) // null ``` 看到這裡,應該十分清晰了。這就是最終為什麼會返回 undefind 的原因:`Object.prototype.__proto__` 指向 null。 # 小練習 做一個簡單又不給你解釋的小練習吧! ```js console.log(o.b) console.log(o.__proto__.b) console.log(o.d) ```
答案
```js // 2 // 3 // undefined ```
# 重要提示 對於 `Object.prototype.__proto__`: ![](https://gitee.com/guangzan/imagehost/raw/master/markdown/proto-tip.jpg) # 測試程式碼 如果看完還是不太明白,動手試一試吧!我把本文用到的程式碼片段放到此處供你快速拷貝。 ```js function f() { this.a = 1 this.b = 2 } const o = new f() f.prototype.b = 3 f.prototype.c = 4 console.log(o.b) // 2 console.log(o.c) // 4 console.log(o.__proto__.c) // 4 console.log(o.__proto__ === f.prototype) // true console.log(o.constructor.prototype.b) // 3 console.log(o.b) // 2 console.log(o.__proto__.b) // 3 console.log(o.d) // undefined console.log({}.constructor) // ƒ Object() { [native code] } console.log({}.__proto__ === Object.prototype) // true console.log(Object.prototype.__proto__) // null // --------- other -------- console.log(o.__proto__) // {b: 3, c: 4, constructor: ƒ} console.log(o.__proto__.__proto__ === Object.prototype) // true console.log(Object.prototype.__proto__) // null console.log(o.__proto__.__proto__.__proto__) // null console.log(f.prototype.__proto__ === Object.prototype) // true ```