若是你是一個經驗豐富的 Vue 開發者,那麼你必定知道 Vue 的響應式原理是經過攔截對象的 get 和 set 實現的javascript
// src/core/observer/index.js
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
//...
},
set: function reactiveSetter (newVal) {
//...
}
})
複製代碼
因此當給響應式變量賦值的時候就會觸發其中的 set 函數,從而更新視圖java
<template>
<div >{{message}}</div>
</template>
<script>
export default {
data() {
return {
message:'hello world'
}
},
mounted() {
this.message = 'hello Vue'
}
}
</script>
複製代碼
本文和 Vue 框架其實並無什麼關係,可是咱們來思考一個問題react
爲何給響應式變量賦值會觸發 set 函數,而不是直接賦值?
bash
你給對象的屬性定義了 set 函數就不會執行默認的賦值邏輯了啊,這不是弟弟問題麼框架
事實上 JavaScript 在訪問對象屬性或者給對象屬性賦值的時候會分別執行 [[Get]] 和 [[Put]] 操做,它們是對象內置的 2 個默認行爲,沒法修改函數
接下來咱們經過 ECMA 規範來分析 JavaScript 在對象取值和賦值的時候內部究竟作了什麼ui
當從對象中獲取某個執行值時,會執行 [[Get]] 操做,它在標準中是這麼定義的this
憑本人的渣渣英語水平大體翻譯的結果是這樣的spa
經過標準就能很明顯的看出 JavaScript 在訪問對象屬性時執行的邏輯,當這個屬性不存在於當前對象會沿着原型鏈查找,這就是爲何空對象也能夠調用 toString,valueOf 等方法,由於這些方法都存在於對象的原型鏈上,同時若是屬性定義了 get 函數也會直接返回執行的結果prototype
[[Put]] 比 [[Get]] 的行爲要複雜一點,規範原文是這麼寫的
[[Put]] 方法依賴一個叫 [[CanPut]] 的內部行爲,咱們來看它的定義
首先會判斷當前屬性是否存在於當前對象中,若是存在則繼續判斷屬性是否有訪問器描述符,即 set 函數,若是 set 函數存在 [[CanPut]] 的結果爲 true,不然若是訪問器描述符爲 undefined 或者不合法則返回 false。或者當屬性存在於當前對象可是沒有定義訪問器描述符,那該屬性必定被定義了數據描述符, [[CanPut]] 的結果爲數據描述符的 writable 值,最後當屬性不存在與當前對象,和 [[Get]] 相同會往上遍歷原型鏈,直到終點,反覆執行以前的邏輯
通俗的來講 [[CanPut]] 返回的是一個布爾值,表示當前屬性是否可被賦值
回到 [[Put]] 中,當 [[CanPut]] 的值是 false 時會直接退出賦值的邏輯,而且根據 Throw 這個參數,當 Throw 爲 true 時,拋出異常,反之靜默,而這個 Throw 對應的是否開啓嚴格模式,同時也驗證了嚴格模式下賦值失敗會拋出錯誤的行爲
當 [[CanPut]] 的值是 true 時,表明當前屬性能夠被賦值,執行如下邏輯
通常狀況下,對象屬性賦值通常都是執行這個邏輯並返回 value 屬性做爲賦值語句的結果值,舉個例子
給 obj 對象的 a 屬性賦值數字123,那麼 123 就是 a 屬性數據描述符中 value 的值,[[Put]] 操做最終返回的值就是 123,對應最後一行賦值語句的結果值
而 觸發 [[DefineOwnProperty]] 這個內部方法
這句話又怎麼理解呢?規範中 [[DefineOwnProperty]] 的行爲很是複雜,這裏我再舉個小例子
經過攔截 defineProperty 和 getOwnPropertyDescriptor 能夠發現,默認的賦值行爲會觸發這個兩個攔截器,更多的行爲有興趣的朋友能夠根據底部連接自行查看
不然若是屬性在當前對象或者原型鏈上,且擁有訪問器描述符,則讓賦值表達式右邊的值做爲惟一參數傳入 set 函數並返回結果
不然若是屬性在當前對象原型鏈上,且擁有數據描述符,則在當前對象建立一個新的屬性,並讓其數據描述符的值爲 {[[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}.
,並拋棄原來的數據描述符,同時觸發 [[DefineOwnProperty]] 內部方法並返回
什麼意思呢,考慮如下狀況
let obj = {}
Object.defineProperty(Object.prototype, 'a', {
configurable: false,
enumerable: false,
value: "",
writable: true
})
obj.a = 1
console.log(Object.getOwnPropertyDescriptor(obj,'a'))
// {value: 1, writable: true, enumerable: true, configurable: true}
複製代碼
obj 對象並無屬性 a,而在 Object 的原型對象中定義了一個 a 屬性,其數據描述符的 configurable,enumerable 都爲 false,但最終賦值的時候 obj 對象上會存在一個 a 屬性,同時 configurable,enumerable 都爲 true
結合《你不知道的 JavaScript 上卷》中對 [[Get]] 和 [[Put]] 的定義,能夠得出如下結論
當給對象取值時,會觸發 [[Get]] 操做,若是當前對象上有該屬性,則判斷
若是當前對象上沒有該屬性,會向上查找原型鏈,直到盡頭,查找過程當中會反覆執行上面兩步
當給對象賦值時,會觸發 [[Put]] ( 不是理想中的 [[Set]] ),若是當前對象上有該屬性,則判斷
若是當前對象沒有該屬性,會向上查找原型鏈,若是在原型鏈上層找到該屬性,則判斷
若是屬性是數據描述符的話還會觸發內部的 [[DefineOwnProperty]] 操做,若是定義了 defineProperty 和 getOwnPropertyDescriptor 會觸發這兩個攔截器
你不知道的 JavaScript 上卷