超详细.Vue源码解析之new Vue()初始化vue实例

触发原型上的_init函数

_init函数做了什么??

一、给vue实例一个递增的_uid(给一个标识没啥好说的)

二、配置处理及合并 mergeOptions

         Ⅰ、 resolveConstructorOptions函数

        

mergeOptions函数的两个参数分别为resolveConstructorOptions(vm.constructor)的返回值和options配置项所以我们先看看resolveConstructorOptions函数返回了什么以及它做了哪些事情

                

 递归合并父类配置项其中也用到了mergeOptions mergeOptions函数作用是对配置项进行一些规则处理 下面将作介绍

        Ⅱ、 mergeOptions函数

                 处理props normalizeProps()

                        1、将数组形式的props处理为对象形式

['name','msg'] => {
    name: {
    	type:null
    },
    msg: {
   	type:null
    }
}

                        2、将{name:String}形式处理为以下格式

{name:String,age:Number} => {
    name: {
        type:String    
    },
    age: {
        type:Number    		             	            
    }
}

                        3、处理属性名称将转化为驼峰形式 例如: font-size => fontSize

                ② 处理Inject normalizeInject()

                        1、将数组形式的inject处理为对象形式

['msg'] => {
    msg : { // 当前组件取值标识
        from: 'msg' // provider取值标识
    }
}

                        2、将对象形式的inject处理为如下形式

{   
    foo: {
         from: 'bar',
         default: 'foo'
    },
    gg: {
         default: 'gg'   
    }
 }  => {
    foo: {
         from: 'bar',
         default: 'foo'
    },
    gg: {
         from: 'gg'
         default: 'gg'   
    } 
 }
 // from有值不变 无值加上from[key]

                ③ 处理directiives normalizeDriectives

                        1、将函数形式的driectives处理为对象形式(将该函数挂在到bind和update钩子上)

directives: {
   focus: ()=> {
          
   }
 } => {
   focus: {
        bind: ()=>{
                    
        },
        update: ()=> {
                    
        }
   }
 }

                   ④合并extends和mixins中的配置项

          ⑤ 最后将parent和child中的配置项合并

  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  // const defaultStrat = function (parentVal: any, childVal: any): any {
  //   return childVal === undefined
  //     ? parentVal
  //     : childVa

parent和child共有的配置项 选择child中的 很好理解全局配置和new vue局部配置 优先使用局部的 后child中独有的配置项再合进options

三、初始化一些属性、事件

         1、initProxy()

 创建vm的proxy代理 主要用于检测错误的使用 调用warnNonPresent方法做出警示

         2、initLifecycle() 处理组件父子关系 以及一些属性的初始化

 抽象组件不参与vue组件的父子关系组件创建后 要添加到父组件实例的$children中

        3、initEvents()一些事件相关的初始化准备工作

 hasHookEvent 该属性表示父组件是否通过"@hook:"把钩子函数绑定在当前组件上 默认值设为false 

vm.$options. _parentListeners是模版编译的时候,检测到当前编译对象是子组建,那么会给当前对象的$options添加一个_parentListeners属性来存储当前组建所绑定的所有事件

一起看看updateComponentListeners函数做了哪些事

export function updateListeners (
  on: Object, // vm.$options._parentListeners
  oldOn: Object, // {}
  add: Function, 
  remove: Function,
  createOnceHandler: Function, //
  vm: Component
) {
  let name, def, cur, old, event
  for (name in on) {
    def = cur = on[name]
    old = oldOn[name]
    event = normalizeEvent(name)
    /* istanbul ignore if */
    if (__WEEX__ && isPlainObject(def)) {
      cur = def.handler
      event.params = def.params
    }
    if (isUndef(cur)) {
      process.env.NODE_ENV !== 'production' && warn(
        `Invalid handler for event "${event.name}": got ` + String(cur),
        vm
      )
    } else if (isUndef(old)) {
      if (isUndef(cur.fns)) {
        cur = on[name] = createFnInvoker(cur, vm)
      }
      if (isTrue(event.once)) {
        cur = on[name] = createOnceHandler(event.name, cur, event.capture)
      }
      add(event.name, cur, event.capture, event.passive, event.params)
    } else if (cur !== old) {
      old.fns = cur
      on[name] = old
    }
  }
  for (name in oldOn) {
    if (isUndef(on[name])) {
      event = normalizeEvent(name)
      remove(event.name, oldOn[name], event.capture)
    }
  }
}

updateComponentListeners函数主要目的就是更新事件列表加入新的移除旧的

首先遍历_parentListeners  _parentListeners[name]存进def 然后是event = normalizeEvent(name) 

normalizeEvent 根据_parentListeners的属性名name转换为一个对象赋值给event

const normalizeEvent = cached((name: string): {
  name: string,
  once: boolean,
  capture: boolean,
  passive: boolean,
  handler?: Function,
  params?: Array<any>
} => {
  const passive = name.charAt(0) === '&'
  name = passive ? name.slice(1) : name
  const once = name.charAt(0) === '~' // Prefixed last, checked first
  name = once ? name.slice(1) : name
  const capture = name.charAt(0) === '!'
  name = capture ? name.slice(1) : name
  return {
    name,
    once,
    capture,
    passive
  }
})

export function cached<F: Function> (fn: F): F {
  const cache = Object.create(null)
  return (function cachedFn (str: string) {
    const hit = cache[str]
    return hit || (cache[str] = fn(str)) // cache.font-size = fontSize
  }: any)
}

这里cached就是利用闭包进行一个去重 normalizeEvent 是将字符串name(23232)转换为以下形式

{

capture: false

name: "23232"

once: true

passive: false

}

如果以! ~ &为首字符则删去第一个字符 capture once passive 分别代表name是否以! ~ &为首字符开头 就是处理一下vue事件的修饰符例如在模板解析的时候.once会变身为~跑到事件名的前面 后面就是正常的赋值 isUndef判断是否为undefined或null 如果vm.$options._parentListeners.xxx.handler.fns 不存在 cur = on[name] = createFnInvoker(cur, vm) jhcur 接下来 看一看createFnInvoker 函数做了什么事情

export function createFnInvoker (fns: Function | Array<Function>, vm: ?Component): Function {
  function invoker () {
    const fns = invoker.fns
    if (Array.isArray(fns)) {
      const cloned = fns.slice()
      for (let i = 0; i < cloned.length; i++) {
        invokeWithErrorHandling(cloned[i], null, arguments, vm, `v-on handler`)
      }
    } else {
      // return handler return value for single handlers
      return invokeWithErrorHandling(fns, null, arguments, vm, `v-on handler`)
    }
  }
  invoker.fns = fns
  return invoker
}

同一个组件上可能有多个事件此方法接受一个函数或函数组成的数组 此方法返回了一个invoker函数且给invoker函数添加一个属性fns该属性指向传入的函数也就是组件绑定的事件方法或方法组成的数组 那么我们接着看invoker函数,该函数调用时就是通过invokeWithErrorHandling函数返回的函数调用器执行传

export function invokeWithErrorHandling (
  handler: Function,
  context: any,
  args: null | any[],
  vm: any,
  info: string
) {
  let res
  try {
    res = args ? handler.apply(context, args) : handler.call(context)
    if (res && !res._isVue && isPromise(res) && !res._handled) {
      res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
      // issue #9511
      // avoid catch triggering multiple times when nested calls
      res._handled = true
    }
  } catch (e) {
    handleError(e, vm, info)
  }
  return res
}

入的方法,该调用器还会做一些异常捕获处理。这样我们就发现了组件事件的调用是通过_parentListeners上每一项上的invoker去调用的。

接下来如果once存在 则去调用createOnceHandler

function createOnceHandler (event, fn) {
  const _target = target
  return function onceHandler () {
    const res = fn.apply(null, arguments)
    if (res !== null) {
      _target.$off(event, onceHandler)
    }
  }
}

没什么好说的调用一次随后被remove($off)掉

我们再一起来看看$on $off 也就是add remove具体是怎么实现的

  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {

    const vm: Component = this

    if (Array.isArray(event)) {

      for (let i = 0, l = event.length; i < l; i++) {

        vm.$on(event[i], fn)

      }

    } else {

      (vm._events[event] || (vm._events[event] = [])).push(fn)

      // optimize hook:event cost by using a boolean flag marked at registration

      // instead of a hash lookup

      if (hookRE.test(event)) {

        vm._hasHookEvent = true

      }

    }

    return vm

  }

$on 接收一个事件名或由事件名组成的数组和一个事件调用器 然后将调器一个个push进vm._events.event(vm._events[name])中 由于组件可复用 同一名称的方法 方法体不同vm._events.event为一个数组

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event]
    if (!cbs) {
      return vm
    }
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    // specific handler
    let cb
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return vm
  }

off函数移除如果没有传参则初始化vm._events 如果vm._events.event不存在什么都不做直接返回当前实例 如果没传调用器则清空vm._events.event 然后就是删除操作如果传入的调用器函数存在则从vm._events.event中删除该项 结合该方法我们可以去理解updateListeners 中的remove(event.name, oldOn[name], event.capture) 这句代码的含义为 如果oldListens中的某一项在Listens中不存在则从此时的vm.events.event中删除该项 实现vm.events.event的更新

  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      for (let i = 0, l = cbs.length; i < l; i++) {
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
  }
}

调用vm._events.event中存放的一个个函数 $emit具体的放到后面讲

        4、initRender 初始化 render 函数

export function initRender (vm: Component) {
  vm._vnode = null // the root of the child tree
  vm._staticTrees = null // v-once cached trees
  const options = vm.$options
  console.log(options, '++++++++++')
  const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  const parentData = parentVnode && parentVnode.data

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  }
}

options._parentVnode 是模板解析时当前组件在父组件挂载的vNode组件节点 我们声明两个组件Layout Bay

  <div id="app">
    <div>
        <layout>
            <h1 slot="header">{{title}}</h1>
            <p>{{msg}}</p>
            <p slot="footer">{{footer}}</p>
        </layout>
        <Bay>
          <h1>我是你爸爸</h1>
        </Bay>
    </div>
</div>
Vue.component('Layout', {
    template: `<div class="container">
                    <header>
                        <slot name="header">默认header</slot>
                    </header>
                    <main>
                        <slot>默认main</slot>
                    </main>
                    <footer>
                        <slot name="footer">默认footer</slot>
                    </footer>
                </div>`
})
Vue.component('Bay', {
    template: `<div class="container">
                    <header>
                        <slot>默认header</slot>
                    </header>
                </div>`
})
new Vue({
    el: '#app',
    template: ``,
    data: {
        title: '这是标题',
        msg: '这是内容',
        footer:'这是footer'
    }
})

打印options._parentVnode 结果如下图

通过打印我们可以看到resolveSlots的两个参数分为 组件节点的context 和子节点也就是写在插槽中的内容  组件节点的context在这里为根节点 接下来我们来看看resolveSlots做了哪些事

export function resolveSlots (
  children: ?Array<VNode>,
  context: ?Component
): { [key: string]: Array<VNode> } {
  if (!children || !children.length) {
    return {}
  }
  // console.log(children,context,222222222222)
  const slots = {}
  for (let i = 0, l = children.length; i < l; i++) {
    const child = children[i]
    const data = child.data
    // remove slot attribute if the node is resolved as a Vue slot node
    if (data && data.attrs && data.attrs.slot) {
      delete data.attrs.slot
    }
    // named slots should only be respected if the vnode was rendered in the
    // same context.
    if ((child.context === context || child.fnContext === context) &&
      data && data.slot != null
    ) {
      const name = data.slot
      const slot = (slots[name] || (slots[name] = []))
      if (child.tag === 'template') {
        slot.push.apply(slot, child.children || [])
      } else {
        slot.push(child)
      }
    } else {
      (slots.default || (slots.default = [])).push(child)
    }
  }
  // ignore slots that contains only whitespace
  for (const name in slots) {
    if (slots[name].every(isWhitespace)) {
      delete slots[name]
    }
  }
  return slots
}

根节点没有插槽的说法children自然不存在直接返回空对象 chiildren为5个vnode对象 分别为元素节点h1 文本节点换行 元素节点p 文本节点换行 元素节点p 如果元素节点含有slot属性则删除该节点对应vNode中的data.attrs.slot 如果当前节点的编译作用域与组件节点的相同且当前节点具有slot属性就将其push进slots[name] 没有slot的所有节点push进slots[defaults]中 最后生成一个如下所示的slots对象 retrun出去

在slots的操作中,vue主要做了以下几点操作:

  • 若组件children子节点为空,则组件中this.$slots对象为{}空对象
  • 若组件子节点存在,则先移除slot属性,接着匿名slot设置一个默认default作为key值,存储到this.$slots对象中,具名slot则按照定义的name进行存储
  • 当定义slot的标签为template时,则获取它的子节点,进行渲染。
  • 最后删除只包含空白字符(即不包含具体内容的slot)

接下来是讲createElement函数添加至vm.$createElement vm._c 其作用为解析用户写的模板html,从而成为浏览器可以识别的格式。此时只是赋值进行初始化工作就不再阐述

最后是defineReactive函数 vue响应式原理中非常重要的一个方法  这里对$attrs 和$listeners 做了处理 $attrs在祖孙传值的时候会用到 父组件通过v-bind $attrs 会将爷爷组件传给父组件且未在父组件props中接受值传递给子组件 

$listeners作用类似就是将爷爷组件传给父组件的事件再在父组件通过v-on="$listeners" 传给子组件

在这里插入图片描述

通过debugger打印可知 此时的$listeners $attrs 还是不存在的 之后通过Object.defineProperty给当前vm实例加上这两个属性 Object.defineProperty内部的实现 我们在响应式原理章节再去讨论

initRender做了四件事 处理vm.$slots 、vm.$createElement 、 响应式的vm.$listeners和vm.$attrs

       5、callHook(vm, 'beforeCreate') vue生命周期函数beforeCreate

先来看看callHook内部怎么实现的

模板编译时钩子函数内的执行体会被放进vm.$options[hook]中 通过invokeWithErrorHandling调用该实例上的生命周期方法 由于mixin混入的存在这里为数组形式 循环执行多个如果是自定义hook事件@hook就直接emit触发

pushTarget popTarget和响应式有关一个targetStack模拟栈 在beforecreate这里传入undefined没有实际意义 就先不讨论

        6、initInjections处理inject

先来看看resolveInject做了哪些事

export function resolveInject (inject: any, vm: Component): ?Object {

  if (inject) {

    // inject is :any because flow is not smart enough to figure out cached

    const result = Object.create(null)

    const keys = hasSymbol

      ? Reflect.ownKeys(inject)

      : Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {

      const key = keys[i]

      // #6574 in case the inject object is observed...

      if (key === '__ob__') continue

      const provideKey = inject[key].from //inject.foo.from

      let source = vm

      while (source) {

        if (source._provided && hasOwn(source._provided, provideKey)) {

          result[key] = source._provided[provideKey]

          break

        }

        source = source.$parent

      }

      if (!source) {

        if ('default' in inject[key]) {

          const provideDefault = inject[key].default

          result[key] = typeof provideDefault === 'function'

            ? provideDefault.call(vm)

            : provideDefault

        } else if (process.env.NODE_ENV !== 'production') {

          warn(`Injection "${key}" not found`, vm)

        }

      }

    }

    return result

  }

}

 由于inject支持symbol 类型先根据使用方式提取属性 之后 遍历keys属性集合 向上查找如果当前查找的目标实例vm._provided 存在切自身含有静态属性from(这里的hasOwn就是hasOwnProperty) 就给result赋值 source._provided.from 然后break 由此可以看出inject从provide查找是就近原则 如果父组件提供了provide 就不会读取爷组件的provide了 

 if (!source)的意思是一直查到根组件都没有对应的from 这时再执行source = source.$parent 根组件没有$parent source为undefined 此时就会使用inject的defalut默认值

然后回到initInjections 接下来就是对inject的值进行响应式处理 响应式我们在后面去说

        7、initState处理当前实例绑定的props methods data computed watch

  • initProps
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 (!isRoot && !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)
}

开始const一些变量 vm.$options.propsData 是父向子传递的数据 vm._props就是当前组件props接收的数据 通过defineReactive将propsOptions(props配置) 响应式添加到 vm._props vm.$options._propKeys 就是暂存props属性 方便后面进行遍历

接下来看看循环内做了哪些事 首先是validateProp函数 这个函数用来确定props每一项的值

export function validateProp (
  key: string,
  propOptions: Object,
  propsData: Object,
  vm?: Component
): any {
  const prop = propOptions[key]
  const absent = !hasOwn(propsData, key)
  let value = propsData[key]
  // boolean casting
  const booleanIndex = getTypeIndex(Boolean, prop.type)
  if (booleanIndex > -1) {
    if (absent && !hasOwn(prop, 'default')) {
      value = false
    } else if (value === '' || value === hyphenate(key)) {
      // only cast empty string / same name to boolean if
      // boolean has higher priority
      const stringIndex = getTypeIndex(String, prop.type)
      if (stringIndex < 0 || booleanIndex < stringIndex) {
        value = true
      }
    }
  }
  // check default value
  if (value === undefined) {
    value = getPropDefaultValue(vm, prop, key)
    // since the default value is a fresh copy,
    // make sure to observe it.
    const prevShouldObserve = shouldObserve
    toggleObserving(true)
    observe(value)
    toggleObserving(prevShouldObserve)
  }
  if (
    process.env.NODE_ENV !== 'production' &&
    // skip validation for weex recycle-list child component props
    !(__WEEX__ && isObject(value) && ('@binding' in value))
  ) {
    assertProp(prop, key, value, vm, absent)
  }
  return value
}

absent 代表没有propsData value就是父向子传递的值 没有就是undefined  key代表props配置项中的每一个key

接下来我们来看看getTypeIndex函数 

我们知道props的type属性是支持[String,Number,Boolean] 这样的数组形式的 所以函数里先判断 如果不是数组 且该type为Boolean 返回0否则返回-1 如果是数组则循环中的每一项 返回Boolean在数组的下标 如果没有Boolean则返回-1 我们就得到了booleanIndex 如果booleanIndex > -1则说明有Boolean类型的声明 如果propsData不存在该key也就是父组件没有向当前组件传值且当前组件该key的配置项没有设置默认值 则value = false 如果父向当前组件传了一个空字符串 key的-形式 然后获取String的下标 如果不存在或Boolean在String前 则value=true 如果父组件没有向当前组件传值且没有type声明为Boolean 则value等于默认值无default 则为undefined 

然后就是响应式的处理

  •  initMethods
