【JS】为什么 Vue3.0 要重写响应式系统

为什么 Vue3.0 要重写响应式系统

西岭老湿发布于 1 月 26 日

面试的时候经常被问到 响应式 相关的内容,而Vue3.0 更新后,面试官又有了新的武器;

面试官:为什么 Vue3.0 要重写响应式系统?

懵逼树上懵逼果,懵逼树下你和我,面试官在问什么,我该怎么回答,完全不知道怎么回事;

有些经验的小伙伴可能会从解释 Proxy 的好处开始简单聊一下,比如: Proxy 是直接代理对象,而不是劫持对象的属性;更好的数组监控;

这样的回答,勉强算是合格吧

那到底应该怎么答呢?

面试官背后的出题逻辑

别急,咱们先整理一下思路,孙子兵法有云:“知己知彼,百战不殆”;面试就像打仗,你来我往,所以我们需要换位思考,想一想,为什么面试官会问这样一个问题?面试官想从这个问题里得到什么回答?这个问题可以考察哪些技术点? 想清楚这个问题,再回到自己身上,这些技术点,你都掌握了吗?

说得直白一点,面试就像考试,你需要先 读题、审题才能答好这道题;

为什么很多人认为 “面试造火箭,工作拧螺丝”?因为没有换位思考,没有想清楚面试题背后的逻辑;

那我们想清楚这个逻辑之后,需要我们做的就是提取技术点,整理思路,做出对应解答;当然,前提是你需要具备这些技术能力

那么接下来,我就尝试拆解一下这个面试题了,提取其中的知识点。

对于你来说,就是要看看这些知识点,你都掌握了多少?

为什么 Vue3.0 要重写响应式系统 ?

为什么重写?如果之前好好的,重写就没有意义,那之前存在什么问题,现在是怎么解决的?就是关键点了;

不知道你对 Vue2.x 的响应式掌握多少,是不是欠下了技术的债呢?没关系,我来帮你还债,先梳理 Vue2.x 的响应式;

其实基于这个面试题,背后还有很多技术点,上面这些,是与当前题目有直接关系的,实际面试中,很有可能基于这些技术点,在进行深入交流,这里就不扩展了,你能把现在这些问题理清楚,就算赚到了;

Vue2.x 响应式

其实关于这一点,在Vue 的官方文档中,早已经有过说明了,而且说得非常详细;官方文档:https://cn.vuejs.org/v2/guide/reactivity.html【JS】为什么 Vue3.0 要重写响应式系统

我们使用官方给的一张图示,来梳理整个流程;
【JS】为什么 Vue3.0 要重写响应式系统

我们先来看一段代码
【JS】为什么 Vue3.0 要重写响应式系统

响应式原理

data 中的 obj 就是一个普通的 JavaScript 对象,通过点击 Click 按钮,将获取到的随机数赋值给 this.message ,而 this.message 指向的就是 data 中 obj 对象的 message 属性;当message 发生数据改变时,页面中 H1 标签的内容会随之改变,这个过程就是就是响应式的;那么Vue 是如何实现的呢?

首先,Vue 内部使用 Object.defineProperty() 将 Data 中的每一个成员都转换为 getter / setter 的形式;getter 用来依赖收集,setter 用来派发更新;而模板内容,最终会被编译为 render 函数,在 render 函数中,我们能发现 _v(_s(message)) message 被访问了,就会触发 getter 来进行依赖收集,而在代码中的点击事件中,一旦事件处理程序被触发执行,那么 message 则会被修改,就会触发 setter来进行派发更新;

【JS】为什么 Vue3.0 要重写响应式系统

虽然流程理清楚了,但是总感觉少点什么,怎么才能更通透呢?

我们用代码来模拟整个的实现过程;

defineProperty 模拟代码

defineProperty 的基本用法,直接看手册就行了:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

我们来看看代码:

<div id="app">

hello

</div>

<script>

// 模拟 Vue 中的 data 选项

let data = {

msg: 'hello'

}

// 模拟 Vue 的实例

let vm = {}

// 数据劫持:当访问或者设置 vm 中的成员的时候,做一些干预操作

Object.defineProperty(vm, 'msg', {

// 可枚举(可遍历)

enumerable: true,

// 可配置(可以使用 delete 删除,可以通过 defineProperty 重新定义)

configurable: true,

// 当获取值的时候执行

get () {

console.log('get: ', data.msg)

return data.msg

},

// 当设置值的时候执行

set (newValue) {

console.log('set: ', newValue)

if (newValue === data.msg) {

return

}

data.msg = newValue

// 数据更改,更新 DOM 的值

document.querySelector('#app').textContent = data.msg

}

})

// 测试

vm.msg = 'Hello World'

console.log(vm.msg)

</script>

