var|「建议收藏」词法作用域( 二 )


JavaScript 有两种机制来实现这个目的 。 社区普遍认为在代码中使用这两种机制并不是什么好主意 , 因为:欺骗词法作用域会导致性能下降 。
在了解性能问题之前 , 先来看看这两种机制分别是什么原理 。
evalJavaScript 的 eval 函数可以接收一个字符串为参数 , 并将其中的内容视为好像在书写时就存在于程序中这个位置的代码 。 换句话说 , 可以在你写的代码中用程序生成代码并运行 , 就好像是写在那个位置一样 。
根据这个原理来理解 eval , 它是如何通过代码欺骗和假装书写时代码就在那儿 , 来实现修改词法作用域环境的?
在执行 eval 之后的代码时 , 引擎并不「知道」前面的代码是以动态的形式插入进来的 , 引擎只会如往常地进行词法作用域查找 。
function foo(stra){
    eval(str) //欺骗
    console.log(a b)


var b = 2
foo('var b = 3'1) // 1 3

eval 调用中的 'var b = 3' , 这段代码会被当作它们本来就在那里一样处理 。 由于那段代码声明了一个新的变量 , 因此它对已经存在的 foo 的词法作用域进行了修改 。 事实上 , 和前面提到的原理一样 , 这段代码实际上在 foo 内部创建了一个变量 b , 并遮蔽了外部作用域中的同名变量 。
当 console.log 被执行的时候 , 会在 foo 的内部同时找到 a 和 b , 但是永远也无法找到外部的 b , 因此会输出 1 3 , 而不是正常情况下的 1 2 。
默认情况下 , 如果 eval 中所执行的代码包含有一个或多个声明 , 就会对 eval 所处的词法作用域进行修改 。 技术上 , 通过一些技巧可以间接调用 eval 来使其运行在全局作用域中 ,并对全局作用域进行修改 。
JavaScript 中还有一些功能效果和 eval 很相似 , setTimeout 和 setInterval 中第一个参数可以是字符串 , 字符串的内容可以被解释为一段动态生成的函数代码 。 这些功能已经过时并不被提倡 。
new Function 函数的行为也很类似 , 最后一个参数可以接受代码字符串 , 并将其转化为动态的函数 , 这种构建函数的语法比 eval 略微安全一些 , 但也要尽量避免使用 。
在程序中动态生成代码的使用场景非常罕见 , 因为它所带来的好处无法抵消性能上的损耗 。
withJavaScript 中另一个难以掌握的用来欺骗词法作用域的功能是 with 关键字 。 可以有很多的方法来解释 with , 在这里我选择从这个角度来解释它:它如何同被它所影响的词法作用域进行交互 。
with 通常被当作重复引用同一个对象中的的多个属性的快捷方式 , 可以不需要重复引用对象本身 。
比如:
var obj = {
    a:1
    b:2
    c:3

// 单调乏味的重复 obj
obj.a = 2;
obj.b = 3
obj.c = 3

// 简单快捷的方式
with(obj){
    a = 3
    b = 4
    c = 5

但实际上 , 这不仅仅是为了方便的访问对象属性 。
function foo(obj) {
    with(obj) {
        a = 2
    


var o1 = {
    a:3

var o2 = {
    b:3


foo(o1)
console.log(o1) // Object { a: 2 
foo(o2)
console.log(o2) // Object { b: 3