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 相关文章推荐
一个tab标签切换效果代码
Mar 27 Javascript
jquery打开直接跳到网页最下面、最低端实现代码
Apr 22 Javascript
JSON无限折叠菜单编写实例
Dec 16 Javascript
js根据日期判断星座的示例代码
Jan 23 Javascript
JavaScript 学习笔记之数据类型
Jan 14 Javascript
js实现同一页面多个运动效果的方法
Apr 10 Javascript
jquery append 动态添加的元素事件on 不起作用的解决方案
Jul 30 Javascript
由浅入深剖析Angular表单验证
Jul 14 Javascript
使用Node.js实现ORM的一种思路详解(图文)
Oct 24 Javascript
深入浅析Vue中的slots/scoped slots
Apr 03 Javascript
Vue数据绑定简析小结
May 07 Javascript
vue-router的hooks用法详解
Jun 08 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
mysql_connect localhost和127.0.0.1的区别(网络层阐述)
2015/03/26 PHP
thinkPHP实现签到功能的方法
2017/03/15 PHP
PHP多进程通信-消息队列使用
2019/03/08 PHP
php intval函数用法总结
2019/04/14 PHP
PHP中用Trait封装单例模式的实现
2019/12/18 PHP
ThinkPHP5.1+Ajax实现的无刷新分页功能示例
2020/02/10 PHP
jQuery position() 函数详解以及jQuery中position函数的应用
2015/12/14 Javascript
基于jQuery实现多标签页切换的效果(web前端开发)
2016/07/24 Javascript
javascript基于牛顿迭代法实现求浮点数的平方根【递归原理】
2017/09/28 Javascript
vue滚动轴插件better-scroll使用详解
2017/10/17 Javascript
vue中子组件的methods中获取到props中的值方法
2018/08/27 Javascript
Vue中Axios从远程/后台读取数据
2019/01/21 Javascript
如何根据业务封装自己的功能组件
2019/04/19 Javascript
详解小程序云开发攻略(解决最棘手的问题)
2019/09/30 Javascript
Vue scoped及deep使用方法解析
2020/08/01 Javascript
利用Python-iGraph如何绘制贴吧/微博的好友关系图详解
2017/11/02 Python
深入理解python中函数传递参数是值传递还是引用传递
2017/11/07 Python
Django中ajax发送post请求 报403错误CSRF验证失败解决方案
2019/08/13 Python
使用python os模块复制文件到指定文件夹的方法
2019/08/22 Python
关于Django Models CharField 参数说明
2020/03/31 Python
CSS3的颜色渐变效果的示例代码
2017/09/29 HTML / CSS
小天鹅官方商城:LittleSwan
2017/06/16 全球购物
State Cashmere官网:半零售价可持续蒙古羊绒
2020/02/26 全球购物
澳大利亚家用电器在线商店:Billy Guyatts
2020/05/05 全球购物
实习生个人的自我评价
2013/12/08 职场文书
监督检查工作方案
2014/05/28 职场文书
信息工作经验交流材料
2014/05/28 职场文书
会计学自荐信
2014/06/03 职场文书
美术学专业求职信
2014/07/23 职场文书
龙潭大峡谷导游词
2015/02/10 职场文书
2016年教师师德师风承诺书
2016/03/25 职场文书
闭幕词的写作格式与范文!
2019/06/24 职场文书
导游词之蜀山胜景瓦屋山
2019/11/29 职场文书
导游词之山东八大关
2019/12/18 职场文书
SQL基础查询和LINQ集成化查询
2022/01/18 MySQL
Win11筛选键导致键盘失灵怎么解决? Win11关闭筛选键的技巧
2022/04/08 数码科技