zoukankan      html  css  js  c++  java
  • 学习Vue源码前的几项必要储备(一)

    从接下来的一段时间里,Mg要进行阅读源码的工作。再阅读源码前,梳理一下准备工作。

    7项重要储备

    1. Flow 基本语法
    2. 发布/订阅模式
    3. ES6+ 语法
    4. 原型链、闭包
    5. 函数柯里化
    6. event loop

    1.flow

    1.1 什么是flow

    没有类型的静态检查是 JavaScript 语言的先天缺失,所有很多变量类型相关的问题只能在运行阶段暴露出来。为了使 JavaScript 语言的类型更加安全,业界的实践有 TypeScript;这些都需要你重新学习一套语言语法,然后由编译器把相应的代码编译成原生的 JavaScript 代码;在一个现有的系统中把当前代码改成 TypeScript 成本比较高,需要重写所有逻辑。

    Facebook 推出的 Flow 是另一个思路。Flow 是一个静态类型检测工具;在现有项目中加上类型标注后,可以在代码阶段就检测出对变量的不恰当使用。Flow 弥补了 JavaScript 天生的类型系统缺陷。利用 Flow 进行类型检查,可以使你的项目代码更加健壮,确保项目的其他参与者也可以写出规范的代码;而 Flow 的使用更是方便渐进式的给项目加上严格的类型检测。那么这么好的 Flow,要怎么开始使用呢?

    1.2 基础类型检查

    Flow 支持原始数据类型,有如下几种:

    boolean
    number
    string
    null
    void( 对应 undefined )

    如下使用:

    let str:string = 'str';
    // 重新赋值
    str = 3  // 报错

    1.3 更多详情有如下资料

    官方文档:flow.org/en/

    Flow 的使用入门:zhuanlan.zhihu.com/p/26204569

    欲知详情,可进一步阅读

    2.发布/订阅模式

    Vue 的双向绑定机制采用数据劫持结合发布/订阅模式实现的: 通过 Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

    而在Vue3.0中,使用proxy代替原来的Object.defineProperty()以实现发布订阅模式。

    从Vue的源码来看,Vue的双向绑定主要做了2件事

    1. 数据劫持
    2. 添加观察者
    // 老版本通过 Object.defineProperty 递归可以实现
    // src/core/observer/index.js
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function reactiveGetter () {
        const value = getter ? getter.call(obj) : val
        if (Dep.target) {
          dep.depend()
          if (childOb) {
            childOb.dep.depend()
          }
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
        return value
      },
      set: function reactiveSetter (newVal) {
        const value = getter ? getter.call(obj) : val
        if (newVal === value || (newVal !== newVal && value !== value)) {
          return
        }
        if (setter) {
          setter.call(obj, newVal)
        } else {
          val = newVal
        }
        childOb = !shallow && observe(newVal)
        dep.notify()
      }
    })

    这里就是劫持了对象的get和set方法。在所代理的属性的get方法中,当dep.Target存在的时候会调用 dep.depend().

    划重点:2行代码

    1. Object.defineProperty
    2. dep.depend()
    // 最新版可以通过 Proxy 实现
    Proxy(data, {
      get(target, key) {
        return target[key];
      },
      set(target, key, value) {
        let val = Reflect.set(target, key, value);
          _that.$dep[key].forEach(item => item.update());
        return val;
      }
    })

    从上面的代码看出,无非就劫持了对象的get和set方法,在数据劫持之外最重要的部分就是 Dep 和 Watcher,这其实是一个观察者模式。用最简单的代码实现以下 Vue 的观察者模式。 

    观察者模式实现:(源码精简)

        // 观察者
        class Dep {
            constructor() {
                this.subs = []
            }
            
            addSub(sub) {
                this.subs.push(sub)
            }
            
            depend() {
                if (Dep.target) { 
                    Dep.target.addDep(this);
                }
            }
            
            notify() {
                this.subs.forEach(sub => sub.update())
            }
        }
        
        // 被观察者
        class Watcher {
            constructor(vm, expOrFn) {
                this.vm = vm;
                this.getter = expOrFn;
                this.value;
            }
    
            get() {
                Dep.target = this;
                
                var vm = this.vm;
                var value = this.getter.call(vm, vm);
                return value;
            }
    
            evaluate() {
                this.value = this.get();
            }
    
            addDep(dep) {
                dep.addSub(this);
            }
            
            update() {
                console.log('更新, value:', this.value)
            }
        }
        
        // 观察者实例
        var dep = new Dep();
        
        //  被观察者实例
        var watcher = new Watcher({x: 1}, (val) => val);
        watcher.evaluate();
        
        // 观察者监听被观察对象
        dep.depend()
        
        dep.notify()

    划重点:3件事

    1. 通过 watcher.evaluate() 将自身实例赋值给 Dep.target
    2. 调用 dep.depend() 将dep实例将 watcher 实例 push 到 dep.subs中
    3. 通过数据劫持,在调用被劫持的对象的 set 方法时,调用 dep.subs 中所有的 watcher.update()

    从此。双向绑定完成。

    3 ES6+ 语法

    3.1 export default 和 export 的区别

    1.export
    //a.js
    export const str = "神奇元";
    //b.js
    import { str } from 'a';   // 导入的时候需要花括号
    
    2.export default
    //a.js
    const str = "神奇元";
    export default str;
    //b.js
    import str from 'a';      // 导入的时候无需花括号

    3.2 箭头函数

    主要提两点

    1. 箭头函数中的 this 指向是固定不变的,即是在定义函数时的指向
    2. 而普通函数中的 this 指向时变化的,即是在使用函数时的指向

    3.3 箭头函数

    Class 可以通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

    class staff {
        constructor() {
            this.company = "ABC";
            this.test = [1, 2, 3];
        }
        companyName() {
            return this.company;
        }
    }
    class employee extends staff {
        constructor(name, profession) {
            super();
            this.employeeName = name;
            this.profession = profession;
        }
    }
    
    // 将父类原型指向子类
    let instanceOne = new employee("Andy", "A");
    let instanceTwo = new employee("Rose", "B");
    instanceOne.test.push(4);
    // 测试 
    console.log(instanceTwo.test); // [1,2,3]
    console.log(instanceOne.companyName()); // ABC
    // 通过 Object.getPrototypeOf() 方法可以用来从子类上获取父类
    console.log(Object.getPrototypeOf(employee) === staff)
    // 通过 hasOwnProperty() 方法来确定自身属性与其原型属性
    console.log(instanceOne.hasOwnProperty('test')) // true
    // 通过 isPrototypeOf() 方法来确定原型和实例的关系
    console.log(staff.prototype.isPrototypeOf(instanceOne)); // true

    super 关键字,它在这里表示父类的构造函数,用来新建父类的 this 对象。

    • 子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的this 对象,而是继承父类的 this 对象,然后对其进行加工。
    • 只有调用 super 之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有 super 方法才能返回父类实例。
    `super` 虽然代表了父类 `A` 的构造函数,但是返回的是子类 `B` 的实例,即` super` 内部的 `this ` 指的是 `B`,因此 `super()` 在这里相当于 A.prototype.constructor.call(this)

    ES5 和 ES6 实现继承的区别

    ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面(Parent.apply(this))。
    ES6 的继承机制完全不同,实质是先创造父类的实例对象 this (所以必须先调用 super() 方法),然后再用子类的构造函数修改 this

    3.4 proxy

    在Vue3.0版本的 Vue 中,会使用 proxy 代替 Object.defineProperty 完成数据劫持的工作。

    尤大说,这个新的方案会使初始化速度加倍,于此同时内存占用减半。

    proxy 对象的用法:

    var proxy = new Proxy(target, handler);

    new Proxy() 即生成一个 Proxy 实例。target 参数表示所要拦截的目标对象,handler 参数也是一个对象,用来定制拦截行为。

    var proxy = new Proxy({}, {
        get: function (obj, prop) {
            console.log('get 操作')
            return obj[prop];
        },
        set: function (obj, prop, value) {
            console.log('set 操作')
            obj[prop] = value;
        }
    });
    
    proxy.num = 2; // 设置 set 操作
    
    console.log(proxy.num); // 设置 get 操作 // 2

    除了 get 和 set 之外,proxy 可以拦截多达 13 种操作。

    注意,proxy 的最大问题在于浏览器支持度不够,IE 完全不兼容。 

     

    ......篇幅过长~大家都看累了吧,请转下一集!

  • 相关阅读:
    [置顶] 一道经典的sql面试题不同的写法
    Spring框架下的单元测试方法
    [Oracle] 参数修改小结
    linux之chdir函数解析
    windows media player 中播放pls的方法
    服务器管理十大误区
    AsyncTask 与 对话框显示 view.WindowManager$BadTokenException: Unable to add window…is not valid; is your a
    php用apc实现的临界区 解决并发,资源互斥同步访问
    Windows无需CygWin 使用NDK开发
    poj 2305(指定进制,大数取模)
  • 原文地址:https://www.cnblogs.com/magicg/p/12610075.html
Copyright © 2011-2022 走看看