你不知道的JS系列 ( 7 ) - 欺骗词法作用域

如果词法作用域完全由写代码期间函数所声明的位置来定义,怎样才能在运行时来“修改”词法作用域呢?有些人喜欢特殊的办法来解决遇到的问题。我们规定词法作用域是代码写在哪里决定的,一旦决定了无法更改,因为一些问题,我们不得不更改作用域,尽管这是不被推荐的,那是什么办法,JavaScript 中有两种机制来实现这个目的

eval

function foo(str, a) {
  eval(str);
  console.log(a, b)
}
var b = 2;
foo("var b = 3", 1); // 1, 3

eval() 函数可以接受一个字符串为参数,然后可以在写的代码中用程序生成代码并运行,就好像代码是写在那个位置一样

eval() 调用中的 "var b - 3;",这段代码声明了一个新的变量 b,因此它对已经存在的 foo 的词法作用域进行了修改。事实上,和前面提到的原理一样,这段代码实际上在 foo 作用域内部创建了一个变量 b,并遮蔽了外部作用域的同名变量。

当 console.log() 被执行时,在 foo 作用域内部同时找到 a 和 b,因为作用域查找会在找到第一个匹配当标识符时停止,所以永远也无法找到外部的 b。

默认情况下,如果 eval() 中所执行的代码包含有一个或多个声明的变量还是函数,就会对 eval() 所处的词法作用域进行修改。严格模式下不会,严格模式下, eval 有自己的词法作用域,意味着其中的声明无法修改所在的作用域。

在程序中动态生成代码的使用场景非常罕见,因为它所带来的好处无法抵消性能上的损失

with

JavaScript 中的另一个难以掌握的用来欺骗词法作用域的功能可能是 with 关键字,现在也不推荐使用。为什么都不推荐使用,我们还要去搞懂它呢?说不定我们遇到要维护老项目,哪个二货就用了,遇到了坑,我们不知道其原理,无法定位问题,岂不是慌的一批

with 通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身,比如:

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;
}
但实际上这不仅仅是为了方便地访问对象属性。考虑如下代码:
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 不好,a 被泄漏到全局作用域了

o2 的作用域,foo 的作用域和全局作用域都没有找到标识符 a,因此当 a = 2 执行时,自动创建了一个全局变量(非严格模式下)。

另外一个不推荐使用 eval() 和 with 的原因是会被严格模式所影响。with 完全禁止,而在保留核心功能的前提下,间接或非安全地使用 eval() 也被禁止了

如果它们能实现更复杂的功能,并且代码更具有扩展行,难道不是非常好的功能吗?答案是否定的。

JavaScript 引擎在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。但如果引擎在代码中发现 eval() 或 with,它只能简单地假设关于标识符位置的判断都是无效的。

如果代码中大量使用 eval() 或 with,那么运行起来一定会非常慢。无论引擎多聪明,试图将这些悲观情况的副作用限制在最小范围内,也无法避免如果没有这些优化,代码会运行的更慢这个事实

原文地址:https://www.cnblogs.com/wzndkj/p/12320918.html

时间: 2024-08-30 00:11:40

你不知道的JS系列 ( 7 ) - 欺骗词法作用域的相关文章

你不知道的JS系列 ( 5 ) - 词法作用域

