IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vue源码:抽象语法树 -> 正文阅读

[JavaScript知识库]Vue源码:抽象语法树

抽象语法树是什么

在这里插入图片描述

在这里插入图片描述

抽象语法树被之上就是一个JS对象

在这里插入图片描述

抽象语法树和虚拟节点的关系

在这里插入图片描述

内容

在这里插入图片描述

相关算法储备 — 指针思想

试寻找字符串中,连续重复次数最多的字符。

在这里插入图片描述

指针就是下标,不是C语言中的指针,C语言中的指针可以操作内存。JS 中的指针就是一个下标位置。
i: 0
j: 1

  • 如果i和j指向的字一样,那么i不动,j后移
  • 如果i和j指向的字不一样,此时说明它们之间的字都是连续相同的,让i追上j,j后移
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 试寻找字符串中,连续重复次数最多的字符。
        var str = 'abbbccc';

        // 指针
        var i = 0;
        var j = 1;
        // 当前重复次数最多的次数
        var maxRepeatCount = 0;
        // 重复次数最多的字符串
        var maxRepeatChar = '';

        // 当i还在范围内的时候,应该继续寻找
        while (i <= str.length - 1) {
            // 看i指向的字符和j指向的字符是不是不相同
            if (str[i] !== str[j]) {
                // console.log('报!!!' + i + '和' + j + '之间的文字连续相同!!都是字母' + str[i] + '它重复了' + (j - i) + '次');
                // 和当前重复次数最多的进行比较
                if (j - i > maxRepeatCount) {
                    // 如果当前文字重复次数(j - i)超过了此时的最大值
                    // 就让它成为最大值
                    maxRepeatCount = j - i;
                    // 将i指针指向的字符存为maxRepeatChar
                    maxRepeatChar = str[i];
                }
                // 让指针i追上指针j
                i = j;
            }
            // 不管相不相同,j永远要后移
            j++;
        }

        // 循环结束之后,就可以输出答案了
        console.log(maxRepeatChar + '重复了' + maxRepeatCount + '次,是最多的连续重复字符');
    </script>
</body>

</html>

相关算法储备 — 递归深入

试输出斐波那契数列的前10 项,即1、1、2、3、5、8、13 、21 、34 、55 。然后请思考,代码是否有大量重复的计算?应该如何解决重复计算的问题?

在这里插入图片描述

cache思想

在这里插入图片描述

{
    "0": 1,
    "1": 1,
    "2": 2,
    "3": 3,
    "4": 5
}

形式转换:试将高维数组[1, 2, [3, [4, 5], 6], 7, [8], 9] 变为图中所示的对

小技巧:只要出现了“规则复现”就要想到用递归。

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 试输出斐波那契数列的前10项,即1、1、2、3、5、8、13、21、34、55

        // 缓存对象
        var cache = {};

        // 创建一个函数,功能是返回下标为n的这项的数字
        function fib(n) {
            // 判断缓存对象中有没有这个值,如果有,直接用
            if (cache.hasOwnProperty(n)) {
                return cache[n];
            }
            // 缓存对象没有这个值
            // 看下标n是不是0或者是不是1,如果是,就返回常数1
            // 如果不是,就递归
            var v = n == 0 || n == 1 ? 1 : fib(n - 1) + fib(n - 2);
            // 写入缓存。也就是说,每算一个值,就要把这个值存入缓存对象。
            cache[n] = v;
            return v;
        }

        for (let i = 0; i <= 9; i++) {
            console.log(fib(i));
        }

    </script>
</body>

</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script>
  let array = [1, 2, [3, [4, 5], 6], 7, [8], 9];

  function help(obj, arr) {
    if (!obj.children) obj.children = [];
    arr.forEach(item => {
      if (Array.isArray(item)) obj.children.push(help({}, item));
      else obj.children.push({value: item});
    })
    return obj;
  }

  console.log(help({}, array));

  function convert(item) {
    if (typeof item === 'number') return {value: item}
    else if (Array.isArray(item)) return {
      children: item.map(_item => {
        return convert(_item)
      })
    }
  }

  console.log(convert(array))
</script>
</body>
</html>

