二、vue响应式对象

Object.defineProperty

Object.defineProperty 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象,先来看一下它的语法:

Object.defineProperty(obj, prop, descriptor)
  • obj 是要在其上定义属性的对象;
  • prop 是要定义或修改的属性的名称;
  • descriptor 是将被定义或修改的属性描述符;

get 是一个给属性提供的 getter 方法,当我们访问了该属性的时候会触发 getter 方法;set 是一个给属性提供的 setter 方法,当我们对该属性做修改的时候会触发 setter 方法。

initState

在 Vue 的初始化阶段,_init 方法执行的时候,会执行 initState(vm) 方法,它的定义在 src/core/instance/state.js 中。

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  // 初始化props
  if (opts.props) initProps(vm, opts.props)
  // 初始化方法
  if (opts.methods) initMethods(vm, opts.methods)
  // 初始化data没有跟数据的话就初始一个
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // 初始computed
  if (opts.computed) initComputed(vm, opts.computed)
  // 出书watach
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

initState 方法主要是对 props、methods、data、computed 和 wathcer 等属性做了初始化操作。这里我们重点分析 props 和 data,对于其它属性的初始化我们之后再详细分析。

if (opts.data) {
  initData(vm)
} else {
  observe(vm._data = {}, true /* asRootData */)
}

首先判断 opts.data 是否存在,即 data 选项是否存在,如果存在则调用 initData(vm) 函数初始化 data 选项,否则通过 observe 函数观测一个空的对象,并且 vm._data 引用了该空对象。其中 observe 函数是将 data 转换成响应式数据的核心入口,另外实例对象上的 _data 属性我们在前面的章节中讲解 $data 属性的时候讲到过,$data 属性是一个访问器属性,其代理的值就是 _data。

initProps

