精华内容
下载资源
问答
  • Vue源码笔记

    2021-04-10 21:22:52
    Vue 源码笔记 使用 import Vue from 'vue'; new Vue({ render: h => h(component), // router }).$mount('#app'); Vue 实例 initData function initData { let data = vm.$options.data // 组件复用,不为...

    Vue 源码笔记

    使用

    import Vue from 'vue';
    
    new Vue({
      render: h => h(component),
      // router
    }).$mount('#app');
    

    Vue 实例

    在这里插入图片描述

    initData

    function initData {
      let data = vm.$options.data
      // 组件复用,不为函数,实例保持同一个对象的引用,导致数据污染
      data = vm._data = typeof data === 'function'
        ? getData(data, vm)   // data.call(vm, vm)
        : data || {}
      
      // 数据响应式处理
      observe(data, true /* asRootData */)  
    }
    

    initComputed

    在这里插入图片描述

    function initComputed () {
      const watchers = vm._computedWatchers = Object.create(null)
      // 每个 computed 创建一个 watcher
      watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            { lazy: true }  // this.dirty = this.lazy = true
       )
      defineComputed()  
    }
    
    function  defineComputed () {
      const shouldCache = !isServerRendering()  
      sharedPropertyDefinition.get = shouldCache
          ? createComputedGetter(key)
          : createGetterInvoker(userDef)
        sharedPropertyDefinition.set = noop  
      
      // 拦截 get  this.xxx 触发
      Object.defineProperty(target, key, sharedPropertyDefinition)  
    }
    
    function createComputedGetter () {
       return function computedGetter () {
         const watcher = this._computedWatchers && this._computedWatchers[key]
         // true, 计算属性需要重新计算
         if (watcher.dirty) {
            watcher.evaluate()
            // this.get-> pushTarget(computed-watcher) 当前 Dep.target 指向 computed-watcher
            // -> this.getter.call(vm, vm) 即 this.xxx 触发 data.get data-dep 收集到 computed-watcher, computed-watcher 收集 data-dep
            // popTarget() 当前 Dep.target 指向 render-watcher
            
            // this.dirty = false  
            // data set, data dep 中 watcher update 时 this.dirty = true 重新计算 
          }
           
          if (Dep.target) {
            // computer dep 收集到 render-watcher
            // render-watcher 不收重复 dep  
            watcher.depend()
          }
          return watcher.value  
       } 
    } 
    

    computed 、watch 对比

    • computed 具有缓存,默认只有 getter
    • watch 可以定义函数

    KeepAlive

    // 挂载组件 KeepAlive
    extend(Vue.options.components, builtInComponents)
    
    const slot = this.$slots.default
    const vnode: VNode = getFirstComponentChild(slot)
    
    if (cache[key]) {
      vnode.componentInstance = cache[key].componentInstance
      // make current key freshest
       remove(keys, key)
       keys.push(key)
    } else {
       cache[key] = vnode
       eys.push(key)
       // prune oldest entry
       if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
    }
    

    path 中 Diff

    vue 中 Vue.prototype.patch 实现

    diff 只会同层级进行,不会跨层级比较

    在这里插入图片描述

    // 新旧节点对比
    function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
        // 
        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, vnodeToMove, refElm
    
        // removeOnly is a special flag used only by <transition-group>
        // to ensure removed elements stay in correct relative positions
        // during leaving transitions
        const canMove = !removeOnly
    
        if (process.env.NODE_ENV !== 'production') {
          checkDuplicateKeys(newCh)
        }
    
        while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
          if (isUndef(oldStartVnode)) {
            oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
          } else if (isUndef(oldEndVnode)) {
            oldEndVnode = oldCh[--oldEndIdx]
          } else if (sameVnode(oldStartVnode, newStartVnode)) {
            patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
            oldStartVnode = oldCh[++oldStartIdx]
            newStartVnode = newCh[++newStartIdx]
          } else if (sameVnode(oldEndVnode, newEndVnode)) {
            patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
            oldEndVnode = oldCh[--oldEndIdx]
            newEndVnode = newCh[--newEndIdx]
          } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
            //排序  
            patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
            canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
            oldStartVnode = oldCh[++oldStartIdx]
            newEndVnode = newCh[--newEndIdx]
          } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
            // 排序   
            patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
            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]
              : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
            if (isUndef(idxInOld)) { // New element
              createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
            } else {
              // 具有相同节点  
              vnodeToMove = oldCh[idxInOld]
              if (sameVnode(vnodeToMove, newStartVnode)) {
                // 对比移动节点  
                patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
                oldCh[idxInOld] = undefined
                // 插入节点
                canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
              } else {
                // same key but different element. treat as new element
                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
              }
            }
            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(oldCh, oldStartIdx, oldEndIdx)
        }
      }
    

    设置 key 和不设置 key 的区别

    • 不设置 key,新旧节点头尾两端相互比较
    • 设置 key,除了头尾两端比较外,还会使用 key 生成的对象 createKeyToOldIdx 中查找匹配的阶段,设置 key 可以高效利用 dom
    function createKeyToOldIdx (children, beginIdx, endIdx) {
      let i, key
      const map = {}
      for (i = beginIdx; i <= endIdx; ++i) {
        key = children[i].key
        if (isDef(key)) map[key] = i
      }
      return map
    }
    

    $nextTick

    export function nextTick (cb?: Function, ctx?: Object) {
      let _resolve
      callbacks.push(() => {
        if (cb) {
          try {
            cb.call(ctx)
          } catch (e) {
            handleError(e, ctx, 'nextTick')
          }
        } else if (_resolve) {
          _resolve(ctx)
        }
      })
      if (!pending) {
        pending = true
        timerFunc()
      }
      // $flow-disable-line
      if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
          _resolve = resolve
        })
      }
    }
    
    // 事件循环,宏任务、微任务,栗子 for(let i) { setTimeout(()=>{ console.log(i) }, 0) }
     timerFunc = () => {
        p.then(flushCallbacks)
        if (isIOS) setTimeout(noop)
      }
     timerFunc = () => {
        setImmediate(flushCallbacks)
     }
     timerFunc = () => {
        setTimeout(flushCallbacks, 0)
      }
     
     function flushCallbacks () {
      pending = false
      const copies = callbacks.slice(0)
      callbacks.length = 0
      for (let i = 0; i < copies.length; i++) {
        copies[i]()
      }
    }
     
    
    展开全文
  • vue源码笔记

    2019-04-13 21:34:48
    仿vue实现的mvvm库地址 https://github.com/DMQ/mvvm 2. 准备知识 2.1 将伪数组转为真数组 /* 将伪数组转换为真数组 */ const lis = document.getElementsByTagName('li') // lis是伪数组,不能使用真...

    2019/4/13 10:26:19

    1. 说明

    2. 准备知识

    2.1 将伪数组转为真数组

    
    	/* 
    	    将伪数组转换为真数组
    	 */
    	const lis = document.getElementsByTagName('li')
    	// lis是伪数组,不能使用真数组的各种方法,如foreach等
    	console.log('lis', lis, lis[1].innerHTML, lis instanceof Array)
    	
    	// 数组的slice方法会对已有的数组进行截取,并返回截取后的新数组
    	// call指定this对象是谁
    	const lis2 = Array.prototype.slice.call(lis)
    	// 将伪数组lis转换为真数组,目的是可以使用数组各种方法了
    	console.log('lis2', lis2, lis2 instanceof Array)
    
    

    2.2 常用的node节点类型

    • document文档
    • element元素
    • attr属性
    • text文本

    代码测试

    <div id="title">我的测试</div>

    
        /* 
            得到节点类型
         */
        const elementNode = document.getElementById('title')
        const attrNode = elementNode.getAttributeNode('id')
        const textNode = elementNode.firstChild  
    
        console.log('节点', elementNode, attrNode, textNode)
        console.log('节点类型', elementNode.nodeType, attrNode.nodeType, textNode.nodeType)
        console.log('节点名称', elementNode.nodeName, attrNode.nodeName, textNode.nodeName)
    	/*
    	节点 <div id=​"title">​我的测试​</div>​ id=​"title" "我的测试"
    	节点类型 1 2 3
    	节点名称 DIV id #text
    	*/
    
    

    2.3 给对象添加属性

    
        /* 
            给对象添加属性(指定描述符)
         */
        const obj1 = {firstName: 'zeng',lastName: 'jie'}
        const obj2 = {firstName: 'zeng',lastName: 'jie'}
        const obj3 = {firstName: 'zeng',lastName: 'jie'}
    
    
        // 这个API IE8浏览器是不能执行的,但是这个API是vue源码的核心,所以vue不支持IE8以下
        Object.defineProperty(obj1,'fullName', {})
    
        Object.defineProperty(obj2,'fullName', {
            // 回调函数,根据对象其他属性变化动态计算这个属性
            get(){
                return this.firstName + '-' + this.lastName
            },
            // 回调函数,监视这个属性变化,更改其他属性的值
            set(value){
                this.firstName = value
                this.lastName = value
            }
        })
        obj2.firstName = 'jay'
    
        Object.defineProperty(obj3,'fullName', {
            // 是否可以重新配置属性, 默认为false
            configurable:false,
            // 是否可以枚举,默认为false
            enumerable:true,
    		// 是否可以修改, 默认为false
    		writable:false,
            value:'peng-yy'
        })
    
        console.log(obj1.fullName, obj2.fullName, obj3.fullName)
    	//  undefined  jay-jie  peng-yy
    
        console.log('枚举', Object.keys(obj1), Object.keys(obj2), Object.keys(obj3))
    	// 枚举 (2) ["firstName", "lastName"] (2) ["firstName", "lastName"] (3) ["firstName", "lastName", "fullName"]
    
    	console.log('是否含有该属性', obj1.hasOwnProperty('fullName')) // true
    
    

    2.4 DocumentFragment

    • DocumentFragments 是DOM节点。它们不是主DOM树的一部分。
    • 文档片段存在于内存中,并不在DOM树中

    代码测试

    html代码

    
        <ul id="list">
            <li>test1</li>
            <li>test2</li>
            <li></li>
            <li></li>
        </ul>
    
    

    JS代码

    
    	<script>
    	    /* 
    	        DocumentFragment
    	    */
    
    	    const list = document.getElementById('list')
    	    
    	    // 1. 创建fragment
    	    const fragment2 = document.createDocumentFragment()
    	
    	    // 2. 取出list子节点依次放入fragment中(最后list子节点为空)
    	    let child
    	    while (child=list.firstChild) {
    	        fragment2.appendChild(child) // 一个节点只能有一个父亲,先将listd的child移除,再放到fragment2的子节点中
    	    }
    	
    	    console.log(fragment2)
    	    console.log(fragment2.childNodes)
    	
    	    // 3. 更新 fragment 中所有子节点的文本
    	    Array.prototype.slice.call(fragment2.childNodes).forEach(node => {
    	        if (node.nodeType === 1) { // 判断为元素节点
    	            node.textContent = 'faefas'
    	        }
    	    })
    	    
    	    list.appendChild(fragment2)
    	</script>
    
    

    3. 数据代理

    1. 数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
    2. Vue数据代理:通过 vm 对象来代理 data 对象中所有属性的操作
    3. 好处:更方便的操作data中的数据
    4. 基本实现流程
      1. 通过 Object.defineProperty() 给 vm 添加与 data 对象的属性对应的属性描述符
      2. 所有添加的属性都包含 getter/setter
      3. getter/setter 内部去操作 data 中对应的属性数据
    
    	// 数据代理实现的核心
    	Object.defineProperty(me,key, {
    		configurable:false,
    		enumerable:true,
    	    // 回调函数,当通过vm.key读取属性值时,返回_data对象中的对应属性值
    	    get(){
    	        return me._data[key]
    	    },
    	    // 回调函数,当通过vm.key改变属性值时,实时更改_data对象中对应的属性值
    	    set(value){
    	        me._data[key] = value
    	    }
    	})
    
    

    4. 模板解析

    4.1 关键三步骤

    1. 取出 el 元素中所有子节点保存到一个 fragment 对象中
      • this.$fragment = this.node2Fragment(this.$el);
    2. 编译 fragment 中所有层次子节点(递归)
      • this.init();
      • 对大括号表达式文本节点进行解析
      • 对元素节点的指令属性进行解析
        • 事件指令解析
        • 一般指令解析
    3. 将编译好的 fragment 添加到页面的 el 元素中
      • this.$el.appendChild(this.$fragment);

    node2Fragment函数的定义

    
    	node2Fragment(el) {
    	    var fragment = document.createDocumentFragment(),
    	        child;
    	
    	    // 将原生节点拷贝到fragment
    	    while (child = el.firstChild) {
    	        fragment.appendChild(child);
    	    }
    	
    	    return fragment;
    	},
    
    

    4.2 模板解析之大括号表达式解析

    1. 根据正则对象得到匹配出的表达式字符串
      • var reg = /\{\{(.*)\}\}/
    2. 从 data 中取出表达式对应的属性值
    3. 将属性值设置为文本节点的 textContent

    4.3 模板解析之事件指令解析

    1. 遍历元素节点中的属性节点,如果有v-xxx的属性名,并且是v-on
    2. 从指令中取出事件名, 如为’click’、‘blur’
    3. 根据指令的值从 methods 中得到对应的事件处理函数对象
    4. 给当前元素节点绑定指定事件名和回调函数的 dom 事件监听
    5. 指令解析完后,移除此指令属性
      • (因此,在浏览器中看一个页面的源代码,标签中看不到 v-on之类的属性)

    4.4 模板解析之一般指令解析

    1. 遍历元素节点中的属性节点,如果有v-xxx的属性名,如v-textv-html
    2. 得到指令名和指令值,如 v-text:'msg'
    3. 从 data 中指令值得到对应的值
    4. 根据指令名确定需要操作元素节点的对应属性
      1. v-text 对应操作节点的 textContent 属性
      2. v-html 对应操作节点的 innerHTML 属性
    5. 将得到的 data 值设置到对应属性上
    6. 移除元素的指令属性
    展开全文
  • 本阅读笔记基于Vue 2.6.10,主要记录了自己对Vue源码的一些理解,并参考了刘博文著的《深入浅出Vue.js》一书以及csdn博主恰恰虎的Vue源码学习系列文章。由于能力有限,笔记中对源码的认识可能不够深入,如果感兴趣,...

    终于开启了Vue源码的阅读之旅!虽然只有三个月左右的使用经验,阅读源码时会比较吃力,但是无所谓,当我们欣赏一件“艺术品”时,重点在于是否用心去欣赏,而不在于欣赏到什么程度。

    本阅读笔记基于Vue 2.6.10,主要记录了自己对Vue源码的一些理解,并参考了刘博文著的《深入浅出Vue.js》一书以及csdn博主恰恰虎的Vue源码学习系列文章。由于能力有限,笔记中对源码的认识可能不够深入,如果感兴趣,可以去官网下载Vue源码进行阅读,github地址为https://github.com/vuejs/vue

    下载的源码在webstorm中打开是这样的,让我们挨个分析,找出需要研究的重点代码:

    1. 项目总体结构

    在这里插入图片描述
    各个文件夹的说明如下:

    vue-dev
      |- .circleci //配置文件,可以忽略
      |- .github   //github相关配置,可以忽略
      |- benchmarks//性能测试代码
      |- dist      //构建后的各个版本的Vue
      |- examples  //官方提供的Vue示例
      |- flow      //facebook的静态类型检查工具
      |- node_modules //项目的依赖库
      |- packages  //存放独立发布的包的目录
      |- scripts   //构建命令及相关配置
      |- src   //Vue源码,重点关注对象
      |- test      //测试文件 
      |- types     //基于typescript的类型声明
         ...       //配置文件,可以忽略 
         package.json  //项目依赖描述
         ...       //配置文件,可以忽略
    

    对项目有一定了解的人应该都很容易看出来,src文件夹存放的就是整个项目的源码,而其他所有的文件都是它的衍生品。阅读源码只需要把目光集中在这里即可。

    下面我们打开src目录,看看各个文件夹的作用:

    2. src子目录概览

    首先打开src下的一级子目录:
    在这里插入图片描述
    各个文件夹的作用如下:

    src
      |- compiler //Vue编译器,用于解析Vue模板:template
      |- core     //Vue核心代码。Vue对象的构造和初始化,
                  //全局API,响应式系统构建,虚拟DOM
                  //的构建都在这里定义,并且是平台无关的
      |- platforms//平台相关的代码,主要指Vue支持的三种平台:
      			  //web(浏览器),weex和server(服务端渲染)
      |- server   //服务端渲染相关代码
      |- sfc      //单文件组件(.vue文件)的解析
      |- shared   //全局方法和共享的静态变量等
    

    compiler:
    上述子目录中,compiler是Vue的编译器,负责将模板template解析成渲染函数render,比如下面的模板:

    <div id="app">
      <ul>
        <li v-for="item in items">
          itemid:{{item.id}}
        </li>
      </ul>
    </div>
    

    会被Vue的编译器编译为下面的渲染函数:

    function render(){
      with(this){
      return 
         _c('div',{
             attrs:{"id":"app"}
         },
         [_c('ul',_l((items),function(item){
           return _c('li',
               [_v("\n itemid:"+_s(item.id)+"\n ")]
           )}
          )
         )]
       )}
    }
    

    执行完编译过程得到这个函数之后,Vue就不再需要模板了。Vue只需要调用这个函数,就可以生成该组件对应的虚拟DOM节点(VNode)。_c,_l,_v,_s等都是Vue内部定义的渲染函数会用到的函数。详细列表可以参考src/core/instance/render-helpers/index.js:
    在这里插入图片描述
    core:
    这里是Vue最为核心的代码。Vue构造函数定义及对象初始化、响应式系统的搭建、全局API定义、平台无关的组件keep-alive的定义、虚拟DOM树的构建等都在这里完成。这里定义的属性和方法是平台无关的,即它们在Vue支持的任意一个平台都是一致的,后面会继续展开介绍。

    platforms:
    Vue被设计为可以在多个平台上运行,这包括web(浏览器)、weex(阿里巴巴的一款跨平台框架)和server(服务端渲染)。对于Vue而言,与平台无关的代码被放到core中,而与平台相关的则放到platforms中定义。例如,使用服务端渲染时不会用到transition组件(服务端渲染不提供类似的过渡效果),那么这个组件的定义就被放到了platforms/web下面去定义,而不会出现在Vue的核心代码core中。而keep-alive组件则是在三个平台下都可以使用的,因此keep-alive在core/components中定义。

    server:
    服务端渲染相关文件,定义了server平台使用的相关方法,由于我们主要探讨Vue在web中的使用,这里将不再详述。

    sfc:
    单文件组件编译。在使用Vue构建单页应用时,我们通常会用到单文件组件,也就是vue后缀的文件,如button.vue。这类文件的结构通常如下:

    //模板定义
    <template>
      ...
    </template>
    //脚本代码,data、methods等都在这里定义
    <script>
      ...
    </script>
    //样式定义,scoped表示该样式只对当前组件有效,
    //如果去掉会泄露为全局样式
    <style scoped>
      ...
    </style
    

    sfc下的代码负责将上述单文件组件解析为对象的形式。

    shared:
    全局方法和共享的静态变量。如shared/constants中定义了生命周期钩子:
    在这里插入图片描述
    shared/utils.js则定义了大量的工具方法,如isUndef、isObject、hasOwn等等。

    从源码阅读的角度来说,我们最应该关注的是compiler、core和platforms这三个目录。compiler负责模板编译,core负责Vue核心功能的搭建,platforms负责处理平台差异。

    下面我们就集中来看这三个目录。

    3. compiler/core/platforms结构

    3.1 src/compiler

    上面讲到,compiler定义了Vue编译器,下面是compiler的结构:
    在这里插入图片描述
    Vue编译模板分为三步:

    1. 解析HTML,生成一棵抽象语法树AST。
    2. 标记静态节点。
    3. 生成渲染函数。

    对应上述目录结构来看:
    compiler/parser下的文件负责第一步,如(该例子来自csdn恰恰虎的文章:VUE源码学习第七篇–编译(parse)):

    <div id="app">
      <ul>
        <li v-for="item in items">
          itemid:{{item.id}}
        </li>
      </ul>
    </div>
    

    将被解析为:

    {
        "type": 1,
        "tag": "div",
        "attrsList": [
        {
           "name": "id",
           "value": "app"
        }
        ],
        "attrsMap": {
          "id": "app"
        },
        "children": [
        {
          "type": 1,
          "tag": "ul",
          "attrsList": [],
          "attrsMap": {},
          "parent": {
          "$ref": "$"
        },
          "children": [
          {
            "type": 1,
            "tag": "li",
            "attrsList": [],
            "attrsMap": {
              "v-for": "item in items"
            },
            "parent": {
              "$ref": "$[\"children\"][0]"
            },
            "children": [
              {
                "type": 2,
                "expression": "\"\\n itemid:\"+_s(item.id)+\"\\n \"",
                "tokens": [
                  "\n      itemid:",
                {
                  "@binding": "item.id"
                },
                "\n    "
                ],
                "text": "\n      itemid:{{item.id}}\n    "
                }
            ],
            "for": "items",
            "alias": "item",
            "plain": true
            }
                ],
                "plain": true
            }
        ],
        "plain": false,
        "attrs": [
            {
                "name": "id",
                "value": "\"app\""
            }
        ]
    }
    

    该对象就被称为一棵抽象语法树。实际上就是对模板字符串的一种结构化描述。

    compiler/optimizer.js负责第二步,标记静态节点。所谓标记静态节点,就是将不需要更新的节点标记出来,提高虚拟DOM的比对速度。比如:

    <div>
      <div>
        <p>{{message}}</p>
      </div>
      
      <div>
        <p>Hello World!</p>
      </div>
    </div>
    

    这里的第一个段落p绑定到了变量message,因此它的内容将随着message值的变化而变化。而第二个段落p里面是静态文本,那么无论业务数据如何变化,它渲染的内容都是不会变的,所以第二个p和包裹它的div将被标记为静态节点,进行虚拟DOM比对时将跳过该节点(其中包裹这个p标签的div会被标记为静态根节点,因为它的所有子节点都是静态节点)。

    compiler/codegen(code generate,代码生成)负责根据上述经过静态标记的抽象语法树生成渲染函数。生成结果即为上文compiler简介中的那个render函数:

    function render(){
      with(this){
      return 
         _c('div',{
             attrs:{"id":"app"}
         },
         [_c('ul',_l((items),function(item){
           return _c('li',
               [_v("\n itemid:"+_s(item.id)+"\n ")]
           )}
          )
         )]
       )}
    }
    

    得到这个渲染函数后,模板就已经没用了,Vue会为当前对象新增一个方法:vm._render用于保存该渲染函数。构建虚拟DOM就借助于该渲染函数。

    注意:
    运行Vue代码并不总是需要编译器。web平台的Vue分为两个版本:完整版本和运行时版本。两者的区别就是前者包含了编译器,而后者不包含编译器。通常在两种情况下我们不需要编译器:

    1. 手写渲染函数。
    2. 使用打包工具打包项目。

    我们知道,编译器的作用就是将模板编译为渲染函数。因此如果我们选择手写渲染函数,就可以使用运行时版本的Vue,它比完整版的Vue代码少了上千行,可以有效压缩框架体积。另外,如果使用打包工具如webpack进行打包,这些打包工具会在打包时提前将模板编译为渲染函数,因此打包后的文件使用的就是运行时版本。

    3.2 src/core

    这一部分是Vue的核心代码,我们来看目录结构(由于文件较多,这里不全部展开):
    在这里插入图片描述
    components,它里面定义了keep-alive组件,由于在各个平台都可以使用该组件,因此它被定义在核心代码中。

    global-api,顾名思义,它定义了一些全局api,包括:Vue.component(全局组件注册)、Vue.directive(定义全局指令)、Vue.filter(全局过滤器)、Vue.extend(Vue继承接口)、Vue.mixin(Vue混入)、Vue.use(插件安装入口)。

    instance,该文件夹定义了Vue实例的相关属性和方法。首先在instance/index.js中定义了Vue构造函数,然后使用mixin(混入)向Vue原型对象上注入了init(初始化)、state(状态)、events(事件)、lifecycle(生命周期)、render(渲染)相关的所有方法。使用new Vue({ … })构造Vue实例对象时,也是在这里进行初始化的,后面的文章将重点讨论。

    observer(观察者),构建响应式系统的相关方法。通过Object.defineProperty这个原生api构建起来的响应式系统,非常值得学习。最重要的概念包括:

    1. Observer:观察者,用于监听数据变化,并通知发布者。
    2. Dep:发布者,用于收集依赖,数据变化时通知watcher更新视图。
    3. Watcher:订阅者,管理视图更新。
    4. queueWatcher:视图更新队列。当数据变化时,Vue默认不会立即更新视图,而是暂时放入一个微任务队列中,等数据全部更新完毕再去更新视图。

    util,工具方法,如debug接口,error工具,环境参数接口等,这里不再详述。

    vdom,虚拟DOM相关代码,vnode.js定义了虚拟DOM节点的结构,patch.js定义了比对和修补DOM树的相关方法,其余文件通过这两个文件来构建和更新虚拟DOM树。

    3.3 src/platfroms

    这里根据平台的不同,向核心版本的Vue添加了一些平台相关的方法和属性。目录结构如下:
    在这里插入图片描述
    这里分为两个目录:web和weex,我们这里不探讨weex平台下Vue的使用,因此该文件夹可以暂时忽略。

    web根目录下有五个带entry前缀的文件,它们是使用打包工具生成Vue代码时的五个入口文件(也就是说打包工具从这里开始打包),不同的入口文件引用的模块不同,最终生成的代码版本也不一样。对应关系如下:

    1. entry-compiler,生成独立的Vue编译器。
    2. entry-runtime,生成运行时版本的Vue,不包含编译器。
    3. entry-runtime-with-compiler,生成完整版Vue。
    4. entry-server-basic-renderer,生成服务端Vue的基本版本。
    5. entry-server-renderer,生成服务端Vue的完整版本。

    除了这五个入口文件,还有4个文件夹:compiler、runtime、server和util。

    compiler,编译器,这里只是从src/compiler/index.js中导入核心版本的编译器,然后传入web平台相关参数,生成用于web平台下的编译器。

    runtime,运行时Vue,从src/core/index.js导入核心版本的Vue,向其扩展一些只能用于web平台下的方法和组件,得到可以运行在web平台上的Vue。

    server,为服务端渲染扩展的一些只能在服务端使用的方法和指令等,关于服务端渲染这里不再详述。

    util,平台处理部分用到的工具方法。

    Vue的整体架构和打包过程图解

    Vue源码的整体结构如下(图片参照深入浅出Vue.js一书手绘):
    在这里插入图片描述
    Vue完整版的打包过程如下(同样参考自深入浅出Vue.js):
    在这里插入图片描述

    总结

    本文是对Vue源码项目结构的大致分析,暂未涉及代码的实现。后面将分别介绍Vue的构造和初始化、响应式原理、编译器、虚拟DOM。由于本人水平有限,可能无法过于深入的讲解它们的实现细节(仅web平台下的完整版Vue就有一万三千多行代码,探究其中的所有细节并没有太大意义),因此将以介绍其实现原理为主。希望在本系列文章完结时,我本人能对Vue的源码有更进一步的认识。

    文章链接

    Vue源码笔记之项目架构
    Vue源码笔记之初始化
    Vue源码笔记之响应式系统
    Vue源码笔记之编译器
    Vue源码笔记之虚拟DOM

    展开全文
  • 这次开始学习vue源码的知识,并且把学习的内容都写成笔记,方便以后复习。 本节的内容是:了解vue源码入口文件。 入口文件 我们上一节分析了Vue.js的构建过程,我们这次分析的是 Runtime + Compiler 构建出来的 Vue...

    这次开始学习vue源码的知识,并且把学习的内容都写成笔记,方便以后复习。

    本节的内容是:了解vue源码入口文件。

    入口文件

    我们上一节分析了Vue.js的构建过程,我们这次分析的是 Runtime + Compiler 构建出来的 Vue.js,它的入口是src/platforms/web/entry-runtime-with-compiler.js。

    但是这个js不是vue最开始的入口,这个文件里面从别的文件里面引用了Vue,并且在文件的最后导出的Vue'

    然后我们一层一层向上找,最后发现vue的入口是src/core/index.js

    Vue本质是一个函数,下面一些Mixin函数都是在向Vue的原型上扩充方法,我们在后续的笔记中会分析每个Mixin都是做什么的,在此就先略过。使用函数扩展原型是因为这种方式比较好扩展,可以把方法拆分到不同的文件中。

    另外一个注意的点是initGlobalAPI

    initGlobalAPI定义在 src/core/global-api/index.js,这里的内容也会在后面的笔记中详细的列出。

    所以Vue本质上就是一个用 Function 实现的 Class,然后它的原型 prototype 以及它本身都扩展了一系列的方法和属性,具体的内容我们后面再详细介绍。

    转载于:https://juejin.im/post/5d0b91fee51d4510bf1d6694

    展开全文
  • 这次开始学习vue源码的知识,并且把学习的内容都写成笔记,方便以后复习。 本节的内容是:了解vue源码目录设计,为后面做源码分析做好准备。 1.目录结构 src ├── compiler # 编译相关 ├── core # 核心代码 ...
  • Vue 源码笔记系列2>依赖收集的触发 · 语雀​www.yuque.com上一章,我们讲了 Vue 依赖收集的准备工作。我们知道,依赖收集一定是触发了我们给 data 定义的 get 属性。回顾一下我们定义的 get 属性:// src/core/...
  • vue源码笔记(1)

    2020-05-22 19:20:39
    Vue源码解析 前言 Vue 数据和视图 UI = render(state) state:状态,包括用户操作和数据变化 UI:页面视图 render:vue扮演得就是这个角色,在state变化之后经过一系列得加工处理,将变化反应在视图UI上。 object...
  • Vue源码笔记之编译器

    千次阅读 2019-09-08 10:27:13
    如果单从Vue的角度来说,template(模板)并没有存在的必要。在Vue中,真正要使用的是从template编译生成的渲染函数,利用它可以直接生成虚拟DOM。实际上Vue向我们提供了直接书写渲染函数的能力(这样就可以不用写...
  • Vue 源码笔记系列6>计算属性 computed 的实现 · 语雀​www.yuque.com上一章我们已经学习过 watch,这一章就来看一下计算属性 computed 的实现。2. 流程图老规矩,先上图。但是本期的流程图比较简略,因为 ...
  • Vue源码笔记之初始化

    2019-08-31 17:04:34
    Vue自身的构造。主要是关于如何向Vue的原型prototype添加方法。 Vue实例的初始化。即new Vue({ … })时执行的操作。 Vue自身的构造过程 1. 定义构造函数 当我们像下面一样引入Vue.js文件时: <script src=...
  • Vue源码笔记之虚拟DOM

    2019-09-16 12:55:21
    Vue源码笔记之响应式系统 一文中我们说到,Vue自动更新视图的基础是响应式系统,即使没有虚拟DOM,响应式系统也足以实现视图的自动更新(早期的Vue就是这样做的)。在响应式系统中,当数据发生变化时,订阅者watcher...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,033
精华内容 413
关键字:

vue源码笔记

vue 订阅