_init() 初始化函数

方法被调用,主要做对初始化操作 以及 对传入 props、methods、data、computedwatch 做处理,在 initState 方法内转换为可观察,beforeCreate、created 钩子函数被触发,组件未挂载

initProps

主要是对 props 进行逐个遍历,验证 value 数据类型,给 props 设置响应式 设置访问代理,不会进行递归遍历。

  1. function initProps (vm, propsOptions) {
  2. const propsData = vm.$options.propsData || {}
  3. const props = vm._props = {}
  4. // ... 省略
  5. for (const key in propsOptions) {
  6. const value = validateProp(key, propsOptions, propsData, vm)
  7. // ... 省略
  8. defineReactive(props, key, value)
  9. // 转接访问,当访问 vm.key 属性,转到访问 vm._props.key 属性
  10. proxy(vm, `_props`, key)
  11. }
  12. }
  • props 数据从何来?

数据是直接从 父组件上传过来的,没有进行拷贝等处理,原样传过来。

  • props 怎么传过来的

模版编译成渲染函数,而当渲染函数执行时作用域指向的是 父组件,变量 数据也就从 父的作用域取包括 子组件 也是,所以 props 接收的数据是直接 引用父组件的,例如下面渲染函数:

  1. // this 指向父组件,父组件有 treeData 数据
  2. with(this) {
  3. return createElement('ul', {
  4. attrs: {
  5. "id": "app"
  6. }
  7. }, [createElement('item', { // item 是组件模版 (<item class="item" :model="treeData"></item>)
  8. staticClass: "item",
  9. attrs: {
  10. "model": treeData // 直接引用父组件的
  11. }
  12. })], 1)
  13. }
  • props 怎么读取?

子组件 初始化时,也会调用 initProps 方法,逐个把数据复制到 当前 子组件实例上(vm._props) 并 转换为 响应式 和 设置访问代理,当访问数据时是从复制过来(vm._props)的数据进行访问,然而当你想修改 props 时是不会影响 父组件 数据变更的,并会给你发出警告。

  • props 怎么更新?

子组件 渲染函数执行时,需要从 父组件 读取数据此时,就会触发 父组件数据的 依赖收集(get)把 子组件 的 watcher 收集到 自己的收集器(dep)中,当 父组件 数据发生变更,则通知自己所有 依赖,重新执行 子组件 渲染函数,重新读取父组件数据。

initMethods

主要对methods 函数遍历 挂载到当前实例(vm),并 使用bind函数,将 this 绑定到 实例(vm),另外会判断跟 props 有重名会 警告。

  1. function initMethods (vm, methods) {
  2. for (const key in methods) {
  3. vm[key] = bind(methods[key], vm)
  4. }
  5. }


initData

主要对 data 数据通过 observe 函数绑定 观察者 保存到 __ ob__ 属性,并设置 访问代理(proxy),另外会判断跟 props methods 函数 是否重名 并且 不能以 $ 开头,否则发出 警告。

  1. function initData (vm) {
  2. let data = vm.$options.data
  3. // 得到 data 数据
  4. data = vm._data = typeof data === 'function'
  5. ? getData(data, vm)
  6. : data || {}
  7. let i = keys.length
  8. // 遍历 data 中的数据
  9. while (i--) {
  10. const key = keys[i]
  11. proxy(vm, `_data`, key)
  12. }
  13. observe(data, true)
  14. }
  • proxy(vm, _data, key) 代理作用?

传入的数据(data)实际被保存到 _data 属性,遍历逐个 设置(proxy) ,当 vm.name 访问属性时,此时 触发 get ,则从 _data 找数据返回,更新属性同理 触发 set,代码如下:

  1. function proxy (target, sourceKey, key) {
  2. Object.defineProperty(target, key, {
  3. enumerable: true,
  4. configurable: true,
  5. function proxySetter (val) {
  6. this[sourceKey][key] = val;
  7. },
  8. function proxyGetter () {
  9. return this[sourceKey][key]
  10. }
  11. });
  12. }
  13. proxy(vm, `_data`, key)

initComputed

