vue-vue2.0源码分析

一、vue2源码目录

  • compiler:将template编译成 render 函数。对于在线编译,render 在运行时执行,执行时会生成 vnode
  • core:核心
  • platform:web平台、weex多平台、mpvue小程序端
  • server:服务端渲染
  • sfc:单文件处理,将.vue文件的template、script、style拆分
  • shared:工具、常量

二、compiler 运行时、编译时

  • 运行时:new Vue时产生一个对象在内存中维护,包含数据、方法、生命周期等,它们通过 数据与模板的绑定关系 维持vue应用的使用,被维持的绑定关系就是运行时。
  • 编译时:是创建对应关系的代码,编译的对象是template,分为离线编译和在线编译:
    • 离线编译:开发、打包的过程,将.vue业务代码编译成js render函数的过程。
    • 在线编译:上线的代码在运行时编译,new Vue代码在浏览器里跑的过程。

vue和react源码区别

  • react做运行时优化,所以react源码复杂
  • vue没有太多运行时优化但也很流畅,因为vue做编译时优化
  • 所以vue模板必须按它的规则去写,而react就很灵活

compiler编译步骤:

  1. 如果有render函数,那么已经是编译完成的,返回
  2. 判断template:
    1. 如果是string,判断 "#app" 还是 "
      ...
      ",分别进行处理
    2. 如果是DOM,获取innerHTML
    3. 如果是离线编译,还要判断编译环境,执行 complieToFunctions 函数
  3. 然后将template编译成函数。

parser:

compiler 的一个步骤,对模板进行 AST 分析(先分词,再做词法分析)。

  • html解析
    • 因为匹配是正则匹配,所以字符串越短,匹配效率越高。所以模板应该尽量小。
    • 匹配顺序:
      1. parseEndTag'
      2. doctype
      3. ie条件注释
      4. 注释
      5. parseStartTag '<'
  • filter解析
  • 分析 v-for 的 key。
  • 优化:
    • 判断静态节点,纯dom、文本、没有vue指令的是纯静态节点,且子节点均为静态节点

三、core 核心

  • component:模板编译代码
  • global-api:文件接口
  • instance:实例,处理初始化、状态、数据、生命周期、事件
  • observer:数据订阅
  • utils:方法
  • vdom:虚拟dom,使用虚拟dom的原因是,原生dom有很多无用的属性,占用太多内存

一、observer

observer 是 core 的核心。

  • defineReactive
    • 核心是创建vue data的监听,通过 Object.defineProperty 方法,为 vue 对象定义 get 和 set 方法。
      • get:第一次执行 get 方法将该数据对应的 watcher 绑定到对应的 dep上。此后均为返回值。
      • set:如果 newValue === value,则返回;否则设置新值,并更新对应的 dep。
      • 区分哪一个 vue data 对应哪一个 dep、watcher:data 本身对应 watcher,dep时在 defineReactive 中作为闭包保存的。
    • 对于数组,因为Array.prototype.__proto__ === Object.prototype,所以理论上也是可以监听的。但是对于新创建的数组下标无法监听,所以针对数组新增了 dependArray 方法。
    • 同样的,为对象添加新的属性,新增的属性也是无法被监听的。
    • 数组重写:
      • 原因:数组的操作是很昂贵的。当我给一个数组 unshift(value) 时,所有原来数组下标的值都要向后移动一位,相当于遍历一遍原始数组。在 vue 中,由于使用了 get 和 set 方法,就会触发多次执行,非常浪费资源。
      • 具体实现:判断浏览器是否支持 __proto__ 属性(比如Android UC不支持)。
        • 如果不支持(选),就找它的原型对象设置到 __proto__ 属性上。
        • 然后重写数组方法,包括 push、pop、shift、unshift、splice、sort、reverse等。因为这些方法会造成数组元素索引改变。
        • 对push、unshift、splice、reverse等会出现新增元素的方法,将新增元素设置为 observer 对象,然后手动更新 dep。
  • observer

    • 将一个数据修改为可观察数据,主要是为了解决数组和对象新增下标或属性时,新元素不是可观察数据的问题。 

    • 它会创建一个属性名为 __ob__ 的不可配置、不可枚举的对象,用于表示当前对象已经是响应式对象了。

    • 对于嵌套对象,递归调用。

  • watcher
    • watcher 是观察者模式的观察者,观察者都应该有回调函数,watcher 的作用就是,对 render 函数和 vnode之间做连接。而watcher 的回调就是执行render。
    • 创建 watcher 对象时,会使 Dep.target = this。然后在 watcher 构造函数中,使 this.value = this[name],这会触发 Object.defineProperty 的 get。在 get 中,将 watcher 添加到 dep中。
    • vue1.0 与 vue2.0的区别
      • vue1.x:
        • 一个指令对应一个 watcher。
        • 因为它精确定位,所以不需要 dom diff,所有的更改都位置都记录下来了,但这样的结果是维护成本高性能差。
      • vue2.x:
        • 一个组件对应一个 watcher。
        • 在render函数中,with(this) 的 this就是这个组件的observer对象。由于 render函数 返回了一串长长的 html 字符串,所以需要对它做dom diff 。
        • 于此同时,维护的 watcher 少很多。因此,组件的代码写的少点,会对运行时计算的压力降低。
  • Dep
    • dep是观察者模式中的发布者,可以有多个指令订阅它。
    • 一般来说,一个数据对应一个 dep ,一个 dep 可以有多个 watcher。
  • scheduler
    • 调度器,负责批处理功能。
    • 将本次更新全部放到一个执行队列中;对于任意数据,按照字段区分,设置等待;将这个执行队列 通过调用 nextTick 延迟执行。所以 vue 数据变化不是立刻执行的。
  • nextTick
    • 延迟到下一帧执行,核心是 timerFunc 函数, 本质是一个延迟函数,尝试先匹配 微任务 。
    • 判断是否支持 promise、mutationObserver、setImmidate,setTimeout 兜底;然后执行回调。
  • 小结:
    • core是vue代码的核心,而 observer 是 core 的核心。它利用 Object.defineProperty 实现对数据的操作拦截,然后将数据绑定到一个 由 观察者模式 为单元(watcher)组成的数据维护中心(Dep)。
    • 对于数组,新增或修改元素可能造成整个数组的元素重新排列,所以对数组进行了重写。所有新增的元素都调用 observer 方法,使其变为一个可观察的对象。
    • 对于任何数据,都进行递归操作,使其任意属性变得可以追踪。

