vue

150 阅读4分钟

一切都从 new Vue(options) 开始

function Vue (options) {
  if (!(this instanceof Vue)) {} // 只能通过new
  this._init(options)
}

_init

Vue.prototype._init = function (options) {
  // 根实例
  let vm = this
  // 子实例合并options,配置会被保存在VueComponent.options上,
  // 另外,vm.$options._renderChildren = children(插槽相关)
  if (options && options._isComponent) initInternalComponent(vm, options);
  
  else 
  // 主要是合并全局组件,全局指令,全局过滤器;
  // 实例属性:data数据,template模板(会编译成render函数),生命周期, 组件,computed, watch...
  vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
  )
  
  
  // 确定组件的父子关系
  initLifecycle(vm);
  // 自定义事件 $emit $on
  initEvents(vm);
  // 定义 $createElement 作为render函数的参数
  initRender(vm);
  // 第一个生命周期
  callHook(vm, 'beforeCreate'); 
  initInjections(vm);
  // 数据响应式
  initState(vm);
  initProvide(vm); 
  callHook(vm, 'created');
  // 挂载
  if (vm.$options.el) vm.$mount(vm.$options.el)
}

响应式: initProps initData initComputed initWatch

  • initProps
function initProps (vm, propsOptions/* 标签里取到的数据 */) {
    // 1. vm.$options.propsData  组件的配置对象里定义的
    // 2. vm._props = {}         根据propsData 和 propsOptions得到的props
    // 3. defineReactive$$1(props, key, value) 
    // 4. 代理 proxy(vm, "_props", key)
}
  • initData
function initData (vm) {
  // 用户定义的 data
  var data = vm.$options.data;
  // 绑定在实例的 _data 上
  data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {};
  // 属性校验: key不能和props还有methods重复
  
  // 响应式处理
  observe(data, true);
}

function observe (value, asRootData) {
  // 省略了一些我认为不是很重要的代码
  // 每个对象对应一个ob实例
  let ob = new Observer(value)
  return ob
}

// 实例化 ob
var Observer = function Observer (value) {
  this.value = value    // 数据
  this.dep = new Dep(); // 给 vue.$set 用

  // 如果数据是数组类型 重写原型方法

  this.walk(value);
};

Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    // 数据的每个key响应式处理
    defineReactive$$1(obj, keys[i]);
  }
}


function defineReactive$$1 (
  obj,
  key,
) {
  // 数据的每个属性都会对应一个 dep
  var dep = new Dep();

  // 深度响应式;
  var childOb = !shallow && observe(val);

  // 利用 Object.defineProperty 属性get的时候执行
  function reactiveGetter () {
    if (Dep.target) {
      dep.depend(); // 将当前的watcher收集起来
      if (childOb) {
        // 嵌套对象的ob的dep也收集这个watcher
        /*
          function set(target, key, val) {
            // 由于也经历了上面的 observe, 所以target存在__ob__
            var ob = (target).__ob__
            // 直接将赋予的属性响应式处理
            defineReactive$$1(ob.value, key, val)
            // 手动触发 notify
            ob.dep.notify()
          }
        */
        childOb.dep.depend();
        if (Array.isArray(value)) {
          dependArray(value);
        }
      }
    }
    return value
  }
  // set的时候执行
  function reactiveSetter (newVal) {
    // 设置的属性响应式
    childOb = !shallow && observe(newVal);
    dep.notify(); // 触发 watcher 渲染
  }
}
  • initComputed
function initComputed (vm, computed) {
  var watchers = vm._computedWatchers = Object.create(null);
  // 处理每个计算属性
  for (var key in computed) {
    var userDef = computed[key];
    // 我们一般使用都是函数
    var getter = typeof userDef === 'function' ? userDef : userDef.get;
  
    watchers[key] = new Watcher(
      vm,
      getter || noop,
      noop,
      computedWatcherOptions // { lazy: true }
    );
    
    // 计算属性响应式处理
    defineComputed(vm, key, userDef);
    // get 的时候触发如下:
    /*
        // 计算属性的 dep 收集渲染watcher, 计算属性依赖的data属性的dep收集计算属性的watcher
        if (watcher) {
          if (watcher.dirty) {
            watcher.evaluate();
          }
          if (Dep.target) {
            watcher.depend();
          }
          return watcher.value
        }
    */
  }
}
  • initWatch
