Vue(v2.6.11)万行源码生啃,就硬刚!(中)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 众所周知,以下代码就是 vue 的一种直接上手方式。通过 cdn 可以在线打开 vue.js。一个文件,一万行源码,是万千开发者赖以生存的利器,它究竟做了什么?让人品味。

第 3517 行至第 3894 行


  • renderMixin // 引入视图渲染混合函数
  • ensureCtor
  • createAsyncPlaceholder
  • resolveAsyncComponent
  • isAsyncPlaceholder
  • getFirstComponentChild
  • initEvents// 初始化事件
  • add
  • remove$1
  • createOnceHandler
  • updateComponentListeners
  • eventsMixin // 挂载事件响应相关方法


第 3898 行至第 4227 行


  • setActiveInstance
  • initLifecycle
  • lifecycleMixin// 挂载生命周期相关方法
  • mountComponent
  • updateChildComponent
  • isInInactiveTree
  • activateChildComponent
  • deactivateChildComponent
  • callHook


几乎所有JS框架或插件的编写都有一个类似的模式,即向全局输出一个类或者说构造函数,通过创建实例来使用这个类的公开方法,或者使用类的静态全局方法辅助实现功能。相信精通Jquery或编写过Jquery插件的开发者会对这个模式非常熟悉。Vue.js也如出一辙,只是一开始接触这个框架的时候对它所能实现的功能的感叹盖过了它也不过是一个内容较为丰富和精致的大型类的本质。


link


阶段小结


这里要对 js 的继承有一个深刻的理解。link


  1. 类继承
function Animal(){
    this.live=true;
}
function Dog(name){
    this.name=name
}
Dog.prototype=new Animal()
var dog1=new Dog("wangcai")
console.log(dog1)// Dog {name: "wangcai"}
console.log(dog1.live)// true


  1. 构造继承
function Animal(name,color){
    this.name=name;
    this.color=color;}
function Dog(){
    Animal.apply(this,arguments)
}
var dog1=new Dog("wangcai","balck")
console.log(dog1)// Dog {name: "wangcai", color: "balck"}


  1. 组合继承(类继承 + 构造继承)
function Animal(name,color){
    this.name=name;
    this.color=color;
    this.live=true;
}
function Dog(){
    Animal.apply(this, arguments);   
}
Dog.prototype=new Animal()
var dog1=new Dog("wangcai","black")
console.log(dog1)// Dog {name: "wangcai", color: "black", live: true}


  1. 寄生组合式继承
  2. extend继承


Vue 同 Jquery 一样,本质也是一个大型的类库。

// 定义Vue构造函数,形参options


function Vue (options) {
  if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // ...  
  this._init(options)
}


// 功能函数


// 引入初始化混合函数
import { initMixin } from './init'
// 引入状态混合函数
import { stateMixin } from './state'
// 引入视图渲染混合函数
import { renderMixin } from './render'
// 引入事件混合函数
import { eventsMixin } from './events'
// 引入生命周期混合函数
import { lifecycleMixin } from './lifecycle'
// 引入warn控制台错误提示函数
import { warn } from '../util/index'
...
// 挂载初始化方法
initMixin(Vue)
// 挂载状态处理相关方法
stateMixin(Vue)
// 挂载事件响应相关方法
eventsMixin(Vue)
// 挂载生命周期相关方法
lifecycleMixin(Vue)
// 挂载视图渲染方法
renderMixin(Vue)


第 4231 行至第 4406 行


  • resetSchedulerState // 重置状态
  • flushSchedulerQueue// 据变化最终会把flushSchedulerQueue传入到nextTick中执行flushSchedulerQueue函数会遍历执行watcher.run()方法,watcher.run()方法最终会完成视图更新

image.png


vue中dom的更像并不是实时的,当数据改变后,vue会把渲染watcher添加到异步队列,异步执行,同步代码执行完成后再统一修改dom。


  • callUpdatedHooks
  • queueActivatedComponent
  • callActivatedHooks
  • queueWatcher

