【手写 Vue2.x 源码】第十六篇 - 生成 render 函数 - 代码拼接

news2024/7/4 5:03:57

一,前言

上篇,生成 ast 语法树 - 构造树形结构部分

  • 基于 html 特点,使用栈型数据结构记录父子关系
  • 开始标签,结束标签及文本的处理方式
  • 代码重构及ast 语法树构建过程分析

本篇,使用 ast 语法树生成 render 函数 - 代码拼接部分


二,生成 render 函数 - 代码拼接

1,前文回顾

第十二篇提到过,compileToFunction 方法是 Vue 编译的入口

compileToFunction方法中做了两件事:

  1. parserHTML:将模板内容编译为 ast 语法树
  2. generate:再根据 ast 语法树生成为 render 函数;

vue 编译阶段的最终产物是 render 函数

//  src/compiler/index.js

export function compileToFunction(template) {
  // 1,将模板编译称为 AST 语法树
  let ast = parserHTML(template);
  // 2,使用 AST 生成 render 函数
  let code = generate(ast);
}

前几篇,通过 parserHTML 将 html 模板编译为 ast 语法树

接下来,通过 generate 使用 ast 生成 render 函数

generate方法:将 ast 生成为 render 函数

通过调用 generate 方法,传入 ast,生成 code

之前简单提到了 render 函数

image.png

左边的模板 -> 生成为 -> 右边的 render 函数

  • _c 等价于 createElement 创建一个元素
  • _v 等价于 _vode
  • _s 等价于 stringify

2,render 函数之代码拼接:generate(ast)

代码生成的方式,就是进行字符串拼接

仿造上图 render 方法,进行字符串的拼接

// src/compiler/index.js

function generate(ast) {
 let code = `_c('${ast.tag}',${
  ast.attrs.length? JSON.stringify({}):'undefined'	// 暂不处理属性,后面单独处理
 }${
  ast.children?`,[]`:''		// 暂不处理儿子,后面单独处理
 })`

 return code;
}

// _c('div',{},[]}

3,处理属性:genProps(ast.attrs)

// src/compiler/index.js

// 将 attrs 数组格式化为:{key=val,key=val,}
function genProps(attrs) {
  let str = '';
  for(let i = 0; i< attrs.length; i++){
    let attr = attrs[i];
    // 使用 JSON.stringify 将 value 转为 string 类型
    str += `${attr.name}:${JSON.stringify(attr.value)},`
  }
  return `{${str.slice(0, -1)}}`;// 去掉最后一位多余的逗号,再在外边套上{}
 }

function generate(ast) {
 let code = `_c('${ast.tag}',${
  ast.attrs.length? genProps(ast.attrs):'undefined'
 }${
  ast.children?`,[]`:''
 })`
 return code;
}

export function compileToFunction(template) {
  let ast = parserHTML(template);
  let code = generate(ast);
  console.log(code)
}

// _c('div',{id:"app",a:"1",b:"2"},[]}

4,处理属性中的样式

在 style 属性中,会存在样式,也需要在属性中机型处理

<div id="app" a='1' b=2 style="color: red;background: blue;">
  <p>{{message}} 
    <span>Hello Vue</span>
  </p>
</div>

继续将样式处理成为为一个对象:

// src/compiler/index.js#genProps

// 将 attrs 数组格式化为:{key=val,key=val,}
function genProps(attrs) {
  let str = '';
  for(let i = 0; i< attrs.length; i++){
    let attr = attrs[i];
    // 将样式处理为对象 {name:id, value:'app'}
    if(attr.name == "style"){
      // <div id="app" style="color: red;background: blue;"></div>
      // 使用 replace 进行正则匹配,对样式进行 key,value 替换
      // ^;: 不是分号(分割属性和值)、冒号(结尾)
      let styles = {};
      attr.value.replace(/([^;:]+):([^;:]+)/g, function () {
        styles[arguments[1]] = arguments[2]
      }) 
      attr.value = styles;
    }
    str += `${attr.name}:${JSON.stringify(attr.value)},`
  }
  return `{${str.slice(0, -1)}}`;
 }


// 打印输出:
// _c('div',
//    {id:"app",a:"1",b:"2",style:{"color":" red","background":" blue"}},
//    []}

测试输出:

image.png

5,递归深层处理儿子:genChildren

继续处理儿子,demo如下:

// _c(div,{},c1,c2,c3...)
function generate(ast) {
  let children = genChildren(ast);
  let code = `_c('${ast.tag}',${
    ast.attrs.length? genProps(ast.attrs):'undefined'
  }${
    children?`,${children}`:''
  })`
  return code;
}