// 每个 watch 都会执行 createWatcher(vm, key, handler/* 函数 或者 对象 */)
function $watch(expOrFn/* key */, cb/* 回调 */, options/* 配置 */) {
  var watcher = new Watcher(vm, expOrFn, cb, options);
  // 存在配置 immediate 初始就会执行回调
  if (options.immediate) {
    pushTarget();
    invokeWithErrorHandling(cb, vm, [watcher.value], vm, info);
    popTarget();
  }
}
// userWatcher
function Watcher (vm,expOrFn, cb, options) {
  // 配置:deep: true
  this.cb = cb;
  this.deps = [];
  this.newDeps = [];
  // 去访问属性值,让属性收集这个userWatcher
  this.getter = parsePath(expOrFn);
  this.value = this.lazy
    ? undefined
    : this.get();
}

function get () {
  pushTarget(this)
  value = this.getter.call(vm, vm)
  // 深度监听
  if (this.deep) {
    traverse(value)
  }
  popTarget();
  this.cleanupDeps();
  return value
}

userWatcher 监听计算属性的情况
// 触发计算属性get  ->  执行计算属性get方法
// 计算属性收集userWatcher
// 数据的属性收集计算属性watcher

created 生命周期

mount

Vue.prototype.$mount = function (el) {
  el = el && inBrowser ? query(el) : undefined;
  return mountComponent(this, el, hydrating)
}

function mountComponent (
  vm,
  el
) {
  vm.$el = el;
  callHook(vm, 'beforeMount');

  updateComponent = function () {
    vm._update(vm._render(), hydrating);
  };
  
  // 开始渲染组件 vm._update(vm._render(), hydrating)
  new Watcher(vm, updateComponent, noop, {
    before: function before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate');
      }
    }
  }, true /* isRenderWatcher */);

  if (vm.$vnode == null) {
    vm._isMounted = true;
    callHook(vm, 'mounted');
  }
  return vm
}

vm._render() 创建子vnode

function _createElement (context, tag, data, children, normalizationType) {
  // 原生dom 
  vnode = new VNode(tag, data/* style class event... */, children, undefined, context)
  // 组件vnode
  vnode = createComponent(
    tag    /* 组件对象(data template components....) */, 
    data, 
    context, 
    children
  )

  /*
    new Vue({
      el: '#app',
      render: h => h(App) // 其实就只是传入了tag,也就是抛出的app组件对象
    })

    一般我们开发写的template,会被vue编译成render函数
    _c(tag, data, children) 
  */
}

// 继承大Vue
// extendOptions  { name, data, template | render, components... }
function extend(extendOptions) {
  // 这也就是为啥子组件实例可以用Vue的原型上的方法
  // Sub.prototype = Object.create(Super.prototype)
  // Sub = function VueComponent (options) { this._init(options) }
  // Sub.prototype.constructor = Sub;
  // 将后期的 $options 保存在构造器的 options 上
  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  )
  return Sub
}


// 组件vnode
// 1. 继承Vue得到构造器  
// 2. 异步组件逻辑: 返回注释节点,在promise then之后forceRender父组件
// 3. 处理props得到propsData
// 3. 组件钩子
// 4. new vnode
function createComponent (
  Ctor,
  data,
  context,
  children,
  tag
) {
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor);
  }
  // 异步组件
  Ctor = resolveAsyncComponent(Ctor, baseCtor);
  if (Ctor === undefined) {
      return createAsyncPlaceholder(
          asyncFactory,
          data,
          context,
          children,
          tag
      )
  }

  // propsData
  
  // 组件钩子
  installComponentHooks(data);

  var vnode = new VNode(
    ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
  return vnode
}

patch 子vnode

