• 一、Vue.js 概述


    本章概要

    • Vue 2.x 响应式系统的实现原理
    • Vue 3.0 响应式系统的实现原理
    • 体验 Vue 3.0 响应式系统
    • Vue 3.0 带来的新变化

    1.1 Web 前端技术的发展

    略,自行百度。

    1.2 MV* 模式

    MVC 是 Web 开发中应用非常广泛的一种框架模式,之后又演变出 MVP 模式和 MVVM 模式

    1.2.1 MVC

    在 MVC 模式中,一个应用被分成三个部分,即模型(Model)、视图(View)和控制器(Controller)。

    1.2.2 MVP

    MVP(Model-View-Presenter)是由经典的 MVC 模式演变而来,他们的基本思想有相同的地方:模型(Model)提供数据,视图(View)负责显示,表示器(Presenter)负责逻辑处理。
    区别:在 MVP 中 View 并不直接使用 Model ,他们之间的通信是通过 Presenter 进行的,所有的交互都发生在 Presenter 内部,而在 MVC 中 View 会直接从 Model 中读取数据而不是通过 Controller。

    1.2.3 MVVM

    MVVM(Model-View-ViewModel)是一种软件框架模式,也是一种简化用户界面的事件驱动编程方式。

    1.3 初识 Vue.js

    Vue 是一套基于 MVVM 模式的用于构建用户界面的 JavaScript 框架,他是以数据和组件化的思想构建的。

    1.3.1 渐进式框架

    Vue 是渐进式的 JavaScript 框架。所谓渐进式,就是把框架进行分层设计,每层都是可选的,不同层可以灵活地替换为其它的方案。

    1.3.2 响应式系统

    MVVM模式最核心的特性就是数据双向绑定,Vue构建了一套响应式系统,可以实现用声明的方式绑定数据,从而在数据变化时自动渲染视图。

    1. Vue 2.x 响应式系统的实现原理

    Vue 2.x 是利用 Object.defineProperty() 方法为对象添加 get() 和 set() 方法来侦测对象的变化,当获取对象属性值时会调用 get() 方法,当修改对象属性值时会调用 set() 方法,于是可以在 get() 和 set() 方法中添加代码,实现数据与视图的双向绑定。

    例1-1

    // 对Object.defineProperty方法进行封装
    function defineReactive(obj, key, value) {
        Object.defineProperty(obj, key, {
            get() {
                return value;
            },
            set(newValue) {
                if (newValue !== value) {
                    updateView(); //在set方法中触发更新
                    value = newValue;
                }
            }
        });
    }
    // 对一个对象中所有属性的变化进行侦测
    function observer(target) {
        // 如果不是对象数据类型直接返回
        if (typeof target !== 'object') {
            return target;
        }
        // 循环遍历对象的所有属性,并将它们转换为getter和setter形式
        for (let key in target) {
            defineReactive(target, key, target[key]);
        }
    }
    // 模拟更新视图的方法
    function updateView() {
        console.log("更新视图");
    }
    let user = {name:'张三'};
    // 对user对象的所有属性变化进行侦测
    observer(user);
    user.name = '李四';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    运行上诉代码,输出结果如下:

    更新视图
    
    • 1

    例1-1 的代码只是简单地侦测了一个对象的属性变化,并没有考虑到对象属性本身又是一个对象的情形。假设 user 对象中有一个属性 address ,该属性本身也是一个对象。代码如下

    let user = {name: '张三', address: {city: '北京'}};
    // 对user对象的所有属性变化进行侦测
    observer(user);
    user.address.city = '天津';
    
    • 1
    • 2
    • 3
    • 4

    运行上述代码,将看不到任何输出,说明对 address 对象的 city 属性的修改并没有被侦测到。
    因此,需要修改例1-1 的代码,当对象的属性也是对象类型时,继续为该属性对象的所有属性添加 get() 和 set() 方法。实现起来也很简单,就是在 defineReactive() 函数中添加一个 observer() 函数的递归调用。代码如下

    // 对Object.defineProperty方法进行封装
    function defineReactive(obj, key, value) {
        // 通过递归调用,解决多层对象嵌套的属性侦测问题
        observer(value);
        Object.defineProperty(obj, key, {
          ...
        }
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    再次运行代码,可以看到“更新视图”的输出。
    考虑一下下面的代码:

    let user = {name: '张三', address: {city: '北京'}};
    // 对user对象的所有属性变化进行侦测
    observer(user);
    user.address = {city: '天津'};
    user.address.city = '成都';
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上述代码有两次属性变化,一次是为 address 属性设置了一个对象字面常量,一次是修改 address 对象的 city 属性,但如果运行代码,就会发现只能看到一次“更新视图”。这是因为在修改 address 属性时是为它赋值了一个新的对象,而这个新对象的属性并没有被侦测,因此后面对这个新对象属性值的更改就没有被侦测到。
    针对这种情况,可以在 set() 方法中为新的值添加 observer() 调用。如下:

      Object.defineProperty(obj, key, {
          get() {
              return value;
          },
          set(newValue) {
              if (newValue !== value) {
                  // 如果newValue是对象类型,则继续侦测该对象的所有属性变化
                  // observer函数中已经有对参数是否是对象类型的判断代码,此处可以省略
                  observer(newValue);
                  updateView(); //在set方法中触发更新
                  value = newValue;
              }
          }
      });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    至此,已经解决了对象侦测的问题,但还需要考虑数字侦测的问题。如下

    let user = {name: '张三', address: {city: '北京'}, emails: ['zhang@163.com']};
    // 对user对象的所有属性变化进行侦测
    observer(user);
    user.emails.push('zhang@sina.com');
    
    • 1
    • 2
    • 3
    • 4

    emails 属性是数组类型,当通过push() 方法改变数组内容时,并不会触发对象的 set() 方法的调用。如果想在调用数组方法修改数字内容时得到通知,就需要替换数组原型对象,代码如下:

    const arrayPrototype = Array.prototype;
    // 使用数组的原型对象创建一个新对象
    const proto = Object.create(arrayPrototype);
    
    // 改变数组自身内容的方法只有如下7个,对它们进行拦截
    ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
        .forEach(method => {
            Object.defineProperty(proto, method, {
                get() {
                    updateView();
                    // 返回数组原有的方法
                    return arrayPrototype[method];
                }
            });
        });
    // 对一个对象中所有属性的变化进行侦测
    function observer(target) {
        // 如果不是对象数据类型直接返回
        if (typeof target !== 'object') {
            return target;
        }
        if (Array.isArray(target)) {
            // 如果target是数组,则将数组的原型对象设置为proto
            Object.setPrototypeOf(target, proto);
            // 对数组中的元素进行侦测
            for (let i = 0; i < target.length; i++) {
                observer(target[i])
            }
            return;
        }
    
        // 循环遍历对象的所有属性,并将它们转换为getter和setter形式
        for (let key in target) {
            defineReactive(target, key, target[key]);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2. Vue 3.0 响应式系统的实现原理

    Vue 2.x 是利用 Object.defineProperty() 方法侦测对象的属性变化,但该方法有一些固有的缺陷:性能较差、在对象上新增属性是无法被侦测的、改变数组的 length 属性是无法被侦测的。
    以下是 Vue 3.0 一个创建代理的简单示例。

    例1-2

    // 处理器对象
    const baseHandler = {
      // 陷阱函数,读取属性值时触发
      // 参数target是目标对象
      // 参数property是要获取的属性名
      // 参数receiver是Proxy对象或者继承Proxy的对象
      get(target, property, receiver){
        console.log("获取值");
      },
      // 陷阱函数,写入属性值时触发
      // 参数value是新的属性值
      set(target, property, value, receiver){
        console.log("设置值");
      },
      // 陷阱函数,删除属性时触发
      deleteProperty(target, property){
        console.log("删除属性");
      }
    }
    // 目标对象
    const target = {name: '张三'};
    // 为目标对象创建代理对象
    const proxy = new Proxy(target, baseHandler);
    // 读取属性值
    proxy.name;
    // 设置属性值
    proxy.name = '李四';
    // 删除属性
    delete proxy.name;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    之后,针对代理对象的相关操作就会触发处理器对象中的对应陷阱函数,在陷阱函数中就可以为目标对象的属性访问添加自定义的业务逻辑。
    运行结果如下:

    获取值
    设置值
    删除属性
    
    • 1
    • 2
    • 3

    下面使用 Proxy 模拟实现 Vue 3.0 的响应式系统。

    例1-3

    // 判断某个值是否是对象的辅助方法
    function isObject(val){
      return val !== null && typeof val === 'object';
    }
    // 响应式核心方法
    function reactive(target){
      return createReactiveObject(target);
    }
    // 创建响应式对象的方法
    function createReactiveObject(target){
      // 如果target不是对象则直接返回
      if(!isObject(target)){
        return target;
      }
      const baseHandler = {
        get(target, property, receiver){
          console.log('获取值');
          const result = Reflect.get(target, property, receiver);
          return result;
        },
        set(target, property, value, receiver){
          console.log('设置值');
          const result = Reflect.set(target, property, value, receiver);
          return result;
        },
        deleteProperty(target, property){
          return Reflect.deleteProperty(target, property);
        }
      }
      const observed = new Proxy(target, baseHandler);
      return observed;
    }
    const proxy = reactive({name: '张三'});
    proxy.name = '李四';
    console.log(proxy.name);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    Reflect 是一个内置对象,它提供了可拦截 JavaScript 操作的方法。每个代理陷阱对应一个命名和参数都相同的 Reflect 方法。
    上述代码的运行结果为:

    设置值
    获取值
    李四
    
    • 1
    • 2
    • 3

    同样,为了解决多层对象侦测的问题,需要在get 陷阱函数中对返回值做一个判断,如果返回值是一个对象,则为返回值也创建代理对象,这也是一个递归调用。
    修改 get 陷阱函数,对返回值进行判断

    ...
    	const baseHandler = {
        get(target, property, receiver){
          console.log('获取值');
          const result = Reflect.get(target, property, receiver);
          return isObject(result) ? reactive(result) : result;
        },
        set(target, property, value, receiver){
          console.log('设置值');
          const result = Reflect.set(target, property, value, receiver);
          return result;
        },
        deleteProperty(target, property){
          return Reflect.deleteProperty(target, property);
        }
      }
      const observed = new Proxy(target, baseHandler);
      return observed;
    }
    ...
    const proxy = reactive({name: 'vue.js', address: {city: '北京'}});
    proxy.address.city = '天津';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    访问proxy.address时,会引起 get 陷阱函数,由于 address 属性本身也是一个对象,因此该属性也创建代码。
    运行结果如下:

    获取值
    设置值
    
    • 1
    • 2

    考虑下面两种情况,第一种情况的代码如下:

    const target = {name: '张三'};
    const proxy1 = reactive(target);
    const proxy2 = reactive(target);
    
    • 1
    • 2
    • 3

    上述代码对同一个目标对象进行了多次代理,如果每次返回一个不同的代理对象,是没有意义的,要解决这个问题,可以在为目标对象初次创建代理后,以目标对象为 key ,代理为 value ,保存到一个 Map 中,然后在每次创建代理前,对目标进行判断,如果已经存在代理对象,则直接返回代理对象,而不再新建代理对象。
    第二种情况的代码如下:

    const target = {name: '张三'};
    const proxy1 = reactive(target);
    const proxy2 = reactive(proxy1);
    
    • 1
    • 2
    • 3

    上述代码对一个目标对象进行了代理,然后又对该代理对象进行了代理,这也是无意义的,也需要进行区分,解决方法与第一种情况类似,不过是以代理对象为 key ,目标对象为 value ,保存到一个 Map 中,然后在每次创建代理前,判断传入的目标对象是否本身也是代理对象,如果是,则直接返回该目标对象(原目标对象的代理对象)。
    继续完善 例1-3 ,定义两个 WeakMap 对象,分别保存目标对象到代理的映射,以及代理对象到目标对象的映射,并添加判断逻辑。如下:

    ...
    const toProxy = new WeakMap(); // 存放目标对象=>代理对象
    const toRaw = new WeakMap();   // 存放代理对象=>目标对象
    ...
    // 创建响应式对象的方法
    function createReactiveObject(target){
      // 如果target不是对象则直接返回
      if(!isObject(target)){
        return target;
      }
      const proxy = toProxy.get(target);
      // 如果目标对象是代理对象,并且有对应的真实对象,那么直接返回
      if(proxy){
        return proxy;
      }
      // 如果目标对象是代理对象,并且有对应的真实对象,那么直接返回
      if(toRaw.has(target)){  
        return target;  // 这里的target是代理对象
      }
      const baseHandler = {
        ...
      }
      const observed = new Proxy(target, baseHandler);
      toProxy.set(target, observed);
      toRaw.set(observed, target);
      return observed;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    接下来解决向数组中添加元素而导致 set 陷阱相应两次的问题,如下

    const proxy = reactive([1, 2, 3]);
    proxy.push(4);
    
    • 1
    • 2

    上述代码会导致 set 陷阱触发两次,因为push() 方法向数组中添加元素的同时还会修改数组的长度,因此有两次 set 陷阱的触发:一次是将数组索引为3的位置设置为4而触发;一次是修改数字的length属性为4而触发。假如在 set 陷阱函数中更新视图,那么就会出现更新两次的情况。
    为了避免上述情况,需要在 set 陷阱函数中区分是新增属性还是修改属性,同时对属性值的修改做一个判断,如果要修改的属性的新值与旧值相同,则无需进行任何操作。
    继续完善例1-3的代码。

    ...
    // 判断当前对象上是否有指定属性
    function hasOwn(target, property){
      return target.hasOwnProperty(property);
    }
    ...
    // 创建响应式对象的方法
    function createReactiveObject(target){
    	...
      const baseHandler = {
        get(target, property, receiver){
        	...
        },
        set(target, property, value, receiver){
          // 判断目标对象上是否已经存在该属性
          const hasProperty = hasOwn(target, property); 
          const oldValue = Reflect.get(target, property);
          const result = Reflect.set(target, property, value, receiver);
          
          if(!hasProperty){
            console.log('新增属性')
          }
          else if(oldValue !== value){ 
            console.log('修改属性')
          }
          return result;
        },
    		...
      }
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    针对上述使用 push() 方法向数组添加元素的代码,修改后的响应式代码只会输出“新增属性”,原因是添加元素后,数字的长度已经是4,当 push() 方法修改数组长度为 4 时,新值和旧值相同,因此不进行任何操作。完善后的响应式代码也避免了对属性值进行无意义的修改。
    接下来,就是Vue3.0 中比较难理解的依赖手机了,所谓依赖,就是指当数据发生变化,要通知谁。
    Vue3.0 使用了 effect() 函数来包装依赖,称为副作用。 effect() 函数的模拟实现如下:

    ...
    // 保存effect的数组,以栈的形式存储
    const effectStack = []; 
    function effect(fn){
      // 创建响应式effect
      const effect = createReactiveEffect(fn);
      // 默认先执行一次effect,本质上调用的是传入的fn函数
      effect(); 
    }
    // 创建响应式effect
    function createReactiveEffect(fn){
      // 响应式effect
      const effect = function(){  
        try{
          // 将effect保存到全局数组effectStack中,以栈的形式存储
          effectStack.push(effect);
          return fn();
        } finally{
          // 调用完依赖后,删除effect
          effectStack.pop();
        }
      }
      return effect;
    }
    ...
    const proxy = reactive({name: '张三'});
    effect(()=>{
      console.log(proxy.name);
    });
    proxy.name = '李四';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    上述代码运行的结果为

    获取值
    张三
    修改属性
    
    • 1
    • 2
    • 3

    从输出结果中可以看到,除了默认执行一次的 effect 外,当name 属性发生变化时,effect 并没有被执行。为了在对象属性发生变化时,让 effect 再次执行,需要将对象的属性与 effect 进行关联,这可以采用 Map 来存储。
    考虑到一个属性可能会关联多个依赖,那么存储的映射关系应该是以对象属性为 key ,保存所有的 effect 的 Set 对象为 value ,之所以选择 Set 而不是数组,是因为 Set 中不能保存重复的元素。
    另外,属性毕竟是对象的属性,不可能脱离对象而单独存在,要跟踪不同对象属性的依赖,还需要一个 WeakMap ,将对象本身最为 key,保存所有属性与依赖映射关系的 Map 最为 value ,存储到这个 WeakMap 中。
    定义好数据结构后,就可以编写一个依赖手机函数 track() ,如下:

    // 保存对象与其属性依赖关系的Map,key是对象,value是Map
    const targetMap = new WeakMap();
    // 跟踪依赖
    function track(target, property){ 
      // 获取全局数组effectStack中的依赖
      const effect = effectStack[effectStack.length - 1];
      // 如果存在依赖
      if(effect){  
        // 取出该对象对应的Map
        let depsMap = targetMap.get(target);
        // 如果不存在,则以目标对象为key,新建的Map为value,保存到targetMap中
        if(!depsMap){
          targetMap.set(target, depsMap = new Map());
        }
        // 从Map中取出该属性对应的所有effect
        let deps = depsMap.get(property);
        // 如果不存在,则以属性为key,新建的Set为value,保存到depsMap中
        if(!deps){
          depsMap.set(property, deps = new Set());
        }
        // 判断Set中是否已经存在effect,如果没有,则添加到deps中
        if(!deps.has(effect)){
          deps.add(effect);
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    接下来是当属性发生变化时,触发属性关联的所有 effect 执行,为此,再编写一个 trigger() 函数,代码如下:

    // 执行属性关联的所有effect
    // 参数type在本例中并没有使用,只是模拟Vue 3.0中的代码,用于区分修改属性还是新增属性
    function trigger(target, type, property){
      const depsMap = targetMap.get(target);
      if(depsMap){
        let deps = depsMap.get(property);
        // 将当前属性关联的所有effect依次执行
        if(deps){
          deps.forEach(effect => {
            effect();
          });
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    依赖收集的函数和触发依赖的函数都写完了,那么自然需要在某个地方去收集依赖和触发依赖执行,收集依赖放到触发器对象的 get 陷阱中,而 触发依赖是在属性发生变化时执行依赖,自然是放到 set 陷阱中。
    修改 createReactiveObject() 函数,添加 track() 和 trigger() 函数的调用。如下:

    // 创建响应式对象的方法
    function createReactiveObject(target){
    	...
      const baseHandler = {
        get(target, property, receiver){
          const result = Reflect.get(target, property, receiver);
          // 收集依赖
          track(target, property); 
          return isObject(result) ? reactive(result) : result;
        },
        set(target, property, value, receiver){
          // 判断目标对象上是否已经存在该属性
          const hasProperty = hasOwn(target, property); 
          const oldValue = Reflect.get(target, property);
          const result = Reflect.set(target, property, value, receiver);
          
          if(!hasProperty){
            trigger(target, 'add', property);
          }
          else if(oldValue !== value){ 
            trigger(target, 'set', property);
          }
          return result;
        },
        deleteProperty(target, property){
          return Reflect.deleteProperty(target, property);
        }
      }
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    至此,模拟实现 Vue 3.0 的响应式系统的代码全部编写完毕。

    1.3.3 体验 Vue 3.0 响应式系统

    例1-4

    DOCTYPE html>
    <html>
    	<head>
    		<meta charset="GBK">
    		<title>Hello Vue.jstitle>
    	head>
    	<body>
    		
    		<div id="app">
    		    <button @click="increment">count值:{{ state.count }}button>
    		div>
    
            
    		<script src="https://unpkg.com/vue@next">script>
    		<script>
    			const App = {
    			    setup(){
    			        // 为目标对象创建一个响应式对象
    			        const state = Vue.reactive({count: 0});
    			        function increment(){
    			            state.count++;
    			        }
    			        return {
    			            state,
    			            increment
    			        }   
    			    }
    			};
    			// 创建应用程序实例,该实例提供应用程序上下文。
    			// 应用程序实例装载的整个组件树将共享相同的上下文.
    			const app = Vue.createApp(App);
    			// 在id为app的DOM元素上装载应用程序实例的根组件
    			app.mount('#app');
    		script>
    	body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    在浏览器中打开此页面,运行效果如下:
    在这里插入图片描述

    点击按钮,可以看到计数值在增长。

    1.4 Vue 3.0 带来的新变化

    Vue 3.0 并没有沿用 Vue 2.x 版本的代码,而是从头重写了,代码采用 TypeScript 进行编写,新版的 API 全部采用普通函数,在编写代码时可以享受完整的类型推断。
    Vue 3.0 具有以下 8 个亮点:

    1. 更好的性能

    Vue 3.0 重写了虚拟 DOM 的实现,并对模版的编译进行了优化,改进了组件初始化的速度,相比 Vue 2.x ,更新速度和内存占用方面都有显著的性能提升。

    2. Tree-shaking 支持

    对无用的模块进行“剪枝”,仅打包需要的,减少了产品发布版本的大小。Vue 3.0 支持按需引入,而 Vue 2.x 中即时用不到的功能也会打包进来。

    3. 组合API(Composition API)

    Vue 2.x 使用 mixin 来复用功能,但 mixin 存在的问题是:如果用多了,则很难知道某个功能是从哪个 mixin 混入的。
    此外,mixin 的类型推断也很差。Vue 3.0 中新增的 Composition API 可以完美替代 mixiin,让用户课可以更灵活且无副作用地复用代码,且 Composition API 可以很好的进行类型推断。Composition API 解决了多组件间逻辑重用的问题。

    4. 碎片(Fragmen)

    Vue 2.x 的组件需要有一个唯一的根节点,而在 Vue 3.0 中,这成了历史,组件模版不再需要单个的根节点了,可以有很多个节点。

    5. 传送(Teleport)

    有时组件模版的一部分在逻辑上属于该组件,而从技术角度来看,最好将模版的这一部分移动到 DOM 中 Vue 应用程序之外的其它位置,使用 Teleport 内置组件可以很容易地实现这个需求。

    6. 悬念(Suspense)

    Suspense 内置组件可以在嵌套层级中等待嵌套的异步依赖项,支持 async setup() ,支持异步组件。

    7. 更好的 TypeScript 支持

    Vue 3.0 的代码完全采用 TypeScript 编写,具有更好的类型支持。前端开发人员现在可以采用 TypeScript 开发 Vue 应用,而无需担心兼容性问题,结合支持 Vue 3.0 的 TypeScript 插件,开发更加高效,并可以拥有类型检查、自动补全等功能。

    8. 自定义渲染器 API

    使用自定义渲染器 API ,用户可以尝试与第三方库集成,如编写 WebGL自定义渲染器。
    需要注意的是,Vue 3.0 目前并不支持 IE 11。

  • 相关阅读:
    简单易懂的 Go 泛型使用和实现原理介绍
    一招解决MySql Specified key was too long; max key length is 767 bytes
    【Javascript】构造函数之new的作用
    Java8 Stream流如何操作集合,一文带你了解!
    20220810
    X(推特)“鸡贼”手段曝光:这些广告并没有标注,你知道吗?
    游戏服务器成DDoS最大攻击重灾区
    1110 区块反转分数 25
    MySQL 全球大会summit 2023年度 --- MySQL 高可用和灾备 (音译)
    【C语言】单词拼写检查
  • 原文地址:https://blog.csdn.net/GXL_1012/article/details/126710863