function genChildren(el) {
  console.log("===== genChildren =====")
  let children = el.children;
  if(children){
    console.log("存在 children, 开始遍历处理子节点...", children)
    let result = children.map(item => gen(item)).join(',');
    console.log("子节点处理完成,result = " + JSON.stringify(result))
    return result
  }
  console.log("不存在 children, 直接返回 false")
  return false;
}

function gen(el) {
  console.log("===== gen ===== el = ",el)
  if(el.type == 1){ 
    console.log("元素标签 tag = "+el.tag+",generate继续递归处理")
    return generate(el);// 递归处理当前元素
  }else{
    console.log("文本类型,text = " + el.text)
    return el.text
  }
}

// _c('div',{id:"app",a:"1",b:"2",style:{"color":" red","background":" blue"}},
//    _c('p',undefined,_v(_s(message)),
//       _c('span',undefined,_v('HelloVue1')),
//       _c('span',undefined,_v('HelloVue2')),
//       _c('span',undefined,_v('HelloVue3'))
//    )
// )

image.png

6,为文本类型包装 _v

function gen(el) {
  console.log("===== gen ===== el = ",el)
  if(el.type == 1){// 
    console.log("元素标签 tag = "+el.tag+",generate继续递归处理")
    return generate(el);// 如果是元素就递归的生成
  }else{// 文本类型
    let text = el.text
    console.log("文本类型,text = " + text)
    return `_v('${text}')`  // 包装_v
  }
}

7,为变量包装 _s

TODO:后续优化描述,添加表达式部分的截取分析和 log 跟踪

image.png

  • 文本 -> 包装 _v
  • 变量 -> 包装 _s
  • 字符串 -> 包装 “”

模板中{{ name }}

  • name有可能是一个对象,需要使用 JSON.stringify 将对象转换成为字符串

检查 text 中,是否包含{{}}:

  • 包含,说明是表达式;
  • 如果不包含,直接返回 _v(‘${text}’) ;

判断是否包含{{}},可以使用正则defaultTagRE:

如果包含,说明是表达式,需要做表达式 和 普通值的拼接

['aaa',_s(name),'bbb'].join('+') ==> _v('aaa' + s_(name) + 'bbb')

先放数组 tokens 中再拼接一下,最后返回 _v(${tokens.join('+')})

8,完整实现

// src/compiler/index.js#gen
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g

function gen(el) {
  console.log("===== gen ===== el = ",el)
  if(el.type == 1){// 
    console.log("元素标签 tag = "+el.tag+",generate继续递归处理")
    return generate(el);// 如果是元素就递归的生成
  }else{// 文本类型
    let text = el.text
    console.log("文本类型,text = " + text)
    if(!defaultTagRE.test(text)){
      return `_v('${text}')`  // 普通文本,包装_v
    }else{
      // 存在{{}}表达式,需进行表达式 和 普通值的拼接 
      // 目标:['aaa',_s(name),'bbb'].join('+') ==> _v('aaa' + s_(name) + 'bbb')
      let lastIndex = defaultTagRE.lastIndex = 0;
      let tokens = []; // <div>aaa {{name}} bbb</div>
      let match
      while(match = defaultTagRE.exec(text)){
        console.log("匹配内容" + text)
        let index = match.index;// match.index:指当前捕获到的位置
        console.log("当前的 lastIndex = " + lastIndex)
        console.log("匹配的 match.index = " + index)
        if(index > lastIndex){  // 将前一段 ’<div>aaa '中的 aaa 放入 tokens 中
          let preText = text.slice(lastIndex, index)
          console.log("匹配到表达式-找到表达式开始前的部分:" + preText)
          tokens.push(JSON.stringify(preText))// 利用 JSON.stringify 加双引号
        }

        console.log("匹配到表达式:" + match[1].trim())
        // 放入 match 到的表达式,如{{ name  }}(match[1]是花括号中间的部分,并处理可能存在的换行或回车)
        tokens.push(`_s(${match[1].trim()})`)
        // 更新 lastIndex 长度到'<div>aaa {{name}}'
        lastIndex = index + match[0].length;  // 更新 lastIndex 长度到'<div>aaa {{name}}'
      }

      // while 循环后可能还剩余一段,如:’ bbb</div>’,需要将 bbb 放到 tokens 中
      if(lastIndex < text.length){
        let lastText = text.slice(lastIndex);
        console.log("表达式处理完成后,还有内容需要继续处理:"+lastText)
        tokens.push(JSON.stringify(lastText))// 从 lastIndex 到最后
      }
      
      return `_v(${tokens.join('+')})`
    }
  }
}

对文本的处理逻辑:

  1. 如果没有特殊的表达式,直接返回
  2. 如果有表达式,需进行匹配和截取处理

