稍微学一下Vue的数据响应式(Vue2及Vue3区别)

什么是数据响应式

从一开始使用 Vue 时,对于之前的 jq 开发而言,一个很大的区别就是基本不用手动操作 dom,data 中声明的数据状态改变后会自动重新渲染相关的 dom。
换句话说就是 Vue 自己知道哪个数据状态发生了变化及哪里有用到这个数据需要随之修改。

因此实现数据响应式有两个重点问题:

  1. 如何知道数据发生了变化?
  2. 如何知道数据变化后哪里需要修改?

对于第一个问题,如何知道数据发生了变化,Vue3 之前使用了 ES5 的一个 API Object.defineProperty Vue3 中使用了 ES6 的 Proxy,都是对需要侦测的数据进行 变化侦测 ,添加 getter 和 setter ,这样就可以知道数据何时被读取和修改。

第二个问题,如何知道数据变化后哪里需要修改,Vue 对于每个数据都收集了与之相关的 依赖 ,这里的依赖其实就是一个对象,保存有该数据的旧值及数据变化后需要执行的函数。每个响应式的数据变化时会遍历通知其对应的每个依赖,依赖收到通知后会判断一下新旧值有没有发生变化,如果变化则执行回调函数响应数据变化(比如修改 dom)。

下面详细分别介绍 Vue2 及 Vue3 的数据变化侦测及依赖收集。

Vue2

变化侦测

Object 的变化侦测

转化响应式数据需要将 Vue 实例上 data 属性中定义的数据通过递归将所有属性都转化为 getter/setter 的形式,Vue 中定义了一个 Observer 类来做这个事情。

function def(obj, key, val, enumerable) {
 Object.defineProperty(obj, key, {
  value: val,
  enumerable: !!enumerable,
  writable: true,
  configurable: true
 })
}

class Observer {
 constructor(value) {
  this.value = value;
  def(value, '__ob__', this);
  if (!Array.isArray(value)) {
   this.walk(value);
  }
 }
 walk(obj) {
  for (const [key, value] of Object.entries(obj)) {
   defineReactive(obj, key, value);
  }
 }
}

直接将一个对象传入 new Observer() 后就对每项属性都调用 defineReactive 函数添加变化侦测,下面定义这个函数:

function defineReactive(data, key, val) {
 let childOb = observe(val);
 Object.defineProperty(data, key, {
  enumerable: true,
  configurable: true,
  get: function () {
   // 读取 data[key] 时触发
   console.log('getter', val);
   return val;
  },
  set: function (newVal) {
   // 修改 data[key] 时触发
   console.log('setter', newVal);
   if (val === newVal) {
    return;
   }
   val = newVal;
  }
 })
}

function observe(value, asRootData) {
 if (typeof val !== 'object') {
  return;
 }
 let ob;
 if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
  ob = value.__ob__;
 } else {
  ob = new Observer(val);
 }
 return ob;
}

函数中判断如果是对象则递归调用 Observer 来实现所有属性的变化侦测,根据 __ob__ 属性判断是否已处理过,防止多次重复处理,Observer 处理过后会给数据添加这个属性,下面写一个对象试一下:

const people = {
 name: 'c',
 age: 12,
 parents: {
  dad: 'a',
  mom: 'b'
 },
 mates: ['d', 'e']
};
new Observer(people);
people.name; // getter c
people.age++; // getter 12 setter 13
people.parents.dad; // getter {} getter a

打印 people 可以看到所有属性添加了 getter/setter 方法,读取 name 属性时打印了 people.age++ 修改 age 时打印了 getter 12 setter 13 说明 people 的属性已经被全部成功代理监听。

Array 的变化侦测

可以看到前面 Observer 中仅对 Object 类型个数据做了处理,为每个属性添加了 getter/setter,处理后如果属性值中有数组,通过 属性名 + 索引 的方式(如:this.people.mates[0])获取也是会触发 getter 的。但是如果通过数组原型方法修改数组的值,如 this.people.mates.push('f'),这样是无法通过 setter 侦测到的,因此,在 Observer 中需要对 Object 和 Array 分别进行单独的处理。