二、components

keep-alive,保存的是 vnode 节点,而不是数据。

由于 vnode 节点比描述状态的数据大一些,所以 keep-alive 能够保存的数据大小有限,所以它存在取舍问题,一般舍弃最老的组件。

对于任意组件,无论是否被添加到 keep-alive 缓存列表中,重新访问时,都会把它设置为列表的结尾。

三、use

用于为vue设置插件,它维护一个插件队列,判断是否已存在,如果未存在,执行插件,并且添加到插件队列中。

四、vue运行流程总结

  1. 编译模板,无论是在线编译,还是离线编译,均会为每一个组件生成一个对应的 render 函数。并且,会根据节点的状态,为它们优化成静态节点 render 函数。

  2. defineReactive 函数利用 Object.defineProperty方法, 使 vue data 重写成可控对象。

    1. 在此方法内,创建一个用于双向数据绑定的、观察者模式中的发布者 dep。

    2. 在 get 方法中,判断是否为第一次访问数据,第一次访问数据时需要通知 dep 发布。

    3. 在 set 方法中,判断 newValue === oldValue,需要更新则通知 dep 发布。

  3. dep 作为 vue data 实现双向数据绑定功能中的发布者。每一个 dep 对应一个数据,维护所有与该数据有关的 watcher。

  4. 这些 render 函数在某个vue钩子的生命周期时被执行,此时会创建 watcher。

    1. watcher 作为 vue data 实现双向数据绑定功能中的观察者,每一个 watcher 对应一个组件。

    2. watcher 最主要的工作是维护 数据 与 dom 之间的渲染关系。

    3. 新创建的 watcher 会调用 get 方法,将触发 Object.defineProperty 的 get 方法,将 watcher 添加到对应的 dep中。

    4. watcher 的 get 方法还会尝试调用渲染函数,所以当创建 watcher 后,组件中的数据就已经被渲染了。如果是在线编译,第一次渲染会删除原生 dom ,此后均为 vnode。

  5. 首次渲染完成。。。

  6. vue data 在后续更新时不是立刻更新,而是将数据更新的过程添加到一个调度器中,实现批量更新而不是每次数据变化时更新 dom 。由 nextTick 执行回调批量更新。

五、性能优化总结

编译时优化:

  • 模板不宜过长:的在对模板进行 compiler 编译时,会使用正则表达式。由于正则表达式匹配的回溯性,过长的模板会造成编译性能问题。

运行时优化:

  • 模板不宜过长:compiler 将模板编译成了render函数,在执行 render 函数时,每一个 render函数对应一个 watcher,模板过长会导致 dom diff 计算量大。
  • 批处理:更新 vue data 时,不是立刻更新,而是将数据通过调度器组织起来,放在 nextTick 后,在下一个微任务/宏任务时作为回调函数执行。

