vue中的数据绑定原理的实现


Posted in Javascript onJuly 02, 2018

本文主要介绍了vue中的数据绑定原理的实现,分享给大家,也给自己留个笔记,具体如下:

vue中的数据绑定原理的实现

vue中的响应式数据绑定是通过数据劫持和观察者模式来实现的。当前学习源码为vue2.0

源码关键目录

src
|---core
|  |---instance
|     |---init.js
|     |---state.js
|  |---observer
|     |---dep.js
|     |---watcher.js

当我们实例化一个vue应用的时候,会伴随着各种的初始化工作,相关的初始化工作代码在init.js文件中

// src/core/instance/init.js

Vue.prototype._init = function (options?: Object) {
 ...
 initLifecycle(vm)
 initEvents(vm)
 callHook(vm, 'beforeCreate')
 initState(vm)
 callHook(vm, 'created')
 initRender(vm)
}

在这里可以看到对state的初始化工作initState()

// src/core/instance/state.js

export function initState (vm: Component) {
 vm._watchers = []
 initProps(vm)
 initData(vm)
 initComputed(vm)
 initMethods(vm)
 initWatch(vm)
}

可以看到这里有对各种sate的初始化工作,我们看initData()

// src/core/instance/state.js

function initData (vm: Component) {
 let data = vm.$options.data
 data = vm._data = typeof data === 'function'
  ? data.call(vm)
  : data || {}
 if (!isPlainObject(data)) {
  data = {}
  process.env.NODE_ENV !== 'production' && warn(
   'data functions should return an object.',
   vm
  )
 }
 // proxy data on instance
 const keys = Object.keys(data)
 const props = vm.$options.props
 let i = keys.length
 while (i--) {
  if (props && hasOwn(props, keys[i])) {
   process.env.NODE_ENV !== 'production' && warn(
    `The data property "${keys[i]}" is already declared as a prop. ` +
    `Use prop default value instead.`,
    vm
   )
  } else {
   proxy(vm, keys[i])
  }
 }
 // observe data
 observe(data)
 data.__ob__ && data.__ob__.vmCount++
}

这里做了一点判断,判断data方法是否返回的是一个对象,以及props中是否有与data中重名的属性,最后会调用observe对data进行监听,看一下observe

// src/core/observer/index.js

export function observe (value: any): Observer | void {
 if (!isObject(value)) {
  return
 }
 let ob: Observer | void
 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
  ob = value.__ob__
 } else if (
  observerState.shouldConvert &&
  !config._isServer &&
  (Array.isArray(value) || isPlainObject(value)) &&
  Object.isExtensible(value) &&
  !value._isVue
 ) {
  ob = new Observer(value)
 }
 return ob
}

可已看到这里也是做了一点判断,如果有__ob__属性的话就用它,或者如果data是数组或对象或可扩展对象的话,就为它新建一个Observer,看一下Observer

// src/core/observer/index.js

export class Observer {
 value: any;
 dep: Dep;
 vmCount: number; // number of vms that has this object as root $data

 constructor (value: any) {
  this.value = value
  this.dep = new Dep()
  this.vmCount = 0
  def(value, '__ob__', this)
  if (Array.isArray(value)) {
   const augment = hasProto
    ? protoAugment
    : copyAugment
   augment(value, arrayMethods, arrayKeys)
   this.observeArray(value)
  } else {
   this.walk(value)
  }
 }

 /**
  * Walk through each property and convert them into
  * getter/setters. This method should only be called when
  * value type is Object.
  */
 walk (obj: Object) {
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {
   defineReactive(obj, keys[i], obj[keys[i]])
  }
 }

 /**
  * Observe a list of Array items.
  */
 observeArray (items: Array<any>) {
  for (let i = 0, l = items.length; i < l; i++) {
   observe(items[i])
  }
 }
}

判断data是不是数组,如果是数组就对数组元素再去调用observe方法做同样的处理,如果不是,就调用walk去劫持该数据,对数据的劫持主要再defineReactive方法中,正如函数名,让数据变得响应式。看一下defineReactive方法

// src/core/observer/index.js