// 传入两个参数vue实例和props的参数
function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted转换
  if (!isRoot) {
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    keys.push(key)
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== ‘production‘) {
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      defineReactive(props, key, value, () => {
        if (vm.$parent && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop‘s ` +
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component‘s prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

props 的初始化主要过程,就是遍历定义的 props 配置。遍历的过程主要做两件事情:一个是调用 defineReactive 方法把每个 prop 对应的值变成响应式,可以通过 vm._props.xxx 访问到定义 props 中对应的属性。对于 defineReactive 方法,我们稍后会介绍;另一个是通过 proxy 把 vm._props.xxx 的访问代理到 vm.xxx 上,我们稍后也会介绍

initData

function initData (vm: Component) {
  let data = vm.$options.data
  data = vm._data = typeof data === ‘function‘
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== ‘production‘ && warn(
      ‘data functions should return an object:\n‘ +
      ‘https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function‘,
      vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== ‘production‘) {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== ‘production‘ && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  observe(data, true /* asRootData */)
}

data 的初始化主要过程也是做两件事,一个是对定义 data 函数返回对象的遍历,通过 proxy 把每一个值 vm._data.xxx 都代理到 vm.xxx 上;另一个是调用 observe 方法观测整个 data 的变化,把 data 也变成响应式,可以通过 vm._data.xxx 访问到定义 data 返回函数中对应的属性,observe 我们稍后会介绍。

可以看到,无论是 props 或是 data 的初始化都是把它们变成响应式对象

let data = vm.$options.data // 首先定义 data 变量,它是 vm.$options.data 的引用
data = vm._data = typeof data === ‘function‘
  ? getData(data, vm)
  : data || {}

vm.$options.data 其实最终被处理成了一个函数,且该函数的执行结果才是真正的数据。在上面的代码中我们发现其中依然存在一个使用 typeof 语句判断 data 数据类型的操作,我们知道经过 mergeOptions 函数处理后 data 选项必然是一个函数,那么这里的判断还有必要吗?答案是有,这是因为 beforeCreate 生命周期钩子函数是在 mergeOptions 函数之后 initData 之前被调用的,如果在 beforeCreate 生命周期钩子函数中修改了 vm.$options.data 的值,那么在 initData 函数中对于 vm.$options.data 类型的判断就是必要的了。

如果 vm.$options.data 的类型为函数,则调用 getData 函数获取真正的数据,getData 函数就定义在 initData 函数的下面

// data 选项是一个函数, 参数是 Vue 实例对象
// getData 函数的作用其实就是通过调用 data 函数获取真正的数据对象并返回
export function getData (data: Function , vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  pushTarget()
  try {
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    popTarget()
  }
}

data.call(vm, vm),而且我们注意到 data.call(vm, vm) 被包裹在 try...catch 语句块中,这是为了捕获 data 函数中可能出现的错误。同时如果有错误发生那么则返回一个空对象作为数据对象:return {}

另外我们注意到在 getData 函数的开头调用了 pushTarget() 函数,并且在 finally 语句块中调用了 popTarget(),这么做的目的是什么呢?这么做是为了防止使用 props 数据初始化 data 数据时收集冗余的依赖,等到我们分析 Vue 是如何收集依赖的时候会回头来说明。总之 getData 函数的作用就是:“通过调用 data 选项从而获取数据对象”

我们再回到 initData 函数中:

data = vm._data = getData(data, vm)

当通过 getData 拿到最终的数据对象后,将该对象赋值给 vm._data 属性,同时重写了 data 变量,此时 data 变量已经不是函数了,而是最终的数据对象

紧接着是一个 if 语句块:

// isPlainObject 函数判断变量 data 是不是一个纯对象
if (!isPlainObject(data)) {
  data = {}
  process.env.NODE_ENV !== ‘production‘ && warn(
    ‘data functions should return an object:\n‘ +
    ‘https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function‘,
    vm
  )
}

触发代码:
data 函数返回了一个字符串而不是对象,所以我们需要判断一下 data 函数返回值的类型。
new Vue({
  data () {
    return ‘我就是不返回对象‘
  }
})

接下来:

// proxy data on instance
const keys = Object.keys(data) // 使用 Object.keys 函数获取 data 对象的所有键
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
  const key = keys[i]
  if (process.env.NODE_ENV !== ‘production‘) {
    if (methods && hasOwn(methods, key)) {
      warn(
        `Method "${key}" has already been defined as a data property.`,
        vm
      )
    }
  }
  if (props && hasOwn(props, key)) {
    process.env.NODE_ENV !== ‘production‘ && warn(
      `The data property "${key}" is already declared as a prop. ` +
      `Use prop default value instead.`,
      vm
    )
  } else if (!isReserved(key)) {
    proxy(vm, `_data`, key)
  }
}

这段代码的意思是非生产环境下如果发现在 methods 对象上定义了同样的 key,也就是说 data 数据的 key 与 methods 对象中定义的函数名称相同,那么会打印一个警告,提示开发者:你定义在 methods 对象中的函数名称已经被作为 data 对象中某个数据字段的 key 了,你应该换一个函数名字.

为什么要这么做呢:

const ins = new Vue({
  data: {
    a: 1
  },
  methods: {
    b () {}
  }
})

ins.a // 1
ins.b // function

在这个例子中无论是定义在 data 中的数据对象,还是定义在 methods 对象中的函数,都可以通过实例对象代理访问。所以当 data 数据对象中的 key 与 methods 对象中的 key 冲突时,岂不就会产生覆盖掉的现象,所以为了避免覆盖 Vue 是不允许在 methods 中定义与 data 字段的 key 重名的函数的。而这个工作就是在 while 循环中第一个语句块中的代码去完成的.

第二个 if 语句块:

// 检测props里面是否有很data同名的
if (props && hasOwn(props, key)) {
  process.env.NODE_ENV !== ‘production‘ && warn(
    `The data property "${key}" is already declared as a prop. ` +
    `Use prop default value instead.`,
    vm
  )
  // 判断定义在 data 中的 key 是否是保留键
} else if (!isReserved(key)) {
  proxy(vm, `_data`, key)
}

另外这里有一个优先级的关系:==props优先级 > data优先级 > methods优先级==

!isReserved(key),该条件的意思是判断定义在 data 中的 key 是否是保留键

isReserved 函数通过判断一个字符串的第一个字符是不是 $ 或 _ 来决定其是否是保留的,Vue 是不会代理那些键名以 $ 或 _ 开头的字段的,因为 Vue 自身的属性和方法都是以 $ 或 _ 开头的,所以这么做是为了避免与 Vue 自身的属性和方法相冲突。

如果 key 既不是以 $ 开头,又不是以 _ 开头,那么将执行 proxy 函数,实现实例对象的代理访问:

proxy

下代理,代理的作用是把 props 和 data 上的属性代理到 vm 实例上,这也就是为什么比如我们定义了如下 props,却可以通过 vm 实例访问到它。

let comP = {
  props: {
    msg: ‘hello‘
  },
  methods: {
    say() {
      console.log(this.msg)
    }
  }
}

say 函数中通过 this.msg 访问到我们定义在 props 中的 msg,这个过程发生在 proxy 阶段

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

例子:

class Vue {
    constructor(data) {
        this.data = data;
        this.initData();
    }
    initData(){
        this.proxy(this, `data`, ‘a‘);
    }
    proxy(target, sourceKey, key){
        const sharedPropertyDefinition = {
            enumerable: true,
            configurable: true,
            get: ()=>{},
            set: ()=>{}
        }
        sharedPropertyDefinition.get = function proxyGetter () {
        return this[sourceKey][key]
        }
        sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val
        }
        Object.defineProperty(target, key, sharedPropertyDefinition)
    }
}

const vue = new Vue({
    a: ‘1‘
})

console.log(vue.data.a, vue.a)

proxy 方法的实现很简单,通过 Object.defineProperty 把 target[sourceKey][key] 的读写变成了对 target[key] 的读写。所以对于 props 而言,对 vm._props.xxx 的读写变成了 vm.xxx 的读写,而对于 vm._props.xxx 我们可以访问到定义在 props 中的属性,所以我们就可以通过 vm.xxx 访问到定义在 props 中的 xxx 属性了。同理,对于 data 而言,对 vm._data.xxxx 的读写变成了对 vm.xxxx 的读写,而对于 vm._data.xxxx 我们可以访问到定义在 data 函数返回对象中的属性,所以我们就可以通过 vm.xxxx 访问到定义在 data 函数返回对象中的 xxxx 属性了

最后经过一系列的处理,initData 函数来到了最后一句代码:

// observe data
observe(data, true /* asRootData */)

调用 observe 函数将 data 数据对象转换成响应式的,可以说这句代码才是响应系统的开始,不过在讲解 observe 函数之前我们有必要总结一下 initData 函数所做的事情,通过前面的分析可知 initData 函数主要完成如下工作:

  • 根据 vm.$options.data 选项获取真正想要的数据(注意:此时 vm.$options.data 是函数)
  • 校验得到的数据是否是一个纯对象
  • 检查数据对象 data 上的键是否与 props 对象上的键冲突
  • 检查 methods 对象上的键是否与 data 对象上的键冲突
  • 在 Vue 实例对象上添加代理访问数据对象的同名属性
  • 最后调用 observe 函数开启响应式之路

observe

observe 的功能就是用来监测数据的变化,它的定义在 src/core/observer/index.js 中:

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
 // 第一个参数是要观测的数据,第二个参数是一个布尔值,代表将要被观测的数据是否是根级数据
export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 观测的数据不是一个对象或者是 VNode 实例,则直接 return
  if (!isObject(value) || value instanceof VNode) {
    return
  }

  let ob: Observer | void
  // 如果有__ob__的就直接返回,优化性能
  //if 分支的判断条件,首先使用 hasOwn 函数检测数据对象 value 自身是否含有 __ob__ //属性,并且 __ob__ 属性应该是 Observer 的实例。如果为真则直接将数据对象自身的 __ob__ //属性的值作为 ob 的值:ob = value.__ob__。那么 __ob__ //是什么呢?其实当一个数据对象被观测之后将会在该对象上定义 __ob__ 属性,所以 if //分支的作用是用来避免重复观测一个数据对象
  if (hasOwn(value, ‘__ob__‘) && value.__ob__ instanceof Observer) {
    ob = value.__ob__

  }
  // 如果数据对象上没有定义 __ob__ 属性,那么说明该对象没有被观测过
  else if (
    shouldObserve &&
    // 2、来判断是否是服务端渲染
    !isServerRendering() &&
    // 3、只有当数据对象是数组或纯对象
    (Array.isArray(value) || isPlainObject(value)) &&
    // 4、要被观测的数据对象必须是可扩展的
    // 不可扩展:Object.preventExtensions()、Object.freeze() 以及 Object.seal()
    Object.isExtensible(value) &&
    // 5、Vue 实例对象拥有 _isVue 属性,所以这个条件用来避免 Vue 实例对象被观测
    !value._isVue
  ) {
    // 执行 ob = new Observer(value) 对数据对象进行观测
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

1、shouldObserve

shouldObserve 变量也定义在 core/observer/index.js 文件内,如下:

/**
 * In some cases we may want to disable observation inside a component‘s
 * update computation.
 */
export let shouldObserve: boolean = true

export function toggleObserving (value: boolean) {
  shouldObserve = value
}

该变量的初始值为 true,在 shouldObserve 变量的下面定义了 toggleObserving 函数,该函数接收一个布尔值参数,用来切换 shouldObserve 变量的真假值,我们可以把 shouldObserve 想象成一个开关,为 true 时说明打开了开关,此时可以对数据进行观测,为 false 时可以理解为关闭了开关,此时数据对象将不会被观测

满足上面的5个条件:接下来我们来看一下 Observer 的作用。

Observer

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object‘s property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */
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
    // 为每一个value设置一个__ob__
    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.
   * 遍历obj的key,去添加get和set变成响应式对象
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(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])
    }
  }
}
简化后的代码:
export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    // 省略...
  }

  walk (obj: Object) {
    // 省略...
  }

  observeArray (items: Array<any>) {
    // 省略...
  }
}

Observer 类的实例对象将拥有三个实例属性,分别是 value、dep 和 vmCount 以及两个实例方法 walk 和 observeArray。Observer 类的构造函数接收一个参数,即数据对象。

constructor 方法的全部代码
constructor (value: any) {
  this.value = value
  this.dep = new Dep() // 这个“筐”并不属于某一个字段,后面我们会发现,这个筐是属于某一个对象或数组的
  this.vmCount = 0
  def(value, ‘__ob__‘, this) // 初始化完成三个实例属性之后,使用 def 函数,为数据对象定义了一个 __ob__ 属性,这个属性的值就是当前 Observer 实例对象
  if (Array.isArray(value)) {
    const augment = hasProto
      ? protoAugment
      : copyAugment
    augment(value, arrayMethods, arrayKeys)
    this.observeArray(value)
  } else {
    this.walk(value)
  }
}
def
/**
 * Define a property.
 def 函数其实就是 Object.defineProperty 函数的简单封装
 */
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,// 那是false,定义不可枚举的属性  在walk中循环的时候,不会取到那个属性
    writable: true,
    configurable: true
  })
}

例子:

const data = {
  a: 1
}

那么经过 def 函数处理之后,data 对象应该变成如下这个样子:

const data = {
  a: 1,
  // __ob__ 是不可枚举的属性
  __ob__: {
    value: data, // value 属性指向 data 数据对象本身,这是一个循环引用
    dep: dep实例对象, // new Dep()
    vmCount: 0
  }
}

回到 Observer 的构造函数,接下来会对 value 做判断,对于数组会调用 observeArray 方法,否则对纯对象调用 walk 方法。可以看到 observeArray 是遍历数组再次调用 observe 方法,而 walk 方法是遍历对象的 key 调用 defineReactive 方法,那么我们来看一下这个方法是做什么的。

defineReactive

defineReactive 的功能就是定义一个响应式对象,给对象动态添加 getter 和 setter,它的定义在 src/core/observer/index.js 中:

==defineReactive 函数的核心就是 将数据对象的数据属性转换为访问器属性==

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
 // 这个 dep 常量所引用的 Dep 实例对象才与我们前面讲过的“筐”的作用相同
 // 即 每一个数据字段都通过闭包引用着属于自己的 dep 常量
 // 每次调用 defineReactive 定义访问器属性时,该属性的 setter/getter 都闭包引用了一个属于自己的“筐
  const dep = new Dep()
  // 不可配置的直接返回
  // 获取该字段可能已有的属性描述对象
  const property = Object.getOwnPropertyDescriptor(obj, key)

  // 判断该字段是否是可配置的
  // 一个不可配置的属性是不能使用也没必要使用 Object.defineProperty 改变其属性定义的。
  if (property && property.configurable === false) {
    return
  }

  // 保存了来自 property 对象的 get 和 set
  // 避免原有的 set 和 get 方法被覆盖
  const getter = property && property.get
  const setter = property && property.set

  // 下面会特殊说明
  if ((!getter || setter) && arguments.length === 2) {
    // 获取到了对象属性的值 val,但是 val 本身有可能也是一个对象
    val = obj[key]

  }
  // 如果是对象继续调用 observe(val) 函数观测该对象从而深度观测数据对象
  // walk 函数中调用 defineReactive 函数时没有传递 shallow 参数,所以该参数是 undefined
  // 默认就是深度观测
  let childOb = !shallow && 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)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    //  通过观察者
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== ‘production‘ && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}
被观测后的数据对象的样子

假设我们有如下数据对象:

const data = {
  a: {
    b: 1
  }
}

observe(data)

数据对象 data 拥有一个叫做 a 的属性,且属性 a 的值是另外一个对象,该对象拥有一个叫做 b 的属性。那么经过 observe 处理之后, data 和 data.a 这两个对象都被定义了 ob 属性,并且访问器属性 a 和 b 的 setter/getter 都通过闭包引用着属于自己的 Dep 实例对象和 childOb 对象:

const data = {
  // 属性 a 通过 setter/getter 通过闭包引用着 dep 和 childOb
  a: {
    // 属性 b 通过 setter/getter 通过闭包引用着 dep 和 childOb
    b: 1
    __ob__: {a, dep, vmCount}
  }
  __ob__: {data, dep, vmCount}
}

defineReactive 函数最开始初始化 Dep 对象的实例,接着拿到 obj 的属性描述符,然后对子对象递归调用 observe 方法,这样就保证了无论 obj 的结构多复杂,它的所有子属性也能变成响应式的对象,这样我们访问或修改 obj 中一个嵌套较深的属性,也能触发 getter 和 setter。最后利用 Object.defineProperty 去给 obj 的属性 key 添加 getter 和 setter。

核心就是利用 Object.defineProperty 给数据添加了 getter 和 setter,目的就是为了在我们访问数据以及写数据的时候能自动执行一些逻辑:getter 做的事情是依赖收集,setter 做的事情是派发更新

原文地址:https://www.cnblogs.com/chenjinxinlove/p/10037359.html

时间: 2024-10-21 17:02:33

二、vue响应式对象的相关文章

读Vue源码二 (响应式对象)

vue在init的时候会执行observer方法,如果value是对象就直接返回,如果对象上没有定义过_ob_这个属性,就 new Observer实例 export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void if (hasOwn(v

Vue.set 向响应式对象中添加响应式属性,及设置数组元素触发视图更新

一.为什么需要使用Vue.set? vue中不能检测到数组和对象的两种变化: 1.数组长度的变化 vm.arr.length = 4 2.数组通过索引值修改内容 vm.arr[1] = ‘aa’ Vue.$set(target,key,value):可以动态的给数组.对象添加和修改数据,并更新视图中数据的显示. vue在构造函数new Vue()时,就通过Object.defineProperty中的getter和setter 这两个方法,完成了对数据的绑定.所以直接通过vm.arr[1] =

Vue响应式原理

前面的话 Vue最显著的特性之一便是不太引人注意的响应式系统(reactivity system).模型层(model)只是普通JS对象,修改它则更新视图(view).这会让状态管理变得非常简单且直观,不过理解它的工作原理以避免一些常见的问题也是很重要的本文将详细介绍Vue响应式系统的底层细节 追踪变化 把一个普通JS对象传给Vue实例的data选项,Vue将遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter.Object.defi

深度解析 Vue 响应式原理

深度解析 Vue 响应式原理 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还是进阶,亦或是源码解读,你都能在本图谱中得到满意的答案,希望这个面试图谱能够帮助到大家更好的准备面试. Vue 初始化 在 Vue 的初始化中,会先对 props 和 data 进行初始化 Vue.prototype._init = function(options?: Object) { // ...

浅谈Vue响应式(数组变异方法)

很多初使用Vue的同学会发现,在改变数组的值的时候,值确实是改变了,但是视图却无动于衷,果然是因为数组太高冷了吗? 查看官方文档才发现,不是女神太高冷,而是你没用对方法. 看来想让女神自己动,关键得用对方法.虽然在官方文档中已经给出了方法,但是在下实在好奇的紧,想要解锁更多姿势的话,那就必须先要深入女神的心,于是乎才有了去探索Vue响应式原理的想法.(如果你愿意一层一层地剥开我的心.你会发现,你会讶异-- 沉迷于鬼哭狼嚎 无法自拔QAQ). 前排提示,Vue的响应式原理主要是使用了ES5的Obj

深入探讨vue响应式原理

现在是时候深入一下了!Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是普通的 JavaScript 对象.而当你修改它们时,视图会进行更新.这使得状态管理非常简单直接,不过理解其工作原理同样重要,这样你可以避开一些常见的问题.在这个章节,我们将研究一下 Vue 响应式系统的底层的细节. 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 get

深入Vue响应式原理

深入Vue.js响应式原理 一.创建一个Vue应用 new Vue({ data() { return { name: 'yjh', }; }, router, store, render: h => h(App), }).$mount('#app'); 二.实例化一个Vue应用到底发生了什么? this._init() callHook(vm, 'beforeCreate') observe(vm._data) vm._data = vm.$options.data() proxy(vm, _

vue响应式原理解析

# Vue响应式原理解析 首先定义了四个核心的js文件 - 1. observer.js 观察者函数,用来设置data的get和set函数,并且把watcher存放在dep中 - 2. watcher.js 监听者函数,用来设置dep.target开启依赖收集的条件,和触发视图的更新函数 - 3. compile.js 编译者函数,用来编译模版和实例化 watcher 函数 - 4. index.js 入口文件 注意dep函数就是一个壳子,用来存放watcher和触发watcher更新的 首先从

vue 响应式原理

Vue 采用声明式编程替代过去的类 Jquery 的命令式编程,并且能够侦测数据的变化,更新视图.这使得我们可以只关注数据本身,而不用手动处理数据到视图的渲染,避免了繁琐的 DOM 操作,提高了开发效率.不过理解其工作原理同样重要,这样可以回避一些常见的问题,下面我们来介绍一下 Vue 是如何侦测数据并响应视图的. Object.defineProperty Vue 数据响应核心就是使用了 Object.defineProperty 方法( IE9 + ) . var obj = {}; Obj