汪图南
  • RAG

    • RAG
  • 快速入门
  • 高级技巧
前端面试之道
  • 打包工具

    • Webpack
    • Rollup
  • TypeScript

    • TypeScript基础
    • TypeScript类型挑战
  • CSS预编译器

    • SASS
  • 自动化测试

    • Vue应用测试
  • Vue2.0源码分析
  • Vue3.0源码分析
  • 数据结构和算法(基础)
  • LeetCode(刷题)
  • JavaScript书籍

    • 你不知道的JavaScript(上)
    • 你不知道的JavaScript(中下)
    • JavaScript数据结构和算法
    • JavaScript设计模式与开发实践
    • 深入理解ES6
  • Git书籍

    • 精通Git
Github
  • RAG

    • RAG
  • 快速入门
  • 高级技巧
前端面试之道
  • 打包工具

    • Webpack
    • Rollup
  • TypeScript

    • TypeScript基础
    • TypeScript类型挑战
  • CSS预编译器

    • SASS
  • 自动化测试

    • Vue应用测试
  • Vue2.0源码分析
  • Vue3.0源码分析
  • 数据结构和算法(基础)
  • LeetCode(刷题)
  • JavaScript书籍

    • 你不知道的JavaScript(上)
    • 你不知道的JavaScript(中下)
    • JavaScript数据结构和算法
    • JavaScript设计模式与开发实践
    • 深入理解ES6
  • Git书籍

    • 精通Git
Github
  • 介绍

    • 介绍和参考
  • 源码目录设计和架构设计

    • 设计
  • Rollup构建版本

    • Rollup基础知识
    • Vue中的Rollup构建
  • 从入口到构造函数整体流程

    • 整体流程
    • initGlobalAPI流程
    • initMixin流程
    • stateMixin流程
    • eventsMixin流程
    • lifecycleMixin流程
    • renderMixin流程
  • 响应式原理

    • 介绍
    • 前置核心概念
    • props处理
    • methods处理
    • data处理
    • computed处理
    • watch处理
    • 深入响应式原理
    • 依赖收集
    • 派发更新
    • nextTick实现原理
    • 变化侦测注意事项
    • 变化侦测API实现
  • 虚拟DOM和VNode

    • 虚拟DOM
    • VNode介绍
    • Diff算法
  • 组件化

    • 介绍
    • $mount方法
    • render和renderProxy
    • createElement
    • createComponent
    • 合并策略
    • update和patch
    • 组件生命周期
    • 组件注册
  • 编译原理

    • 介绍
    • compileToFunctions
    • parse模板解析
    • optimize优化
    • codegen代码生成
  • 扩展

    • 扩展
    • directive指令
    • filter过滤器
    • event事件处理
    • v-model
    • 插槽
    • Keep-Alive
    • Transition
    • Transition-Group
    • Vue.use插件机制
  • Vue-Router

    • 介绍
    • 路由安装
    • matcher介绍
    • 路由切换
    • 内置组件
    • 路由hooks钩子函数
  • Vuex

    • 介绍
    • Vuex安装
    • Vuex初始化
    • Vuex辅助API
    • Store实例API

data处理

Vue中关于data的处理,根实例和子组件有一点点区别,接下来我们着重分析子组件中关于data的处理过程。

export function initState (vm: Component) {
  const opts = vm.$options
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
}

在以上代码中,首先判断了opts.data,如果值为真则代表是子组件(子组件如果没有显示定义data,则使用默认值),否则代表是根实例。对于根实例而言我们不需要执行initData的过程,只要对vm._data进行observe即可。

接下来,我们详细分析initData的过程,它是定义在src/core/instance/state.js文件中的一个方法:

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 */)
}

虽然initData()方法的代码有点长,但我们详细观察后可以发现,其主要做的就是四件事情:类型判断取值、命名冲突判断、proxy代理以及observe(data)。

然后,我们分别对以上几块进行详细解释:

  • 类型判断取值:对于子组件而言,由于组件可以多次复用,因此函数必须通过工厂函数模式返回一个对象,这样在组件多次复用时就能避免引用类型的问题。
// Child Component
// 抛出错误:data functions should return an object
export default {
  data: {
    msg: 'Hello, Data'
  }
}

对于data是一个函数的情况,我们调用getData方法来取值,getData方法定义如下:

export function getData (data: Function, vm: Component): any {
  pushTarget()
  try {
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    popTarget()
  }
}

代码分析:pushTarget是一个与响应式依赖收集有关的,我们会在后续进行详细说明。getData的取值过程包裹在try/catch中,通过data.call(vm, vm)进行调用返回,如果函数调用出错,则使用handleError进行错误统一处理。

  • 命名冲突判断:由于props和methods有更高的优先级,因此data属性的命名不能和props、methods中的命名冲突,因为无论是props、methods还是data最后都会反映在实例上。另外一种命名冲突,是不能以$或者_开头,因为这样很容易和实例私有方法、属性或对外暴露以$开头的方法、属性冲突。
// 1.与methods命名冲突
// 抛出错误:Method name has already been defined as a data property.
export default {
  data () {
    return {
      name: 'data name'
    }
  },
  methods: {
    name () {
      console.log('methods name')
    }
  }
}

// 2.与props命名冲突
// 抛出错误:The data property name is already declared as a prop.
//          Use prop default value instead.
export default {
  props: ['name'],
  data () {
    return {
      name: 'data name'
    }
  }
}

// 3.不能以$和_开头
export default {
  data () {
    return {
      $data: '$data'
      _isVue: true
    }
  }
}
  • proxy代理:我们在之前已经介绍过proxy代理的作用,也讲过proxy代理_props的例子,这里代理_data跟代理_props是同样的道理。
export default {
  data () {
    return {
      msg: 'Hello, Msg'
    }
  }
}
// 代理前
console.log(this._data.msg)
proxy(vm, '_data', 'msg')
// 代理后
console.log(this.msg)
  • observe(data):observe的作用是把传入值所有的属性(包括嵌套属性)递归的进行响应式defineReactive,我们会在之后的章节中详细介绍observe的实现原理,在initData中我们只要知道observe(data)会把data函数返回对象的所有属性全部变成响应式的即可。

在分析完initData的实现后,我们可以得到initData的整体流程图。

最后更新时间: 2025/5/24 09:00
贡献者: wangtunan
Prev
methods处理
Next
computed处理