Vue源码

Mustache模板引擎

  • vue模板引擎的鼻祖,模板引擎,就是v-for,负责将用户给的数据和模板进行处理之后返回成一个dom字符串

  • 分为七个模块,Scanner,index,parseTemplateToTokens,nestTokens,renderTemplate,lookup,parseArray

Scanner
  • 主模块,生成并且导出一个类,构造函数里面接受一个参数就是要扫描的字符串,里面对于有三个方法:
  • 第一个是进行扫描指定内容方法—scanUtil,会返回扫描之前走过的文本,所传递的参数就是要扫描的内容
  • 第二个是跳过指定内容方法—scan,会返回跳过的内容,所传递的参数就是要跳过的指定内容
  • 第三个是是否扫描完毕—eos,返回状态true或false
index
  • 生成一个类进行导出,用于后面在界面引入之后可以直接通过Mastache.render()函数进行解析,里面对于有一个方法:
  • 调用render的时候需要传递字符串和对应的数据,然后将其交给parseTemplateToTokens进行处理,所返回的结果就是一个最终结果dom字符串
parseTemplateToTokens
  • 是一个函数方法,用于将字符串处理成最基础的tokens
  • 导入Scanner这个类,然后while循环里面的eos方法,也就是是否扫描完毕,如果没有则一直扫描,通过不断的扫描跳过,将所有的数据存储到tokens里面,每个项里面有两项,例如text对应的是内容,name对应的是数据名,#是要开始入栈的地方,/是要出栈的地方
  • 在最后数据处理完毕的时候,会先进行两次处理,第一次交给nestTokens处理成嵌套格式的tokens,第二次交给renderTemplate将tokens和数据进行合并返回成dom字符串
nestTokens
  • 用于将普通的tokens变成有嵌套格式的tokens
  • 通过循环这个tokens的长度,然后拿到当前指定项的tokens,通过判断是否有#,来进行入栈,遇到/,则进行出栈,此时位于上一层栈
  • 对于其他情况text或者name的时候,直接追加进去,将最终的结果进行返回
  • 这里对于入栈出栈使用到了一个收集器的概念,通过指向同一数组,进行嵌套追加,实现栈堆的效果
renderTemplate
  • 该函数方法主要就是用于将tokens和数据进行合并并且返回成dom字符串
  • 接受两个参数,第一个是tokens,第二个是数据源data,准备一个变量字符串,最终进行返回结果
  • 循环遍历这个tokens,判断第0项是text的时候直接追加上去下标为1的内容,为name的时候需要将数据名和数据交给lookup函数,所返回的就是对于名称的数据,为#的时候,此时代表里面有子项,需要交给parseArray函数进行处理
lookup
  • 接受当前键名还有数据源,进行查找到之后返回结果
  • 如果当前键名里面有".“并且”."的下标不是0,此时通过字符串分割成数组,进行循环逐一取,直到最后取到结果
  • 如果有"."的话,就表明只是循环一个普通数组,直接返回所传递过来的结果即可
  • 其他情况就是不涉及到嵌套多层,直接从data中取就可以
parseArray
  • 只要在碰到#的时候才会将数据给这个函数进行处理
  • 此时也就代表了需要进行循环,先进行使用lookup取到对于的数据
  • 根据数据的长度来进行循环,循环完成只会所返回的结果会返回给renderTemplate,然后进行接着拼接即可,在这个函数里面,会借用renderTemplate进行处理结果,如果里面没有#接着嵌套的话就直接返回处理好的字符串,然后parseArray将这个字符串再返回给上一层调用它的renderTemplate,如果有的话renderTemplate会再次调用parseArray进行处理,处理完成之后交给renderTemplate进行处理,逐层循环遍历,遍历完成之后再逐层进行将结果返回给上一层,递归进行循环

虚拟Dom和diff算法

  • 虚拟dom就是通过模板引擎进行生成最初始的模板字符串,然后交给对应的函数生成虚拟节点,最后进行上树
  • diff算法就是用最小的代价去更新,实现最小量更新,diff算法就是对比的是新虚拟节点和旧虚拟节点进行对比
  • key在diff算法中及其重要,是唯一标识,告诉diff算法,在更改前后它们都是同一DOM节点
  • 是同一个虚拟节点的时候,才可以进行精细化比较,否则就进行暴力删除旧的,插入新的
  • 必须是同层比较,不会进行跨层比较,对于跨层的,直接删除旧的,然后插入新的
  • 分为七个模块,vnode,h,createElement,patch,patchVnode,upDataChildren