你没有看错,加上注释,一共 36行代码,这就是 Vue2.x 对响应式实现的整个流程;
继续实现多个数据的响应式

<body>

<div id="app">

hello

</div>

<script>

// 模拟 Vue 中的 data 选项

let data = {

msg: 'hello',

count: 10

}

// 模拟 Vue 的实例

let vm = {}

proxyData(data)

function proxyData(data) {

// 遍历 data 对象的所有属性

Object.keys(data).forEach(key => {

// 把 data 中的属性,转换成 vm 的 setter/setter

Object.defineProperty(vm, key, {

enumerable: true,

configurable: true,

get () {

console.log('get: ', key, data[key])

return data[key]

},

set (newValue) {

console.log('set: ', key, newValue)

if (newValue === data[key]) {

return

}

data[key] = newValue

// 数据更改,更新 DOM 的值

document.querySelector('#app').textContent = data[key]

}

})

})

}

// 测试

vm.msg = 'Hello World'

console.log(vm.msg)

</script>

</body>

上面的代码只是模拟了 响应式 的原理,但Vue在实现中,肯定不会那么简单,接下来,我们看一下源码呀……

Vue2 源码解读

首先找到响应式代码的处理位置:

关键位置作用源码位置
function Vue () {}Vue 构造函数core/instance/index.js:8
Vue.prototype._init初始化组件实例对象core/instance/init.js:16
initState初始化组件状态相关成员core/instance/state.js:48
initData初始化用户传入的 data 数据core/instance/state.js:112
observe观察 datacore/observer/index.js:110

关键位置作用源码位置
observe观察 datacore/observer/index.js:110
class ObserverObserver 逻辑core/observer/index.js:37
walk遍历对象成员分别处理core/observer/index.js:64
defineReactive为组件实例定义响应式数据core/observer/index.js:135
Object.defineProperty拦截数据的访问和修改core/observer/index.js:157

看完Vue2.x 响应式的代码,我们再回过头来思考最开始的问题,为什么 Vue3.0 要重写响应式系统 ?

为什么重写?如果之前好好的,重写就没有意义,那之前存在什么问题,换句话问就是 defineProperty 有什么问题?

Object.defineProperty 的问题

其实, defineProperty 的问题,在Vue2.x 的手册中,已经说过了;“哎,很多人就是不看文档啊”
https://cn.vuejs.org/v2/guide/reactivity.html#%E5%AF%B9%E4%BA%8E%E6%95%B0%E7%BB%84
下面分别使用 Vue2 和 Vue3 实现了一个小功能,代码一模一样,功能当然也一样,但是,在 Vue2 中就会有Bug,而运行在vue3中的,则没有任何问题;

Vue2:
【JS】为什么 Vue3.0 要重写响应式系统

Vue3:
【JS】为什么 Vue3.0 要重写响应式系统

其核心点在于 defineProperty 不能很好的实现对数组下标的监控,而在 Vue2 的实现代码中,没有更好的方案对此进行改善,尤大索性直接放弃了实现;关于这个问题,尤大也在 github 做过回应,截个图给大家看看;

【JS】为什么 Vue3.0 要重写响应式系统

那么,Vue 目前还没有解决的问题,Vue3中显然是已经解决的了,问题是,Vue3 是如何解决的呢?前面在 Vue3 的代码中我们使用的是传统的 Options Api 来实现的数据响应式, 而在 Vue3 中全新的 Composition Api 也实现了响应式系统,我们先来感受一下 Composition Api 的基础用法

Composition API 的响应式系统

ref 响应式
【JS】为什么 Vue3.0 要重写响应式系统

reactive 响应式
【JS】为什么 Vue3.0 要重写响应式系统

Vue3 中的响应式是如何实现的呢?关键点在于Proxy 函数;

Proxy 实现原理

使用 Proxy 实现的响应式代码,要比使用 defineProperty 的代码简单得多,因为 Proxy 天然的能够对整个对象做监听,而不需要对数据行遍历后做监听,同时也就解决了数组下标的问题;
我们来一段模拟代码看一下:

<div id="app">

hello

</div>

<script>

// 模拟 Vue 中的 data 选项

let data = {

msg: 'hello',

count: 0

}

// 模拟 Vue 实例

const vm = new Proxy(data, {

// 执行代理行为的函数

// 当访问 vm 的成员会执行

get (target, key) {

console.log('get, key: ', key, target[key])

return target[key]

},

// 当设置 vm 的成员会执行

set (target, key, newValue) {

console.log('set, key: ', key, newValue)

if (target[key] === newValue) {

return

}

target[key] = newValue

document.querySelector('#app').textContent = target[key]

}

})

// 测试

vm.msg = 'Hello World'

console.log(vm.msg)

</script>

javascript前端vue.js

阅读 66发布于 1 月 26 日

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议

avatar

西岭老湿

0 声望