export function defineReactive (
 obj: Object,
 key: string,
 val: any,
 customSetter?: Function
) {
 const dep = new Dep()
// data中的每一个成员都有一个对应的Dep,在此闭包创建。

 const property = Object.getOwnPropertyDescriptor(obj, key)
 if (property && property.configurable === false) {
  return
 }

 // cater for pre-defined getter/setters
 const getter = property && property.get
 const setter = property && property.set

 let childOb = observe(val)
 Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
   const value = getter ? getter.call(obj) : val
   if (Dep.target) {
    dep.depend() // 依赖收集
    if (childOb) {
     childOb.dep.depend()
    }
    if (Array.isArray(value)) {
     for (let e, i = 0, l = value.length; i < l; i++) {
      e = value[i]
      e && e.__ob__ && e.__ob__.dep.depend()
     }
    }
   }
   return value
  },
  set: function reactiveSetter (newVal) {
   const value = getter ? getter.call(obj) : val
   if (newVal === value) {
    return
   }
   if (process.env.NODE_ENV !== 'production' && customSetter) {
    customSetter()
   }
   if (setter) {
    setter.call(obj, newVal)
   } else {
    val = newVal
   }
   childOb = observe(newVal)
   dep.notify() // 发布通知
  }
 })
}

遍历状态,修改状态的getter和setter,当页面上对应状态被首次渲染的时候,会为页面上每一个使用到data的地方新建一个watcher,并将当前watcher保存到全局变量Dep.target中,在对应data的getter中就会调用Dep.depend方法,将当前的watcher添加到当前的Dep中,一个Dep对应一个或多个watcher,着取决于,此状态被使用的数量。当data被修改时,对应的setter就会被触发,会调用对应的Dep中的notify方法,通知所有观察者,进行更新。

这里出现了两个定的类:Dep和Watcher,其中Dep管理观察者,Wathcer代表观察者

先看一下Dep

// src/core/observer/dep.js

export default class Dep {
 static target: ?Watcher;
 id: number;
 subs: Array<Watcher>;

 constructor () {
  this.id = uid++
  this.subs = []
 }

 addSub (sub: Watcher) {
  this.subs.push(sub)
 }

 removeSub (sub: Watcher) {
  remove(this.subs, sub)
 }

 depend () {
  if (Dep.target) {
// 调用当前target,也就是正在处理的watcher的addDep方法,并把此Dep传进去
   Dep.target.addDep(this)
  }
 }

 notify () {
  // stablize the subscriber list first
  const subs = this.subs.slice()
  for (let i = 0, l = subs.length; i < l; i++) {
   subs[i].update()
  }
 }
}

看一下watcher.js

// src/core/observer/watcher.js

export default class Watcher {
...
 addDep (dep: Dep) {
  const id = dep.id
  if (!this.newDepIds.has(id)) {
   this.newDepIds.add(id)
   this.newDeps.push(dep)
   if (!this.depIds.has(id)) {
    // 将当前watcher添加到当前的Dep中
    dep.addSub(this)
   }
  }
 }
...
}

总结

vue的响应式数据绑定主要依赖Object.defineProperty和观察者模式。

  1. 在我们新建一个vue实例的时候,做一系列的初始化工作,这部分的逻辑集中在src文件夹下的core文件夹下的instance和observer文件夹内
  2. 响应式数据绑定是在状态的初始化阶段完成的,在initState方法中的initData中进行data的数据绑定。
  3. 在initData中调用observe方法,为该data新建一个Observer类,然后最终调用为data中的每一个成员调用walk方法,在walk中通过defineReactive方法劫持当前数据
  4. 在defineReactive中通过Object.defineProperty去修改数据的getter和setter
  5. 在页面渲染的时候,页面上每一个用到data的地方都会生成一个watcher,并将它保存到全局变量Dep.target中,watcher改变每一个观察者,Dep用来管理观察者。
  6. 然后在data的getter中将调用Dep的depend方法,将Dep.target中的watcher添加到此data对应的Dep中,完成依赖收集
  7. 在data被修改的时候,对应data的setter方法就会被出动,会调用Dep.notify()方法发布通知,调用每个watcher的uptade方法进行更新。

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