使用正则进行捕获处理,可能存在较复杂的情况,如:

<div>aaa {{name}} bbb</div>
<!-- 或 --> 
<div>aaa {{name}} bbb {{age}} ccc</div>
  1. 使用正则 defaultTagRE 捕获表达式,将表达式前面的一段’
    aaa ’ 中的 aaa 放入 tokens 数组
    备注:本次捕获完成后,得到偏移量在表达式后,待表达式处理完成后统一调整即可;
  2. 将捕获到表达式名称 name 放入 tokens 数组中并修改匹配偏移量,同理继续处理其余表达式
    备注:每次捕获成功后,重复 1,2 两个步骤
  3. 当表达式全部捕获完成后,若文本长度仍大于当前匹配偏移量,说明还有最后一段没有处理,
    将 ’ bbb’ 中的 bbb 也放入 tokens 数组
  4. 都放入 tokens 数组后,拼接返回 _v(${tokens.join('+')})

image.png


三,结尾

本篇,主要介绍了生成 render 函数 - 代码拼接

  • render 函数的代码拼接:generate(ast)
  • 处理属性:genProps(ast.attrs)
  • 处理属性中的样式
  • 递归深层处理儿子:genChildren

至此,render 函数中 with 内部的代码拼接已经完成
下一篇,生成 render 函数 - 函数生成

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/155075.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

双软认证-深圳市

双软认证是软件企业的认证和软件产品的登记&#xff0c;企业申请双软认证除了获得软件企业和软件产品的认证资质&#xff0c;同时也是对企业知识产权的一种保护方式&#xff0c;更可以让企业享受国家提供给软件行业的税收优惠政策。 想要在这个残酷的市场中生存下去的话&#x…

cc1200 Sub-1 GHz RF Transceivers 开发

一些应用需要定制开发无线串口、指定发送频点、调制方式、加密传输等等&#xff0c;需要使用无线数据的传输场景&#xff0c;需要使用公用频段进行数据传输。一些场景需要使用Sub-1 GHz频点进行数据传输&#xff0c;比如无线串口&#xff0c;其他无线申请&#xff0c;在国内选择…

集群调度情况

1 集群调度 2 调度简介 Scheduler是kubernetes的调度器&#xff0c;主要任务是把定义的pod分配到集群的节点上。听起来非常简单&#xff0c;但有很多要考虑的问题 公平&#xff1a; 如何保证每个节点都能被分配资源 资源高效利用&#xff1a;集群所有资源最大化被使用 效率&…

【 uniapp - 黑马优购 | 购物车页面(1)】如何创建购物车编译模式、 商品列表区域实现

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;讨厌编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;小新爱学习. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc…

LeetCode[692]前K个高频单词

难度&#xff1a;中等题目&#xff1a;给定一个单词列表 words和一个整数 k&#xff0c;返回前 k个出现次数最多的单词。返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率&#xff0c; 按字典顺序 排序。示例 1&#xff1a;输入: words ["i"…

【异常】记一次因scripts编写错误导致无法正常build的问题

一、npm 与 scripts之间的关系 Node 开发离不开 npm&#xff0c;而脚本功能是 npm 最强大、最常用的功能之一。 npm 允许在package.json文件里面&#xff0c;使用scripts字段定义脚本命令。 比如以下&#xff1a; "scripts": {"dev": "vue-cli-se…

【C++】引用详解

作者&#xff1a;阿润菜菜 专栏&#xff1a;C &#x1f3c3;&#x1f3c3;&#x1f3c3;&#x1f3c3;&#x1f3c3;&#x1f3c3; 本文目录 概念及用法 特性 使用场景 1.做参数 2. 做返回值 从函数栈帧角度理解引用 传值、传引用效率比较 引用和指针的区别 概念及用法 引…

洛谷 P1194 买礼物 (图论 最小生成树)

鸽了好几天了今天写个洛谷的题解 题目描述 又到了一年一度的明明生日了&#xff0c;明明想要买 BB 样东西&#xff0c;巧的是&#xff0c;这 BB 样东西价格都是 AA 元。 但是&#xff0c;商店老板说最近有促销活动&#xff0c;也就是&#xff1a; 如果你买了第 II 样东西&#…

Python OpenCV 数字验证码 字母验证码 图片验证码 自动识别方案 第三方库 识别成功率较高 通用解决方案

前言 在学习的前期可使用现有封装好的轮子试试效果,实际调试能否满足需求。使用已经造好的轮子的好处就是能快速解决当下的问题。若能就继续使用,若不能就接入下一步的深度学习模型训练,其实再验证码识别业务场景大多是情况下用于自动化测试仅针对公司内某一单一的业务线,而…

