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方法进行更新。

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

(0)

相关推荐

  • 浅谈Vue.js之初始化el以及数据的绑定说明

    1.初始化el 2.数据绑定说明 3.监听值的变化 以上这篇浅谈Vue.js之初始化el以及数据的绑定说明就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

  • Vue入门之数据绑定(小结)

    1. 什么是双向绑定? Vue框架很核心的功能就是双向的数据绑定. 双向是指:HTML标签数据 绑定到 Vue对象,另外反方向数据也是绑定的.通俗点说就是,Vue对象的改变会直接影响到HTML的标签的变化,而且标签的变化也会反过来影响Vue对象的属性的变化. 这样以来,就彻底变革了之前Dom的开发方式,之前Dom驱动的开发方式尤其是以jQuery为主的开发时代,都是dom变化后,触发js事件,然后在事件中通过js代码取得标签的变化,再跟后台进行交互,然后根据后台返回的结果再更新HTML标签,异常

  • vue.js数据绑定操作详解

    本文实例讲述了vue.js数据绑定操作.分享给大家供大家参考,具体如下: 数据绑定 响应式的数据绑定系统.建立绑定之后,DOM将和数据保持同步,无须手动维护DOM.使代码能够更加简洁易懂.提升效率. 数据绑定语法 1.文本插值 {{ }}Mustache标签 <span>Hello {{ name }}</span> data:{ name: 'vue' } == > Hello vue 单次插值 首次赋值后再更改vm实例属性值不会引起DOM的变化 <span v-on

  • Vue.js实现双向数据绑定方法(表单自动赋值、表单自动取值)

    1.使用Vue.js实现双向表单数据绑定,例子 <!--html代码--> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>财产查勘处理</title> <link r

  • vue.js数据绑定的方法(单向、双向和一次性绑定)

    这两天学习了vue.js数据绑定这个地方知识点挺多的,而且很重要,所以,今天添加一点小笔记. 前言 感觉 vue 的很多方面的内容,都参考了 angular 的东西,数据绑定方面,更是赤裸裸的"抄袭".对照来看,更有助于我们学习和理解框架本身透露出来的思想,而非框架本身. 一.单向绑定 (一)Mustache 语法,双大括号 {{}}(html 内字符串绑定) <div id="app"> <p>{{text}}</p> <

  • Vue数据绑定实例写法

    为什么绑定 简单的数据渲染,包括表达式.函数在内.其实都只是在标签中渲染,如果遇到以下情况怎么办呢: 需要在标签内部进行某种 "骚操作" . 需要控制流来控制不同数据下的不同渲染效果. 需要渲染一个数组. 这时候简单渲染就不能很好的解决问题了,怎么办 ? 来一发数据绑定吧! 绑定是什么 在了解绑定是什么之前,先了解一下什么是指令: 在 " vue" 中,指令是带有 v- 前缀的特殊属性,用来修饰标签的(自定义组件在这里也统一归为标签,因为其使用方式和原生标签一样),

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

    本文主要介绍了vue中的数据绑定原理的实现,分享给大家,也给自己留个笔记,具体如下: vue中的响应式数据绑定是通过数据劫持和观察者模式来实现的.当前学习源码为vue2.0 源码关键目录 src |---core | |---instance | |---init.js | |---state.js | |---observer | |---dep.js | |---watcher.js 当我们实例化一个vue应用的时候,会伴随着各种的初始化工作,相关的初始化工作代码在init.js文件中 //

  • 深入了解Vue中双向数据绑定原理

    目录 数据的变化反应到视图 命令式操作视图 声明式操作视图 小结 视图的变化反应到数据 现存的问题 数据的变化反应到视图 前面我们了解到数据劫持之后,我们可以在数据发生修改之后做任何我们想要做的事情,操作视图当然也是OK的 命令式操作视图 目标:我们通过原始的操作dom的方式让每一次的name的最新值都能显示到p元素内部 <div id="app"> <p></p> </div> <script> let data = { n

  • vue中的v-model原理,与组件自定义v-model详解

    VUE中的v-model可以实现双向绑定,但是原理是什么呢?往下看看吧 根据官方文档的解释,v-model其实是一个语法糖,它会自动的在元素或者组件上面解析为 :value="" 和 @input="", 就像下面这样 // 标准写法 <input v-model="name"> // 等价于 <input :value="name" @input="name = $event.target.val

  • vue学习笔记之Vue中css动画原理简单示例

    本文实例讲述了Vue中css动画原理.分享给大家供大家参考,具体如下: 当transition包裹了一个元素之后,vue会自动分析元素的css样式,构建动画流程. so,我们需要定义style. vue中的css动画,其实就是某一个时间点,给元素再增加了一个css样式体现的. v-if.v-show.动态组件 都可以实现过渡效果. 如果没有给transition定义name,vue中默认是.v-enter..v-leave-to. <!DOCTYPE html> <html lang=&

  • 详解Vue中的MVVM原理和实现方法

    下面由我阿巴阿巴的详细走一遍Vue中MVVM原理的实现,这篇文章大家可以学习到: 1.Vue数据双向绑定核心代码模块以及实现原理 2.订阅者-发布者模式是如何做到让数据驱动视图.视图驱动数据再驱动视图 3.如何对元素节点上的指令进行解析并且关联订阅者实现视图更新 一.思路整理 实现的流程图: 我们要实现一个类MVVM简单版本的Vue框架,就需要实现一下几点: 1.实现一个数据监听Observer,对数据对象的所有属性进行监听,数据发生变化可以获取到最新值通知订阅者. 2.实现一个解析器Compi

  • Vue中CSS动画原理的实现

    下面这段代码,是点击按钮实现hello world显示与隐藏 <div id="root"> <div v-if="show">hello world</div> <button @click="handleClick">按钮</button> </div> let vm = new Vue({ el: '#root', data: { show:true }, method

  • vue双向数据绑定原理探究(附demo)

    昨天被导师叫去研究了一下vue的双向数据绑定原理...本来以为原理的东西都非常高深,没想到vue的双向绑定真的很好理解啊...自己动手写了一个. 传送门 双向绑定的思想 双向数据绑定的思想就是数据层与UI层的同步,数据再两者之间的任一者发生变化时都会同步更新到另一者. 双向绑定的一些方法 目前,前端实现数据双向数据绑定的方法大致有以下三种: 1.发布者-订阅者模式(backbone.js) 思路:使用自定义的data属性在HTML代码中指明绑定.所有绑定起来的JavaScript对象以及DOM元

  • 详解vue 中 scoped 样式作用域的规则

    哈喽!大家好!我是木瓜太香,今天我们来聊一个 vue 的样式作用域的问题,通常我们开发项目的时候是要在 style 上加上 scoped 来起到规定组件作用域的效果的,所以了解他们的规则也是很有必要的,可以让你更清晰的了解你的项目样式是怎么运作的. 先来说说实现方式 vue中的样式作用域是通过属性选择器来实现的,例如同样一个类名,我们是通过 .类名[属性名] 来做区分的,我们这里主要是要搞清楚这里的属性名是怎么分配的. 样式作用域规则 接下来我们分情况来说一下样式作用域: 对于有样式作用域的组件

  • vue中的双向数据绑定原理与常见操作技巧详解

    本文实例讲述了vue中的双向数据绑定原理与常见操作技巧.分享给大家供大家参考,具体如下: 什么是双向数据绑定? vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化.这也是算是vue的精髓之处了.值得注意的是,我们所说的数据双向绑定,一定是对于UI控件来说的,非UI控件不会涉及到数据双向绑定.单向数据绑定是使用状态管理工具的前提,如果我们使用vuex,那么数据流也是单向的,这时就会和双向数据绑定有冲突,我们可以这么解决.

  • Vue中的scoped实现原理及穿透方法

    何为scoped? 在vue文件中的style标签上,有一个特殊的属性:scoped.当一个style标签拥有scoped属性时,它的CSS样式就只能作用于当前的组件,也就是说,该样式只能适用于当前组件元素.通过该属性,可以使得组件之间的样式不互相污染.如果一个项目中的所有style标签全部加上了scoped,相当于实现了样式的模块化. scoped的实现原理 vue中的scoped属性的效果主要通过PostCSS转译实现,如下是转译前的vue代码: <style scoped> .examp

随机推荐