为侦测到数组原型方法的操作,Vue 中是通过创建一个拦截器 arrayMethods,并将拦截器重新挂载到数组的原型对象上。

下面是拦截器的定义:

const ArrayProto = Array.prototype;
const arrayMethods = Object.create(ArrayProto);
;[
 'push',
 'pop',
 'shift',
 'unshift',
 'splice',
 'sort',
 'reverse'
].forEach(method => {
 const original = ArrayProto[method];
 Object.defineProperty(arrayMethods, method, {
  value: function mutator(...args) {
   console.log('mutator:', this, args);
   return original.apply(this, args);
  },
  enumerable: false,
  writable: true,
  configurable: true
 })
})

这里 arrayMethods 继承了 Array 的原型对象 Array.prototype,并给它添加了 push pop shift unshift splice sort reverse 这些方法,因为数组是可以通过这些方法进行修改的。添加的 push pop... 方法中重新调用 original(缓存的数组原型方法),这样就不会影响数组本身的操作。

最后给 Observer 中添加数组的修改:直接将拦截器挂载到数组原型对象上

class Observer {
 constructor(value) {
  this.value = value;
  def(value, '__ob__', this);
  if (Array.isArray(value)) {
   value.__proto__ = arrayMethods;
  } else {
   this.walk(value);
  }
 }
 walk(obj) {
  for (const [key, value] of Object.entries(obj)) {
   defineReactive(obj, key, value);
  }
 }
}

再来验证一下:

const people = {
 name: 'c',
 age: 12,
 parents: {
  dad: 'a',
  mom: 'b'
 },
 mates: ['d', 'e']
};
new Observer(people);
people.mates[0]; // getter (2) ["d", "e"]
people.mates.push('f'); // mutator: (2) ["d", "e"] ["f"]

现在数组的修改也能被侦测到了。

依赖收集

目前已经可以对 Object 及 Array 数据的变化进行截获,那么开始考虑一开始提到的 Vue 响应式数据的第二个问题:如何知道数据变化后哪里需要修改?

最开始已经说过,Vue 中每个数据都需要收集与之相关的依赖,用来表示该数据变化时需要进行的操作行为。

通过数据的变化侦测我们可以知道数据何时被读取或修改,因此可以在数据读取时收集依赖,修改时通知依赖更新,这样就可以实现数据响应式了。

依赖收集在哪

为每个数据都创建一个收集依赖的对象 dep,对外暴露 depend(收集依赖)、notify(通知依赖更新)的两个方法,内部维护了一个数组用来保存该数据的每项依赖。

对于 Object,可以在 getter 中收集,setter 中通知更新,对 defineReactive 函数修改如下:

function defineReactive(data, key, val) {
 let childOb = observe(val);
 // 处理每个响应式数据时都创建一个对象用来收集依赖
 let dep = new Dep();
 Object.defineProperty(data, key, {
  enumerable: true,
  configurable: true,
  get: function () {
   // 收集依赖
   dep.depend();
   return val;
  },
  set: function (newVal) {
   if (val === newVal) {
    return;
   }
   val = newVal;
   // 通知依赖更新
   dep.notify();
  }
 })
}

上面代码中依赖是收集在一个 Dep 实例对象上的,下面看一下 Dep 这个类。

class Dep {
 constructor() {
  this.subs = [];
 }
 addSub(sub) {
  this.subs.push(sub);
 }
 removeSub(sub) {
  if (this.subs.length) {
   const index = this.subs.indexOf(sub);
   this.subs.splice(index, 1);
  }
 }
 depend() {
  if (window.target) {
   this.addSub(window.target);
  }
 }
 notify() {
  const subs = this.subs.slice();
  for (let i = 0; i < subs.length; i++) {
   subs[i].update();
  }
 }
}