link


第 4412 行至第 4614 行


  • Watcher// !important 重中之重的重点

这一 part 在 Watcher 的原型链上定义了get、addDep、cleanupDeps、update、run、evaluate、depend、teardown 方法,即 Watcher 的具体实现的一些方法,比如新增依赖、清除、更新试图等。


每个Vue组件都有一个对应的watcher,这个watcher将会在组件render的时候收集组件所依赖的数据,并在依赖有更新的时候,触发组件重新渲染。


第 4618 行至第 5071 行


export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++
    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }
    // 如果是Vue的实例,则不需要被observe
    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    // 第一步: options参数的处理
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      // mergeOptions接下来我们会详细讲哦~
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    // 第二步: renderProxy
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 第三步: vm的生命周期相关变量初始化
    initLifecycle(vm)
    // 第四步: vm的事件监听初始化
    initEvents(vm)
    // 第五步: vm的编译render初始化
    initRender(vm)
    // 第六步: vm的beforeCreate生命钩子的回调
    callHook(vm, 'beforeCreate')
    // 第七步: vm在data/props初始化之前要进行绑定
    initInjections(vm) // resolve injections before data/props
    // 第八步: vm的sate状态初始化
    initState(vm)
    // 第九步: vm在data/props之后要进行提供
    initProvide(vm) // resolve provide after data/props
    // 第十步: vm的created生命钩子的回调
    callHook(vm, 'created')
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    // 第十一步:render & mount
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}


主要是为我们的Vue原型上定义一个方法_init。然后当我们执行new Vue(options) 的时候,会调用这个方法。而这个_init方法的实现,便是我们需要关注的地方。 前面定义vm实例都挺好理解的,主要我们来看一下mergeOptions这个方法,其实Vue在实例化的过程中,会在代码运行后增加很多新的东西进去。我们把我们传入的这个对象叫options,实例中我们可以通过vm.$options访问到。


link


0 至 5000 行 总结


image.png


从 0 至 5000 行我们可以清晰看到 Vue 模板编译的轮廓了。


  • 笔者将这一部分出现的关键词进行按顺序罗列:
  1. function (global, factory)
  2. 工具函数
  3. Dep
  4. Observe
  5. VNode
  6. nextTick
  7. 事件机制
  8. Render
  9. components
  10. Watcher


我们可以总结:Vue 的核心就是 VDOM !对 DOM 对象的操作调整为操作 VNode 对象,采用 diff 算法比较差异,一次 patch。


render 的流程是:

  1. Vue使用HTML的Parser将HTML模板解析为AST
  2. function render(){}
  3. Virtual DOM
  4. watcher将会在组件render的时候收集组件所依赖的数据,并在依赖有更新的时候,触发组件重新渲染


推荐阅读:link


第 5073 行至第 5446 行


// 定义 Vue 构造函数
function Vue (options) {
      if (!(this instanceof Vue)
      ) {
        warn('Vue is a constructor and should be called with the `new` keyword');
      }
      this._init(options);
    }
// 将 Vue 作为参数传递给导入的五个方法
initMixin(Vue);// 初始化 Mixin
stateMixin(Vue);// 状态 Mixin
eventsMixin(Vue);// 事件 Mixin
lifecycleMixin(Vue);// 生命周期 Mixin
renderMixin(Vue);// 渲染 Mixin


这一部分就是初始化函数的调用。


// 
Object.defineProperty(Vue.prototype, '$isServer', {
      get: isServerRendering
    });


为什么这么写?


Object.defineProperty能保护引入的库不被重新赋值,如果你尝试重写,程序会抛出“TypeError: Cannot assign to read only property”的错误。


link-【译】Vue框架引入JS库的正确姿势


// 版本
Vue.version = '2.6.11';


阶段小结


这一部分是 Vue index.js 的内容,包括 Vue 的整个挂在过程


  1. 先进入 initMixin(Vue),在prototype上挂载
