你不知道的javascript章节总结(第二章)

第二章(词法作用域,为什么我们不使用eval与with)

 

如果要解释不使用eval与with的原因,首先我们得先了解词法作用域是什么,它会如何影响整个编译器的编译过程。

 

正如书中所说作用域有两种工作模型

1、词法作用域;

2、动态作用域。

 

词法作用域:就是指在定义词法阶段的作用域,词法阶段做的工作是将我们声明的一些变量与块作用域标识化并识别其在代码中的物理位置,如果是有状态的解析过程,那么还会对单词赋予语义,说人话就是指标识每个变量与块作用域的单词在代码中的位置,并对其进行解析(如声明变量,划分块作用域)。

 

由此我们知道了词法作用域就是指在引擎工作之前,编译器对代码的解析与位置的锁定过程,而我们所说的eval具有欺骗性就是指eval中执行的js语句改变了当前作用域中的变量,而这些变量是在这之前的编译器所没有进行编译的。

 

我们知道eval的用处是将eval(...)中的字符串读作js语句来执行,也就是说编译器是不会读到eval(...)中的变量的,在编译器眼里它仅仅就是定义为字符串,但是编译器后的引擎并不会知道,也不关心这里的代码是动态形式插入的,引擎会当eval中的语句是本身存在的来执行,举个例子来说明:

 

就像你在教室的位置一样,老师(编译器)会事先安排好所有同学(变量及作用域与词法解析)的位置,在开学时,同学们直接对号入座我们就可以开始上课(引擎执行)了,但正是因为突然在上课时有新同学(eval(...)中的变量)突然来到,而老师(编译器)事先并没有安排他(eval(...)中的变量)的位置,那么课程(引擎)并不会、也不能因为新同学的来临而停课,但是还是要第一时间帮他想办法坐下并继续开始为所有人上课(执行代码逻辑),但是没有位置,新同学(eval(...)中的变量)坐去哪了,没有办法只能跟已经坐下的同学挤一挤了(将全局中的同名变量’遮蔽’了),这个时候老师问了一个问题(引擎执行代码逻辑),新同学(eval(...)中的变量)便遮蔽了旧同学(原有的同名),那么就只能由新同学来回答问题了。而这个情况老师(编译器)事先并没有预料与准备到,那么我们就可以说校方欺骗了老师(编译器)。

 

所以我们说eval改变作用域的行为具有欺骗性,而欺骗的就是编译器。(例子举得比较差,希望大家原谅)。

 

上一个demo来解释这个过程:

FunctionFoo(str,a){

Eval(str);       //欺骗

Console.log(a,b);

}

 

Var b=2;

Foo(“var b=3;”,1) ;      //1,3

 

从这个demo的结果我们可以看出再eval(“var b=3;”)后,在标识符Foo这个作用域中突然新声明了一个局部变量var b=3而’遮蔽’了外部的全局变量var b=2,从而导致控制台输出的a=1,b=3,而正常情况下应为1,2 ,这个b的声明过程,编译器在词法阶段并没有读取到,也没有对其中的b给上标识符,让其事先定好位置,这里这个过程就是欺骗编译器的过程,所以我们说eval具有欺骗性。

 

书中还明确指出eval(...)十分的影响性能,因为如果不用eval,那么一切都会被编译器事先预存好,引擎执行代码时,一切代码都是原本就放在那准备好的,而eval却打破了他~

 

下面放上性能的测试demo

 

 

显而易见这段是没有使用eval的情况累加的执行完成时间为0.061s,下面我们看看使用eval后;

 

由于是在太慢,我将时间换成秒来计算,显而易见结果是9s,这个结果的差距是在是太大了,所以性能的影响也显而易见了。

 

值得一提的是在js的严格模式下,eval会产生一个属于自己的新的作用域,而不是像非严格模式下是直接在当前作用域下执行,所以eval在其中的声明无法修改所在的作用域,所以我们在严格模式下更失去了使用它的意义。

 

论证demo如下

Function Foo(str){

“use strict”;

Eval(str);              //产生了一个新的作用域,而不是在Foo的作用域下

Console.log(a);    //Reference:a is not defined

}

Foo(“var a=2”);

 

好了,说完了eval再让我们来说说with,with的弊端就比eval好说多了,而且现在with在严格模式下是直接被禁止的。

 

首先with怎么用呢?上一段demo来说明


正常的调用对象

Var obj={

A:1,

B:2,

C:3

}


//单调乏味的重复”obj”;

obj.A=2

obj.B=3 

obj.C=4 

 

//简单的快捷方式

With (obj){

A=3;

B=4;

C=5;

}

 

或许你会觉得哇,with好方便啊!但是请考虑以下情况

 

Function foo(obj){

With(obj){

A=2;

}

}

 

Var o1={

A:3;

};

 

Var o2={

B:3;

};

 

Foo(o1);

Console.log(o1.A);      //2

 

Foo(o2);

Console.log(o2.A);      //undefined

Console.log(A);           //2 --oops,a被泄露到全局作用域了(with相当于更改了当前的标识符为obj,但此时未找到o2中的A,所以引擎通过冒泡去向上寻找A直到全局作用域,非 严格模式下,当发现全局没有,则自己创建了变量A)


通过上述demo示例我们可以很明显的看出with的弊端,就是很容易一不小心就泄露变量到全局作用域,导致改变全局变量的值,这对于写了一大段复杂的逻辑代码用于项目的朋友来说是非常致命的,因为你可能要用很长时间才能排查到这个原因,而在多人协作的情况下更是会影响他人的工作效率与调试时间,还会影响自己~虽然我并没有什么多人协作的经历,又在此暴漏了自己很菜的事实= =。。。

 

以上部分demo示例截取于《你不知道的javascript》书籍中的原示例


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值