一点点学会编译文本

简介: 一点点学会编译文本

一点点学会编译文本


使用 vue,都知道{{msg}},能变成data.msg相对应的值hello

此文循序渐进实现最终vue中编译文本的效果。

{{msg}} => 'hello'

这里先不关注 dom 之类的事情,只专注于其中的逻辑。

首先看看以下代码,想想compileText怎么实现

let data = {
  msg: "hello"
};
let text = "{{msg}} world";
function compileText(data, text) {
  // 想想怎么实现
}
text = compileText(data, text);
// 想输出 => hello world
console.log(text);

其实这里的逻辑并不难,无外乎先找到{{msg}},然后将整那个字符串替换成data.msg的值。

function compileText(data, text) {
  // 匹配正则
  let reg = /\{\{(.+)\}\}/;
  let newText = text.replace(reg, (...args) => {
    // 拿到msg
    let expr = args[1];
    // data里面msg属性的值
    let value = data[expr];
    return value;
  });
  // 返回替换完的新字符串
  return newText;
}

但是 data 里可能也有对象,如{msg:'hello',person:{name:'hua'}}

text 可能就变成{{person.name}} 喜欢西湖

{{person.name}} => 'hua'

其实就是在 compileText 里拿到 value 值的时候,多些操作。

思考下面的 getValue 怎么实现

let data = { person: { name: "hua" } };
let expr = "person.name";
function getValue(data, expr) {
  // 想想这里怎么写
}
// 想输出 => hua
console.log(getValue(data, expr));

这里其实就是一层层往下返回,为了写成公用的,会考虑很多层的情况。 理解难点是reduce,如果不明白,可以看入门怎么使用 reduce

function getValue(data, expr) {
  // 先将每个key分隔开,放进数组里
  let arr = expr.split(".");
  // 下面就是reduce的使用了,写出init和fn,就基本ok了
  let init = data;
  let fn = (acc, cur) => acc[cur];
  let value = arr.reduce(fn, init);
  return value;
}

然后升级下 compileText

function compileText(data, text) {
  // 匹配正则
  let reg = /\{\{(.+)\}\}/;
  let newText = text.replace(reg, (...args) => {
    // 拿到msg
    let expr = args[1];
    // 就改了这里,就能支持多重key
    let value = getValue(data, expr);
    return value;
  });
  // 返回替换完的新字符串
  return newText;
}
// demo演示
let data = {
  person: { name: "hua" }
};
let text = "{{person.name}} 喜欢西湖";
text = compileText(data, text);
// hua 喜欢西湖
console.log(text);

文本节点编译 <div id='app'> {{msg}} <b>1</b> {{msg}} </div>

先试着将div子文本节点里面的表达式正确编译。

let data = { msg: "hello" };
let app = document.querySelector("#app");
// 找到所有子节点
let children = [...app.childNodes];
children.forEach(child => {
  // 如果是文本节点,就编译
  if (child.nodeType === 3) {
    compileTextNode(child, data);
  }
});
function compileTextNode(node, data) {
  // 试着写写,就一行代码啦
}

嗯哼,结果如下

function compileTextNode(node, data) {
  node.textContent = compileText(data, node.textContent);
}

元素节点编译 <div id='app'> <span>{{msg}}</span></div>

现在 span 元素里面有的话,怎么编译呢。 其实就是元素节点的话,将其文本元素再挑出来。这里如果深层嵌套,依旧涉及到递归。 可以先试试想想,甚至敲敲compileElement

let data = { msg: "hello" };
let app = document.querySelector("#app");
compileElement(app, data);
// 增加一个方法,编译元素节点,这里注意,其实只会编译文本节点,如果是元素节点就去找其子文本节点
function compileElement(node, data) {
  let children = [...node.childNodes];
  children.forEach(child => {
    if (child.nodeType === 1) {
      compileElement(child, data);
    }
    if (child.nodeType === 3) {
      compileTextNode(child, data);
    }
  });
}

避免频繁操作 dom,试试 fragment

这里 app 里面的节点一直在频繁操作,是很不利于性能的。

  • 先将 app 里所有的节点放进文档碎片
  • 编译完之后,再把文档碎片塞进 app 里,完美。

代码想想也就没啥大的事了

let data = { msg: "hello" };
let app = document.querySelector("#app");
// 塞进碎片
let appFragment = moveToFragment(app);
// 编辑碎片
compileElement(appFragment, data);
// 碎片在塞进app
app.appendChild(appFragment);
function moveToFragment(node) {
  let fragment = document.createDocumentFragment();
  // 来试试写这里实现功能
  return fragment;
}

答案这样子

function moveToFragment(node) {
  let fragment = document.createDocumentFragment();
  // 高阶可以这样
  // let firstNode;
  // while ((firstNode = node.firstChild)) {
  //   fragment.appendChild(firstNode);
  // }
  let children = [...node.childNodes];
  children.forEach(child => {
    fragment.appendChild(child);
  });
  return fragment;
}