diff算法更新策略
  • 对应四个指针,分别是:

  • 会进行使用这四种方式进行查找,如果查找不到的话就接着使用下一种方式进行查找,如果找到的话就不会再使用下一种方式查找了

  • 如果这四种方式都没有查找到,就会进行循环遍历旧节点,看看旧节点中是否有新前指向的节点,如果有则会标记成undefined,把这个移到所有未处理的前面,也就是处理完毕的后面,循环完毕之后删除旧前与旧后之间的节点,包括旧前和旧后

  • 会有一个循环语句,while(新前<=新后 && 旧前<=旧后),当新节点先执行完毕的时候,那么旧节点里面所剩余的节点就是要删除的节点,当旧节点先执行完毕的时候,那么新节点里面所剩余的节点就是要新增的节点

  • 新前与旧前
    新后与旧后
    新后与旧前(命中之后,会将新后(旧前)命中的这个节点移动到老节点中所有未处理的后面,相当于就是处理完的前面,旧节点中与之一样的节点就会变成undefined)
    新前与旧后(命中之后,会将新前(旧后)命中的这个节点移动到老节点中所有未处理的前面,相当于就是处理完的后面,旧节点中与之一样的节点就会变成undefined)
    
vnode
  • 该函数的作用就是将传入的数据拼接成一个对象进行返回,所拼接的这个对象就是虚拟节点
h
  • h函数主要的作用就是将判断参数所传递的是什么类型的参数,然后调用vnode函数生成虚拟节点进行返回
createElement
  • createElement函数接受一个虚拟节点,如果里面只是单独文本,没有子元素,就之间添加文本,将其挂载到elm上进行返回
  • 如果有,则就会循环递归调用这个createElement函数,将上一次的elm追加到上一个上面的elm里面,直至最后进行返回
patch
  • 接受两个参数,旧虚拟节点(DOM)和新虚拟节点,会先进行判断是虚拟节点还是DOM节点,如果是DOM节点的话就会将其转换成虚拟节点
  • 判断key和节点(标签)是否一样,不一样的话直接使用createElement函数创建新的虚拟DOM然后进行渲染,并且删除旧的
  • 如果是同一节点的话就会将旧节点还有新节点交给patchVnode函数进行处理
patchVnode
  • 接受两个参数,旧虚拟节点和新虚拟节点,进行diff比较
  • 如果旧节点和新节点一样,代表无任何改变,直接返回
  • 如果新节点的text有内容并且没有子元素,就判断新旧节点的text是否一样,一样返回,不一样的话将旧节点中的内容变成新节点的内容
  • 如果新节点中有子元素,就判断旧节点中是否有子元素,如果没有的话就直接将旧节点中的text清空,循环新节点中的子元素,创建虚拟DOM进行追加,最后把旧虚拟节点中的elm添加到新的上面一份
  • 如果有的话,就需要调用upDataChildren函数进行详细diff比较了
upDataChildren
  • 接受三个参数,旧节点的elm,旧的子元素,新的子元素

  • 8个指针,旧前,旧后,新前,新后,旧前节点,旧后节点,新前节点,新后节点

  • 定义一个函数comparisonNode判断是否同时节点

  • 定义一个循环,循环条件就是(新前 <= 新后 && 旧前 <= 旧后)

  • 先进行四种情况的判断,这四种情况的时候没必要做任何处理:

    • 旧前节点或者使用旧前取出的节点为undefined的时候直接往下移
    • 旧后节点或者使用旧后取出的节点为undefined的时候直接往上移
    • 新前节点或者使用新前取出的节点为undefined的时候直接往下移
    • 新后节点或者使用新后取出的节点为undefined的时候直接往上移
  • 再进行五种情况的判断,通过comparisonNode判断是否是同一节点:

    • 旧前与新前
    • 旧后与新后
    • 新后与旧前
    • 新前与旧后
    • 前四种都没有命中
  • 旧前与新前与旧后与新后这两种情况命中的时候各自的指针向下或者向上移动,将这俩节点传入到patchVnode里面接着进行diff

  • 新后和旧前与新前和旧后这两种情况命中的时候各自的指针向下或者向上移动,将这俩节点传入到patchVnode里面接着进行diff,同时进行在旧节点的elm里面前面追加还是在后面追加

  • 前四种都没有命中的时候,先判断缓存是否有东西,没有的话循环旧前到旧后,从旧节点中取出来,如果不是undefined的时候就添加到这个缓存之中,然后通过新前节点的key去缓存中取,如果取不到的话就是没有,直接在旧开始节点之前进行添加即可,如果有的话就进行移动就可以,先从旧子元素节点中取出,然后使用patchVnode进行diff比较,将旧节点中的当前项设置为undefined,并且添加到旧后节点之前,最后将新节点进行下移

  • 最后判断旧的先执行完毕还是新的先执行完毕:

    • 旧的先执行完就证明新的里面还有剩余的,这些都是新增要添加的,直接循环新前到新后,将循环到的新节点使用createElement转换成虚拟DOM,然后添加到旧前所取到的节点之前
    • 新的先执行完就证明旧的里面还有剩余的,这些都是要删除的,直接循环旧前到旧后,如果取到的节点是undefined,就证明该节点已经移动,不需要管了,如果不是undefined,直接删除节点即可

