zoukankan      html  css  js  c++  java
  • Vue.js 源码学习笔记 -- 分析前准备1 -- vue三大利器

    主体

      实例方法归类:    先看个作者推荐, 清晰易懂的  23232

      简易编译器

     

    重点:

     最简单的订阅者模式    

    // Observer
    class Observer {
        constructor (data) {
            this.walk(data)
        }
        walk (data) {
            // 遍历
            let keys = Object.keys(data)
            for(let i = 0; i < keys.length; i++){
                defineReactive(data, keys[i], data[keys[i]])
            }
        }
    }
    
    function defineReactive (data, key, val) {
        observer(val)

    // dep 为什么要在这里实例化, 就是为了实现, 对象每一层的 每一个key都有自己的一个订阅实例, 比如 a.b 对应 dep1, a.c 对应dep2, 这里虽然都是let dep = new Dep()
    // 但每次来到这个方法, dep都是独立的, 会一直保留在内存. 这样在每次调用set方法都能找到这个a.b对应的dep
    // dep 这里会一直保存, 是因为闭包的关系, Object这个全局的函数, 引用了上层的作用域, 这个作用域包含了 dep, 除非Object = null, 或者退出浏览器, dep才会消失

    //实例化之后, dep就有了被订阅, 和发布消息的功能, dep不写在这里也是可以的, 多定义一个全局函数, 每次obser的时候增加一个dep let dep
    = new Dep() Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function () {
    //每次new Watch('a.b'), 都会先执行get方法, 进而来到这里, 触发 dep.depend(), 这个dep就是 a.b 对应的 订阅, dep.depend()
    return val }, set: function (newVal) { if(val === newVal){ return } observer(newVal) dep.notify() } }) } function observer (data) { if(Object.prototype.toString.call(data) !== '[object Object]') { return } new Observer(data) } class Dep { constructor () { this.subs = [] } depend () {
         //这里是收集回调函数的过程, 也就是收集依赖项, 数据改变后, 需要触发的改变UI和其他函数
    this.subs.push(Dep.target) } notify () { for(let i = 0; i < this.subs.length; i++){ this.subs[i].fn() } } } Dep.target = null function pushTarget(watch){ Dep.target = watch } // watch class Watch { constructor (exp, fn) { this.exp = exp this.fn = fn pushTarget(this) data[exp] } } var data = { a: 1, b: { c: 2 } }
    //递归对象的属性 , 层层监听 observer(data)
    //new 产生 this, this挂载上exp 和 回调fn, 再利用data[exp] 触发get方法 从而订阅 dep.push( this)
    new Watch('a', () => { console.log(9) }) new Watch('a', () => { console.log(90) }) new Watch('b.c', () => { console.log(80) }) setTimeout(() => { data.a = 2 }, 1000)

     observer: 检测每一个对象每一层的属性, 每个属性都具备get set的方法, 如果这些属性有变化, 调用相对的dep处理

     Dep: 根据不同的数据生成不同的dep依赖, 这个依赖收集了相关回调方法, 和触发这些回调执行

     wathcer: 包含需要触发的回调函数, 在get方法中, 订阅这个属性的dep,  如果  wacher('a.b', fn1)    wacher('a.b', fn2),

         就对同一个类型生成了 两个订阅者 subs1, subs2  ,当 a.b = 3; 赋值

                   的时候, 触发set方法, 从而依次执行了:  subs1的回调方法fn1, 和 subs2的回调方法 fn2,    

    主要还是利用了 Object.defineProperty 方法, set get 会找到同一个dep00,  第一次访问数据的时候, 触发get , dep00收集依赖, 后面设置新值的时候, 触发set方法, dep00触发所有订阅者的回调函数。


    整个vm 核心, 就是实现了 observer(观察数据变化)  parser(解析依赖)  watcher(观察到的数据更新,通知指令的执行更新相应页面方法) 这三个东西

     observer watcher 的极简单的实现

    具体实现

      

    数据监听机制

    如何监听某一个对象属性的变化呢?我们很容易想到 Object.defineProperty 这个 API,

    为此属性设计一个特殊的 getter/setter,然后在 setter 里触发 ob.notify一个函数 来执行指令,就可以达到监听的效果。

    看数组的方法的监听例子

    [ 'push', 'pop', 'shift', 'unshift', splice', 'sort', 'reverse'].forEach(function( method){
     
      var original = arrayProto[method] // Array.prototype.sort
    
      //数组的方法执行的时候, 会触发下面这个函数
      _.define( arrayMethods, method, function mutator(){
    
    	//先在原生的数组原型方法中按传入的参数执行一遍, 得到结果
    	var result = original.apply(this , args);
    
    	var ob = this.__ob__;
    	
    	var inserted 
    
    	switch (method){
    		case 'push': inserted = args ;break
    		case 'unshift': inserted = args ; break
    		case 'splice': inserted = args.slice(2);
    	
    	}
    	 
    	if(inserted) ob.observeArray(inserted) 
         
    	ob.notify()
    
    	return result
    	
      })
      
    })
    

      

    同时 Vue.js 提供了两个额外的“糖方法”  arr.$set[0] = "c"  和 $remove(index) 来弥补这方面限制带来的不便。

    path 解析器

    var path = 'a.b[1].v'

    var obj = {
    a: { b:[ {v:1}, {v:2}, {v:3} ] }
    }

    parse( obj, path ) // => 2

    如何解析 这个字符串 成为 js语句 是关键

    vue.js 是通过状态机管理 来实现对路径的解析的

    Vue 将表达式的访问路径字符串 解析成 更易于js使用的状态

    比如 b.c.d 将会解析成 ['b', 'c', 'd' ]

    经过js处理后 变成 a[ arr[0] ][ arr[1] ][ arr[2] ] 就可以访问的这个深层的属性值


    对一个合法的路径来说, 是有规律的, 如果第一个字符为a

    第二个字符可能是有四种情况

    a.

    a[

    ab

    a(没有了, undefinde 就是解析完毕了)


    Vue 的状态机模式解析 path 实际上是将 path 的索引的字符视为一个状态

    接下来一个字符 视为 当前状态的 输入

    并 根据输入 进行 状态转移 以及 响应操作,


    如果输入不是期望的,那么状态机将异常中止。

    只有状态机正常运行直到转移到结束状态,才算解析成功。

    Vue 的 pathStateMachine 有八种状态,例如 BEFORE_PATH


    BEFORE_PATH 是 pathStateMachine 的初始状态,它的状态模型为


    pathStateMachine[BEFORE_PATH] = {
      'ws': [BEFORE_PATH],
      'ident': [IN_IDENT, APPEND],
      '[': [IN_SUB_PATH],
      'eof': [AFTER_PATH]
    }


    从状态模型中知道 BEFORE_PATH 接受四种输入,

    这些状态起的作用是分割字符, 再将这些字符依次放入数组中

    ws ,状态转移到 BEFORE_PATH
    indent ,状态转移到 IN_IDENT,并执行 APPEND 操作

    [ ,状态转移到 IN_SUB_PATH
    eof ,AFTER_PATH

    indent 表示 a-z A-Z字符, 继续输入, 这个子路径名称还没有结束


    ws 表示 空格 换行等
    [ 表示要开记录下一个子路径

    eof 表示路径结束

    具体的意思和其他7种状态模型可以看这个函数 getPathCharType

    ( https://github.com/vuejs/vue/blob/e9872271fa9b2a8bec1c42e65a2bb5c4df808eb2/src/parsers/path.js#L33-L81 )

    状态机运行过程中,Vue 在通过 action 处理每一级 path 的路径值。

    比如当处于状态 IN_IDENT 时,再次输入字符,会执行 APPEND 操作,将该字符串与之前的字符作 字符串拼接

    再次输入 .或  会执行 PUSH 操作,将之间的字符串视为访问对象的一个属性。

    Vue 的 pathStateMachine 有四种 action,他们主要是根据 path 特征和状态提取出对象的访问属性,并按照层级关系依次推入数组。

    详细见代码。

    下面是是一个详细例子分析状态机的状态转移过程,需要分析的 path 为 md[0].da["ky"]

    先声明
    keys        = 存放对象访问属性的数组
    key         = 临时变量
    index       = 索引
    mode        = 当前状态
    input       = 输入
    transfer    = 状态转移
    action      = 操作
    

    现在进入状态极 (简单使用字符串 for循环)

    index = 0  (md[0].da["ky"])  

    mode        = BEFORE_PATH
    input       = 'm'
    transfer    => IN_IDENT
    action      => APPEND
    keys        = []
    key         = 'm'
    

    index = 1  (md[0].da["ky"])  

    mode        = IN_IDENT
    input       = 'd'
    transfer    => IN_IDENT
    action      => APPEND
    keys        = []
    key         = 'md'
    

    index = 2    (md[0].da["ky"])  

    mode        = IN_IDENT
    input       = '['
    transfer    => IN_SUB_PATH
    action      => PUSH
    keys        = ['md']
    key         = undefined
    

    index = 3    md[0].da["ky"]

    mode        = IN_SUB_PATH
    input       = '0'
    transfer    => IN_SUB_PATH
    action      => APPEND
    keys        = ['md']
    key         = '0'
    

    index = 4  (md[0].da["ky"])  

    mode        = IN_SUB_PATH
    input       = ']'
    transfer    => IN_PATH
    action      => INC_SUB_PATH_DEPTH
    keys        = ['md', '0']
    key         = undefined
    

    index = 5    md[0].da["ky"]

    mode        = IN_PATH
    input       = '.'
    transfer    => BEFORE_IDENT
    action      => None
    keys        = ['md', '0']
    key         = undefined
    

    index = 6

    mode        = BEFORE_IDENT
    input       = 'd'
    transfer    => IN_IDENT
    action      => APPEND
    keys        = ['md', '0']
    key         = 'd'
    

    index = 7  

    mode        = IN_IDENT
    input       = 'a'
    transfer    => IN_IDENT
    action      => APPEND
    keys        = ['md', '0']
    key         = 'da'
    

    index = 8     md[0].da["ky"]

    mode        = IN_IDENT
    input       = '['
    transfer    => IN_SUB_PATH
    action      => PUSH
    keys        = ['md', '0', 'da']
    key         = undefined
    

    index = 9

    mode        = IN_SUB_PATH
    input       = '"'
    transfer    => IN_DOUBLE_QUOTE
    action      => APPEND
    keys        = ['md', '0', 'da']
    key         = '"'
    

    index = 10    md[0].da["ky"]

    mode        = IN_DOUBLE_QUOTE
    input       = 'k'
    transfer    => IN_DOUBLE_QUOTE
    action      => APPEND
    keys        = ['md', '0', 'da']
    key         = '"k'
    

    index = 11  

    mode        = IN_DOUBLE_QUOTE
    input       = 'y'
    transfer    => IN_DOUBLE_QUOTE
    action      => APPEND
    keys        = ['md', '0', 'da']
    key         = '"ky'
    

    index = 12

    mode        = IN_DOUBLE_QUOTE
    input       = '"'
    transfer    => IN_SUB_PATH
    action      => APPEND
    keys        = ['md', '0', 'da']
    key         = '"ky"'
    

    index = 13

    mode        = IN_SUB_PATH
    input       = ']'
    transfer    => IN_PATH
    action      => PUSH_SUB_PATH
    keys        = ['md', '0', 'da', 'ky']
    key         = undefined
    

    index = 14

    mode        = IN_SUB_PATH
    input       = 'eof'
    transfer    => AFTER_PATH
    action      => None
    keys        = ['md', '0', 'da', 'ky']
    key         = undefined

     最后的结果是

     ['md', '0', 'da', 'ky']

    再拼接成   md[0]['da']['ky']  就可以访问这个属性值了  arr[0][ arr[1]] [ arr[2] ]..

    比如这个 

    var a = { b:[ {t1:1},{t2:2} ]};

     a['b'][0]['t1']  => 1

    表达式解析


    vue 的表达式是通过自己的来解析的, 做了setter监听处理

    而不是直接调用eval方法, 所以并不是所以的js表达式都支持

    其解析 {{ mess.splite('').reverse().join('') }} 过程:

    首先, 调用Vue.parsers.text.parseText(str), 解析成一个tokens对象

    tokens = [
    {
    html: false,
    hasOneTime: false,
    tag: true,
    value: "mess.split('').reverse().join('')" //通过正则获取到了大括号的内容
    }
    ]
    

      


    然后用 Vue.parsers.text.tokensToExp(tokens) 取出 value, 赋值为一个字符串表达式:

    expression = "mess.split('').reverse().join('')";


    这个 expression 正是创建 watcher 时所用的表达式,


    wathcer 为表达式 和 数据 建立联系的时候, 会解析这个表达式 并获取值

    var res = Vue.parsers.expresssion.parseExpresstion(expression)

    解析这个表达式, 其实是为它定义了 getter 和 setter 方法

    为了能定义并使用这两个方法, 表达式必须是合法的路径, 而且要有值

    表达式的getter 方法结合组件的数据获取表达式的值,

    通过 Function 构造器为表达式求值

    var getter = function( s, expression ){
    return new Function(   'return ' + s+'.'+ expression + ';' );
    }

    获取表达式的值时, 执行 getter方法 从作用域对象内取值

    var model = { mess: 'abc' }

    这里利用 new Function 可以传入字符串,解析成js语句, 来解析字符串表达式;

    比如页面有 {{mess.substring(0,1)}}
    var fn = getter( 'model', 'mess.substring(0,1)' ); 

    fn(); // 输出 a

    Vue中的双向绑定原理: 

    在视图中改变组件数据,驱动数据更新。

    进而触发表达式的中熟悉的setter方法,  在根据订阅者更新表达式的内容, 和页面的内容

     大概的实现参考 $watch方法,  所有的表达式 , 比如 mess.children.split('')

  • 相关阅读:
    UE4 Cel Shading(卡通渲染)
    UE4 常用数学
    锈迹材质全流程实例:Blender-》SP-》UE4
    ue4 优化建议与经验
    VisualStudio开发UE4工程设置
    Procedural Mesh Component in C++:Getting Started
    Java容器有哪些?
    java 连接mongodb
    mongodb 系列 ~ mongo 用户验证系列
    mongodb连接认证失败
  • 原文地址:https://www.cnblogs.com/dhsz/p/6389268.html
Copyright © 2011-2022 走看看