这里有新鲜出炉的 vue.js 教程,程序狗速度看过来!
Vue.js 是构建 web 界面的 JavaScript 库,提供数据驱动的组件,还有简单灵活的 API,使得 MVVM 更简单。
这篇文章主要教大家如何简单实现 Vue 的 observer 和 watcher,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
非庖丁瞎解牛系列~ =。=
在日常项目开发的时候,我们将 js 对象传给 vue 实例中的 data 选项,来作为其更新视图的基础,事实上是 vue 将会遍历它的属性,用 Object.defineProperty 设置它们的 get/set,从而让 data 的属性能够响应数据变化:
- Object.defineProperty(obj, name, {
- // 获取值的时候先置入vm的_data属性对象中
- get() {
- // 赋值的时候显示的特性
- },
- set() {
- // 值变化的时候可以做点什么
- }
- })
接下来可以利用其实现一个最简单的 watcher. 既然要绑定数据执行回调函数,data 属性和 callback 属性是少不了的,我们定义一个 vm 对象 (vue 中 vm 对象作为根实例,是全局的):
- /**
- * @param {Object} _data 用于存放data值
- * @param {Object} $data data原始数据对象,当前值
- * @param {Object} callback 回调函数
- */
- var vm = {
- _data: {},
- $data: {},
- callback: {}
- }
在设置值的时候,如果检测到当前值与存储在_data 中的对应值发生变化,则将值更新,并执行回调函数,利用 Object.definedProperty 方法中的 get() & set() 我们很快就可以实现这个功能~
- vm.$watch = (obj, func) => {
- // 回调函数
- vm.callback[ obj ] = func
- // 设置data
- Object.defineProperty(vm.$data, obj, {
- // 获取值的时候先置入vm的_data属性对象中
- get() {
- return vm._data[ obj ]
- },
- set(val) {
- // 比较原值,不相等则赋值,执行回调
- if (val !== vm._data[ obj ]) {
- vm._data[ obj ] = val
- const cb = vm.callback[ obj ]
- cb.call(vm)
- }
- }
- })
- }
- vm.$watch('va', () => {console.log('已经成功被监听啦')})
- vm.$data.va = 1
虽然初步实现了这个小功能,那么问题来了,obj 对象如果只是一个简单的值为值类型的变量,那以上代码完全可以满足;但是如果 obj 是一个具有一层甚至多层树结构对象变量,我们就只能监听到最外层也就是 obj 本身的变化,内部属性变化无法被监听(没有设置给对应属性设置 set 和 get), 因为对象自身内部属性层数未知,理论上可以无限层(一般不会这么做),所以此处还是用递归解决吧~
咱们先将 Object.defineProperty 函数剥离,一是解耦,二是方便我们递归~
- var defineReactive = (obj, key) => {
- Object.defineProperty(obj, key, {
- get() {
- return vm._data[key]
- },
- set(newVal) {
- if (vm._data[key] === newVal) {
- return
- }
- vm._data[key] = newVal
- const cb = vm.callback[ obj ]
- cb.call(vm)
- }
- })
- }
咦,说好的递归呢,不着急,上面只是抽离了加 get 和 set 功能的函数, 现在我们加入递归~
- var Observer = (obj) => {
- // 遍历,让对象中的每个属性可以加上get set
- Object.keys(obj).forEach((key) =>{
- defineReactive(obj, key)
- })
- }
这里仅仅只是遍历,要达到递归,则需要在 defineReactive 的时候再加上判断,判断这个属性是否为 object 类型,如果是,则执行 Observer 自身~我们改写下 defineReactive 函数
- // 判断是否为object类型,是就继续执行自身
- var observe = (value) => {
- // 判断是否为object类型,是就继续执行Observer
- if (!value || typeof value !== 'object') {
- return
- }
- return new Observer(value)
- }
- // 将observe方法置入defineReactive中Object.defineProperty的set中,形成递归
- var defineReactive = (obj, key) => {
- // 判断val是否为对象,如果对象有很多层属性,则这边的代码会不断调用自身(因为observe又执行了Observer,而Observer执行defineReactive),一直到最后一层,从最后一层开始执行下列代码,层层返回(可以理解为洋葱模型),直到最前面一层,给所有属性加上get/set
- var childObj = observe(vm._data[key])
- Object.defineProperty(obj, key, {
- get() {
- return vm._data[key]
- },
- set(newVal) {
- // 如果设置的值完全相等则什么也不做
- if (vm._data[key] === newVal) {
- return
- }
- // 不相等则赋值
- vm._data[key] = newVal
- // 执行回调
- const cb = vm.callback[ key ]
- cb.call(vm)
- // 如果set进来的值为复杂类型,再递归它,加上set/get
- childObj = observe(val)
- }
- })
- }
现在我们来整理下,把我们刚开始实现的功能雏形进行进化
- var vm = {
- _data: {},
- $data: {},
- callback: {}
- }
- var defineReactive = (obj, key) = >{
- // 一开始的时候是不设值的,所以,要在外面做一套observe
- // 判断val是否为对象,如果对象有很多层属性,则这边的代码会不断调用自身(因为observe又执行了Observer,而Observer执行defineReactive),一直到最后一层,从最后一层开始执行下列代码,层层返回(可以理解为洋葱模型),直到最前面一层,给所有属性加上get/set
- var childObj = observe(vm._data[key]) Object.defineProperty(obj, key, {
- get() {
- return vm._data[key]
- },
- set(newVal) {
- if (vm._data[key] === newVal) {
- return
- }
- // 如果值有变化的话,做一些操作
- vm._data[key] = newVal
- // 执行回调
- const cb = vm.callback[key] cb.call(vm)
- // 如果set进来的值为复杂类型,再递归它,加上set/get
- childObj = observe(newVal)
- }
- })
- }
- var Observer = (obj) = >{
- Object.keys(obj).forEach((key) = >{
- defineReactive(obj, key)
- })
- }
- var observe = (value) = >{
- // 判断是否为object类型,是就继续执行Observer
- if (!value || typeof value !== 'object') {
- return
- }
- Observer(value)
- }
- vm.$watch = (name, func) = >{
- // 回调函数
- vm.callback[name] = func
- // 设置data
- defineReactive(vm.$data, name)
- }
- // 绑定a,a若变化则执行回调方法
- var va = {
- a: {
- c: 'c'
- },
- b: {
- c: 'c'
- }
- }
- vm._data[va] = {
- a: {
- c: 'c'
- },
- b: {
- c: 'c'
- }
- }
- vm.$watch('va', () = >{
- console.log('已经成功被监听啦')
- }) vm.$data.va = 1
在谷歌浏览器的 console 中粘贴以上代码,然后回车发现,结果不出所料,va 本身被监听了,可以,我们试试 va 的内部属性有没有被监听,改下 vm.data.va=1 为 vm.data.va.a = 1, 结果发现报错了
什么鬼?
我们又仔细检查了代码,WTF,原来我们在递归的时候,Object.defineProperty 中的回调函数 cb 的 key 参数一直在发生变化,我们希望的是里面的属性变化的时候执行的是我们事先定义好的回调函数~那么我们来改下方法,将一开始定义好的回调作为参数传进去,确保每一层递归 set 的回调都是我们事先设置好的~
- var vm = {
- _data: {},
- $data: {},
- callback: {}
- }
- var defineReactive = (obj, key, cb) = >{
- // 一开始的时候是不设值的,所以,要在外面做一套observe
- var childObj = observe(vm._data[key], cb) Object.defineProperty(obj, key, {
- get() {
- return vm._data[key]
- },
- set(newVal) {
- if (vm._data[key] === newVal) {
- return
- }
- // 如果值有变化的话,做一些操作
- vm._data[key] = newVal
- // 执行回调
- cb()
- // 如果set进来的值为复杂类型,再递归它,加上set/get
- childObj = observe(newVal)
- }
- })
- }
- var Observer = (obj, cb) = >{
- Object.keys(obj).forEach((key) = >{
- defineReactive(obj, key, cb)
- })
- }
- var observe = (value, cb) = >{
- // 判断是否为object类型,是就继续执行Observer
- if (!value || typeof value !== 'object') {
- return
- }
- Observer(value, cb)
- }
- vm.$watch = (name, func) = >{
- // 回调函数
- vm.callback[name] = func
- // 设置data
- defineReactive(vm.$data, name, func)
- }
- // 绑定a,a若变化则执行回调方法
- var va = {
- a: {
- c: 'c'
- },
- b: {
- c: 'c'
- }
- }
- vm._data.va = {
- a: {
- c: 'c'
- },
- b: {
- c: 'c'
- }
- }
- vm.$watch('va', () = >{
- console.log('又成功被监听啦')
- }) vm.$data.va.a = 1
再执行一次以上代码,发现内部的 a 属性也被监听到了,而且属性值变化的时候执行了我们事先定义好的回调函数~嘻嘻嘻~
虽然实现了 $watch 的基本功能,但是和 vue 的源码还是有一定的距离,特别是一些扁平化和模块化的思想需要涉及到一些设计模式,其实我们在看源码的时候,常常是逆着作者的思维走的,功能从简单到复杂往往涉及到代码的模块化和解耦,使得代码非常地分散,读起来晦涩难懂,自己动手,从小功能代码块实现,然后结合源码,对比思路,慢慢丰富,也不失为一种学习源码的方式~
下一篇将会结合源码来浅谈下 vue 的 watcher 和 observer
来源: http://www.phperz.com/article/17/0428/329323.html