Vue数据响应式

  • vue的数据响应式主要借助于js中的Object.defineProperty中的get和set进行数据的读取和改写的,对于数组需要进行改写原本的方法
  • 同时最后还需要对数据进行依赖收集和侦听
  • 分为七个模块,observe,Observer,utils,defineReactive,array,Dep,Watcher
observe
  • 这个函数的作用是接受一个参数
  • 判断这个参数如果不是对象类型,不是的话直接进行返回
  • 如果是对象就判断里面有没有ob这个原型对象,有的话ob就是当前的ob原型对象,并且进行返回
  • 如果没有的话就通过new Observer这个类进行生成
  • 最后返回ob
utils
  • 工具函数,接受五个参数,对象(obj),当前键(key),当前值(value),是否枚举(enumerable)
  • 会使用defineProperty进行生成
array
  • 这个模块的功能就是进行对数组方法重新
  • 先从最原始的array身上取出原型,也就是所以的方法
  • 然后通过取出的原型进行创建对象
  • 定义一个数组,里面是要重写的所有方法名
  • 遍历这个数组,先从最原始的array原型身上找到当前方法,进行备份
  • 使用工具函数utils,里面的第一个参数就是通过array原型创建出来的对象,第二个参数是当前循环这个数组里面的方法名,第三个参数是一个对象,需要先进行处理,首先拿到传递过来的参数,因为使用数组方法的时候,可以添加,因此需要拿到所传递的参数,同时ob就是当前的原型ob,再创建一个inserted,用于存储添加时候要添加的内容,然后判断是不是添加方法,如果是添加方法,就去取到当前要添加的值,循环完毕之后,判断当前添加数组的长度,如果为0则就是不添加,如果不为0就是有要添加的内容,进行添加响应式处理即可,最后恢复其原本的功能进行返回
Observer
  • 返回一个类,构造函数接受一个参数,先往自身上面挂载一个dep实例和ob实例(就是当前类的实例)
  • 判断传入的这个是数组还是其他的,是数组的情况下,将这个数组对象的原型强行指向array模块中利用原始array取出的原型进行创建后对象原型
  • 进行循环遍历这个数组,遍历出来的每一项都再次交给observe进行处理,看看数组里面是否还有数组或者对象,形成递归
  • 如果传入的是一个对象,直接调用遍历对象方法,将遍历的原对象和当前key交给defineReactive进行处理
defineReactive
  • 先进行生成一个dep
  • 从当前对象中根据key取出对应的值
  • 再次调用observe,将当前参数传递进去,如果是对象,那么就会接着observe,Observer,defineReactive循环调用递归
  • 将当前数据使用Object.defineProperty进行创建
  • get(读取)的时候,判断当前的Dep.target,然后进行调用dep身上的depend进行收集依赖,同时如果有子元素,也要收集子元素,然后返回值
  • set(修改)的时候,判断当前值是否等于新值,如果等于就直接返回,不需要做任何处理,如果不是,那么当前的值就等于新值,同时将这个新值交给observe进行响应式处理,并且调用dep的notify通知更新方法
Dep
  • 创建导出一个类,这个类里面有一个数组,存储自己的订阅者,也就是watcher的实例
  • 对应三个方法:addSub(添加订阅),depend(添加依赖),notify(通知更新)
  • depend触发的时候会判断Dep.target,然后调用自身实例上面的addSub
  • addSub接受一个Dep.target,然后将这个Dep.target添加到subs中
  • notify会先从subs身上克隆一份数组,然后循环这个数组,调用其身上的update方法来更新
Watcher
  • 创建导出一个类
  • 构造函数里面接受三个参数,第一个是数据对象,第二个是要拿到的数据字符串,第三个是回调函数
  • 里面有一个函数parsePath,传入的就是要拿到的数据字符串,所返回的就是一个函数,返回的这个函数传入数据就可以拿到值
  • 对应有四个方法:update,get,run,getAndInvoke
  • get是进入收集依赖,然后进行查找,最后返回查找结果
  • getAndInvoke需要接受一个回调函数,先拿到最新的值,判断旧值和新值是否一样,并且新值是不是一个对象,然调用传递过来的参数,将旧值新值都进行传入进去
  • update方法是用来跑run方法的,run方法里面调用的就是getAndInvoke,并且将回调函数进行传入