Dep 的每个实例都有一个保存依赖的数组 subs,收集依赖时是从全局的一个变量上获取到并插入 subs,通知依赖时就遍历所有 subs 成员并调用其 update 方法。

Object 的依赖收集和触发都是在 defineProperty 中进行的,因此 Dep 实例定义在 defineReactive 函数中就可以让 getter 和 setter 都拿到。

而对于 Array 来说,依赖可以在 getter 中收集,但触发却是在拦截器中,为了保证 getter 和 拦截器中都能访问到 Dep 实例,Vue 中给 Observer 实例上添加了 dep 属性。

class Observer {
 constructor(value) {
  this.value = value;
  this.dep = new Dep();
  def(value, '__ob__', this);
  if (Array.isArray(value)) {
   value.__proto__ = arrayMethods;
  } else {
   this.walk(value);
  }
 }
 walk(obj) {
  for (const [key, value] of Object.entries(obj)) {
   defineReactive(obj, key, value);
  }
 }
}

Observer 在处理数据响应式时也将自身实例添加到了数据的 __ob__ 属性上,因此在 getter 和拦截器中都能通过响应式数据本身的  __ob__.dep 拿到其对应的依赖。修改 defineReactive 和 拦截器如下:

function defineReactive(data, key, val) {
 let childOb = observe(val);
 let dep = new Dep();
 Object.defineProperty(data, key, {
  enumerable: true,
  configurable: true,
  get: function () {
   dep.depend();
   // 给 Observer 实例上的 dep 属性收集依赖
   if (childOb) {
    childOb.dep.depend();
   }
   return val;
  },
  ...
 })
}

;[
 'push',
 'pop',
 'shift',
 'unshift',
 'splice',
 'sort',
 'reverse'
].forEach(method => {
 const original = ArrayProto[method];
 def(arrayMethods, method, (...args) => {
  const result = original.apply(this, args);
  const ob = this.__ob__;
  ob.dep.notify();
  return result;
 })
})

依赖长什么样

现在已经知道了依赖保存在每个响应式数据对应的 Dep 实例中的 subs 中,通过上面 Dep 的代码可以知道,收集的依赖是一个全局对象,且该对象对外暴露了一个 update 方法,记录了数据变化时需要进行的更新操作(如修改 dom 或 Vue 的 Watch)。

首先这个依赖对象的功能主要有两点:

  1. 需要主动将自己收集到对应响应式数据的 Dep 实例中;
  2. 保存数据变化时要进行的操作并在 update 方法中调用;

其实就是一个中介角色,Vue 中起名为 Watcher。

class Watcher {
 constructor(vm, expOrFn, cb) {
  this.vm = vm;
  // 保存通过表达式获取数据的方法
  this.getter = parsePath(expOrFn);
  this.cb = cb;
  this.value = this.get();
 }
 get() {
  // 将自身 Watcher 实例挂到全局对象上
  window.target = this;
  // 获取表达式对应的数据
  // 会自动触发该数据的 getter
  // getter 中收集依赖时从全局对象上拿到这个 Watcher 实例
  let value = this.getter.call(this.vm, this.vm);
  window.target = undefined;
  return value;
 }
 update() {
  const oldValue = this.value;
  this.value = this.get();
  // 将旧值与新值传递给回调函数
  this.cb.call(this.vm, this.value, oldValue);
 }
}

对于第一点,主动将自己收集到 Dep 实例中,Watcher 中设计的非常巧妙,在 get 中将自身 Watcher 实例挂到全局对象上,然后通过获取数据触发 getter 来实现依赖收集。

第二点实现很简单,只需要将构造函数参数中的回调函数保存并在 update 方法中调用即可。

构造函数中的 parsePath 方法就是从 Vue 实例的 data 上通过表达式获取数据,比如表达式为 "user.name" 则需要解析该字符串然后获取 data.user.name 数据。