function initMethods (vm: Component, methods: Object) {
  const props = vm.$options.props
  for (const key in methods) {
    if (process.env.NODE_ENV !== 'production') {
      if (typeof methods[key] !== 'function') {
        warn(
          `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        )
      }
      if (props && hasOwn(props, key)) {
        warn(
          `Method "${key}" has already been defined as a prop.`,
          vm
        )
      }
      if ((key in vm) && isReserved(key)) {
        warn(
          `Method "${key}" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
        )
      }
    }
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}

没什么好说的直接一个个绑定到当前vm实例

  • 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为函数调用getData获取data中的对象 data为对象报错 之后就是判断不可与props methods 名称重复然后监听data

  • initComputed

通过defineComputed循环创建computed

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

全局sharedPropertyDefinition对象就是Object.defineProerty的参数 noop是一个支持三个可选参数的空函数 isServerRendering判断是否为ssr环境 一般使用createComputedGetter构建getter  

12

function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

createComputedGetter基本是响应式的处理 computed监听的数据改变 dirty就会变成true 总之就是给getter一个监听函数

  • initWatch

 循环为每个fn创建watcher响应式都放在后面进行说明

        8、initProvide处理provide

 模板渲染时provide会挂在vm.$options.provide上  然后挂在vm._provided 很简单

        9、callHook(vm, 'created')

和beforeCreate一样

         9、vm.$mount(vm.$options.el)

挂载根节点

主要是query mountComponent 这两个函数

el dom不存在则返回一个div dom  存在则返回该el dom

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

将真实dom赋值给当前实例的vm.$el 如果 vm.$options.render 不存在则赋值一个createEmptyVNode

 之后是一些异常处理 别告诉我你看不懂 然后调用beforemount 生产环境加上mark标识 再通过new Watcher去调用vm._update实现渲染 这个我们放在虚拟dom章节去说 最后执行mounted 钩子函数

=========================================================================

四、总结

1. 初始化大致流程:

        处理options配置项 => 处理关于组件父子关系的一些属性 =>  自定义事件添加调用器 => 初始化render处理vm.$slots 、vm.$createElement 、 响应式的vm.$listeners和vm.$attrs => 触发beforeCreate钩子 => 处理inject => 处理组件状态包括props methods data computed watch 其中computed watch 监听在此初始化 => 处理provide => 触发created钩子

2. new Vue初始化过程 触发了 beforeCreate created beforeMount mounted 四个生命周期函数

学好大数据啊啊啊啊啊
关注 关注
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
new Vue()深入理解
weixin_43905830的博客
11-07 3178
我们都知道new Vue()将执行Vue的构造函数,进而执行this_init方法,那this,_init在哪里 ,它做了哪些事?先来看看_init函数的实现。 3.1、_init函数实现 Vue.prototype._init = function (options?: Object) { const vm: Component = this//定义全局变量vm,等于初始化vue对象 // a uid vm._uid = uid++//当前Vue实例唯一标识 //非生产
Vue源码Vue实例初始化
程序猿的救赎
08-23 311
这一节主要记录一下:Vue初始化过程 以下正式开始: Vue官网的生命周期图示表 [外链图片转存失败(img-LD0xeILd-1566566311651)(https://raw.githubusercontent.com/shisanOnly/blogImage/master/lifecycle.png)] 重点说一下 new Vue()后的初始化阶段,也就是created之前发生了什么。...
05-Vue入门系列之Vue实例详解与生命周期
derfyfxh588690的博客
12-25 534
Vue实例Vue框架的入口,其实也就是前端的ViewModel,它包含了页面中的业务逻辑处理、数据模型等,当然它也有自己的一系列的生命周期的事件钩子,辅助我们进行对整个Vue实例生成、编译、挂着、销毁等过程进行js控制。 5.1. Vue实例初始化的选项配置对象详解 前面我们已经用了很多次new Vue({...})的代码,而且Vue初始化的选项都已经用了data、method...
Vue中如何初始化数组的子元素
最新发布
湖北太米网络科技有限公司
09-02 399
Vue 中,可以使用以下几种方式初始化数组的子元素:使用数组的 push() 方法:登录后复制 data() { return { array: [] }; }, methods: { initArray() { this.array.push(1); this.array.pus...
初始化vue项目实例vue-cli)
mandy的博客
04-08 271
准备工作 vs code编辑器、chrome浏览器、node安装 步骤 全局安装vue-cli npm install vue-cli -g 初始化一个项目 vue init <template-name> <projectName> 第一个是模板名称,第二个是项目名称,模板有webpack,webpack-sim...
Vuenew Vue()
热门推荐
预立数据科技
10-10 1万+
1.newVue()创建一个新的Vue实例 2.el挂在原色 el绑定的元素内,都是Vue的作用范围 3.data数据对象 当一个Vue实例被创建时,它将data对象中的所有的属性加入到Vue的响应式系统中。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。 <html> <head> <script src="...
Vue2.0源码学习】生命周期篇-初始化阶段(new Vue)
一个前端开发者
08-14 973
Vue中,每个Vue实例从被创建出来到最终被销毁都会经历一个过程,就像人一样,从出生到死亡。在这一过程里会发生许许多多的事,例如设置数据监听,编译模板,组件挂载等。在Vue中,把Vue实例从被创建出来到最终被销毁的这一过程称为Vue实例的生命周期,同时,在Vue实例生命周期的不同阶段Vue还提供了不同的钩子函数,以方便用户在不同的生命周期阶段做一些额外的事情。那么,接下来的几篇文章我们就从源码角度深入剖析一下一个Vue实例在从生到死的生命周期里到底都经历了些什么,每个阶段都做了哪些事情。
Vue源码分析之Vue实例初始化详解
10-16
在深入理解Vue实例初始化的过程中,我们需要了解几个关键步骤,这些步骤都是在我们调用`new Vue()`时发生的。 首先,我们来到`initLifecycle`阶段。这个阶段主要是为了设置Vue实例的一些基本属性和关系。当创建一个...
vue源码解析.zip
01-12
下面我们将详细探讨"vue源码解析.zip"中的关键知识点。 首先,我们来看"Vue源码探秘之指令和生命周期"。Vue.js的指令是用于在DOM上执行特定操作的特殊属性,如`v-if`、`v-for`等。这些指令的处理逻辑在源码中表现为...
vue源码分析系列一:new Vue初始化过程
开心大表哥
06-04 3971
import Vue from ‘vue’(作者用的vue-cli一键生成) node环境下import Vue from 'vue'的作用是什么意思? 在 NPM 包的 dist/ 目录你将会找到很多不同的 Vue.js 构建版本。这里列出了它们之间的差别: 具体参考:官网 完整版:同时包含编译器和运行时的版本。 编译器:用来将模板字符串编译成为 JavaScript 渲染函数的代码。 运行时...
vue源码学习(一)实例初始化
zxl1990_ok的博客
06-30 439
vue源码版本为2.6.11(cdn地址为: https://lib.baomitu.com/vue/2.6.11/vue.jsvue源码里有这样一段代码: 其中定义了Vue构造函数,然后依次调用initMixin、stateMixin、eventsMixin、lifecycleMixin、renderMixin方法,并将Vue构造函数作为参数。 注:this instanceof Vue 用于判断this是否是Vue对象构造函数的实例。 ...
Vue源码阅读(三)——实例初始化
riddle1981的博客
08-29 416
可以看到在实例初始化的过程当中,在vue的原型链当中挂载了_init方法,该方法当中进行了uid递增, performance标记标记,_isVue赋值, 判定实例化是否传入组件化参数,是则进行内部组件初始化,否则进行传入参数覆盖式合并构造器参数的数据。 在非生产环境对实例进行代理,在生产环境则将实例自身赋值给_renderProxy 将实例赋值给_self属性,并进行一系列初始化操作 非生产环境下存在性能属性则进行监测 实例的$options对象存在el属性则挂载 // src/core/instance
Vue源码系列讲解——生命周期篇【二】(new Vue)
Miller777_的博客
02-21 1208
上篇文章中介绍了Vue实例的生命周期大致分为4个阶段,那么首先我们先从第一个阶段——初始化阶段开始入手分析。从生命周期流程图中我们可以看到,初始化阶段所做的工作也可大致分为两部分:第一部分是new Vue(),也就是创建一个Vue实例;第二部分是为创建好的Vue实例初始化一些事件、属性、响应式数据等。接下来我们就从源码角度来深入分析一下初始化阶段所做的工作及其内部原理。new Vue()。首先,分析了new Vue()时其内部都干了些什么。
new Vue({})、 Vue.extend、Vue.component
weixin_58562753的博客
05-17 3330
new Vue({})、 Vue.extend、Vue.component
vue2源码解析——new Vue()这个过程究竟做了什么?
涵盖了计算机专业基础知识、数学建模相关实践、复杂网络论文研究、LeetCode算法刷题经验、C语言开发经验、前端Vue、React框架开发实战相关知识
04-02 1220
new Vue() 是 Vue.js 框架中用于创建根 Vue 实例的语法。当我们使用 Vue.js 构建一个单页面应用时,通常会先创建一个根 Vue 实例,然后在该实例中定义应用的数据、方法、计算属性等,以及挂载根组件。具体来说,new Vue() 的作用是:创建一个 Vue 实例,该实例代表整个 Vue 应用的根实例。通过传入的选项对象来配置该 Vue 实例,包括数据、计算属性、方法、生命周期钩子函数等。将该 Vue 实例挂载到一个真实的 DOM 元素上,使得应用
vue2源码解读:new Vue()细节及应用点
coderMozart的博客
11-02 388
源码地址 地址1 操作方式: F12打开控制台, F5刷新即可跳入断点, 如下图所示: 知识点: 1. instanceof 检测是不是运用了 new 操作符, 保证了通过原型链(prototype chain)继承了默认的属性,例如$delete,$destroy等 2.通过 Object.defineProperty(Vue.prototype, '$data', dataDef)定义的是被继承但不被共享的属性 。Vue.prototype.$set = set;直接定...
vue2 框架运行原理剖析系列(一)之 new Vue()实例化过程到底做了什么!!!
05-10 998
new Vue 实例化过程
Vue初始化
玉案轩窗的博客
08-25 1657
前言 之前分析过Moon的源码,对于Moon实现的双向绑定、computed、事件分发、生命周期有了比较详细的阅读分析,学到了很多。不足之处也比较明显,对于component、虚拟DOM、模板解析不够深入,之后会逐渐完善,这个高仿库是非常值得深入学习的。 本文会介绍Vue.js加载过程中主要的初始化工作,vue版本是2.5.16。 具体分析 Vue源码中使用rollup来实现多种环境的打包...
写文章

热门文章

  • 微信小程序云开发获取数据时,记录总数超过20条的解决方案 5650
  • fastmock使用说明 4684
  • 超详细.Vue源码解析之new Vue()初始化vue实例 4643
  • 算法-给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标(js实现)。 3583
  • typescript 实现数组push unshift pop shift 1196

分类专栏

  • 其他 5篇
  • 算法 js实现 1篇
  • node.js
  • 微信小程序 2篇

最新评论

  • 微信小程序云开发获取数据时,记录总数超过20条的解决方案

    Weng991215: 我也是解决了吗

  • 微信小程序云开发获取数据时,记录总数超过20条的解决方案

    qq_25812807: 非常感谢,简单干练,没有多余代码影响我这个小白判断表情包,有一个问题,可能网络传输需要时间,必须加暂时才能渲染到页面。大佬有没有什么建议。

  • 手写call apply bind bind源码解析

    m0_69992869: 大叔大叔我爱你,真的有用!

  • 微信小程序云开发获取数据时,记录总数超过20条的解决方案

    榮昇地產陈稳 Mark: 调式器里面有返回200条数据, 可是页面为什么不显示,连20条都不显示了

  • 微信小程序云开发获取数据时,记录总数超过20条的解决方案

    weixin_47287608: 这个该怎么解决,我现在数据排序有问题

最新文章

  • Vue源码解析之异步更新
  • typescript 实现数组push unshift pop shift
  • 超详细.Vue源码解析之响应式原理
2022年6篇
2021年7篇
2020年2篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

深圳坪山网站建设公司能源网站优化联系方式网站优化师难学吗天津网站优化推广效果优化网站需要什么数据分析网站seo优化工资待遇东湖高端网站优化深圳seo网站关键词优化推广天河网站优化比较好自贡网站整站优化金华网站优化服务收费在线咨询河北网站布局优化网站推广优化团队乐山优化网站的公司厦门网站优化推广网站产品优化游云速捷跟进济南家装行业网站优化推广特点萝岗网站搜索优化黄冈市网站关键词优化推广金堂网站建设与优化网站关键词优化哪种手机微网站怎么优化福州靠谱的餐饮行业网站优化宜昌网站seo优化推广网站优化的必要因素分析广州正规SEO网站优化公司从江网站优化公司如何选择网站优化的核心关键词瑞安网站优化优化网站如何选择关键词深圳综合网站优化怎么做香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声卫健委通报少年有偿捐血浆16次猝死汪小菲曝离婚始末何赛飞追着代拍打雅江山火三名扑火人员牺牲系谣言男子被猫抓伤后确诊“猫抓病”周杰伦一审败诉网易中国拥有亿元资产的家庭达13.3万户315晚会后胖东来又人满为患了高校汽车撞人致3死16伤 司机系学生张家界的山上“长”满了韩国人?张立群任西安交通大学校长手机成瘾是影响睡眠质量重要因素网友洛杉矶偶遇贾玲“重生之我在北大当嫡校长”单亲妈妈陷入热恋 14岁儿子报警倪萍分享减重40斤方法杨倩无缘巴黎奥运考生莫言也上北大硕士复试名单了许家印被限制高消费奥巴马现身唐宁街 黑色着装引猜测专访95后高颜值猪保姆男孩8年未见母亲被告知被遗忘七年后宇文玥被薅头发捞上岸郑州一火锅店爆改成麻辣烫店西双版纳热带植物园回应蜉蝣大爆发沉迷短剧的人就像掉进了杀猪盘当地回应沈阳致3死车祸车主疑毒驾开除党籍5年后 原水城县长再被查凯特王妃现身!外出购物视频曝光初中生遭15人围殴自卫刺伤3人判无罪事业单位女子向同事水杯投不明物质男子被流浪猫绊倒 投喂者赔24万外国人感慨凌晨的中国很安全路边卖淀粉肠阿姨主动出示声明书胖东来员工每周单休无小长假王树国卸任西安交大校长 师生送别小米汽车超级工厂正式揭幕黑马情侣提车了妈妈回应孩子在校撞护栏坠楼校方回应护栏损坏小学生课间坠楼房客欠租失踪 房东直发愁专家建议不必谈骨泥色变老人退休金被冒领16年 金额超20万西藏招商引资投资者子女可当地高考特朗普无法缴纳4.54亿美元罚金浙江一高校内汽车冲撞行人 多人受伤

深圳坪山网站建设公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化