相关算法储备 — 栈

  • 栈(stack )又名堆栈,它是一种运算受限的线性表,仅在表尾能进行入和删除操作。这一端被称为栈顶,相对地,把另一端称为栈底。
  • 向一个栈插入新元素又称作进栈、入栈或压栈;从一个栈删除元素又称作出栈或退栈。
  • 后进先出(LIFO )特点:栈中的元素,最先进栈的必定是最后出栈,后进栈的一定会先出栈
  • JavaScript 中,栈可以用数组模拟。需要限制只能使用push() 和pop() ,不能使用unshift() 和shift()。即,数组尾是栈顶。
  • 当然,可以用面向对象等手段,将栈封装的更好。

在这里插入图片描述

利用"栈"的题目

试编写"智能重复"smartRepeat函数,实现:

  • 将3[abc]变为abcabcabc
  • 将3[2[a]2[b]]变为aabbaabbaabb
  • 将2[1[a]3[b]2[3[c]4[d]]]变为abbbcccddddcccddddabbbcccddddcccdddd

不用考虑输入字符串是非法的情况,比如:

  • 2[a3[b]]是错误的,应该补一个1,即2[1[a]3[b]]
  • [abc]是错误的,应该补一个1,即1[abc]

使用"栈"优雅接替

  • 词法分析的时候,经常要用到栈这个数据结构;

  • 初学者大坑:栈的题目和递归非常像,这类题目给人的感觉都是用递归解题。信心满满动手开始写了,却发现递归怎么都递归不出来。此时就要想到,不是用递归,而是用栈。

在这里插入图片描述

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        // 试编写“智能重复”smartRepeat函数,实现:
        // 将3[abc]变为abcabcabc
        // 将3[2[a]2[b]]变为aabbaabbaabb  
        // 将2[1[a]3[b]2[3[c]4[d]]]变为abbbcccddddcccddddabbbcccddddcccdddd

        function smartRepeat(templateStr) {
            // 指针
            var index = 0;
            // 栈1,存放数字
            var stack1 = [];
            // 栈2,存放临时字符串
            var stack2 = [];
            // 剩余部分
            var rest = templateStr;

            while (index < templateStr.length - 1) {
                // 剩余部分
                rest = templateStr.substring(index);

                // 看当前剩余部分是不是以数字和[开头
                if (/^\d+\[/.test(rest)) {
                    // 得到这个数字
                    let times = Number(rest.match(/^(\d+)\[/)[1]);
                    // 就把数字压栈,把空字符串压栈
                    stack1.push(times);
                    stack2.push('');
                    // 让指针后移,times这个数字是多少位就后移多少位加1位。
                    // 为什么要加1呢?加的1位是[。
                    index += times.toString().length + 1;
                } else if (/^\w+\]/.test(rest)) {
                    // 如果这个字符是字母,那么此时就把栈顶这项改为这个字母
                    let word = rest.match(/^(\w+)\]/)[1];
                    stack2[stack2.length - 1] = word;
                    // 让指针后移,word这个词语是多少位就后移多少位
                    index += word.length;
                } else if (rest[0] === ']') {
                    // 如果这个字符是],那么就①将stack1弹栈,②stack2弹栈,③把字符串栈的新栈顶的元素重复刚刚弹出的那个字符串指定次数拼接到新栈顶上。
                    let times = stack1.pop();
                    let word = stack2.pop();
                    // repeat是ES6的方法,比如'a'.repeat(3)得到'aaa'
                    stack2[stack2.length - 1] += word.repeat(times);
                    index++;
                }

                console.log(index, stack1, stack2);
            }

            // while结束之后,stack1和stack2中肯定还剩余1项。返回栈2中剩下的这一项,重复栈1中剩下的这1项次数,组成的这个字符串。如果剩的个数不对,那就是用户的问题,方括号没有闭合。
            return stack2[0].repeat(stack1[0]);
        }

        var result = smartRepeat('3[2[3[a]1[b]]4[d]]');
        console.log(result);
    </script>
</body>

</html>

正则表达式的相关方法

在这里插入图片描述

在这里插入图片描述

手写实现AST抽象语法树

在这里插入图片描述

在这里插入图片描述

识别attrs

在这里插入图片描述

完整代码

index.js

import parse from './parse.js';

var templateString = `<div>
    <h3 class="aa bb cc" data-n="7" id="mybox">你好</h3>
    <ul>
        <li>A</li>
        <li>B</li>
        <li>C</li>
    </ul>
</div>`;