总结

  • 数据先通过调用 new Observer() 为每项属性添加变化侦测,并创建一个 Dep 实例用来保存相关依赖。在读取属性值时保存依赖,修改属性值时通知依赖;
  • Dep 实例的 subs 属性为一个数组,保存依赖是向数组中添加,通知依赖时遍历数组一次调用依赖的 update 方法;
  • 依赖是一个 Watcher 实例,保存了数据变化时需要进行的操作,并将实例自身放到全局的一个位置,然后读取数据触发数据的 getter,getter 中从全局指定的位置获取到该 Watcher 实例并收集在 Dep 实例中。

以上就是 Vue2 中的响应式原理,在 Observer 处理完后,外界只需要通过创建 Watcher 传入需要监听的数据及数据变化时的响应回调函数即可。

Vue3

Vue3 中每个功能单独为一个模块,并可以单独打包使用,本文仅简单讨论 Vue3 中与数据响应式相关的 Reactive 模块,了解其内部原理,与 Vue2 相比又有何不同。

因为该模块可以单独使用,先来看一下这个模块的用法示例:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>vue3 demo</title>
</head>
<body>
 <div id="app">
  <div id="count"></div>
  <button id="btn">+1</button>
 </div>
 <script src="./vue3.js"></script>
 <script>
  const countEl = document.querySelector('#count')
  const btnEl = document.querySelector('#btn')
  // 定义响应式数据
  const state = reactive({
   count: 0,
   man: {
    name: 'pan'
   }
  })
  // 定义计算属性
  let double = computed(() => {
   return state.count * 2
  })
  // 回调函数立即执行一次,内部使用到的数据更新时会重新执行回调函数
  effect(() => {
   countEl.innerHTML = `count is ${state.count}, double is ${double.value}, man's name is ${state.man.name}`
  })
  // 修改响应式数据触发更新
  btnEl.addEventListener('click', () => {
   state.count++
  }, false)
 </script>
</body>
</html>

通过示例可以看到实现 Vue3 这个数据响应式需要有 reactive、computed、effect 这几个函数,下面仍然通过从变化侦测及依赖收集两个方面介绍,简单实现这几个函数。

变化侦测

示例中的 reactive 函数是对数据进行响应式化的,因此该函数的功能就类似于 Vue2 中的 defineReactive 函数的 getter/setter 处理,处理后能够对数据的获取及修改操作进行捕获。

const toProxy = new WeakMap()
const toRaw = new WeakMap()

const baseHandler = {
 get(target, key) {
  console.log('Get', target, key)
  const res = Reflect.get(target, key)
  // 递归寻找
  return typeof res == 'object' ? reactive(res) : res
 },
 set(target, key, val) {
  console.log('Set', target, key, val)
  const res = Reflect.set(target, key, val)
  return res
 }
}
function reactive(target) {
 console.log('reactive', target)
 // 查询缓存
 let observed = toProxy.get(target)
 if (observed) {
  return observed
 }
 if (toRaw.get(target)) {
  return target
 }
 observed = new Proxy(target, baseHandler)
 // 设置缓存
 toProxy.set(target, observed)
 toRaw.set(observed, target)
 return observed
}

reactive 中使用 Proxy 对目标进行代理,代理的行为是 baseHander ,然后对目标对象及代理后的对象进行缓存,防止多次代理。

baseHandler 中就是对数据的获取及修改进行拦截,并通过 Reflect 执行 get/set 的原本操作,并在获取值为 Object 时递归进行响应式处理。很简单地就完成了数据的响应式处理。

依赖收集

依赖收集与 Vue2 类似,在 getter 中收集依赖,setter 中触发依赖,修改 baseHandler 如下:

const baseHandler = {
 get(target, key) {
  const res = Reflect.get(target, key)
  // 收集依赖
  track(target, key)
  return typeof res == 'object' ? reactive(res) : res
 },
 set(target, key, val) {
  const info = {
   oldValue: target[key],
   newValue: val
  }
  const res = Reflect.set(target, key, val)
  // 触发更新
  trigger(target, key, info)
  return res
 }
}