Vue.prototype._init = function (options) {} 


  1. 进入 stateMixin(Vue),在prototype上挂载 Vue.prototype.$data
Vue.prototype.$props 
Vue.prototype.$set = set 
Vue.prototype.$delete = del 
Vue.prototype.$watch = function(){} 


  1. 进入eventsMixin(Vue),在prototype上挂载
Vue.prototype.$on 
Vue.prototype.$once 
Vue.prototype.$off 
Vue.prototype.$emit


  1. 进入lifecycleMixin(Vue),在prototype上挂载
Vue.prototype._update 
Vue.prototype.$forceUpdate 
Vue.prototype.$destroy


  1. 最后进入renderMixin(Vue),在prototype上挂载 Vue.prototype.$nextTick
Vue.prototype._render 
Vue.prototype._o = markOnce 
Vue.prototype._n = toNumber 
Vue.prototype._s = toString 
Vue.prototype._l = renderList 
Vue.prototype._t = renderSlot
Vue.prototype._q = looseEqual 
Vue.prototype._i = looseIndexOf 
Vue.prototype._m = renderStatic 
Vue.prototype._f = resolveFilter 
Vue.prototype._k = checkKeyCodes 
Vue.prototype._b = bindObjectProps 
Vue.prototype._v = createTextVNode 
Vue.prototype._e = createEmptyVNode 
Vue.prototype._u = resolveScopedSlots 
Vue.prototype._g = bindObjectListeners

mergeOptions使用策略模式合并传入的options和Vue.options合并后的代码结构, 可以看到通过合并策略components,directives,filters继承了全局的, 这就是为什么全局注册的可以在任何地方使用,因为每个实例都继承了全局的, 所以都能找到。


推荐阅读:

link

link


new 一个 Vue 对象发生了什么:


image.png


第 5452 行至第 5655 行


// these are reserved for web because they are directly compiled away
// during template compilation
// 这些是为web保留的,因为它们是直接编译掉的
// 在模板编译期间


  • isBooleanAttr
  • genClassForVnode// class 转码获取vonde 中的staticClass 静态class  和class动态class转义成真实dom需要的class格式。然后返回class字符串
  • mergeClassData// mergeClassData
  • renderClass// 渲染calss 这里获取到已经转码的calss
  • stringifyClass// 转码 class,把数组格式,对象格式的calss 全部转化成 字符串格式
  • stringifyArray// 数组字符串变成字符串,然后用空格 隔开 拼接 起来变成字符串
  • stringifyObject// 对象字符串变成字符串,然后用空格 隔开 拼接 起来变成字符串
  • namespaceMap
  • isHTMLTag
  • isSVG// 判断svg 标签
  • isUnknownElement// 检查dom 节点的tag标签 类型 是否是VPre 标签 或者是判断是否是浏览器自带原有的标签
  • isTextInputType //  //匹配'text,number,password,search,email,tel,url'


这一 part 没有特别要说的,主要是对 class 的转码、合并和其他二次封装的工具函数。实际上我们在 Vue 源码很多地方看到了这样的封装,在平常的开发中,我们也得要求自己封装基本的函数。如果能形成自己习惯用的函数的库,会方便很多,且对自己能力也是一个提升。


第 5659 行至第 5792 行


  • createElement // 创建元素,实例化 VNode
  • createElementNS
  • createTextNode
  • createComment
  • insertBefore
  • removeChild
  • appendChild
  • parentNode
  • nextSibling
  • tagName
  • setTextContent
  • setStyleScope
  • nodeOps


