这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战
这个系列也没啥花头,就是来整平时面试的一些手写函数,考这些简单实现的好处是能看出基本编码水平,且占用时间不长,更全面地看出你的代码实力如何。一般不会出有很多边界条件的问题,那样面试时间不够用,考察不全面。
平时被考到的 api 如果不知道或不清楚,直接问面试官就行, api 怎么用这些 Google 下谁都能马上了解的知识也看不出水平。关键是在实现过程,和你的编码状态、习惯、思路清晰程度等。
注意是简单实现,不是完整实现,重要的是概念清晰和实现思路清晰,建议
先解释清楚概念
=>写用例
=>写伪代码
=>再实现具体功能
,再优化
,一步步来。
33. 手写简单 Vue2.x 响应式原理
分析
首先响应式原理,请看官方文档,说的也是很清晰了,这个图非常好
基本原理就是:
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”
过的数据 property
记录为依赖。之后当依赖项的 setter 触发时
,会通知 watcher,从而使它关联的组件重新渲染。
当然 vue2.x 会有下面两个点要注意
-
Vue 无法检测
property 的添加或移除
。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在data
对象上存在才能让 Vue 将它转换为响应式的。- 但是,可以使用
Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式 property。
- 但是,可以使用
-
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
- 你也可以使用
vm.$set
实例方法,vm.$set(vm.items, indexOfItem, newValue)
来处理
- 当你利用索引直接设置一个数组项时,例如:
那么我们手写一个基本的响应式demo,应该从那些个方面思考呢?
根据上面的原理分解成这几块:
- Observer
- Watcher
- Dep
其实你把这几块搞清楚是什么,以及他们之间的关系,响应式基本就明白了。
-
首先
Observer
是遍历组件中的数据,并把这些数据经过defineReactive
方法变成响应式的,也就是对数据进行劫持。从而在每次取值(get
)、设值时(set
) 都能做操作。 -
Watcher
每个组件实例都对应一个 watcher 实例,watcher 负责渲染和更新视图 (图中向左灰色平箭头)- 其中 watcher 分类型
- 视图
data
的watcher
是renderWatcher
, - 计算属性
Computed
的watcher
是computedWatcher
, - 用户自己定义的
watch
选项中的watcher
叫userWatcher
- 视图
- 其中 watcher 分类型
-
Dep
依赖收集器,我们了解了Observer
监听了数据变化,Watcher
用来更新视图,而Dep
是Observer
和Watcher
的桥梁,负责- 记录渲染的 watcher
- 当被监听 data 变化时通知之前记住的 watcher 去更新视图
了解了这些,还建议看下之前的观察者模式的手写实现
手写实现
我们直接上代码, 一块块实现
function Observer(data) {
if (!data || typeof data != 'object') return
for (let key in data) {
defineReactive(data, key);
}
}
const defineReactive = function(obj, key) {
let val = obj[key];
// 如果 data 中有嵌套对象,需要递归去深度监听
Observer(val)
// 依赖收集器实例
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log('get val');
// 在对模板属性进行求值来触发依赖收集
if (target) {
dep.addSub(target)
}
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
console.log('set val')
val = newVal;
// 当值发生变更时,依赖收集器通知更新每个需要更新的Watcher去更新视图 update(),
// 就是dep.subs中的所有依赖项
dep.notify();
}
});
}
Observer 监听我们搞完了,下面解析代码模板,遇到{{}}
或者 computed, 就要依赖收集过程
。
// 作为观察者的消息中心, 解耦属性的依赖和更新操作
class Dep {
constructor() {
this.subs = []
}
// 把对应 watcher 放入依赖收集器的 subs 中
addSub(watcher) {
this.subs.push(watcher)
}
// 通知 subs 中所有 watcher 去更新视图
notify() {
this.subs.forEach(watcher => {
watcher.update()
})
}
}
// 全局属性,用来配置 watcher
target = null
class Watcher {
constructor(obj, key) {
// 将 target 指向自己
target = this
this.obj = obj
this.key = key
this.value = obj[key]
// 最后置空 target
// 是为了防止 notify 触发时,不停的绑定 Watcher 与 Dep, 会死循环
target = null
}
update() {
this.value = this.obj[this.key]
this.divChange(this.value)
}
// 更新视图, 改 DOM
divChange(value) {
document.querySelector('div').innerText = value
}
}
var data = { name: 'keal' }
// 当然这些在 Vue 框架都帮你做了
Observer(data)
new Watcher(data, 'name')
// 现在你可以随便改 data.name 已经变成响应式了
data.name = 'neverMore'
// 你可以打开百度,打开 devtool 复制代码运行试试
今天就到这里。
另外向大家着重推荐下另一个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列 记得点赞哈
今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友
Or 搜索我的微信号infinity_9368
,可以聊天说地
加我暗号 "天王盖地虎" 下一句的英文
,验证消息请发给我
presious tower shock the rever monster
,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧
参考
- vue 文档
- 面试之道