若是你是一个经验丰富的 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 上卷