0 粉丝

0 条评论

得票时间

avatar

西岭老湿

0 声望

0 粉丝

宣传栏

面试的时候经常被问到 响应式 相关的内容,而Vue3.0 更新后,面试官又有了新的武器;

面试官:为什么 Vue3.0 要重写响应式系统?

懵逼树上懵逼果,懵逼树下你和我,面试官在问什么,我该怎么回答,完全不知道怎么回事;

有些经验的小伙伴可能会从解释 Proxy 的好处开始简单聊一下,比如: Proxy 是直接代理对象,而不是劫持对象的属性;更好的数组监控;

这样的回答,勉强算是合格吧

那到底应该怎么答呢?

面试官背后的出题逻辑

别急,咱们先整理一下思路,孙子兵法有云:“知己知彼,百战不殆”;面试就像打仗,你来我往,所以我们需要换位思考,想一想,为什么面试官会问这样一个问题?面试官想从这个问题里得到什么回答?这个问题可以考察哪些技术点? 想清楚这个问题,再回到自己身上,这些技术点,你都掌握了吗?

说得直白一点,面试就像考试,你需要先 读题、审题才能答好这道题;

为什么很多人认为 “面试造火箭,工作拧螺丝”?因为没有换位思考,没有想清楚面试题背后的逻辑;

那我们想清楚这个逻辑之后,需要我们做的就是提取技术点,整理思路,做出对应解答;当然,前提是你需要具备这些技术能力

那么接下来,我就尝试拆解一下这个面试题了,提取其中的知识点。

对于你来说,就是要看看这些知识点,你都掌握了多少?

为什么 Vue3.0 要重写响应式系统 ?

为什么重写?如果之前好好的,重写就没有意义,那之前存在什么问题,现在是怎么解决的?就是关键点了;

不知道你对 Vue2.x 的响应式掌握多少,是不是欠下了技术的债呢?没关系,我来帮你还债,先梳理 Vue2.x 的响应式;

其实基于这个面试题,背后还有很多技术点,上面这些,是与当前题目有直接关系的,实际面试中,很有可能基于这些技术点,在进行深入交流,这里就不扩展了,你能把现在这些问题理清楚,就算赚到了;

Vue2.x 响应式

其实关于这一点,在Vue 的官方文档中,早已经有过说明了,而且说得非常详细;官方文档:https://cn.vuejs.org/v2/guide/reactivity.html【JS】为什么 Vue3.0 要重写响应式系统

我们使用官方给的一张图示,来梳理整个流程;
【JS】为什么 Vue3.0 要重写响应式系统

我们先来看一段代码
【JS】为什么 Vue3.0 要重写响应式系统

响应式原理

data 中的 obj 就是一个普通的 JavaScript 对象,通过点击 Click 按钮,将获取到的随机数赋值给 this.message ,而 this.message 指向的就是 data 中 obj 对象的 message 属性;当message 发生数据改变时,页面中 H1 标签的内容会随之改变,这个过程就是就是响应式的;那么Vue 是如何实现的呢?

首先,Vue 内部使用 Object.defineProperty() 将 Data 中的每一个成员都转换为 getter / setter 的形式;getter 用来依赖收集,setter 用来派发更新;而模板内容,最终会被编译为 render 函数,在 render 函数中,我们能发现 _v(_s(message)) message 被访问了,就会触发 getter 来进行依赖收集,而在代码中的点击事件中,一旦事件处理程序被触发执行,那么 message 则会被修改,就会触发 setter来进行派发更新;

【JS】为什么 Vue3.0 要重写响应式系统

虽然流程理清楚了,但是总感觉少点什么,怎么才能更通透呢?

我们用代码来模拟整个的实现过程;

defineProperty 模拟代码

defineProperty 的基本用法,直接看手册就行了:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

我们来看看代码:

<div id="app">

hello

</div>

<script>

// 模拟 Vue 中的 data 选项

let data = {

msg: 'hello'

}

// 模拟 Vue 的实例

let vm = {}

// 数据劫持:当访问或者设置 vm 中的成员的时候,做一些干预操作

Object.defineProperty(vm, 'msg', {

// 可枚举(可遍历)

enumerable: true,

// 可配置(可以使用 delete 删除,可以通过 defineProperty 重新定义)

configurable: true,

// 当获取值的时候执行

get () {

console.log('get: ', data.msg)

return data.msg

},

// 当设置值的时候执行

set (newValue) {

console.log('set: ', newValue)

if (newValue === data.msg) {

return

}

data.msg = newValue

// 数据更改,更新 DOM 的值

document.querySelector('#app').textContent = data.msg

}

})

// 测试

vm.msg = 'Hello World'

console.log(vm.msg)

</script>

你没有看错,加上注释,一共 36行代码,这就是 Vue2.x 对响应式实现的整个流程;
继续实现多个数据的响应式