初始化计算属性,遍历为个属性创建一个 new Watcher 传入 并 计算属性的值(fn)或者 noop 空的函数 以计算属性的 key,保存到 保存到 vm._computedWatchers 对象内,接着为每个 计算属性 通过 Object.defineProperty 函数,设置 getter/getter 响应式,也会有判断重名。

  1. function initComputed (vm, computed) {
  2. // 存放 wathcer
  3. const watchers = vm._computedWatchers = Object.create(null)
  4. // 遍历计算属性
  5. for (const key in computed) {
  6. const userDef = computed[key]
  7. // 创建 watcher
  8. const getter = typeof userDef === 'function' ? userDef : userDef.get
  9. watchers[key] = new Watcher(vm, getter || noop, noop, { lazy: true })
  10. // 设置响应式
  11. const propertyDefinition = {
  12. enumerable: true,
  13. configurable: true,
  14. get: noop,
  15. set: noop
  16. }
  17. if (typeof userDef === 'function') {
  18. propertyDefinition.get = createComputedGetter(key)
  19. } else {
  20. propertyDefinition.get = userDef.get
  21. ? (userDef.cache !== false ? createComputedGetter(key) : userDef.get)
  22. : noop
  23. }
  24. Object.defineProperty(vm, key, propertyDefinition)
  25. }
  26. }
  • 创建 watcher 作用是什么?

用来在 读取 data 数据时 收集依赖(watcher)
设想:有 data.name 数据,一个计算属性 getName ,计算属性的方法内 读取 data.name,此时 触发 name 的getter 进行收集依赖,其实就是把 计算属性 watcher 收集起来,当 data.name 发生变更时 会通知到这个 watcher。

  • computed(计算属性) 如何控制缓存?

通过【脏数据标志位 dirty】,dirty 是 watcher 的一个属性,当 new Watcher 时 传入 lazy:true 赋值给 dirty ,通过 dirty 控制是否需要重新计算结果,当 计算属性的依赖更新时会 设置为 true 表示需要重新计算计算属性 再被读取 返回计算好的结果。
dirty 为 true 时,读取 computed 会重新计算
dirty false 时,读取 computed 会使用缓存

  • 依赖 data 变化,computed(计算属性) 如何更新?

场景:
A 页面 引用两个 计算属性 computed getName,依赖 data.name

  1. 当 data.name 变更时,通知 computed getName 对应的 watcher 把 【标志位 dirty】变在 true
  2. 接着 通知 A 页面 watcher 进行更新渲染,接着会重新读取 computed getName,然后执行 getName 得到新的值保存起来

为何能通知 A 页面的 watcher?
点击查看【processon】

initWatch

逐个遍历传入 watch 为每个属性创建 new Watcher ,在创建的同时会对 key 进行解析,拿 key 从当前实例 查找 data,找到 则会 触发 data - getter 依赖收集 并把当前 watch - Watcher 收集起来,当 data 更新则会通知 Watcher 执行,执行完结后 执行 watch 定义的回调,伪代码如下:

  1. function initWatch (vm, watch) {
  2. for (const key in watch) {
  3. // 拿到 watch 回调方法
  4. const handler = watch[key]
  5. // handler
  6. options = handler
  7. options.user = true
  8. const watcher = new Watcher(vm, key, handler, options)
  9. if (options.immediate) {
  10. // watcher.value 在 new 时已经取到值了
  11. cb.call(vm, watcher.value)
  12. }
  13. }
  14. }
  • 设置 immediate 参数,会如何工作?

设置 immediate 会在初始化时就调用一次你设置的 watch 回调,并传入初始化取到的值,否则只会在 监听的 data 更新时才会触发

  • 设置 deep 参数,会如何工作?

设置 deep 时,当监听的 data 属性值是个对象,会递归把所以属性都读取一遍,所以 data 属性嵌套得多深都会收集到 watch - Watcher 的依赖,嵌套的属性发生变更也会触发 watch 回调。

标记位

vm.hasHookEvent 标记 是否有事件钩子,在判断的时候就不用调用 哈希表的方法 _来判断是否 有,以减少不必要开销
vm._isVue 标记 防止 vm 实例自身被观察

工具函数

原生 bind 方法实现

  1. function polyfillBind (fn, ctx) {
  2. function boundFn (a) {
  3. var args = arguments.length;
  4. return args ? args > 1 ? fn.apply(ctx, arguments)
  5. : fn.call(ctx, a)
  6. : fn.call(ctx)
  7. }
  8. boundFn._length = fn.length;
  9. return boundFn
  10. }

属性是否以 $ 开头

  1. function isReserved (str) {
  2. const c = (str + '').charCodeAt(0)
  3. return c === 0x24 || c === 0x5F
  4. }

解析嵌套对象属性 Key (obj.user.name)

  1. const unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/
  2. const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\\d]`)
  3. function parsePath (path, obj){
  4. if (bailRE.test(path)) {
  5. return
  6. }
  7. const segments = path.split('.')
  8. for (let i = 0; i < segments.length; i++) {
  9. if (!obj) return
  10. obj = obj[segments[i]]
  11. }
  12. return obj
  13. }

流程

点击查看【processon】