function patch (oldVnode, vnode, hydrating, removeOnly) {
  // 保存 子dom节点 子vnode, 用于最后执行 指令的钩子  mounted(先子后父);
  var insertedVnodeQueue = []
  // 创建组件(实例化子组件 或者 创建dom)
  createElm(vnode, insertedVnodeQueue, parentElm)
  // vnode.parent.data.pendingInsert = queue
  invokeInsertHook(vnode, insertedVnodeQueue)
  return vnode.elm
}
function createElm (vnode, insertedVnodeQueue, parentElm) {
  // 组件vnode(递归)
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) return

  // 原生dom
  vnode.elm = createElement(tag, vnode)
  // 创建children
  createChildren(vnode, children, insertedVnodeQueue); 
  // 事件,style,class等处理
  if (isDef(data)) invokeCreateHooks(vnode, insertedVnodeQueue)
  // 插入父节点
  insert(parentElm, vnode.elm, refElm) 
}
function createComponent (vnode, insertedVnodeQueue, parentElm) {
  // 组件 _init
  initComponent(vnode, insertedVnodeQueue);
  insert(parentElm, vnode.elm);
}

vue的编译

template 解析成ast

// 编译 template 返回root ast节点
var ast = parse(template.trim(), options)
// 通过 while template字符串,每个标签都是一个ast对象,如下:
{
  tag,       // 标签
  attrsMap: { :class: value, @click: 'handle(text)' },  // 标签属性
  parent,
  children: [],  // 子ast
  alias: v-for的每一项 // 如果存在 v-for 会丰富ast对象
  classBinding        // classBinding  的变量
  for:                // v-for数组
  staticClass         // 静态class
  iterator1:          // v-for 索引变量
  if:  v-if标识       // v-if
  ifConditions: [{
      block: ast,
      exp
  }]
  // 事件相关属性
  events: {
    click: {
        value: "handle(text)"
    }
  }
}

codeGen (最终的render函数)

// 编译 render 函数
var code = generate(ast, options)
// 最终返回的代码
_c(tag, data/* 事件 class style等属性 */, children) 

事件

  • dom原生事件
/*
  标签ast { @click.stop: 'value',  class:  , :class: }
  @click.stop修饰符 => modifiers = { native: true, stop: true... }
  丰富ast属性:
  ast[events | nativeEvents] = {
      // 同种类型事件绑定多个就会是数组函数
      eventName: [{}]   |   {value, modifiers}
  }

  codeGen阶段:
  遍历 ast[events | nativeEvents] 生成一种事件类型的代码
  
  根据是否存在modifiers会有不同的规则:
  1. 不存在modifiers,常规的事件回调;
  根据不同书写形式会生成不同的代码
  --------  @click=method    on: { click: method }
  最终绑定在dom上的回调:function invoker() { method.apply(null, arguments事件对象) }


  --------  @click=method($event, param) 
  on: { click: function($event){return method($event, param)} }
  最终绑定在dom上的回调:
  function invoker() {
      (function($event){return method($event, param)}).apply(null, arguments)
  }


  --------  @click=() => { console.log() }  这种形式就很有意思了,因为最后生成的代码是:
  with(this) { return _c('div', { on: () => { console.log() } }, []) }
  会从this(vm)中找 console 由于vue对实例做了一层proxy包装 所以导致直接throw;
  所以你可以这样写 ($event)=> { method($event, param) } 这样才能接受到 param 这个参数
  ($event, param) => { method($event, param) } 因为最后是 apply(null, arguments) 只能接受到事件对象
  最终绑定在dom上的回调:
  function invoker() {
      // 这种写法就只能拿到一个参数了
      (($event, param) => { method($event, param) }).apply(null, arguments)
  }
  
  
  2. 存在modifiers(所有代码都会做一层包装)
  --------  @click=method(参数只能接受一个 $event)
  function($event){
      $event.stopPropagation();
      return method.apply(null, arguments)
  }
  
  
  --------  @click=method($event, param)(可以接受多个参数)
  function ($event){
      $event.stopPropagation();
      return method($event, param)
  }
  
  
  --------  @click=() => {}
  function($event){
      $event.stopPropagation();
      return (()=>{}).apply(null, arguments)
  }


  两个时机会执行事件的绑定:
  1.createChildren 创建真实dom后
  最终绑定的函数是: 
  function invoker {
      fns = invoker.fns   // fn[] | fn 
      // fns.apply(null, arguments)
  }
  ** 为啥将 fns 绑定在invoker上:**  
  其实最后绑定在dom上的是 invoker 这个函数,这样操作那么事件只需要绑定一次
  只需要在更新的时候更改 invoker.fns 那么最终执行的函数就会变化
  ********************************
  
  
  ** 关于this指向 **
  你会发现最终的函数都是apply null,那vue怎么做到methods中方法的this都是指向vm呢
  其实就是在 initState 过程中将methods方法都bind了一下
  function polyfillBind (fn, ctx) {
      function boundFn (a) {
      var l = arguments.length;
      return l
          ? l > 1
          ? fn.apply(ctx, arguments)
          : fn.call(ctx, a)
          : fn.call(ctx)
      }

      boundFn._length = fn.length;
      return boundFn
  }
  ******************
*/
  • 自定义事件