<body>

<div id="app">

hello

</div>

<script>

// 模拟 Vue 中的 data 选项

let data = {

msg: 'hello',

count: 10

}

// 模拟 Vue 的实例

let vm = {}

proxyData(data)

function proxyData(data) {

// 遍历 data 对象的所有属性

Object.keys(data).forEach(key => {

// 把 data 中的属性,转换成 vm 的 setter/setter

Object.defineProperty(vm, key, {

enumerable: true,

configurable: true,

get () {

console.log('get: ', key, data[key])

return data[key]

},

set (newValue) {

console.log('set: ', key, newValue)

if (newValue === data[key]) {

return

}

data[key] = newValue

// 数据更改,更新 DOM 的值

document.querySelector('#app').textContent = data[key]

}

})

})

}

// 测试

vm.msg = 'Hello World'

console.log(vm.msg)

</script>

</body>

上面的代码只是模拟了 响应式 的原理,但Vue在实现中,肯定不会那么简单,接下来,我们看一下源码呀……

Vue2 源码解读

首先找到响应式代码的处理位置:

关键位置作用源码位置
function Vue () {}Vue 构造函数core/instance/index.js:8
Vue.prototype._init初始化组件实例对象core/instance/init.js:16
initState初始化组件状态相关成员core/instance/state.js:48
initData初始化用户传入的 data 数据core/instance/state.js:112
observe观察 datacore/observer/index.js:110

关键位置作用源码位置
observe观察 datacore/observer/index.js:110
class ObserverObserver 逻辑core/observer/index.js:37
walk遍历对象成员分别处理core/observer/index.js:64
defineReactive为组件实例定义响应式数据core/observer/index.js:135
Object.defineProperty拦截数据的访问和修改core/observer/index.js:157

看完Vue2.x 响应式的代码,我们再回过头来思考最开始的问题,为什么 Vue3.0 要重写响应式系统 ?

为什么重写?如果之前好好的,重写就没有意义,那之前存在什么问题,换句话问就是 defineProperty 有什么问题?

Object.defineProperty 的问题

其实, defineProperty 的问题,在Vue2.x 的手册中,已经说过了;“哎,很多人就是不看文档啊”
https://cn.vuejs.org/v2/guide/reactivity.html#%E5%AF%B9%E4%BA%8E%E6%95%B0%E7%BB%84
下面分别使用 Vue2 和 Vue3 实现了一个小功能,代码一模一样,功能当然也一样,但是,在 Vue2 中就会有Bug,而运行在vue3中的,则没有任何问题;

Vue2:
【JS】为什么 Vue3.0 要重写响应式系统

Vue3:
【JS】为什么 Vue3.0 要重写响应式系统

其核心点在于 defineProperty 不能很好的实现对数组下标的监控,而在 Vue2 的实现代码中,没有更好的方案对此进行改善,尤大索性直接放弃了实现;关于这个问题,尤大也在 github 做过回应,截个图给大家看看;

【JS】为什么 Vue3.0 要重写响应式系统

那么,Vue 目前还没有解决的问题,Vue3中显然是已经解决的了,问题是,Vue3 是如何解决的呢?前面在 Vue3 的代码中我们使用的是传统的 Options Api 来实现的数据响应式, 而在 Vue3 中全新的 Composition Api 也实现了响应式系统,我们先来感受一下 Composition Api 的基础用法

Composition API 的响应式系统

ref 响应式
【JS】为什么 Vue3.0 要重写响应式系统

reactive 响应式
【JS】为什么 Vue3.0 要重写响应式系统

Vue3 中的响应式是如何实现的呢?关键点在于Proxy 函数;

Proxy 实现原理

使用 Proxy 实现的响应式代码,要比使用 defineProperty 的代码简单得多,因为 Proxy 天然的能够对整个对象做监听,而不需要对数据行遍历后做监听,同时也就解决了数组下标的问题;
我们来一段模拟代码看一下:

<div id="app">

hello

</div>

<script>

// 模拟 Vue 中的 data 选项

let data = {

msg: 'hello',

count: 0

}

// 模拟 Vue 实例

const vm = new Proxy(data, {

// 执行代理行为的函数

// 当访问 vm 的成员会执行

get (target, key) {

console.log('get, key: ', key, target[key])

return target[key]

},

// 当设置 vm 的成员会执行

set (target, key, newValue) {

console.log('set, key: ', key, newValue)

if (target[key] === newValue) {

return

}

target[key] = newValue

document.querySelector('#app').textContent = target[key]

}

})

// 测试

vm.msg = 'Hello World'

console.log(vm.msg)

</script>

以上是 【JS】为什么 Vue3.0 要重写响应式系统 的全部内容, 来源链接: utcz.com/a/109312.html

回到顶部