作用域分为两种,一种是词法作用域,一种是动态作用域,我们先看第一种,词法作用域 词法作用域就是定义在词法阶段的作用域(编译器的第一个工作阶段叫做词法化,词法化的过程会对源代码中的字符进行检查).换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的.因此词法分析器处理代码时会保持作用域不变 function foo(a) { var b = a * 2; function bar(c){ console.log( a, b, c ); } bar( b * 3 ); } foo(

js 欺骗词法作用域

如果词法作用域完全由写代码期间函数所声明的位置来定义,怎样才能在运行时来"修改"(也可以说欺骗)词法作用域呢?    JavaScript 中有两种机制来实现这个目的.社区普遍认为在代码中使用这两种机制并不是什么好注意.但是关于它们的争论通常会忽略掉最重要的点:欺骗词法作用域会导致性能下降.在详细解释性能问题之前,先来看看这两种机制分别是什么原理 1.eval JavaScript 中的 eval(..) 函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位

深入理解javascript作用域系列第二篇——词法作用域和动态作用域

× 目录 [1]词法 [2]动态 前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极易出错.这实际上是由两种作用域工作模型导致的,作用域分为词法作用域和动态作用域,分清这两种作用域模型就能够对变量查找过程有清晰的认识.本文是深入理解javascript作用域系列第二篇——词法作用域和动态作用域 词法作用域 第一篇介绍过,编译器的第一个工作阶段叫作分词,就是把由字符组成的字符串分解成

JS中函数的词法作用域

在javascript中,每一个函数都拥有自己的词法作用域.简单理解就是,每个函数在被定义(注意不是调用!)时,都会创建一个属于自己的环境(作用域). 比如, function foo(){ var a = 1; foo2(); } function foo2(){ console.log(a); }fon(); 运行结果: ReferenceError: fon is not defined. 因为,函数foo2在被定义时,它可以访问的环境只有全局作用域和它自己的函数作用域,并不能访问到函数f

你不知道的JS系列 ( 12 ) - 声明提升

我们直觉上会认为 JavaScript 代码在执行时是由上到下一行一行执行的.但实际这并不完全正确 a = 2; var a; console.log(a); 这里可能会认为是 undefined,因为 var a 声明在 a = 2 之后.实际输出了 2. console.log(a); var a = 2; 鉴于上面的代码可能会是 2,还有人认为可能会报异常 ReferenceError,不幸这两种猜测都不对,输出来的是 undefined 要搞明白这个,要明白编译的原理,在编译阶段中的一部

你不知道的JS系列 ( 13 ) - 什么是闭包

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行 function foo(){ var a = 2; function bar() { console.log(a); } return bar } var baz = foo(); baz(); // 2 —— 朋友,这就是闭包的效果 在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收用来释放不再使用的内存空间. 而闭包的“神奇”之处正是可以阻止这件事情的

你不知道的JS系列(2)- 引擎怎么查找变量

对代码进行处理的三个角色 引擎:从头到尾负责整个 JavaScript 程序的编译和执行过程 编译器:负责语法分析及代码生成等 作用域:负责收集并维护所有变量的查询 var a = 2; 编译器首先会将这段程序分解成词法单元,然后将词法单元流解析成一个树结构.然后将树结构转换成可执行代码,也就是计算机懂的指令.为一个变量分配内存,将其命名为 a,然后将值 2 保存进这个变量.这符合编译原理 然而并不完全正确 事实上编译器会进行如下处理 1.在词法分析中,遇到 var a,编译器会询问当前作用域是

你不知道的JS系列 ( 19 ) - this 调用位置

我们排除了一些对于 this 对错误理解并且明白了每个函数的 this 是在调用时被绑定的,完全取决于函数的调用位置.寻找调用位置就是寻找“函数被调用的位置”,但是做起来并没有这么简单,因为某些编程模式可能会隐藏真正的调用位置 最重要的是要分析调用栈,就是为了到达当前执行位置所调用的所有函数 function baz(){ // 调用位置是全局作用域,调用栈 baz console.log('baz'); bar(); // bar 的调用位置 } function bar(){ // 调用位置

你不知道的JS系列 ( 33 ) - 对象复制

JS 初学者最常见的问题之一就是如何复制一个对象.看起来应该有一个内置的 copy() 方法,实际上比想象中的更复杂,我们无法选择一个默认的复制算法 function anotherFunction(){/** ... */}; var anotherObject = { c: true }; var anotherArray = []; var myObject = { a: 2, b: anotherObject, // 引用,不是复本! c: anotherArray, // 另一个引用