今天来说一下这些东西:
- 闭包;
- 作用域链;
- 执行上下文;
- this值;
实际上,它们有表示不同的意思的术语,所指向的几乎是同一部分的知识,我们来看一下图片。
闭包
闭包的英文closure,在计算机领域它有三个不同的含义:编译原理中,它是处理语法产生式的一个步骤;在计算几何中,它表示凸包;而在编程语言中,它表示一种函数。
简单的来说,闭包其实就是一个绑定了执行环境的函数,这个函数并不是印在书本的一条简单的表达式,闭包与普通函数的区别是,它携带了执行的环境,就想人在外太空需要自带吸氧设备一样。
在闭包定义中,闭包包含了两个部分。
-
环境部分
环境
标识符列表
-
表达式部分
当我们把视角放在javascrip的标准中,我们发现,标准中并没有出现过closure这个术语,但是,在javascri中找到对应的闭包组成的部分。
-
环境部分
环境:函数的词法环境
标识符列表:函数中用到的未声明的变量
-
表达式部分:函数体
我们可以认为,javascript中的函数完全符合闭包的定义,它的环境部分 是函数词法环境部分的组成,它的标识符列表是函数中用到的未声明变量,它的表达式部分就是函数体。
实际上javascript中的闭包对应的感念就是"函数",可能是这个概念太过于普通,跟闭包看起来又没什么联系。
执行上下文:执行的基础设施
相比普通函数,javascript函数的主要复杂性来自于它携带的"环境部分"。当然,发展到今天的javascript设计中,词法环境只是javascript执行上下文的一部分。
在ES5中,
我们改进了命名方式,把执行上下文最初的三个部分改为下面的样子。
- lexical environment:词法环境,当获取变量时使用。
- variable environment:变量环境,当声明变量时使用。
- this value:this值。
在ES2018中,
执行上下文又变成了这个样子,this值被归入lexical environment,但是增加了不少内容。
- lexical environment:词法环境,当获取变量时使用。
- variable environment:变量环境,当声明变量时使用。
- code evaluation state:恢复代码执行位置。
- Function:执行的任务是函数时使用,表示当前生成器。
从实际的代码示例出发:
var b = {}
let c = 1
this.a = 2;
想要正确执行它,我们需要知道下面的信息:
1.var 把b声明到哪里;
2.b表示那个变量;
3.b的原型是哪个对象;
4.let 把c声明到哪里;
5.this指向那个对象。
var声明与赋值
我们分析一下代码:
var b = 1
通常我们认为声明了b,并且赋值为1,var声明作用域函数执行的作用域,也就是说,var会穿透for、if等语句。
由于语法规定了function关键字开头是函数声明,所以想让函数变成函数表达式,我们必须得加点东西,最常见的做法是加括号。
(function(){
var a;
//code
}());
(function(){
var a;
//code
})();
但是,括号有个去缺点,那就是如果上一行代码不写分号,括号会被解释为上一行代码最末端的函数调用,产生不符合预期,而且难以调试。所以一些推荐不加分号的代码风格规范。
;(function(){
var a;
//code
}())
;(function(){
var a;
//code
})()
使用void关键字。下面这种形式。
void function(){
var a;
//code
}();
需要注意的是,有时候var的特性会导致声明的变量和被赋值的变量是两个b,javascript中有特例,使用with的时候:
var b;
void function(){
var env = {b:1};
b = 2;
console.log("In function b:", b);
with(env) {
var b = 3;
console.log("In with b:", b);
}
}();
console.log("Global b:", b);
可以看到,在 Global function with 三个环境中,b 的值都不一样,而在 function 环境中,并没有出现 var b,这说明 with 内的 var b 作用到了 function 这个环境当中。
var b = {} 这样一句对两个域产生了作用,从语言的角度是个非常糟糕的设计,这也是一些人坚定地反对在任何场景下使用 with 的原因之一。
let
let 是 ES6 开始引入的新的变量声明模式,比起 var 的诸多弊病,let 做了非常明确的梳理和规定。
为了实现 let,JavaScript 在运行时引入了块级作用域。也就是说,在 let 出现之前,JavaScript 的 if for 等语句皆不产生作用域。 下面语句产生let使用额作用域:
- for;
- if;
- switch;
- try/catch/finally。
Realm
var b = {}
在ES2016之前的本本中,标准中甚少提及{}原型问题,Realm中包含一组完整的内置对象,而且是复制关系。
一下代码示例:
var iframe = document.createElement('iframe')
document.documentElement.appendChild(iframe)
iframe.src="javascript:var b = {};"
var b1 = iframe.contentWindow.b;
var b2 = {};
console.log(typeof b1, typeof b2); //object object
console.log(b1 instanceof Object, b2 instanceof Object); //false true
可以看到,由于 b1、 b2 由同样的代码“ {} ”在不同的 Realm 中执行,所以表现出了不同的行为。