日期:2014-05-16  浏览次数:20394 次

如何将let结构(block scope)转换到当前的JavaScript代码
本文是对如何将let结构转换到ES3代码的补充。

首先,原文所说的将let转换为with的方法有几个缺陷需要说明:

1. with虽然可附加一个新的scope,但是由于引入的是一个JS对象,所以Object.prototype上的属性也被引入了该scope。比方说你无法在with里访问外部的toString()方法,因为你访问到的实际上变成了Object.prototype.toString。

再来一个例子:

function login() {
	var user = getUserNameFromCookie()
	for (let i = 0; i < 3; i++) {
		let pwd = prompt('Please enter your password:')
		if (securityCheck(user, pwd)) {
			return true
		} else {
			alert('Wrong password! Please try again...')
		}
	}
	return false
}

if (login()) {
	alert('Welcome!')
} else {
	alert('Failed to login!')
}

function securityCheck(user, pwd) {
	//...do some security checking
}
function getUserNameFromCookie() {
	//...read user name from cookie
}


如果这段代码里的 login 函数被转换为:
function login() {
	var user = getUserNameFromCookie()
	for (var i = 0; i < 3; i++) {
		var temp = prompt('Please enter your password:')
		with({pwd:temp}) {
			if (securityCheck(user, pwd)) {
				return true
			} else {
				alert('Wrong password! Please try again...')
			}
		}
	}
	return false
}


就会存在一些隐患。

例如:
如果在login执行之前,注入了这样的代码:
Object.prototype.user = 'hacker'

那只要输入hacker用户对应的密码就可以通过检查。

更简单的方式是:
Object.prototype.securityCheck = function() { return true }

【注意上述只是示例,不代表真实世界里会有这样的写法。】


另一个隐藏问题是,如果with(scope)引用了一个函数,则该函数在with块内被调用是以scope作为this对象的。这造成采用with时行为不一致,并可能影响其他reference。

比如:
function test() {
     let x = 1
     {
        let f = function(){ this.x = 2 }
        f()
        return x
     }
}
test()

这段代码执行的结果应该是返回1,并且global上的x=2。

但是
function test() {
     with(x:1)
     {
        with({f: function(){ this.x = 2 }}) {
            f()
            return x
        }
     }
}
test()

这段代码的结果是返回2,global上并不会产生x。


2. with语句在ES5的strict模式下被禁用。

with实际上是在lexical scope上开了一个后门,这对依赖静态代码分析的辅助工具(如IDE、压缩器等)和性能优化(如新的支持JIT的引擎)都造成了障碍。
另外从第1条可以看到with的设计天生残疾,易造成语句的二义性,并且这种二义性可以由完全无关的代码引入。

因此ES5的strict模式直接禁用了该特性。


上述两点注定了block scope转换到with是一个不太好的做法。

那么还有什么其他方式么?

幸运的是,确实存在一个我以前遗忘的特性,try catch语句中,catch语句也会增加一层新的scope!

Traceur是一个Google开源项目,可以将ES5和Harmony的一些新特性编译为传统的ES3代码。让我们看看它是怎样转换上述代码的:

function login() { 
  var user = getUserNameFromCookie(); 
  for(var i = 0; i < 3; i ++) { 
    try { 
      throw undefined; 
    } catch(pwd) { 
      pwd = prompt('Please enter your password:'); 
      if(securityCheck(user, pwd)) { 
        return true; 
      } else { 
        alert('Wrong password! Please try again...'); 
      } 
    } 
  } 
  return false; 
} 


不需要更多解释了。


总结,用try/catch进行let转换方式如下:

    statement {  
        ...  
        let n1 = exp1  
        ...  
        let n2 = exp2  
        ...  
        let n3 = exp3, n4 = exp4, ...  
        ...  
    }  

转换为
    statement {
        try { throw undefined } catch (n1) {
        try { throw undefined } catch (n2) {
        try { throw undefined } catch (n3) {
        try { throw undefined } catch (n4) {
            ...
            n1 = exp1
            ...
            n2 = exp2
            ...
            n3 = exp3; n4 = exp4; ...
        }}}}
    }


此外,原文中的let语句形式也可以容易的转换:

let语句
    var x = 5;    
    var y = 0;    
        
    let (x = x+10, y = 12) {    
      print(x+y + "\n");    
    }    
        
    print((x + y) + "\n");    

可转换为:
    var x = 5;    
    var y = 0;    
        
    t