细读《你不知道的JavaScript·上卷》1-2 词法作用域

栏目: JavaScript · 发布时间: 5年前

内容简介:在第作用域共有两种主要的工作模型,一是在第1章学习了,大部分标准语言编译器的

在第 1 章中,学习了 作用域,它是一套规则,用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。

作用域共有两种主要的工作模型,一是 词法作用域JavaScript 等);二是 动态作用域bash 脚本等)。

词法作用域

  • 什么是词法作用域
  • 为什么废弃欺骗词法作用域的两种机制

2.1 词法阶段

在第1章学习了,大部分标准语言编译器的 第一个工作阶段 是词法化( token 化 )。

词法作用域就是在词法分析时定义的作用域,即在写代码时,由变量和块作用域的位置决定的。因此,在词法分析时也是固定不变的了(不考虑欺骗词法作用域情况)。

下面这段示例代码有三个嵌套作用域:

细读《你不知道的JavaScript·上卷》1-2 词法作用域
  • 圈1 包含了全局作用域,只有一个标识符号 foo
  • 圈2 包含 foo 作用域,有三个标识符 abarb
  • 圈3 包含 bar 作用域 ,有一个标识符 c

作用域的范围是根据作用域代码块定义的位置决定的,在这里每个函数创建了一个作用域。

这里作用域嵌套是严格的,一个函数不能同时存在于两个外部函数中。

2.1.1 查找

  • 作用域查找会在找到第一个匹配的标识符时停止。

  • 遮蔽效应:在多层嵌套作用域中可以定义同名的标识符,内部的标识符会 遮蔽 外部的标识符。

  • 全局变量是全局对象的属性,被覆盖的非全局对象则无法被访问到。

    window.a
    复制代码
  • 词法作用域查找只会查找一级标识符,比如 abc 。如果代码中引用了 foo.bar.baz ,词法作用域只会查找 foo 标识符,找到后, 对象属性访问规则 会分别接管对 barbaz 属性的访问。

2.2 欺骗词法

欺骗词法作用域会导致性能下降,以下两种方法都 不推荐使用

2.2.1 eval

  • eval(...) 函数可以接受一个字符串作为参数,并把字符串的内容当作代码运行,从而实现对词法作用域环境的修改。
  • 在执行 eval() 之后的那些代码,引擎不知道、也不去关心前面的代码是 动态编译 的,且 修改 了词法作用域环境。引擎只会一如既往地进行词法作用域查找。

非严格模式下:

function foo(a, str){
	eval(str); // 欺骗!
	//console.log(evla(str));//Uncaught ReferenceError: evla is not defined
	console.log(a, b); // 1  3
	console.log(a, window.b); //1  2
}
var b = 2;
foo(1, " var b = 3 ;"); // 1  3
复制代码
  • eval() 被调用时,字符串参数 “ var b = 3; ” 被当作真正的代码声明了变量 b ,并修改了 foo() 的词法作用域。在 foo() 内部创建了一个变量 b , 遮蔽了外部全局作用域中的同名变量 b
  • console.log() 被执行时,会在 foo() 的内部同时找到 ab , 但是永远也无法找到外部的 b 。因此会输出 1 , 3 ,而不是正常情况下会输出的 1 ,2

严格模式下:

function foo(a, str){
	"use strict";
     eval(str); 
     // console.log(evla(str));//Uncaught ReferenceError: evla is not defined
     console.log(a, b);// 1  2
     console.log(a, window.b); //1  2
}
var b = 2;
foo(1, " var b = 3 ;");// 1  2
复制代码
  • eval() 在严格模式下,有自己的词法作用域,其中的声明无法修改作用域。
  • setTimeout(...)setInterval(...) 的第一个参数可以是字符串,字符串的内容会被解释为一段动态生成的函数代码。 已废弃使用
  • 构造函数 new Function() 的最后一个参数可以接受代码字符串(前面的参数是新生成的函数的形参), 避免使用

2.2.2 width

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 : 4
};

console.log(o1.a);//3
foo(o1);
console.log(o1.a);//2

foo(o2);
console.log(02.a);//undefined
console.log(a);//2 -> 不好,a 被泄露到全局作用域上了!
复制代码
  • o1 传进后, with 声明的作用域是 o1a = 2 赋值操作找到 o1.a 并将 2 赋值给它。
  • o2 传进后,作用域 o2 中没有 a 属性,则进行 LHS 标识符查找, o2 的作用域、 foo() 的作用域 和全局作用域都没找到标识符 a ,因此当 a = 2 执行时,产生副作用,自动创建了一个全局变量(非严格模式) a ,并将 2 赋值给 a ,所以 o2.a 保持 undefined

在严格模式下,with 语句被完全禁用,eval() 则只保留核心功能,都不推荐使用。

2.2.3 性能

JavaScript 引擎在 编译阶段 进行各种性能优化,一些优化在词法分析阶段,静态分析了代码,预先确定了变量和函数声明的位置,所以在执行期间就可以快速解析标识符。

2.3小结

词法作用域只由函数被声明时所处的位置决定。

以下两个机制可以 欺骗 词法作用域:

  • eval(...) : 对一段包含一个或多个声明的 代码 字符串进行演算,借此来修改已经存在的词法作用域( 运行时 )。
  • with : 将一个对象的引用 当作 作用域,将对象的属性当作作用域的标识符,创建一个新的词法作用域( 运行时 )。

副作用是引擎无法在编译时对作用域查找进行优化。因为引擎只能谨慎地认为这样的优化是无效的,使用任何一个机制都将导致代码运行变慢。 废弃它们。

最后,读书是由厚到薄,又由薄到厚的双向过程,注重领悟、实践,不断踩坑、提升,若有帮助,请点个赞,谢谢您的支持与指教。

参考文献:

木易杨博客

隙游尘博客

taopoppy 博客


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

微交互

微交互

塞弗 (Dan Saffer) / 李松峰 / 人民邮电出版社 / 2013-11-1 / 35.00元

平庸的产品与伟大的产品差就差在细节上。作者Dan Saffer将通过这本书展示怎么设计微交互,即位于功能之内或周边的那些交互细节。你的手机怎么静音?你怎么知道有新邮件了?怎么修改应用的设置?诸如此类的交互细节,既可以毁掉一个产品,也可以成就一个产品。高效而有趣的微交互 ,涉及触发器、规则、循环和模式,还有反馈。透过书中生动、真实的设备及应用示例,读者将理解微交互对于塑造产品个性、赋予产品卖点的重要......一起来看看 《微交互》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

在线进制转换器
在线进制转换器

各进制数互转换器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换