Watcher使用
  • 在主要文件里面进行使用

  • 先new出来,然后将总数据和要取到的值进行传入,通过回调函数拿到新值和旧值,进行数据的检测,相当于就是vue中的watch监听属性

  • import Watcher from "./Watcher"
    
    new Watcher(obj, "a.b.c", (newValue,oldValue) => {
        console.log("*",newValue)
        console.log("#",oldValue)
    })
    

AST抽象语法树

介绍

抽象语法树主要服务于模板编译,将真实标签转换成字符串,然后再解析成AST所生成的AST会通过h函数,生成虚拟节点再进行diff和patch渲染到界面

指针思想

js中的指针就是当前的下标位置,例如下面的案例

算出重复次数最多的字母以及重复次数, 就可以使用指针思想,前指针和后指针,前指针指向的是开头,后指针指向是后面,当前指针所指向的下标和后指针指向的下标相同的时候,就直接让后指针移动,当不同的时候先把当前的字符保存起来,以及重复次数进行保存,然后前指针指向后指针,后指针接着移动,循环结束的条件就是前指针不小于当前字符串的长度减一

var str = "aaaaabbbbbbbbbbcccccccdddddddddddddeeeeeeeeeeeeeeeeeeeeeeeeeee"
var startIdx = 0
var endIdx = 1
var repetitionTime = 0
var repetitionContent = ''
while (startIdx <= str.length - 1) {
    if (str[startIdx] == str[endIdx]) {
    } else {
        repetitionTime = endIdx - startIdx
        repetitionContent = str[startIdx]
        startIdx = endIdx
    }
    endIdx++
}
console.log(`${repetitionContent}重复次数最多,重复了${repetitionTime}次`)
递归思想

递归就是自身调用自身,直到达到条件就不再进行递归,递归完毕之后,递归会将上一次的结果返回给它的上一层,以此类推,直至返回到最顶层

只要出现规则复现,就需要使用递归

以案例,斐波那契数列为参考

cache是用来左缓存的,防止重复计算产生太大性能消耗,对于递归来说,尽量使用缓存来解决重复计算的问题

var cache = {}
function fib(index) {
    if (index < 0) {
        throw new Error("不得小于0")
    } else {
        if(index in cache){
            return cache[index]
        }
        var v = index == 1 || index == 0 ? 1 : fib(index - 1) + fib(index - 2)
        cache[index] = v
        return v
    }
}
var result = fib(7)
console.log(result)

通过递归生成对应的格式,使用map进行递归,每一项都会进行递归,如果传入的是数组就直接递归,不是数组就直接返回

这种写法对比for循环看起来更加的高级,但是没有for性能好,因为每一项都会再次调用这个函数

var arr = [1, 2, 3, [4, 5, [6, 7], 8], 9]
function arrDispose(data) {
    if (Array.isArray(data)) {
        return {
            children: data.map(item => arrDispose(item))
        }
    } else {
        return {
            value: data
        }
    }
}
console.log(arrDispose(arr))

又名堆栈,它是一种运算受限的线性表,仅能在尾部进行插入和删除,这一端被称为栈顶,相对的另一端称为栈底

添加元素称为:进栈,入栈,压栈

删除元素称为:出栈或者退栈

后进先出,先进后出

parse

主模块

导出一个函数,接受一个模板字符串,对应参数有指针,剩余部分的内容,栈1,栈2

通过while循环遍历整个模板字符串,根据指针改变剩余部分

通过正则,找到开始的时候,结束的时候,以及匹配到文字的时候

开始的时候,分别往栈1和栈2追加当前所查找到的内容,并且改变当前的指针等于追加内容的长度加上对应里面值的长度加上2(<>)

遇到标签里面有值的时候,就会将这个值交给parseAttrsString进行处理

结束的时候会进行栈1和栈2出栈,然后将栈2出栈的结果放到上一个栈的children里面,并且指针的长度增加当前结束字符串的长度再加上3(</>)

当遇到文字的时候,会将这个文字组成一个对象,添加到当前的children里面

最后返回结果

parseAttrsString

处理标签里面有值的时候,例如class类名,id等等

先进行判断,有值得时候就进行处理,没有值的时候就直接进行返回空数组即可

先创建一个数组进行存储,传进来的值进行左右去空格,当前位置下标以及是否在引号种的状态,循环,遇到”或者‘改变引号状态,并且有一个数组存储当前半处理的结果

引号状态为false并且遇到空格进行分割,并且将结果放到半处理数组中

最后遍历这个半处理数组中,遇到=进行分割,存储,最后进行返回

指令和生命周期

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值