/*
  <Comp @select='method'/>
  Comp的 ast 属性:
  ast.events = {
      select: {
          value: 'method'  // 其他情况: method($event, b)    () => {}
      }
  }

  生成的code中的data的部分属性: {on: code}
  function ($event) {
      method($event,b) // 包装
  }
  () => {}
  method.apply(null, arguments)

  Comp的vnode
  _c(Comp, { on: { select: fn } })
  var vnode = new VNode(
    name,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners/* data.on */, tag, children }
  )

  组件实例化时:
   执行 initEvents 会执行 updateListeners(listeners)
   function invoker () {
      fns = invoker.fns  // method   () => {}
      fns.apply(null, arguments)
   }
   $on(name, invoker)
   $emit(name, arguments /*通过子组件给父传参*/) -> invoker.apply(vm, arguments)
   method.apply(null, arguments) // 注意: method也是被bind后的函数,this是指向父实例的
   写法 method($event, b) 只能接受一个 $event 参数(第一个子传过来的参数)后一个参数是父组件实例上的
*/

指令

  在dom被创建后会执行vue平台的一些钩子: updateClass updateStyles updateDirective
  1. 先执行所有指令的bind钩子  bind(vnode.elm/* dom */, dir/* 指令配置:可以定义一些工具方法 */)
  2. 将后面执行 inserted 钩子的回调函数放进 domVnode 的hook中 
  3. invokeCreateHooks 
      if (isDef(domVnode.data.hook.insert)) { insertedVnodeQueue.push(domVnode) }
  4. 组件init后会把自身 vnode 也 push 进 insertedVnodeQueue
  5. 所以最终遍历这个queue执行钩子 顺序就是:inserted钩子  组件mounted生命周期

v-model

 1. addProps -> value   :value=... 
 2. event -> @input=if($event.target.composing)return; demo=$event.target.value
 
 创建节点后为其绑定 input 事件 注意这是一个自定义事件
 model指令 inserted 钩子中去绑定 compositionStart compositionEnd 事件
 compositionStart:e.target.composing = true 
 compositionEnd: 触发标签input事件

插槽

<组件>
  <a slot='name' slot-scope='demo'>1</a>
  <div slot-scope='demo'></div>
  <div></div>
</组件>

1.  标签parse阶段
      存在 slot-scope  parent.scopedSlots = { name:  ast } // 同样name的slot只能存在一个
      不存在 slot-scope  parent.children = [ast...]
2.  组件生成code阶段
      存在 scopedSlots 丰富data属性
      { scopedSlots: _u( [ {  key: name, fn/* 接收props 返回vnode */  } ] ) }
      function (slotScope) {
          return _c(tag, data, children)
      }
3.  组件vnode  data.scopedSlots = {name: fn} // _u return的结果
4.  组件实例化  $slots   $scopedSlots
5.  这个组件执行自己的render会创建子vnode,就会碰上slot组件 
6.  slot   _t(name, children, props)
7.  先找 $scopedSlots  根据name  执行fn(props) 生成vnode 
8.  后找 $slots 返回 [vnode, vnode]