track 函数收集依赖,trigger 函数触发依赖更新。

首先需要两个全局变量,用于保存当前待收集的依赖对象的 effectStack 及一个记录所有数据及其对应依赖的表 targetMap 。

const effectStack = []
const targetMap = new WeakMap()

接下来定义这收集依赖及触发依赖更新这两个函数:

function track(target, key) {
 // 从栈中拿到待收集的依赖对象
 let effect = effectStack[effectStack.length - 1]
 if (effect) {
  // 通过 target 及 key 从依赖映射表中拿到对应的依赖列表(Set类型)
  // 首次需要对依赖映射表初始化
  let depsMap = targetMap.get(target)
  if (depsMap === undefined) {
   depsMap = new Map()
   targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (dep === undefined) {
   dep = new Set()
   depsMap.set(key, dep)
  }
  // 若 target.key 对应的依赖列表中不存在该依赖则收集
  if (!dep.has(effect)) {
   dep.add(effect)
  }
 }
}
function trigger(target, key, info) {
 // 依赖映射表中取出 target 相关数据
 const depsMap = targetMap.get(target)
 if (depsMap === undefined) {
  return
 }
 // 普通依赖对象的列表
 const effects = new Set()
 // 计算属性依赖对象的列表
 const computedRunners = new Set()
 if (key) {
  // 取出 key 相关的依赖列表遍历分类存入 effects 及 computedRunners
  let deps = depsMap.get(key)
  deps.forEach(effect => {
   if (effect.computed) {
    computedRunners.add(effect)
   } else {
    effects.add(effect)
   }
  })
 }
 // 遍历执行所有依赖对象
 const run = effect=> effect()
 effects.forEach(run)
 computedRunners.forEach(run)
}

track 及 trigger 的大致代码也很简单,track 是拿到待收集的依赖对象 effect 后收集到 effectStack,trigger 是从 effectStack 拿到对应的依赖列表遍历执行。

到现在就差这个依赖对象了,根据上面 trigger 函数可以知道,这个依赖 effect 首先是个函数可以执行,并且还有自身属性,如 computed 表示其为一个计算属性的依赖,有时会根据该标识进行写特殊处理。

下面开始介绍这个依赖对象是如何产生的:

// 创建依赖对象
function createReactiveEffect(fn, options) {
 const effect = function effect(...args) {
  return run(effect, fn, args)
 }
 effect.computed = options.computed
 effect.lazy = options.lazy
 return effect
}

function run(effect, fn, args) {
 if (!effectStack.includes(effect)) {
  try {
   effectStack.push(effect)
   return fn(...args)
  } finally {
   effectStack.pop()
  }
 }
}

createReactiveEffect 是一个高阶函数,内部创建了一个名为 effect 的函数,函数内部返回的是一个 run 函数,run 函数中将依赖 effect 对象存入全局的待收集依赖栈 effectStack 中,并执行传入的回调函数,该回调函数其实就是一开始示例中 effect 函数传入的修改 Dom 的函数。也就是说依赖对象作为函数直接执行就会添加依赖到全局栈并执行回调函数。

回调函数中如果有读取了响应式数据的话则会触发 proxy 的 get 收集依赖,这时就能从 effectStack 上拿到该依赖对象了。

然后给 effect 增加了 computed lazy 属性后返回。

最后就是对外暴露的 effect 及 computed 函数了:

// 创建依赖对象并判断非计算属性则立即执行
function effect(fn, options = {}) {
 let e = createReactiveEffect(fn, options)
 if (!options.lazy) {
  e()
 }
 return e
}

// computed 内部调用 effect 并添加计算属性相关的 options
function computed(fn) {
 const runner = effect(fn, {
  computed: true,
  lazy: true
 })
 return {
  effect: runner,
  get value() {
   return runner()
  }
 }
}

computed 就不多说了,effect 就是将传入的回调函数传给 createReactiveEffect 创建依赖对象,然后执行依赖对象就会执行回调函数并收集该依赖对象。

总结

  • reactive 将传入的数据对象使用 proxy 包装,通过 proxy 的 get set 拦截数据的获取及修改,与 Vue2 的 defineProperty 一样,在 get 中收集依赖,在 set 中触发依赖;
  • effect 函数接受一个回调函数作为参数,将回调函数包装一下作为依赖对象后执行回调函数,回调函数执行时触发相关数据的 get 后进行依赖收集;

到此 Vue2 及 Vue3 中的数据响应式原理都分析完了。

Vue2 及 Vue3 数据响应式的对比

本次 Vue 对于数据响应式的升级主要在变化侦测部分。

Vue2 中的变化侦测实现对 Object 及 Array 分别进行了不同的处理,Objcet 使用了
Object.defineProperty API ,Array 使用了拦截器对 Array 原型上的能够改变数据的方法进行拦截。虽然也实现了数据的变化侦测,但存在很多局限 ,比如对象新增属性无法被侦测,以及通过数组下边修改数组内容,也因此在 Vue2 中经常会使用到 $set 这个方法对数据修改,以保证依赖更新。

Vue3 中使用了 es6 的 Proxy API 对数据代理,没有像 Vue2 中对原数据进行修改,只是加了代理包装,因此首先性能上会有所改善。其次解决了 Vue2 中变化侦测的局限性,可以不使用 $set 新增的对象属性及通过下标修改数组都能被侦测到。

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

(0)

相关推荐

  • 浅谈Vue 数据响应式原理

    前言 Vue的数据响应主要是依赖了Object.defineProperty(),那么整个过程是怎么样的呢?以我们自己的想法来走Vue的道路,其实也就是以Vue的原理为终点,我们来逆推一下实现过程. 本文代码皆为低配版本,很多地方都不严谨,比如 if(typeof obj === 'object')这是在判断obj是否为为一个对象,虽然obj也有可能是数组等其他类型的数据,但是本文为了简便,就直接这样写来表示判断对象,对于数组使用Array.isArray(). 改造数据 我们先来尝试写一个函数

  • Vue3.0数据响应式原理详解

    基于Vue3.0发布在GitHub上的第一版源码(2019.10.05)整理 预备知识 ES6 Proxy,整个响应式系统的基础. 新的composition-API的基本使用,目前还没有中文文档,可以先通过这个仓库(composition-api-rfc)了解,里面也有对应的在线文档. 先把Vue3.0跑起来 先把vue-next仓库的代码clone下来,安装依赖然后构建一下,vue的package下的dist目录下找到构建的脚本,引入脚本即可. 下面一个简单计数器的DEMO: <!DOCTY

  • 稍微学一下Vue的数据响应式(Vue2及Vue3区别)

    什么是数据响应式 从一开始使用 Vue 时,对于之前的 jq 开发而言,一个很大的区别就是基本不用手动操作 dom,data 中声明的数据状态改变后会自动重新渲染相关的 dom. 换句话说就是 Vue 自己知道哪个数据状态发生了变化及哪里有用到这个数据需要随之修改. 因此实现数据响应式有两个重点问题: 如何知道数据发生了变化? 如何知道数据变化后哪里需要修改? 对于第一个问题,如何知道数据发生了变化,Vue3 之前使用了 ES5 的一个 API Object.defineProperty Vue

  • 详解实现vue的数据响应式原理

    这篇文章主要是给不了解或者没接触过 vue 响应式源码的小伙伴们看的,其主要目的在于能对 vue 的响应式原理有个基本的认识和了解,如果在面试中被问到此类问题,能够知道面试官想让你回答的是什么?「PS:文中如有不对的地方,欢迎小伙伴们指正」 响应式的理解 响应式顾名思义就是数据变化,会引起视图的更新.这篇文章主要分析 vue2.0 中对象和数组响应式原理的实现,依赖收集和视图更新我们留在下一篇文章分析. 在 vue 中,我们所说的响应式数据,一般指的是数组类型和对象类型的数据.vue 内部通过

  • 深入理解Vue的数据响应式

    1. ES语法的getter和setter 在开始了解 Vue 的数据响应式原理前应该先搞清楚 ES语法 中的 getter 和 setter 方法的具体用法. getter和setter 方法是以 get 和 set 关键字来为对象添加虚拟属性的一种方式.这种属性其实并不真实存在,而是以取值函数 getter 和存值函数 setter 来模拟的一种属性.目的是对某个属性设置存值函数和取值函数,拦截该属性的存取行为,以便于对该属性的存取做一些限定处理.如下所示(以下代码来源于 mdn) gett

  • vue.js数据响应式原理解析

    目录 Object.defineProperty() 定义 defineReactive 函数 递归侦测对象的全部属性 流程分析 observe 函数 Observer 类 完善 defineReactive 函数 One More Thing Object.defineProperty() 得力于 Object.defineProperty() 的特性,vue 的数据变化有别于 react 和小程序,是非侵入式的.详细介绍可以看 MDN 文档,这里特别说明几点: get / set 属性是函数

  • vue3.x源码剖析之数据响应式的深入讲解

    目录 前言 什么是数据响应式 数据响应式的大体流程 vue2.x数据响应式和3.x响应式对比 大致流程图 实现依赖收集 代码仓库 结尾 前言 如果错过了秋枫和冬雪,那么春天的樱花一定会盛开吧.最近一直在准备自己的考试,考完试了,终于可以继续研究源码和写文章了,哈哈哈.学过vue的都知道,数据响应式在vue框架中极其重要,写代码也好,面试也罢,数据响应式都是核心的内容.在vue3的官网文档中,作者说如果想让数据更加响应式的话,可以把数据放在reactive里面,官方文档在讲述这里的时候一笔带过,笔

  • Vue data的数据响应式到底是如何实现的

    研究过程 一般形式 data:{ n:0 } :以这样的方式存储数据,vue能够监听其变化吗?显然是不能的. 使用Obj.defineProperty let data1 = {} Object.defineProperty(data1, 'n', { value: 0 }) 为什么要使用defineProperty呢?这不是把一般形式复杂化了吗? 引出主角getter setter. 如果我们想对数据监听进行处理呢?(假设修改的数据必须>=0) let data2 = {} data2._n

  • vue数据响应式原理知识点总结

    vue2.0数据响应式原理 对象 Obect.defineproperty 定义对象的属性mjm defineproperty 其实不是核心的为一个对象做数据双向绑定,而是去给对象做属性标签,设置一系列操作权限,只不过属性里的get和set实现了响应式 var ob = { a: 1, b: 2 } //1-对象 2-属性 3-对于属性的一系列配置 Object.defineProperty(ob, 'a' , { //a对象则是ob的绝对私有属性,,默认都是true writable: fal

  • Vue 数据响应式相关总结

    在说数据响应式之前,我们要解决一个很重要的问题,那就是Vue到底对data做了什么?先从getter和setter说起,我们用那个他们来对虚拟的属性进行读写. getter和setter 有如下代码 let obj0 = { 姓: "高", 名: "圆圆", age: 18 }; // 需求一,得到姓名 let obj1 = { 姓: "高", 名: "圆圆", 姓名() { return this.姓 + this.名; }

  • Vue深入讲解数据响应式原理

    目录 响应式是什么 如何实现数据响应式 实现对象属性拦截 通用的劫持方案 总结 响应式是什么 简而言之就是数据变页面变 如何实现数据响应式 在Javascript里实现数据响应式一般有俩种方案,分别对应着vue2.x 和 vue3.x使用的方式,他们分别是: 对象属性拦截 (vue2.x) Object.defineProperty 对象整体代理 (vue3.x) Proxy 其中对象属性拦截,道理都是相通的 实现对象属性拦截 字面量对象定义 let data = { name:'小兰同学' }

随机推荐