既然有MySQL了,为什么还要有MongoDB?

目录一、基本概念走起二、MongoDB的主要特征三、MongoDB优缺点&#xff0c;扬长避短1、优点2、缺点四、何时选择MongoDB&#xff1f;为啥要用它&#xff1f;1、MongoDB事务2、多引擎支持各种强大的索引需求3、具体的应用场景4、以下是几个实际的应用案例&#xff1a;5、选择Mon…

gcc后续——链接时的静态库和动态库

本篇文章是链接阶段静动态库的理解&#xff0c;点击查看gcc四个阶段 文章目录1 . 库检测linux所用库查找库的位置2. 动静态库的感性理解1. 动态库的理解2. 静态库的理解3. 静动态库整体理解1. 静态库和静态链接2. 动态库和动态链接3. 静动态库对比1.查询当前linux所用库2. 查看…

【洛谷】P1966 [NOIP2013 提高组] 火柴排队

其实这题本身并不难&#xff0c;考的知识点就是归并排序和逆序对&#xff1b;那么难点在哪呢&#xff1f;就在如何发现这题是个逆序对&#xff1a;至少读到这里我们可以知道&#xff0c;虽然火柴高度是唯一的&#xff0c;但我们不可能直接开一个 max long int 大小的数组&#…

数据库分片

文章目录一、为什么要分片二、什么是数据分片1、垂直分片2、水平分片三、常用分片策略1、Range2、Hash四、相关中间件1、Sharding-Sphere2、Sharding-jdbc一、为什么要分片 从性能方面来说&#xff0c;由于关系型数据库大多采用B树类型的索引&#xff0c;在数据量超过阈(yu)值…

【python】re解析和re模块

目录 正则 RE概念 常见的元字符 量词 贪婪&惰性 修饰符 re模块 findall finditer search match 预加载正则式 内容提取 正则 RE概念 常见的元字符 量词 贪婪&惰性 贪婪匹配.* 惰性匹配.*? 修饰符 修饰符描述re.I使匹配对大小写不敏感re.L做本地化识别&…

接口测试框架实战 | 流程封装与基于加密接口的测试用例设计

接口测试仅仅掌握 Requests 或者其他一些功能强大的库的用法&#xff0c;是远远不够的&#xff0c;还需要具备能根据公司的业务流程以及需求去定制化一个接口自动化测试框架的能力。所以&#xff0c;接下来&#xff0c;我们主要介绍下接口测试用例分析以及通用的流程封装是如何…

GO语言基础-06-匿名函数和闭包

文章目录1. 匿名函数概念语法示例2. 闭包概念语法语法示例1. 匿名函数 概念 如其名&#xff0c;匿名函数不声明函数名。因此要调用匿名函数只能定义一个变量等于该匿名函数。 语法 func(参数 参数类型)(返回值 返回值类型){函数体 }示例 代码 package mainimport "fm…

Jenkins操作文档

前言 jenkins概述 持续集成是一种实践&#xff0c;而jenkins可以帮助团队去尽量好的去完成这种实践 jenkins是⼀个开源软件项⽬&#xff0c;是基于Java开发的⼀种持续集成⼯具&#xff0c;⽤于监控持续重复的⼯作&#xff0c;旨在提供⼀个开放易⽤的软件平台&#xff0c;使软…

“锂”想护航|深耕重点工段,用AI为锂电池安全生产加倍提速!

行业发展DEVELOPMENT -受益于新能源汽车市场的爆发增长、“双碳”政策影响下的储能市场扩大等影响&#xff0c;中国锂电行业现已进入高度产业化、规模化发展时期。锂电池生产工艺较长&#xff0c;生产设备庞杂&#xff0c;专用性强&#xff0c;而当前已迈入国际化竞争的锂电行业…

【Vue】032-尚硅谷-尚品汇-mockjs模拟数据---20230111

032-尚硅谷-尚品汇-mockjs模拟数据 官网链接 第一步:安装依赖包mockjs 安装mockjs npm install --save mockjs第二步:在src文件夹下创建一个文件夹mock。 第三步:准备模拟的数据。&#xff01; mock/banner.json [{"id": "1","imgUrl": &q…

哪种蓝牙耳机音质好?2023公认音质最好的蓝牙耳机推荐

现如今&#xff0c;蓝牙耳机的使用频率越来越高&#xff0c;其在音质上的表现也越来越好。那么&#xff0c;在众多的蓝牙耳机当中&#xff0c;哪种蓝牙耳机音质好&#xff1f;下面&#xff0c;我来给大家推荐几款公认音质最好的蓝牙耳机&#xff0c;一起来看看吧。 一、南卡小…