// nodeOps:
    createElement: createElement$1, //创建一个真实的dom
    createElementNS: createElementNS, //创建一个真实的dom svg方式
    createTextNode: createTextNode, // 创建文本节点
    createComment: createComment,  // 创建一个注释节点
    insertBefore: insertBefore,  //插入节点 在xxx  dom 前面插入一个节点
    removeChild: removeChild,   //删除子节点
    appendChild: appendChild,  //添加子节点 尾部
    parentNode: parentNode,  //获取父亲子节点dom
    nextSibling: nextSibling,     //获取下一个兄弟节点
    tagName: tagName,   //获取dom标签名称
    setTextContent: setTextContent, //  //设置dom 文本
    setStyleScope: setStyleScope  //设置组建样式的作用域


  • ref
  • registerRef // 注册ref或者删除ref。比如标签上面设置了ref='abc' 那么该函数就是为this.$refs.abc 注册ref 把真实的dom存进去


阶段小结


这里的重点想必就是 “ref” 了

在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。不过也确实在一些情况下做这些事情是合适的。ref 为我们提供了解决途径。

ref属性不是一个标准的HTML属性,只是Vue中的一个属性。


第 5794 行至第 6006 行


Virtual DOM !

没错,这里就是 虚拟 dom 生成的源码相关。


  • sameVnode
  • sameInputType
  • createKeyToOldIdx
  • createPatchFunction // !important:patch 把 vonde 渲染成真实的 dom
  • emptyNodeAt
  • createRmCb
  • removeNode
  • isUnknownElement?1
  • createElm // 创造 dom 节点
  • createComponent // 创建组件,并且判断它是否实例化过
  • initComponent

createElement方法接收一个tag参数,在内部会去判断tag标签的类型,从而去决定是创建一个普通的VNode还是一个组件类VNode;


createComponent 的实现,在渲染一个组件的时候的 3 个关键逻辑:


  1. 构造子类构造函数,
  2. 安装组件钩子函数
  3. 实例化 vnode。createComponent 后返回的是组件 vnode,它也一样走到 vm._update 方法


我们传入的 vnode 是组件渲染的 vnode,也就是我们之前说的 vm._vnode,如果组件的根节点是个普通元素,那么 vm._vnode 也是普通的 vnode,这里 createComponent(vnode, insertedVnodeQueue, parentElm, refElm) 的返回值是 false。接下来的过程就系列一的步骤一样了,先创建一个父节点占位符,然后再遍历所有子 VNode 递归调用 createElm,在遍历的过程中,如果遇到子 VNode 是一个组件的 VNode,则重复过程,这样通过一个递归的方式就可以完整地构建了整个组件树。


initComponent 初始化组建,如果没有tag标签则去更新真实dom的属性,如果有tag标签,则注册或者删除ref 然后为insertedVnodeQueue.push(vnode);


参考link


第 6008 行至第 6252 行


  • reactivateComponent
  • insert
  • createChildren
  • isPatchable
  • invokeCreateHooks
  • setScope
  • addVnodes // 添加 Vnodes
  • invokeDestroyHook
  • removeVnodes // 移除 Vnodes
  • removeAndInvokeRemoveHook
  • updateChildren // 在patchVnode中提到,如果新老节点都有子节点,但是不相同的时候就会调用 updateChildren,这个函数通过diff算法尽可能的复用先前的DOM节点。


// diff 算法就在这里辣!详解link


function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, elmToMove, refElm 
    while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        if (isUndef(oldStartVnode)) {
            oldStartVnode = oldCh[++oldStartIdx]
        } else if (isUndef(oldEndVnode)) {
            oldEndVnode = oldCh[--oldEndIdx]
        } else if (sameVnode(oldStartVnode, newStartVnode)) {
            patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
            oldStartVnode = oldCh[++oldStartIdx]
            newStartVnode = newCh[++newStartIdx]
        } else if (sameVnode(oldEndVnode, newEndVnode)) {
            patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
            oldEndVnode = oldCh[--oldEndIdx]
            newEndVnode = newCh[--newEndIdx]
        } else if (sameVnode(oldStartVnode, newEndVnode)) {
            patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
            canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
            oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
        } else if (sameVnode(oldEndVnode, newStartVnode)) {
            patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
            canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
            oldEndVnode = oldCh[--oldEndIdx]
            newStartVnode = newCh[++newStartIdx]
        } else {
            if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
            idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
            if (isUndef(idxInOld)) {
                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
                newStartVnode = newCh[++newStartIdx]
            } else {
                elmToMove = oldCh[idxInOld]
                if (sameVnode(elmToMove, newStartVnode)) {
                    patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
                    oldCh[idxInOld] = undefined
                    canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
                    newStartVnode = newCh[++newStartIdx]
                } else {
                    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
                    newStartVnode = newCh[++newStartIdx]
                }
            }
        }
    }
    if (oldStartIdx > oldEndIdx) {
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
}
  • checkDuplicateKeys
  • findIdxInOld


