js 作用域有哪些?闭包是什么?

作用域是什么

javascript中的作用域是一套规则,用于确定在何处以及如何查找变量(标识符)
如果查找的目的是对变量进行赋值,那么就会进行LHS查询
如果查找的目的是为了获取变量的值,那么就会使用RHS查询
下面通过一个简单的例子介绍LHSRHS的出现场景

function demo(a){ //变量a进行了隐式的LHS赋值查询
	var b = a; //b为LHS赋值查询,a为RHS获取查询
	return a + b; //a和b都为RHS获取查询
}
var c = demo(2);//c为LHS赋值查询,demo(2)为RHS获取查询,获取到demo函数

LHSRHS两种查询方式都会从当前作用域开始,如果当前作用域未找到,则会向上级作用域继续查询,直到抵达全局作用域,当全局作用域也未找到时,如果是RHS获取查询就会抛出异常,如果是LHS赋值查询则会自动隐式的创建一个变量(如果在严格模式下就不会进行创建并抛出异常)

词法作用域

词法作用域的定义:
词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。编译的词法分析阶段基本能够知道全部标识符在哪里以及如何声明,从而能够预测在执行过程中如何对他们进行查找。–来自《你不知道的JavaScript》
看一个简单的例子:

function demo(a){
	var b = "demoB"
	console.log(a,b)
}
var a = "windowsA"
var b = "windowsB"
demo(a); //windowsA demoB

这段代码对于初学者来说也应该很容易就看懂的,console.log在执行时会去查找a和b两个变量
其中a是传入的参数,就直接查找到了
b的话在demo函数内部进行了创建,查找到了之后就不会再向上去查找了
作用域查找会在找到第一个匹配的标识符时停止
词法作用域中还有另外一种情况叫做欺骗词法
欺骗词法主要使用evalwith进行的
这两种欺骗词法的方式具体我也不介绍了,只需要记住,欺骗词法作用域会影响JavaScript引擎的优化,会导致代码运行变慢,不要使用他们!!!

函数作用域

函数作用域的定义:
每个函数都会创建一个自身的函数作用域,在该函数之外无法访问函数内部创建的变量,声明一个函数内部的变量或函数会在所处的作用域中“隐藏”起来,这是良好软件的设计原则
考虑一下下面的代码:

var a = "windwosA"
function demo(){
	var a = "demoA"
	var b = "demoB"
	console.log(a)
}
demo();
console.log(a);
console.log(b);

该代码执行结果为:
demoA
windwosA
ReferenceError: b is not defined
这就是函数作用域的表现
再看一下下面的代码:

var a = "windwosA"
function demo(){
	console.log(a)
	var a = "demoA"
	console.log(a)
}
demo();
console.log(a);

先不看运行结果,用自己所学的知识来想象一下运行结果
上面代码运行结果为:
undefined
demoA
windwosA
你想的对吗?
这里其实涉及到提示的概念
对于提示你只需要记住,无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。
可以想象为所有声明(函数或变量)都会被移动到各自作用域的最顶端
提升中函数是最优先的并且重复的声明会被覆盖,可以看下面代码:

fn(); //3
function fn(){
	console.log(1)
}
var fn = function(){
	console.log(2)
}
function fn(){
	console.log(3)
}

不仅仅是函数,变量也会覆盖的,后面声明的可以覆盖前面的

块作用域

块级作用域是指变量和函数不仅可以输入所处的作用域,也可以属于某个代码块(通常是{…}内部)
看下如下代码:

var a = "windowsA"
{
	var a = "demoA"
	console.log(a); //demoA
}
console.log(a); //demoA 糟糕,泄漏到了全局作用域

代码块中进行创建的a变量泄漏到了全局的作用域
此时就需要ES6中引入的let关键词来解决这个问题
一般我们都是使用let关键词来创建块级作用域,防止污染全局作用域

var a = "windowsA"
{
	let a = "demoA"
	console.log(a); //demoA
}
console.log(a); //windowsA

let关键词可以将变量绑定到所在的任意作用域中
注意!使用let关键词声明的变量不会进行提升

{
	console.log(a); //抛出异常
	let a = "demoA"
}

闭包

闭包的定义:
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行 --来自《你不知道的JavaScript》
我的理解:函数内部有有一个函数,并且内部函数可以访问外部函数的变量,那么内部函数就是闭包
我们拿一个经典的面试题来对闭包做介绍:

for(var i=0;i<10;i++){
	setTimeout(function(){
		console.log(i)
	},i*1000)
}

这段代码想象中是为了每隔一秒输出i,实现输出0-9的效果,实际上,却是输出10次10,为什么呢?其实转变点代码你可能就知道为什么了

var i;
for(i=0;i<10;i++){
	setTimeout(function(){
		console.log(i)
	},i*1000)
}

seTimeout计时器在执行时,就会在作用域中寻找i,然而这时候for循环已经执行完毕,每次执行循变量i都会变化,在到了执行计时器时,变量i就已经是最终的值了,这时候去获取,也就一直是10了
解决的方法:
使用闭包

for(var i=0;i<10;i++){
	(function(i){
		setTimeout(function(){
			console.log(i)
		},i*1000)
	})(i)
}

原理:使用每次都将循环的i传入到函数内,函数内形成一个闭包,每次循环都会正确保存到i的值
使用let关键词

for(let i=0;i<10;i++){
	setTimeout(function(){
		console.log(i)
	},i*1000)
}

原理:let关键词会使每次循环都创建一个块级作用域,在作用域中i的值并不会污染到全局作用域中,推荐使用这样的方式去解决,因为我觉得很酷!

《你不知道的JavaScript》学习记录系列
其他笔记请查看专栏:
《你不知道的JavaScript》学习笔记

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值