前言
在 vue 中我们可以在模板里使用插值表达式,可以很方便地访问 vue 实例上定义的响应式数据。现在有一个场景,需要实现一个 render 函数:该函数接受两个参数,一个是模版,一个是数据对象。模版通过插值表达式引用数据对象上的数据,通过 render 函数能够把模版转换为真实的字符串。
let template = '大家好,我的名字叫做{{ name }},我是一名{{ job }}。'
let data = {
name: 'Henry',
gender: 'male',
job: 'software engineer'
}
// 实现 render 函数
function render (template, data) {
// ...
}
简版
简化版只需要考虑访问到 key-value 这种简单场景,其中 value 类型都是 String。
function render (template = '', data) {
if (data === null) return
const MUSTACHE_REG = /{{.*?}}/ig
const KEY_REG = /(?<={{(\s*?)).*?(?=(\s*?)}})/ig
return template.replace(MUSTACHE_REG, function (match = '') {
let key = match.match(KEY_REG)[0].trim()
return data[key]
})
}
这种方式对于数组就不适用了,会返回 undefined,考虑到插值表达式还能执行一些运算,所以这么写是不行的。
使用 eval 函数
eval()
函数会将传入的字符串当做 JavaScript 代码进行执行。
但是这仍然存在一个问题,就是 eval 执行字符串的时候,里面的变量上下文该如何获取?这时候就可以使用 with 函数,把 data 传递进去,这样就避免了访问数据时还得加上 data. 前缀。
let template = "大家好,我的名字叫做${ name.includes('H') ? name : '' },我是一名${ job },今年${ age[1] }岁。"
let data = {
name: 'Henry',
gender: 'male',
job: 'software engineer',
age: [24, 25]
}
function render (template = '', data) {
if (data === null) return
const MUSTACHE_REG = /\$\{.*?\}/ig
const KEY_REG = /(?<=\$\{(\s*?)).*?(?=(\s*?)\})/ig
return template.replace(MUSTACHE_REG, function (match = '') {
let content = match.match(KEY_REG)[0].trim()
let result
with (data) {
result = eval(content)
}
return result
})
}
console.log(render(template, data)) // 大家好,我的名字叫做Henry,我是一名software engineer,今年25岁。
使用 eval 最大的问题就是存在安全隐患。eval()
是一个危险的函数,它使用与调用者相同的权限执行代码。如果你用 eval()
运行的字符串代码被恶意方(不怀好意的人)修改,最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。更重要的是,第三方代码可以看到某一个 eval()
被调用时的作用域,这也有可能导致一些不同方式的攻击。相似的 Function
就不容易被攻击。
eval()
通常比其他替代方法更慢,因为它必须调用 JS 解释器,而许多其他结构则可被现代 JS 引擎进行优化。
使用 new Function
更加稳妥的做法使用 new Function 的方式来执行插值表达式里的字符串,这时候需要开发者自行拼接函数体代码。
let template = "大家好,我的名字叫做 ${ name.includes('H') ? name : '' },我是一名 ${ job },今年 ${ age[1] } 岁。"
let data = {
name: 'Henry',
gender: 'male',
job: 'software engineer',
age: [24, 25]
}
function render (template = '', data) {
if (data === null) return
const MUSTACHE_REG = /\$\{.*?\}/ig
const KEY_REG = /(?<=\$\{(\s*?)).*?(?=(\s*?)\})/ig
return template.replace(MUSTACHE_REG, function (match = '') {
let content = match.match(KEY_REG)[0].trim()
let result = new Function('obj', 'with (obj) { return ' + content + '}')(data)
return result
})
}
console.log(render(template, data)) // 大家好,我的名字叫做 Henry,我是一名 software engineer,今年 25 岁。
到此为止就实现了一个插值表达式处理函数了。
不使用正则
function render(template, data) {
let result = '';
let i = 0;
while (i < template.length) {
if (template[i] === '$' && template[i + 1] === '{') {
let endBraceIndex = template.indexOf('}', i);
if (endBraceIndex !== -1) {
let key = template.slice(i + 2, endBraceIndex).trim();
if (data.hasOwnProperty(key)) {
result += data[key];
}
i = endBraceIndex + 1;
} else {
result += template[i];
i++;
}
} else {
result += template[i];
i++;
}
}
return result;
}
// let template = '大家好,我的名字叫做${ name },我是一名${ job }。';
let template = '大家好,我的名字叫做{{ name }},我是一名{{ job }}。';
let data = {
name: 'Henry',
gender: 'male',
job: 'software engineer'
};
// console.log(render(template, data));
function replace (template = '', obj = {}) {
if (!template) return ''
let i = 0, len = template.length
let result = ''
while (i < len) {
if (template[i] === '{' && template[i + 1] === '{') {
let endBraceIndex = template.indexOf('}}', i)
if (endBraceIndex !== -1) {
let key = template.slice(i + 2, endBraceIndex).trim()
const value = obj[key]
result += value
i = endBraceIndex + 2
} else {
result += template[i]
i++
}
} else {
result += template[i]
i++
}
}
return result
}
console.log(666, replace(template, data))