reactivateComponent 承接上文 createComponent


第 6259 行至第 6561 行


  • patchVnode // 如果符合sameVnode,就不会渲染vnode重新创建DOM节点,而是在原有的DOM节点上进行修补,尽可能复用原有的DOM节点。
  • invokeInsertHook
  • isRenderedModule
  • hydrate
  • assertNodeMatch
  • patch // !important: patch的本质是将新旧vnode进行比较,创建、删除或者更新DOM节点/组件实例


阶段小结


Vue 的核心思想:组件化。


这一部分是关于构建组件树,形成虚拟 dom ,以及非常重要的 patch 方法。


再来亿遍:


  1. 原因:当修改某条数据的时候,这时候js会将整个DOM Tree进行替换,这种操作是相当消耗性能的。所以在Vue中引入了Vnode的概念:Vnode是对真实DOM节点的模拟,可以对Vnode Tree进行增加节点、删除节点和修改节点操作。这些过程都只需要操作VNode Tree,不需要操作真实的DOM,大大的提升了性能。修改之后使用diff算法计算出修改的最小单位,在将这些小单位的视图进行更新。
  2. 原理:data中定义了一个变量a,并且模板中也使用了它,那么这里生成的Watcher就会加入到a的订阅者列表中。当a发生改变时,对应的订阅者收到变动信息,这时候就会触发Watcher的update方法,实际update最后调用的就是在这里声明的updateComponent。 当数据发生改变时会触发回调函数updateComponent,updateComponent是对patch过程的封装。patch的本质是将新旧vnode进行比较,创建、删除或者更新DOM节点/组件实例。


联系前后QA


Q:vue.js 同时多个赋值是一次性渲染还是多次渲染DOM?

A:官网已给出答案:cn.vuejs.org/v2/guide/re…


可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。


这样是不是有种前后连贯起来的感觉,原来 nextTick 是这样子的。


第 6566 行至第 7069 行


  • directives // 官网:cn.vuejs.org/v2/guide/cu…
  • updateDirectives // 更新指令
  • _update
  • normalizeDirectives // 统一directives的格式
  • getRawDirName // 返回指令名称 或者属性name名称+修饰符
  • callHook$1 //触发指令钩子函数
  • updateAttrs // 更新属性
  • setAttr // 设置属性
  • baseSetAttr
  • updateClass // 更新样式
  • klass
  • parseFilters // 处理value 解析成正确的value,把过滤器 转换成 vue 虚拟dom的解析方法函数 比如把过滤器 ' ab | c | d' 转换成 _f("d")(_f("c")(ab))
  • wrapFilter // 转换过滤器格式
  • baseWarn // 基础警告
  • pluckModuleFunction //循环过滤数组或者对象的值,根据key循环 过滤对象或者数组[key]值,如果不存在则丢弃,如果有相同多个的key值,返回多个值的数组
  • addProp //在虚拟dom中添加prop属性
  • addAttr //添加attrs属性
  • addRawAttr //添加原始attr(在预转换中使用)
  • addDirective //为虚拟dom 添加一个 指令directives属性 对象
  • addHandler // 为虚拟dom添加events 事件对象属性


前面围绕“指令”和“过滤器”的一些基础工具函数。

后面围绕为虚拟 dom 添加属性、事件等具体实现函数。


第 7071 行至第 7298 行


  • getRawBindingAttr
  • getBindingAttr //  获取 :属性 或者v-bind:属性,或者获取属性 移除传进来的属性name,并且返回获取到 属性的值
  • getAndRemoveAttr // 移除传进来的属性name,并且返回获取到 属性的值
  • getAndRemoveAttrByRegex
  • rangeSetItem
  • genComponentModel // 为虚拟dom添加model属性


/*
    * Parse a v-model expression into a base path and a final key segment.
    * Handles both dot-path and possible square brackets.
    * 将 v-model 表达式解析为基路径和最后一个键段。
    * 处理点路径和可能的方括号。
    */
  • parseModel //转义字符串对象拆分字符串对象  把后一位key分离出来


// 如果数据是object.info.name的情况下 则返回是 {exp: "object.info",key: "name"} // 如果数据是object[info][name]的情况下 则返回是 {exp: "object[info]",key: "name"}


  • next
  • eof
  • parseBracket //检测 匹配[] 一对这样的=括号
  • parseString // 循环匹配一对''或者""符号


这一部分包括:原生指令 v-bind 和为虚拟 dom 添加 model 属性,以及格式校验工具函数。


第 7300 行至第 7473 行


  • model
  • genCheckboxModel // 为input type="checkbox" 虚拟dom添加 change 函数 ,根据v-model是否是数组,调用change函数,调用 set 去更新 checked选中数据的值
  • genRadioModel // 为虚拟dom  inpu标签 type === 'radio' 添加change 事件 更新值
  • genSelect // 为虚拟dom添加change 函数 ,change 函数调用 set 去更新 select 选中数据的值
  • genDefaultModel //  如果虚拟dom标签是  'input' 类型不是checkbox,radio 或者是'textarea' 标签的时候,获取真实的dom的value值调用 change或者input方法执行set方法更新数据

参考link


阶段小结


  • v-bind、v-model


区别:

  1. v-bind 用来绑定数据和属性以及表达式,缩写为':'
  2. v-model 使用在表单中,实现双向数据绑定的,在表单元素外使用不起作用


Q:你知道v-model的原理吗?说说看

A: v-model本质上是语法糖,即利用v-model绑定数据,其实就是既绑定了数据,又添加了一个input事件监听 link


  • 自定义指令钩子函数


一个指令定义对象可以提供如下几个钩子函数 (均为可选):

1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
3. update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
4. componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
5. unbind:只调用一次,指令与元素解绑时调用。


  • 指令钩子函数会被传入以下参数:
1. el:指令所绑定的元素,可以用来直接操作 DOM 。
2. binding:一个对象,包含以下属性:
     name:指令名,不包括 v- 前缀。
     value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
     oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
     expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
     arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
     modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
3. vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
4. oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。


【译】vue 自定义指令的魅力


相关文章
|
1天前
|
JavaScript 开发者
|
1天前
|
前端开发 JavaScript 开发者
|
1天前
|
JavaScript
【小白懂系列】Vue CLi脚手架创建第一个VUE项目
【小白懂系列】Vue CLi脚手架创建第一个VUE项目
13 2
|
1天前
|
存储 消息中间件 JavaScript
vue组件传值的12种方式
【10月更文挑战第1天】
12 1
|
1天前
|
缓存 JavaScript 前端开发
qiankun 微应用vue接入到基座
【10月更文挑战第4天】
|
1天前
|
缓存 监控 JavaScript
如何提高 Vue Render 函数的性能?
【10月更文挑战第4天】
9 0
|
1天前
|
JavaScript
|
1天前
|
JavaScript 开发者
Vue Render函数
【10月更文挑战第4天】
8 0
|
1天前
|
存储 JSON JavaScript
Vue基本学习
Vue基本学习
7 0
|
1天前
|
JavaScript
【有手就行系列】Vue快速入门案例
【有手就行系列】Vue快速入门案例
8 0