• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com专业计算机教程网站
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • html/xhtml
  • html5
  • CSS
  • XML/XSLT
  • Dreamweaver教程
  • Frontpage教程
  • 心得技巧
  • bootstrap
  • vue
  • AngularJS
  • HBuilder教程
  • css3
  • 浏览器兼容
  • div/css
  • 网页编辑器
  • axure
您的位置:首页 > 网页设计 >vue > 简单实现Vue的observer和watcher

简单实现Vue的observer和watcher

作者:史俊男 字体:[增加 减小] 来源:互联网 时间:2017-05-30

本文主要包含observer模式的实现,observer,observer是什么意思,the observer,contentobserver等相关知识,史俊男 希望在学习及工作中可以帮助到您

非庖丁瞎解牛系列~ =。=

在日常项目开发的时候,我们将js对象传给vue实例中的data选项,来作为其更新视图的基础,事实上是vue将会遍历它的属性,用Object.defineProperty 设置它们的 get/set,从而让 data 的属性能够响应数据变化:

 Object.defineProperty(obj, name, {
 // 获取值的时候先置入vm的_data属性对象中
 get() {
  // 赋值的时候显示的特性
 },
 set() {
  // 值变化的时候可以做点什么
 }
 })
</div>

接下来可以利用其实现一个最简单的watcher.既然要绑定数据执行回调函数,data属性和callback属性是少不了的,我们定义一个vm对象(vue中vm对象作为根实例,是全局的):

/**
 * @param {Object} _data 用于存放data值
 * @param {Object} $data data原始数据对象,当前值
 * @param {Object} callback 回调函数
 */
var vm = { _data: {}, $data: {}, callback: {} }

</div>

在设置值的时候,如果检测到当前值与存储在_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

</div>

虽然初步实现了这个小功能,那么问题来了,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)
 }
 })
}

</div>

咦,说好的递归呢,不着急,上面只是抽离了加get和set功能的函数,
现在我们加入递归~

var Observer = (obj) => {
 // 遍历,让对象中的每个属性可以加上get set
 Object.keys(obj).forEach((key) =>{
 defineReactive(obj, key)
 })
}

</div>

这里仅仅只是遍历,要达到递归,则需要在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)
 }
 })
}
</div>

现在我们来整理下,把我们刚开始实现的功能雏形进行进化

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

</div>

在谷歌浏览器的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

</div>

再执行一次以上代码,发现内部的a属性也被监听到了,而且属性值变化的时候执行了我们事先定义好的回调函数~嘻嘻嘻~

虽然实现了$watch的基本功能,但是和vue的源码还是有一定的距离,特别是一些扁平化和模块化的思想需要涉及到一些设计模式,其实我们在看源码的时候,常常是逆着作者的思维走的,功能从简单到复杂往往涉及到代码的模块化和解耦,使得代码非常地分散,读起来晦涩难懂,自己动手,从小功能代码块实现,然后结合源码,对比思路,慢慢丰富,也不失为一种学习源码的方式~

下一篇将会结合源码来浅谈下vue的watcher和observer

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

</div>

您可能想查找下面的文章:

  • 简单实现Vue的observer和watcher

相关文章

  • 2017-05-30详解Vuejs2.0之异步跨域请求
  • 2018-01-28vue+element的tree组件学习模板
  • 2017-05-30详解如何使用vue-cli脚手架搭建Vue.js项目
  • 2017-05-30分分钟玩转Vue.js组件(二)
  • 2017-09-27Vue 搭建脚手架
  • 2017-05-30vuejs2.0子组件改变父组件的数据实例
  • 2017-05-30vuex实现简易计数器
  • 2017-05-30Vue系列:通过vue-router如何传递参数示例
  • 2017-05-30基于Vue.js实现数字拼图游戏
  • 2017-05-30探究Vue.js 2.0新增的虚拟DOM

文章分类

  • html/xhtml
  • html5
  • CSS
  • XML/XSLT
  • Dreamweaver教程
  • Frontpage教程
  • 心得技巧
  • bootstrap
  • vue
  • AngularJS
  • HBuilder教程
  • css3
  • 浏览器兼容
  • div/css
  • 网页编辑器
  • axure

最近更新的内容

    • vue+vuex+axio从后台获取数据存入vuex实现组件之间共享数据
    • Vuex之理解Mutations的用法实例
    • Vue.js组件tree实现无限级树形菜单
    • Vue.js每天必学之Class与样式绑定
    • vue-css过渡效果
    • vuejs2.0子组件改变父组件的数据实例
    • webpack+vue.js快速入门教程
    • Javascript vue.js表格分页,ajax异步加载数据
    • vue2.0构建单页应用最佳实战
    • Vue动态实现评分效果

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有