todo。。。key

六、vue源码原理




    
    
    
    vue源码分析



index.js 

//  是一个构造函数,返回vue实例
function Vue({ data, el }){
    //  数据
    this.data = data;
    //  双向数据
    defineReactive(this, this.data);
    //  编译
    compiler(this, document.getElementById(el));
}

compiler.js

//  编译模板
//  ⚠️源码中用到了大量的正则,这里仅仅做案例,ast分析也忽略了
function compiler(vm, el){
    const div = el.querySelector('#div');
    const input = el.querySelector('#input');

    //  新创建dom,是可以绑定很多与vue有关的值,这里模拟的是 createElement
    const _div = document.createElement('div');
    const _input = document.createElement('input');

    const parentElement = div.parentElement;
    parentElement.removeChild(div);
    parentElement.removeChild(input);
    //  替换成新创建的dom
    parentElement.appendChild(_div);
    parentElement.appendChild(_input);

    //  'msg'
    const msg = div.getAttribute('v-text');

    //  div 的 render,正常的render函数是 width(this){xxx},xxx是ast分析后的结构,并且通过不同的渲染函数渲染
    const divRender = function (innerHTML){
        _div.innerHTML = innerHTML;
    };

    const inputRender = function (value){
        _input.value = value;
    };

    //  watcher
    new Watcher(vm, msg, divRender);
    const inputWatcher = new Watcher(vm, msg, inputRender);

    _input.oninput = function (){
        //  input中输入的值
        const value = this.value;
        //  触发 Object.defineProperty 的 set
        inputWatcher.vm[inputWatcher.key] = value;
    };
}

dep.js

let depId = 0;

//  观察者模式的发布者
function Dep(){
    //  区分是哪个发布者
    this.depId = depId++;
    //  维护关心某个数据的所有watcher,这里用set可以去重
    this.watcherList = new Set();
}

//  当且仅当 创建任意 watcher 时,会指向正在被创建的 watcher
Dep.target = null;
//  添加 watcher
Dep.prototype.add = function (watcher){
    if (this.watcherList.has(watcher)) {
        return;
    }
    watcher.depId = this.depId;
    this.watcherList.add(watcher);
};
//  发布
Dep.prototype.notify = function (){
    for (let o of this.watcherList) {
        //  通知到每一个订阅了该数据的 watcher
        o.update(this.depId);
    }
};

observer.js

function defineReactive(vm, data){
    Reflect.ownKeys(data).forEach(key => {
        let value = data[key];
        //  为每一个key创建新的发布者
        const dep = new Dep();
        Object.defineProperty(vm, key, {
            get(){
                //  第一次执行get,只有在创建 watcher 时 === true
                if (!!Dep.target) {
                    //  添加一个 watcher 到 发布者 dep
                    dep.add(Dep.target);
                }
                return value;
            },
            set(v){
                //  相同则什么都不做
                if (v === value) {
                    return;
                }
                //  先设置值
                value = v;
                //  再更新
                dep.notify();
            },
        });
    });
}

schedule.js

//  调度器是为了批量更新和同步调度,这里只写了nextTick和批量更新
const taskSet = new Map();

//  添加任务,然后调用nextTick
function addTask(depId, watcher){
    if (!taskSet.has(depId)) {
        taskSet.set(depId, new Set());
    }
    const set = taskSet.get(depId);
    set.add(watcher);
    nextTick(depId);
}

function nextTick(depId){
    //  nextTick优先尝试使用微任务,然后才是宏任务,这里做了简化
    setTimeout(() => {
        const set = taskSet.get(depId);
        for (let watcher of set) {
            //  执行每一个 watcher 的渲染函数
            watcher.render();
        }
        //  执行完后清空任务
        set.clear();
    });
}

watcher.js

function Watcher(vm, key, render){
    //  表示当前正在被创建的 watcher,源码中是一个 watcher 的栈
    Dep.target = this;
    this.vm = vm;
    this.key = key;
    //  渲染函数,由compiler生成
    this.render = function (){
        render(this.value);
    };
    this.value = this.get();
    Dep.target = null;
    //  更新这个 watcher 所在的 dep
    this.update(this.depId);
}

//  ⚠️源码中,get含义不同,get直接更新了视图
Watcher.prototype.get = function (){
    //  会触发 defineReactive 中的 get,从而添加这个 watcher 到 dep
    return this.vm[this.key];
};
Watcher.prototype.update = function (depId){
    //  先得到新的值
    this.value = this.get();
    //  再添加到批量更新任务列表
    addTask(depId, this);
};

你可能感兴趣的:(vue,vue)