这里只是基于js讲一点语法分析parsing、解释器的东西,涉及到关键字’eval、new Function’的使用,主要讲”eval”
PS.15号标注,这个部分目前讲述的比较乱,最近比较忙,后续更新,预计写一个算数运算的解释器
通常我们说的”语法解析”(parsing)指的是:从符号到模型的转换过程,这个是我们在计算机科学中已经很熟悉的一个概念
这里大概解释一下,比如小学数学课:1
3+4 = ? // 当然我们现在一眼就可以看出来是7
其实对一个儿童来说,这个3是什么?也只是一个符号,需要经过学习,才能形成意识:这个像大耳朵图图的耳朵一样的图案代表的就是数值3,代表的大小和三个手指头的大小一样
然后才会把这个抽象成数据模型: 左边伸出3个手指代表数值3,右边伸出4个手指代表数值4,加起来数一下,结果是7
当我们接受过专业的训练之后,立刻就能明白:这个就是一个表达式(expression),一个数值运算的表达式
我们脑海中大概会建立如下的数据模型
中间这个+,在计算机科学中一般是一个加法器,当然我们现在要说的是js
在js中,这个 ‘3+4=’数据模型所代表的其实是一个函数,输入2个参数,返回结果1
2
3
4
5fn(3,4) // 7
fn(a,b){
return a+b
}
如像Vue、Angular这样的template模板中含有行级指令的框架,我们可以很确定在js中没有如v-model,ng-model这样的指令,那这些指令又是如何在js中运行的呢?
1 | <input ng-model="name"> |
处理方式一般都是将模板进行语法解析,挑出对应的自定义指令,在解析结束之后使用new Function生成对应函数来解释这个指令到底是干吗的(angular除外,自己另写了个针对自己框架指令的js parser)
详情看vue和angular的源代码
https://github.com/angular/angular.js/blob/master/src/ng/parse.js
https://github.com/vuejs/vue/blob/dev/src/compiler/to-function.js
这里只是简单介绍,不深入到框架内,主要用我们自己的demo去解释基本的原理
“new Function”可以说是我们经常会碰到的一个东西,而且使用起来很爽,尤其是在尝试写模板引擎或者一些渲染函数的时候
但是使用”new Function”其实有时候满足不了这样一种需求:我已经预定义好了指令函数,解析完对应指令之后只管执行就好
而new Function偏要我每次都传函数体,很明显不满足需求(如果您知道好的方法可以交流下),这个时候就需要“eval”了
意思可能表述的还是不够明确,直接上我们自己的demo,结合paring过程讲述一下
1
2
3
4已知 Q等价于 a * b - (a + b),
N等价于 return a * a + b,
括号代表运算优先进行;
形如 3Q4 = 3*4 - (3+4) = 5
现在有一个需求,需要我们解析: ‘2Q(3N4)’,并出给结果
这个时候我们很直观的想到,Q、N就是两个函数
1
2
3
4
5
6
7function Q(a, b) {
return a * b - (a + b)
}
function N(a, b) {
return a * a + b;
}
我们可以很快的定下思路:
- 定义一个parser函数,接收表达式,如’2Q(3N4)’
- 函数内将表达式按括号划分成多个表达式,如’2Q(3N4)’ => ‘2Q’、’3N4’
- 单独处理一个单指令表达式,解析指令,调用对应函数来解释语义,得到结果;如’3N4’中’N’ => N(3,4)
- 将上一表达式的结果作为当前表达式的参数传递进来,继续解析指令,调用对应函数解释语义
- 直到解析结束,返回数据
这里需要注意一点:解析指令,调用对应函数,我们确实定义了function N ,function ,但是解析出来的是字符串,该如何将字符串转化成对应函数呢?
这里就需要用到eval了(这个关键字不推荐使用的原因可以自己搜索一下看看为什么,这里不做解释)
代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28let exp = '2Q(3N4)'
console.log(parser(exp))
function parser(str) {
let arr = str.split('(').map(function (item) {
return item.replace(')', '')
})
let res;
for (let i = arr.length - 1; i >= 0; i--) {
if(i === arr.length-1){
let fn = arr[i][1]+'('+arr[i][0]+','+arr[i][2]+')'
res = eval(fn)
}else{
let fn = arr[i][1]+'('+arr[i][0]+','+res+')'
res = eval(fn)
}
}
return res
}
function Q(a, b) {
return a * b - (a + b)
}
function N(a, b) {
return a * a + b;
}
再进一步拓展,我们是否可以用一些字符来表示html标签呢?当然需要我们自己来做一个parser,举例如:1
2
3
4
5
6
7<div>
<ul>
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
</ul>
</div>
很明显是可以的,比如假设html标签都是闭合标签
- (q)代表div标签
- (n)代表ul标签
- (l)代表li标签
如上可以表示为:1
2
3
4
5(q)
(n)
(l)Coffee
(l)Tea
(l)Milk
因为已经假设是闭合标签,在做paring的时候在每个结尾都加个闭合标签就行