你所不知道的JS-------作用域

一. 编译原理

JavaScript事实上是一种编译语言,但它不是提前变异的,也不会像其他语言一样,编译结果在分布式系统中进行移植。

JavaScript引擎比想象要复杂的多。JavaScript在执行任何代码前都要进行编译,比如: var a =2; ,先编译这段代码,然后做好执行的准备,通常马上就会执行它。


1.1 分词 / 词法分析

1.2 解析 / 语法分析

1.3 代码生成

二. 理解作用域

当你看见var a = 2; 这段程序时,很可能认为这是一句声明。但我们的新朋友引擎却不这么看。事实上,引擎认为这里有两个完全不同的声明,一个由编译器在编译时处理,另个在引擎运行时处理。

变量的赋值要进行2个操作,首先编译器在当前作用中声明一个变量 (如果之前没有声明过) ,然后在运行时引擎在作用中查找该变量,如果能够到就对它进行赋值。。

2.1作用域的嵌套

引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没找到,查找过程都会停止。

LHS 和RHS查询是重要的。如果查找的操作是对变量进行赋值,那么使用LHS查询;若使用的是获取变量的值,则进行RHS查询。

如果RHS 查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出ReferenceError异常。值得注意的ReferenceError 是非常重要的异常类型。相较之下,当引擎执行LHS 查询时如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在非“严格模式”下。

ES5 中引入了“严格模式”。同正常模式,或者说宽松/ 懒惰模式相比,严格模式在行为上有很多不同。其中一个不同的行为是严格模式禁止自动或隐式地创建全局变量。因此,在严格模式中LHS 查询失败时,并不会创建并返回一个全局变量,引擎会抛出同RHS 查询失败时类似的ReferenceError 异常。

接下来,如果RHS 查询找到了一个变量,但是你尝试对这个变量的值进行不合理的操作,比如试图对一个非函数类型的值进行函数调用,或着引用null 或undefined 类型的值中的属性,那么引擎会抛出另外一种类型的异常,叫作TypeError。

ReferenceError 同作用域判别失败相关,而TypeError 则代表作用域判别成功了,但是对结果的操作是非法或不合理的。

2.2函数作用域:
我们已经知道,在任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。例如:
	var a = 2;
	function foo() { // <-- 添加这一行
		var a = 3;
	console.log( a ); // 3
	} // <-- 以及这一行
	foo(); // <-- 以及这一行
	console.log( a ); // 2

虽然这种技术可以解决一些问题,但是它并不理想,因为会导致一些额外的问题。首先,必须声明一个具名函数foo(),意味着foo 这个名称本身“污染”了所在作用域(在这个例子中是全局作用域)。其次,必须显式地通过函数名(foo())调用这个函数才能运行其中的代码。如果函数不需要函数名(或者至少函数名可以不污染所在作用域),并且能够自动运行,
这将会更加理想。幸好,JavaScript 提供了能够同时解决这两个问题的方案、既闭包的使用。
var a = 2;
函数作用域和块作用域 | 27
(function foo(){ // <-- 添加这一行
	var a = 3;
	console.log( a ); // 3
})(); // <-- 以及这一行
console.log( a ); // 2

此段代码与上端代码不同是因为这是一个函数表达式,第一个词是(),而不是fucntion,上面为一个函数声明,区分函数表达式和函数声明:第一个词为fucntion 为函数声明。
换句话说,(function foo(){ .. }) 作为函数表达式意味着foo 只能在..中存在,foo只能在内部访问,在外部作用域访问不到,foo变量被隐藏在自身中不会污染到外部的作用域(闭包的理解)。
3.1匿名和具名
函数表达式可以匿名,而函数声明不能匿名,必须要有名称标识符,在JavaScript是非法的。
3.2块级作用域
Es6的let 关键字可以将变量绑定到所在的任意作用域中(通常是{ .. } 内部)。换句话说,let
为其声明的变量隐式地了所在的块作用域。
块作用域指的是变量和函数不仅可以属于所处的作用域,也可以属于某个代码块(通常指{ .. } 内部)。从ES3开始try/catch语句在函数中具有了块级作用域。
3.3提升
直觉上会认为JavaScript 代码在执行时是由上到下一行一行执行的。但实际上这并不完全正确,有一种特殊情况会导致这个假设是错误的。
	a = 2;
	var a;
	console.log( a );
	你认为console.log(..) 声明会输出什么呢?

很多开发者会认为是undefined,因为var a 声明在a = 2 之后,他们自然而然地认为变量被重新赋值了,因此会被赋予默认值undefined。但是,真正的输出结果是2。
考虑另外一段代码:

console.log( a );
var a = 2;
鉴于上一个代码片段所表现出来的某种非自上而下的行为特点,你可能会认为这个代码片段也会有同样的行为而输出2。还有人可能会认为,由于变量a 在使用前没有先进行声明,因此会抛出ReferenceError 异常。输出来的会是undefined。
正确的思考思路是,包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。

分析:我们的第一个代码片段会以如下形式进行处理:

var a;
a = 2;
console.log( a );

其中第一部分是编译,而第二部分是执行。类似地,我们的第二个代码片段实际是按照以下流程处理的:

var a;
console.log( a );

   a = 2;
在来看这样一个函数声明的提升;
只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。如果提升改变了代码执行的顺序,会造成非常严重的破坏。
foo();
function foo() {
console.log( a ); // undefined
var a = 2;
}

这段代码被执行的顺序:
function foo() {
var a;
console.log( a ); // undefined

可以看到,函数声明会被提升,但是函数表达式却不会被提升。
foo(); // 不是ReferenceError, 而是TypeError!
var foo = function bar() {
// ...
};
这段程序中的变量标识符foo() 被提升并分配给所在作用域(在这里是全局作用域),因此foo() 不会导致ReferenceError。但是foo 此时并没有赋值(如果它是一个函数声明而不是函数表达式,那么就会赋值)。foo() 由于对undefined 值进行函数调用而导致非法操作,因此抛出TypeError 异常。
同时也要记住,即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用:
foo(); // TypeError
bar(); // ReferenceError
var foo = function bar() {
// ...
};

这个代码片段经过提升后,实际上会被理解为以下形式:
var foo;
foo(); // TypeError
bar(); // ReferenceError
foo = function() {
var bar = ...self...
// ...
}

a = 2;
}
foo();

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值