Vue源码之数据响应式

之前尝试研究Vue2.x的数据响应式原理,上次更新还是2019-11-26,最近又有兴趣了,尝试补充和完善。

[[toc]]

响应式原理

Object.defineProperty

对一个对象里的key做拦截,手动设置 getter setter方法。有了这个特性,我们操作数据时候就能拦截到,配合后面的依赖搜集机制,能完成最基本的数据响应式变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const obj = {}
/**
* 响应式
* @param {object} obj
* @param {string} key
* @param {any} val
*/

function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`你正在读取${key}:${val}`)
return val
},
set(newVal) {
if (newVal !== val) {
val = newVal
console.log(`setter: old:${val}, new:${newVal}`)
// do sth.
document.querySelector('#app').innerHTML = newVal
}
},
})
}
defineReactive(obj, 'time', '')
setInterval(() => {
obj.time = +new Date()
}, 1000)
1
2
3
4
5
$ obj.time
> 你正在读取time:

$ obj.time=3
> setter: old:3, new:3

通过这个简单函数,我们修改对应的key属性,就能自动更新数据状态。

注意,这里只是一个定义,也不支持嵌套。需要遍历设置。当然也不支持数组。数组的需要单独控制。

需要对传入的值进行判断,如果是对象还需要进一步判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function defineReactive(obj, key, val) {
// val可能还是对象,此时我们需要递归
observe(val)

// 参数3是描述对象
Object.defineProperty(obj, key, {
// xxx
})
}

function observe(obj) {
if (typeof obj !== 'object' || obj === null) {
return
}

// 遍历
Object.keys(obj).forEach((key) => defineReactive(obj, key, obj[key]))
}

// 对于新加入属性,需要单独处理
function set(obj, key, val) {
defineReactive(obj, key, val)
}
1
set(obj,'newKey','')

这样,对象的数据劫持、数据监听就基本完事了。

Vue数据响应式基本实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="app">
<p>{{counter}}</p>
<p k-text="counter"></p>
<p k-html="desc"></p>
</div>

<script src="my-vue.js"></script>

<script>
const app = new KVue({
el: '#app',
data: {
counter: 1,
desc: '<span style="color:red">xxx</span>',
},
})
setInterval(() => {
app.counter++
}, 1000)
</script>

基本这样使用使用。

原理:

  • new Vue 初始化,数据响应式.Observer
  • 编译模板,获取动态数据,Compile
  • 定义更新函数和Watcher,数据变化通知watcher调用更新函数
  • data.a 可能会使用多次,每个a都要有一个管家Dep管理watcher
  • data发生变化,找到对应的管家Dep,通知Watcher,watcher执行更新函数

用到的技术实现:

  • Kvue

Vue设计思想

MVVM

MVVM三要素:数据响应式、模板引擎、模板渲染。

数据响应式

监听数据变化,完成视图更新:

  • Object.defineProperty() - Vue2.x
  • Proxy - Vue3

Object.defineProperty

看上面

Proxy

先略

数组

思路:拦截数组7个变更方法push、pop。。。,扩展他们,使他们在变更数据的同时

额外的执行一个通知更新的任务*

模板引擎

描述视图的模板语法:

  • 插值 \{\{]}\}
  • 指令 v-bind v-on v-model v-for v-if

模板渲染

把模板变为HTML: 模板 – vdom – dom

数据驱动

视图是由数据驱动生成的,修改视图,修改的是数据。

new Vue背后

src/core/instance/index.js部分。
要求通过new实例化。然后跳到 initMixin(Vue),因为它挂载了Vue.prototype._init方法

src/core/instance/init.js的line15export function initMixin(){}部分,它就提供了刚才提到的_init方法。
大概都做了:

  • 合并options
  • 初始化生命周期 lifecycle
  • 初始化事件 events
  • 初始化渲染 render
  • hook beforeCreate
  • 初始化injections

看一下 initState(vm)。定义了 props methods data computed watch
看一下 initData

  • data是function要不然就报警。

参考链接

请我喝杯咖啡吧~