合并写下,编译的整体 demo

可以直接复制,在本地试试。希望里面的方法能有一些启发。

<body>
  <div id="app">
    {{msg}}
    <div>{{msg}}</div>
    {{msg}}
  </div>
  <script>
    let data = { msg: "hello" };
    let app = document.querySelector("#app");
    // 塞进碎片
    let appFragment = moveToFragment(app);
    // 编辑碎片
    compileElement(appFragment, data);
    // 碎片在塞进app
    app.appendChild(appFragment);
    function moveToFragment(node) {
      let fragment = document.createDocumentFragment();
      // let firstNode;
      // while ((firstNode = node.firstChild)) {
      //   fragment.appendChild(firstNode);
      // }
      let children = [...node.childNodes];
      children.forEach(child => {
        fragment.appendChild(child);
      });
      return fragment;
    }
    function compileElement(node, data) {
      let children = [...node.childNodes];
      children.forEach(child => {
        if (child.nodeType === 1) {
          compileElement(child, data);
        }
        if (child.nodeType === 3) {
          compileTextNode(child, data);
        }
      });
    }
    function compileTextNode(node, data) {
      node.textContent = compileText(data, node.textContent);
    }
    function compileText(data, text) {
      // 匹配正则 注释是高级点的匹配 哈哈哈 我还不会 先粘贴在这里
      // let reg = /\{\{((?:.|\r?\n)+?)\}\}/g;
      let reg = /\{\{(.+)\}\}/;
      let newText = text.replace(reg, (...args) => {
        // 拿到msg
        let expr = args[1];
        // 就改了这里,就能支持多重key
        let value = getValue(data, expr);
        return value;
      });
      // 返回替换完的新字符串
      return newText;
    }
    function getValue(data, expr) {
      // 先将每个key分隔开,放进数组里
      let arr = expr.split(".");
      // 下面就是reduce的使用了,写出init和fn,就基本ok了
      let init = data;
      let fn = (acc, cur) => acc[cur];
      let value = arr.reduce(fn, init);
      return value;
    }
  </script>
</body>


目录
相关文章
|
3月前
|
数据采集 机器学习/深度学习 数据可视化
分享261个Python源码源代码总有一个是你想要的
分享261个Python源码源代码总有一个是你想要的
129 0
|
3月前
|
文字识别 NoSQL 物联网
分享55个C源码源代码总有一个是你想要的
分享55个C源码源代码总有一个是你想要的
30 1
【教程】好多好看好酷的代码注释,喜欢就选一个;还可以将自己喜欢的图片一键生成代码注释
【教程】好多好看好酷的代码注释,喜欢就选一个;还可以将自己喜欢的图片一键生成代码注释
|
10月前
|
编译器 C语言
【程序环境和程序预处理】万字详文,忘记了,看这篇就对了(1)
1.程序翻译环境和运行环境 假设一个test.c文件经过编译器编译运行后生成可执行文件test.exe,这中间存在两个过程: 一个是翻译,在这个环境中源代码被转换为可执行的机器指令。 一个是运行,它用于实际执行代码。 在翻译环境阶段,会进行编译和链接操作。 在汇编阶段,是将汇编指令转换成二进制指令。 1.1程序翻译中的的编译和链接
|
10月前
|
编译器
【程序环境和程序预处理】万字详文,忘记了,看这篇就对了(2)
1.程序翻译环境和运行环境 假设一个test.c文件经过编译器编译运行后生成可执行文件test.exe,这中间存在两个过程: 一个是翻译,在这个环境中源代码被转换为可执行的机器指令。 一个是运行,它用于实际执行代码。 在翻译环境阶段,会进行编译和链接操作。 在汇编阶段,是将汇编指令转换成二进制指令。
|
11月前
EasyX基础内容(和易错的地方)(二)
EasyX基础内容(和易错的地方)
146 0
|
11月前
EasyX基础内容(和易错的地方)(一)
注意代码里面的文字,是易错点
104 0
|
Python Windows
你真的看得懂报错吗?
你真的看得懂报错吗?
274 0
你真的看得懂报错吗?
|
开发者
🤡公号文章排版利器 | 🐁尾汁Markdown转换工具来咯~(下)
从可定制和易用性两方面入手优化,这不第一个可用版本来咯~
152 0
千万别再一直无脑使用ES6的箭头函数了,它虽然很有用但并不是万能的
相信很多小伙伴自从知道了ES6的箭头函数以后,都疯狂得使用,渐渐的淡忘了普通函数的使用。不过确实,箭头函数看起来比较简洁,用起来也舒服,不过它的出现是为了解决某一部分问题的,并不是用来替代普通函数的,所以我们不能在每一个地方都使用箭头函数
120 0
千万别再一直无脑使用ES6的箭头函数了,它虽然很有用但并不是万能的