你不知道的JavaScript-------作用域和闭包

你不知道的JavaScript-------作用域和闭包


在《你不知道的JavaScript(上)》中提到:作用域可分为 词法作用域动态作用域,JavaScript在编译中使用的词法作用域。

词法作用域

在我的理解里:作用域就好比一块块区域,每个区域有着不同的事物和规则,区域里的事物需要遵守区域的规则,同时受到区域的保护,不会被区域外的事物污染。

JavaScript代码的执行分为两步:预编译和执行期,词法作用域是在预编译阶段产生的,ES5中,词法作用域分为全局作用域和函数作用域。

var a = 2
var b = 34
function foo() {
	a = 3
	console.log(a)  //3
	console.log(b)  //34
	}
console.log(a) //2
foo()

/* 执行过程
	1. 变量声明和函数声明 => GO {
								a : undefined,
								b : undefined,
								foo : undefined
							}
	2.执行 => GO{
					a : 2,
					b : 34,
					foo : function (){...}
				}
	3.console.log(a) 执行,输出2
	4.foo()执行,产生函数作用域AO => {
										a : 3
									}
	5.执行console。输出 3 34
*/
  1. 预编译过程中,会先将变量声明和函数声明在作用域内生产并全部标志位undefined,如上代码,先预编译全局作用域GO,当在作用域GO生产所有变量名和函数名后,对变量和函数进行赋值undefined,最后GO中会有{a = undefined; b = undefined; foo = undefined…}
  2. 在预编译还没完成前,执行语句不会执行。预编译完成后,代码执行,同时对作用域中的变量赋值真实值。当执行console.log(a)时,是在全局中执行的,因此可以访问GO中的变量a,最后输出2
  3. 当foo()执行时,foo是一个函数,函数内部也是一个作用域,这时会再次生产作用域AO,并进行预编译。从函数内部可以看到 a = 3 前面并没有var关键字,但是引擎会默认帮a作变量声明,在AO中生成一个变量a并赋值,但是GO和AO作用域的变量a不是同一个变量,它们是互不干扰的
  4. 当console.log(a)和console.log(b)执行时,需要访问的a和b都是变量,引擎会采用就近原则,先访问最近的作用域,也就是AO,AO中有变量a,所以直接访问AO中的变量a,输出3;但是AO中没有变量b,就会逐层往外查找,若最后没找到,这时会报错;如上代码中,GO中有变量b,所以访问GO中的变量b,输出34;

块级作用域

在ES5中,只有全局作用域和函数作用域,上一点中的GO就表示全局作用域,AO就表示函数作用域;而在ES6发布后,新增了一个概念:块级作用域;块级作用域一般是在{…}内部,它规定变量只作用于{…}内部

{
	let a = 8;
	console.log(a)  //8
}
//这是一个块级作用域
console.log(a)  //报错:ReferenceError: a is not defined

其实在ES6之前,也有类似于块作用域的概念,with和try…catch能产生块级作用域,但不建议使用,会影响性能,ES6引入了块作用域,它们的使用就更不必要了

var、let和const

  • ES6引入了let和const,let和const会自动生产块级作用域,并将声明的变量绑定在块级作用域中,一般在{…}内部
  • let常被用于声明变量,const常备用于声明常量,当用const声明的常量在后面执行过程中值被修改时,会报错。(不过,当修改的常量的地址不变时,不会报错,比如:修改对象中的属性的属性值)
  • var会造成变量提升,不会自动产生块级作用域
//代码一
if(true) {
	var a = 12
}
console.log(a)  //输出什么??
console.log(b)  //输出什么??
var b = 34
//代码二
if(true) {
	let a = 12
}
console.log(a)  //输出什么??
console.log(b)  //输出什么??
let b = 34
  • 代码一输出:12; undefined,而代码二输出:ReferenceError: a is not defined,第二句console不执行,若注释第一句,第二句执行同样会报错:ReferenceError: b is not defined。
  • 代码一:预编译时,b会变量提升至console前并赋值undefined,if语句中的a也会被声明在全局作用域中,所以console.log(b)时会输出:undefined;console.log(a)会输出:12
  • 代码二:let声明的变量会被绑定在块级作用域中,也不会变量提升。if中的a用let声明,会将a变量绑定在if后面的{…}中,只作用于其中,外部不能访问;用let声明的b不会变量提升,先访问再声明时会报错

变量提升

  • var声明的变量会提升至执行语句之前,如:
console.log(a) // 输出:undefined
var a = 89

/* 预编译时,引擎解析:
	1.var a
	2.console.log(a)
	3.a = 89
*/
  • 不声明的变量不会变量提升,如:
console.log(a) // 输出:ReferenceError: a is not defined
a = 89

/* 预编译时,引擎解析:
	1.console.log(a)
	2.a = 89 ==> var a; a = 89
*/
  • 里作用域内变量不声明,变量会提升至全局
function fn() {
	a = 78
}
fn()
console.log(a) // 输出:78

/* 预编译时,引擎解析:
	1.fn()执行
	2.检测到a没var且全局没声明a
	3.在全局:var a; a = 78
	4.console.log(a)执行
*/
  • 函数声明也会提升,当函数声明和变量声明都存在时,函数声明优先提升,如:
foo()  //输出:1
var foo
function foo() {
	console.log(1)
}
foo = function (){
	console.log(2)
}

/* 编译时,引擎解析:
	1.function foo()
	2.foo()
	3.foo = function()
*/

作用域链

在嵌套作用域中,查找变量时,引擎会像冒泡事件那样,从自身作用域向外逐层逐层查找,直到查到找全局作用域中,好像一条链子般。

let a = 1;
//...
function fn1() {
	let b =2;
	//...
	function fn2() {
		let c = 3
		//...
	}
}
// GO -> AO1 -> AO2......

闭包

什么是闭包?当函数在声明的词法作用域之外被调用时,被称为发生了闭包。我们在日常的代码书写中,处处都是闭包,只要使用了callback函数,都是在使用了闭包:定时器、事件监听器、Ajax请求、异步等等。

//典型例子
for(var i = 0; i < 5; i ++) {
	setTimeout(function timer() {
		console.log(i)
	}, 1000)	
}

原本想逐个输出0,1,2,3,4的,结果输出5个5,为什么呢?setTimeout里的延迟函数会在循环结束时才执行,访问的是全局的变量i,可此时i已经变成了5,console.log(i)会输出5

解决(给每个迭代都绑定一个新的作用域):

//let声明变量i
for(let i = 0; i < 5; i ++) {
	setTimeout(function timer() {
		console.log(i)
	}, 1000)
}

//立即执行函数传参
for(var i = 0; i < 5; i ++) {
	(function foo(i){
		setTimeout(function timer() {
			console.log(i)
		}, 1000)
	})(i)
}

以上能正常逐个输出:0,1,2,3,4。原因是:let和立即执行函数使setTimeout被包裹在块级作用域中,每次循环都产生一个对应变量i的块级作用域,setTimeout执行时,里面的函数访问对应块级作用域中的变量i

立即执行函数(IIFE)不是闭包,原因:函数并不是在它本身的词法作用域以外执行的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值