const ast = parse(templateString);
console.log(ast);

parse.js

import parseAttrsString from './parseAttrsString.js';

// parse函数,主函数
export default function (templateString) {
    // 指针
    var index = 0;
    // 剩余部分
    var rest = '';
    // 开始标记
    var startRegExp = /^\<([a-z]+[1-6]?)(\s[^\<]+)?\>/;
    // 结束标记
    var endRegExp = /^\<\/([a-z]+[1-6]?)\>/;
    // 抓取结束标记前的文字
    var wordRegExp = /^([^\<]+)\<\/[a-z]+[1-6]?\>/;
    // 准备两个栈
    var stack1 = [];
    var stack2 = [{ 'children': [] }];

    while (index < templateString.length - 1) {
        rest = templateString.substring(index);
        // console.log(templateString[index]);
        if (startRegExp.test(rest)) {
            // 识别遍历到的这个字符,是不是一个开始标签
            let tag = rest.match(startRegExp)[1];
            let attrsString = rest.match(startRegExp)[2];
            // console.log('检测到开始标记', tag);
            // 将开始标记推入栈1中
            stack1.push(tag);
            // 将空数组推入栈2中
            stack2.push({ 'tag': tag, 'children': [], 'attrs': parseAttrsString(attrsString) });
            // 得到attrs字符串的长度
            const attrsStringLength = attrsString != null ? attrsString.length : 0;
            // 指针移动标签的长度加2再加attrString的长度,为什么要加2呢?因为<>也占两位
            index += tag.length + 2 + attrsStringLength;
        } else if (endRegExp.test(rest)) {
            // 识别遍历到的这个字符,是不是一个结束标签
            let tag = rest.match(endRegExp)[1];
            // console.log('检测到结束标记', tag);
            let pop_tag = stack1.pop();
            // 此时,tag一定是和栈1顶部的是相同的
            if (tag == pop_tag) {
                let pop_arr = stack2.pop();
                if (stack2.length > 0) {
                    stack2[stack2.length - 1].children.push(pop_arr);
                }
            } else {
                throw new Error(pop_tag + '标签没有封闭!!');
            }
            // 指针移动标签的长度加3,为什么要加2呢?因为</>也占3位
            index += tag.length + 3;
        } else if (wordRegExp.test(rest)) {
            // 识别遍历到的这个字符,是不是文字,并别不能是全空
            let word = rest.match(wordRegExp)[1];
            // 看word是不是全是空
            if (!/^\s+$/.test(word)) {
                // 不是全是空 
                // console.log('检测到文字', word);
                // 改变此时stack2栈顶元素中
                stack2[stack2.length - 1].children.push({ 'text': word, 'type': 3 });
            }
            // 指针移动标签的长度加3,为什么要加2呢?因为</>也占3位
            index += word.length;
        } else {
            index++;
        }
    }

    // 此时stack2就是我们之前默认放置的一项了,此时要返回这一项的children即可
    return stack2[0].children[0];
};

parseAttrsString.js

// 把attrsString变为数组返回
export default function (attrsString) {
    if (attrsString == undefined) return [];
    console.log(attrsString);
    // 当前是否在引号内
    var isYinhao = false
    // 断点
    var point = 0;
    // 结果数组
    var result = [];

    // 遍历attrsString,而不是你想的用split()这种暴力方法
    for (let i = 0; i < attrsString.length; i++) {
        let char = attrsString[i];
        if (char == '"') {
            isYinhao = !isYinhao;
        } else if (char == ' ' && !isYinhao) {
            // 遇见了空格,并且不在引号中
            console.log(i);
            if (!/^\s*$/.test(attrsString.substring(point, i))) {
                result.push(attrsString.substring(point, i).trim());
                point = i;
            }
        }
    }
    // 循环结束之后,最后还剩一个属性k="v"
    result.push(attrsString.substring(point).trim());

    // 下面的代码功能是,将["k=v","k=v","k=v"]变为[{name:k, value:v}, {name:k, value:v}, {name:k,value:v}];
    result = result.map(item => {
        // 根据等号拆分
        const o = item.match(/^(.+)="(.+)"$/);
        return {
            name: o[1],
            value: o[2]
        };
    });

    return result;
}
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-09 17:30:43  更:2021-07-09 17:31:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/8 15:27:01-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码