Vue响应式原理

作为写业务的码农,几乎不必知道原理。但是当你去找工作的时候,可是需要造原子弹的,什么都得知道一些才行。所以找工作之前可以先复习下,只要是关于vue的,必定会问响应式原理。

核心:

//es5
Object.defineProperty(obj,key,{
get() {
// 获取obj[key]的时候触发
},
set(val) {
// obj[key] = 'xxx'时触发
}
})

其实,只需要在修改data值的时候,需要触发一个回调方法,来更新与此值有关的数据,就完了。但是你面试的时候,那些大佬可不会觉得是这样的,需要把整个流程说明白才行。

简单的Vue响应式代码如下:

Vue.js:

class Vue {
constructor(opts) {
this.opts = opts
if (opts.data) this.initData(opts.data);
if (opts.watch) this.initWatch(opts.watch);
if (opts.computed) this.initComputed(opts.computed);
if (opts.el) this.$mount(opts.el)
}
initData(data) {
// 让data上的数据被get的时候能够搜集watcher,data变为观察者
new Observable(data);
this.data = data
// 数据可在this上直接读取
Object.keys(data).forEach(key => {
this.proxy(key)
})
} initWatch(watch) {
Object.keys(watch).forEach(key => {
//
new Watcher(this, key, watch[key])
})
}
initComputed(data) {
Object.keys(data).forEach(key => {
new Watcher(this, key)
this.proxy(key, {
get: data[key],
})
}) } $mount(el) {
el = document.querySelector(el)
this.template = el.innerHTML this.el = el
const fn = _ => {
const nwTemp = this.parseHTML(this.template)
this.el.innerHTML = nwTemp
if(this.opts.mounted) {
this.opts.mounted.call(this)
}
}
new Watcher(this, fn)
}
parseHTML(template) { return template.replace(/\{\{(.*?)\}\}/g, (str, str1) => {
return this[str1.trim()]
})
} // 将数据直接挂到this上,使用getterSetter代理,获取vm.data上的值
proxy(key, getterSetter) {
const vm = this
getterSetter = getterSetter || {
set(value) {
vm.data[key] = value
},
get() {
return vm.data[key]
}
}
Object.defineProperty(vm, key, getterSetter)
}
}
// 给data作为观察者用的
class Observable {
constructor(obj) { Object.keys(obj).forEach(key => {
this.defineReact(obj, key, obj[key])
});
} defineReact(obj, key, value) { const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
// 获取data里面信息的时候,能够搜集依赖,这些依赖都是watcher实例
if (Dep.target) {
dep.append(Dep.target)
}
return value
},
set(val) {
value = val
// 修改data里面数据的时候,去通知已搜集的依赖更新
dep.notify()
}
})
} } // 搜集与触发wacher
class Dep { constructor() {
this.subs = []
}
append(watcher) {
// 避免重复添加watcher
// watcher 在update的时候,会重新获取值,此时不必再添加
if(this.subs.includes(watcher)) return
this.subs.push(watcher)
}
notify() {
this.subs.forEach(watcher => {
watcher.update()
})
}
}
Dep.target = null class Watcher {
// keyOrFn 为字符串或者function,opts.watch为字符串,computed,$mount中为function
constructor(vm, keyOrFn, cb) {
this.cb = cb
if (typeof keyOrFn === 'string') {
this.getter = function () {
return vm[keyOrFn] // 例:initWatch时,watch:{a(){}} ,a为data里的数据,此处获取vm.a会触发a的收集
}
} else { this.getter = keyOrFn // 如果为fn(computed中)值为此函数的返回值
} this.value = this.get()
}
get() {
Dep.target = this // 此watcher记录下来
const value = this.getter(this.vm)
// 运行getter,如果是watch,获取一次data里的值,完成收集。
// 如果是computed,运行其方法,其方法中含有data的值时,会触发收集
// 当更新时,也会触发getter
Dep.target = null
return value
} update() {
// 更新
const oldValue = this.value
// 重新获取值
this.value = this.get()
if (this.cb) {
//如果是watch,会触发watch的函数
this.cb.call(this.vm, this.value, oldValue)
}
}
}

index.html

  <div id="app">
<h2>Vue响应式原理</h2>
<br />
msg: {{ msg }}
<br />
<p>num: {{ num }}</p> <p>num+1计算属性值:{{ add1 }}</p>
<button>add</button>
</div>
<script src="./Vue.js"></script> <script>
const app = new Vue({
el: "#app",
data: {
msg: "这是msg",
num: 1
},
watch: {
msg(newVal, oldVal) {
console.log(newVal, oldVal);
}
},
computed: {
add1() {
return this.num + 1;
}
},
mounted() {
const btn = document.querySelector("button");
btn.onclick = _ => {
this.num += 1;
};
}
});
</script>

总结

1.  需要this.xxx 访问的做proxy
2. data做 Observer 动态数据化处理。
3. data每一个属性都new Dep 管理此属性的订阅及改变数据后的发布。
4. computed、watch各属性及$mount 做 new Watch订阅。

最新文章

  1. [No000079]罗辑思维2016.1.2日前的所有每日语音,python3做的网络爬虫
  2. 初试TinyIoCContainer笔记
  3. 应对Memcached缓存失效,导致高并发查询DB的四种思路(l转)
  4. UVa 458 - The Decoder
  5. 使用RAML描述API文档信息的一些用法整理
  6. [转]高并发访问下避免对象缓存失效引发Dogpile效应
  7. virtualBox 安装CentOS 全屏
  8. 你会用swift创建复杂的加载动画吗(1)
  9. Angular+ionic2 web端 启动程序出现短暂 白屏或黑屏 的处理小妙招
  10. python 字符串操作方法详解
  11. mysql distinct field1,field2,field3, .... from table
  12. jar包 pom
  13. Django----将列表按照一定的顺序展示
  14. APP产品设计及运营时常见的问题
  15. Pagedown learning notes
  16. IBM 3650 M3 yum upgrade后系统无法登陆问题
  17. 测试思想-流程规范&#160;SVN代码管理与版本控制
  18. 通过JSP网页连接MySQL数据库,从MySQL数据库中读出一张表并显示在JSP网页中
  19. Bash 中的特殊字符大全【转】
  20. 忘记MySQL root密码重置MySQL root密码

热门文章

  1. ESA2GJK1DH1K基础篇: 测试APP扫描Air202上面的二维码绑定通过MQTT控制设备(兼容SIM800)
  2. [NOI2010]超级钢琴 主席树
  3. 洛谷 P5535 【XR-3】小道消息
  4. Unix/Linux系统下的nobody用户是什么?
  5. 图文详解如何使用VMWare创建一套虚拟机“集群”
  6. Java的策略和保护域
  7. GitLab修改root用户密码
  8. 【IntelliJ IDEA学习之一】IntelliJ IDEA安装激活、VM参数
  9. Pycharm 个人认为舒服漂亮又好用的主题风格
  10. POJ 1276 Cash Machine(完全背包模板题)