Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

深入JavaScript with语句 #80

Open
eightHundreds opened this issue Sep 19, 2022 · 0 comments
Open

深入JavaScript with语句 #80

eightHundreds opened this issue Sep 19, 2022 · 0 comments

Comments

@eightHundreds
Copy link
Owner

一般而言,所有写JS的人都有一个通常的概念:“不要用with语句”。这个准则毫无疑问一直是正确的,但要说为什么的话,并不是每个人可以回答的很好。是否去回答这个为什么并无多大意义,因为只须记住结果“不去用”就完全足够。然而去深入的理由还是有的,刚好最近有人这么问起我...刚好自己想总结一下...刚好这个题目作为草稿在博客后台躺了很久...

with语句

with的初衷是为了避免冗余的对象调用:

foo.bar.baz.x = 1;
foo.bar.baz.y = 2;
foo.bar.baz.z = 3;

with(foo.bar.baz){
    x = 1;
    y = 2;
    z = 3;
}

但其实用变量替换的写法也挺简单:

var p = foo.bar.baz;
p.x = 1;
p.y = 2;
p.z = 3;

所以with似乎本来就没有存在的必要。到了如今,会去用with的人才真的是罕见。到了strict模式里,使用with会直接报错:

function foo(){'use strict'; with({});}


所以with已经被彻底抛弃,人们甚至都懒的关注理由~

书中的with语句

既然是总结,我想尽可能完整一些,所以就先从书籍开头吧。有关JavaScript的书我书柜里确实不少,比Java要多出60%!好吧,这是个冷笑话。回到正题,关于基础内容的JavaScript的书,我书柜里主要有4本,下面依次拿出来说说:

《JavaScript权威指南》(第五版, David Flanagan, P109):

with (Object)
    statement

with语句用于暂时修改作用域链...这一个语句能够有效地将Object添加到作用域链的头部,然后执行statement,再把作用域链恢复到原始状态...虽然有时使用with语句比较方便,但是人们反对使用它。使用了with语句的JavaScript代码很难优化,因此它的运行速度比不使用with语句的等价代码要慢得多。而且,在with语句中的函数定义和变量初始化可能会产生令人惊讶的、和直觉相抵触的行为(这一行为以及产生的原因非常复杂,在这里我们不做解释)

以现在的眼光看《权威指南》会感觉它说的很啰嗦,一大段内容到最后还来一句相当拗口的话(当然我能理解作为译本这无法避免),到了最后还“我们不做解释”,真是尴尬...


《JavaScript高级程序设计》(第3版, Nicholas C.Zakas, P60):

with语句的作用是将代码的作用域设置到一个特定的对象中...由于大量使用with语句会导致性能下降,同时也会给调试代码造成困难,因此在开发大型应用程序时,不建议使用with语句。

Zakas的《高级程序设计》是我JavaScript的入坑书,在with语句这个问题上也没有多深入什么,但写的还算简练,并且说法相当委婉。


《JavaScript语言精粹》(修订版, Douglas Crockford, P110),将with语句列为“糟粕”,并用例子讲述了它的不可预料性,结论上:

with语句在这个语言里存在,本身就严重影响了JavaScript处理器的速度,因为它阻断了变量名的词法作用域绑定。它的本意是好的,但如果没有它,JavaScript语言会更好一点。

老道虽也提到了速度,但更着重解释了不可预测性。


《深入理解JavaScript》(Axel Rauschmayer, P153),我手里唯一一本用了超过1页的篇幅来详细解释废弃with的原因的基础参考书。当时买这本书还是为了支持译者非常长,他曾经给我的博客Host提供过帮助,当然其实也想略微吐槽一下此书的翻译错漏:)

此书中的解释是比较全面的,所以我会以此书的解释作为基础,再加上自己的理解,总结在本文。


好了,看了那么多书,下面就进入本文的正题:

为什么不要使用with语句?

总而言之,主要是这几方面的考量:

  1. 性能
  2. 不可预测
  3. 优化

性能问题

with语句存在显而易见的性能问题,基本上所有的参考书都会提及这一点,但基本不会有什么例子说明。自己可以简单的做一个代码测试,使得对with语句的性能有更直观量化的了解。

var a = {a: {a: 1}};
function useWith(){
	with(a.a){
		for(var i = 0; i < 1000000; i++){
			a = i; 
		}
	}
}

var b = {b: {b: 1}};
function noWith(){
	for(var i = 0; i < 1000000; i++){
		b.b.b = i; 
	}
}

var t1 = new Date().getTime();
useWith()
alert(new Date().getTime() - t1);

var t2 = new Date().getTime();
noWith()
alert(new Date().getTime() - t2);

简单对一个对象属性赋值100万次,是否使with的结果差距还是很明显:

Chrome Firefox Edge IE11
Use with 603 1411 245 103
No with 2 1 3 3

当然实际使用上极少会用到百万次循环,且损耗在可接受范围内,所以其实性能损失并不是废弃with语句的真正原因。

不可预测性

使用with语句后代码产生的不可预测性是废弃with的根本原因。with强行割裂了词法作用域,将对象临时性地插入到了作用域链中。这使得出现了难以捉摸的代码。

比如最简单的例子:

function foo(a){
    with(a){
        console.log(a);
    }
}
foo("sword");       
foo({});            
foo({a: "sword"});  

在这个简单的例子里,字符串"sword"和空对象没有问题,但当传入的参数是带有同名a属性的a对象时,with强行访问了a.a

这仅仅只是有一个参数的情况下,如果有很多个参数呢?当不知道调用传进来的参数带有何种属性时,多个参数间的各种属性的混乱指向可想而知,这就是“令人惊讶的、和直觉相抵触的行为”的本质。

另外,在with语句中声明的变量,并不属于with指定的参数对象:

var a ={};   
with(a){
    x = 'sword'
    var y = 'wang';
}
console.log(a.x);       
console.log(a.y);       
console.log(window.x);  
console.log(window.y);  

在with中声明的变量实际上是被添加到外层的function上的:

function foo(){
    with({}) { x = 'sword'; }
    console.log(x)
}
foo();  

这点可能也与想象中有些不同。

仅仅通过标识符及其上下文,无法确定一个with中的标识符指向什么,这是with被废弃的真正原因。它强行混乱了上下文使得程序的预测和解析变得困难,从而产生了之后要说的优化问题。

代码无法优化

由于无法进行预测,代码含义一直在发生变化,不同的调用,或者即使相同的调用也会因为运行时的变化而出现偏差,从而使得代码无法被优化。

优化指两方面,一方面解析和运行变得缓慢,指的就是之前已经谈到的性能。另一方面对于代码优化和压缩工具来说,无法确定是变量还是属性,就不能进行重命名(因为属性无法被重命名)。

结语

游览了几本书,讲了一堆没啥用的内容,我果然很闲...另外,上面那个冷笑话:“JavaScript比Java要多出60%!”指的是字符数量上...好吧,天热了,自己冷自己~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant