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 相关文章推荐
js实现拖拽 闭包函数详细介绍
Nov 25 Javascript
关于jQuery参考实例2.0 用jQuery选择元素
Apr 07 Javascript
JavaScript中的对象的extensible属性介绍
Dec 30 Javascript
jquery图片倾斜层叠切换特效代码分享
Aug 27 Javascript
js实现下拉列表选中某个值的方法(3种方法)
Dec 17 Javascript
JS日程管理插件FullCalendar简单实例
Feb 07 Javascript
详解AngularJS脏检查机制及$timeout的妙用
Jun 19 Javascript
javascript高级模块化require.js的具体使用方法
Oct 31 Javascript
spring+angular实现导出excel的实现代码
Feb 27 Javascript
详解vue父子组件关于模态框状态的绑定方案
Jun 05 Javascript
es6数组的flat(),flatMap()函数用法实例分析
Apr 18 Javascript
HTML+VUE分页实现炫酷物联网大屏功能
May 27 Vue.js
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 网上商城促销设计实例代码
2012/02/17 PHP
Linux下手动编译安装PHP扩展的例子分享
2014/07/15 PHP
54个提高PHP程序运行效率的方法
2015/07/19 PHP
DIV菜单层实现代码
2010/11/19 Javascript
浅析Js中的单引号与双引号问题
2013/11/06 Javascript
js数组的操作指南
2014/12/28 Javascript
易被忽视的js事件问题总结
2016/05/14 Javascript
JavaScript中的this使用详解
2016/07/27 Javascript
原生js实现淘宝购物车功能
2020/06/23 Javascript
JS基于正则实现数字千分位用逗号分隔的方法
2017/06/16 Javascript
nodejs 图解express+supervisor+ejs的用法(推荐)
2017/09/08 NodeJs
vuex实现及简略解析(小结)
2019/03/01 Javascript
详解微信小程序自定义组件的实现及数据交互
2019/07/22 Javascript
前端开发基础javaScript的六大作用
2020/08/06 Javascript
[35:34]Liquid vs Winstrike 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
从零学Python之hello world
2014/05/21 Python
python静态方法实例
2015/01/14 Python
python简单实现旋转图片的方法
2015/05/30 Python
Python用for循环实现九九乘法表
2018/05/31 Python
python开发准备工作之配置虚拟环境(非常重要)
2019/02/11 Python
PyQt QCombobox设置行高的方法
2019/06/20 Python
django认证系统实现自定义权限管理的方法
2019/08/28 Python
在python中计算ssim的方法(与Matlab结果一致)
2019/12/19 Python
解决pytorch报错:AssertionError: Invalid device id的问题
2020/01/10 Python
python 成功引入包但无法正常调用的解决
2020/03/09 Python
python如何调用java类
2020/07/05 Python
h5调用摄像头的实现方法
2016/06/01 HTML / CSS
澳大利亚自然和有机的健康美容产品一站式商店:Ziani Beauty
2017/12/28 全球购物
大三在校生电子商务求职信
2013/10/29 职场文书
质量工程师岗位职责
2013/11/16 职场文书
四风对照检查剖析材料
2014/10/07 职场文书
2015年中秋节演讲稿
2015/03/20 职场文书
龙猫观后感
2015/06/09 职场文书
麦田里的守望者读书笔记
2015/06/30 职场文书
vue里使用create, mounted调用方法
2022/04/26 Vue.js
python如何将mat文件转为png
2022/07/15 Python