Javascript 相关文章推荐
JavaScript 存在陷阱 删除某一区域所有节点
May 10 Javascript
滚动图片效果 jquery实现回旋滚动效果
Jan 08 Javascript
JS 对输入框进行限制(常用的都有)
Jul 30 Javascript
使用JavaScript实现连续滚动字幕效果的方法
Jul 07 Javascript
jQuery实现div拖拽效果实例分析
Feb 20 Javascript
Vue 中使用 CSS Modules优雅方法
Apr 09 Javascript
Vue脚手架的简单使用实例
Jul 10 Javascript
Vue自定义toast组件的实例代码
Aug 15 Javascript
JS跨域请求的问题解析
Dec 03 Javascript
对layui中table组件工具栏的使用详解
Sep 19 Javascript
vue下canvas裁剪图片实例讲解
Apr 16 Javascript
Vue 构造选项 - 进阶使用说明
Aug 14 Javascript
Vue实现双向绑定的原理以及响应式数据的方法
Jul 02 #Javascript
jsonp跨域获取数据的基础教程
Jul 01 #Javascript
vue + webpack如何绕过QQ音乐接口对host的验证详解
Jul 01 #Javascript
关于Vue组件库开发详析
Jul 01 #Javascript
D3.js实现拓扑图的示例代码
Jun 30 #Javascript
详解angular如何调用HTML字符串的方法
Jun 30 #Javascript
angular6.0使用教程之父组件通过url传递id给子组件的方法
Jun 30 #Javascript
You might like
PHP 操作文件的一些FAQ总结
2009/02/12 PHP
30个php操作redis常用方法代码例子
2014/07/05 PHP
Zend Framework 2.0事件管理器(The EventManager)入门教程
2014/08/11 PHP
WordPress中调试缩略图的相关PHP函数使用解析
2016/01/07 PHP
PHP函数checkdnsrr用法详解(Windows平台用法)
2016/03/21 PHP
PHP自定义图片缩放函数实现等比例不失真缩放的方法
2016/08/19 PHP
JQuery困惑—包装集 DOM节点
2009/10/16 Javascript
关于javascript中this关键字(翻译+自我理解)
2010/10/20 Javascript
Jquery EasyUI中弹出确认对话框以及加载效果示例代码
2014/02/13 Javascript
AngularJS基础学习笔记之指令
2015/05/10 Javascript
轻量级javascript 框架Backbone使用指南
2015/07/24 Javascript
jquery编写Tab选项卡滚动导航切换特效
2020/07/17 Javascript
20分钟轻松创建自己的Bootstrap站点
2016/05/12 Javascript
javascript 四十条常用技巧大全
2016/09/09 Javascript
浅谈js中同名函数和同名变量的执行问题
2017/02/12 Javascript
Vue实现6位数密码效果
2018/08/18 Javascript
用Golang运行JavaScript的实现示例
2019/11/25 Javascript
vue 里面的 $forceUpdate() 强制实例重新渲染操作
2020/09/21 Javascript
[44:51]2018DOTA2亚洲邀请赛 4.4 淘汰赛 VP vs Liquid 第二场
2018/04/05 DOTA
Python代码解决RenderView窗口not found问题
2016/08/28 Python
Pycharm技巧之代码跳转该如何回退
2017/07/16 Python
Python判断文件和字符串编码类型的实例
2017/12/21 Python
Pandas 按索引合并数据集的方法
2018/11/15 Python
Python3.5集合及其常见运算实例详解
2019/05/01 Python
Python 调用有道翻译接口实现翻译
2020/03/02 Python
Python的Tqdm模块实现进度条配置
2021/02/24 Python
用CSS3写的模仿iPhone中的返回按钮
2015/04/04 HTML / CSS
图解CSS3制作圆环形进度条的实例教程
2016/05/26 HTML / CSS
Hotels.com英国:全球领先的酒店住宿提供商
2019/01/24 全球购物
Timberland俄罗斯官方网上商店:全球领先的户外品牌
2020/03/15 全球购物
一些Unix笔试题和面试题
2012/09/25 面试题
新媒传信软件测试面试题
2013/02/24 面试题
批评与自我批评总结
2014/10/17 职场文书
网站文案策划岗位职责
2015/04/14 职场文书
2015年全国保险公众宣传日活动方案
2015/05/06 职场文书
2019客服个人年终工作总结范文
2019/07/08 职场文书