1. 什么是作用域?
通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突
js的作用域是靠函数来形成的,作用域控制着变量与函数的可见性和生命周期。
全局作用域
1.1 函数外定义变量有全局作用
var a=1 //函数外定义变量有全局作用
function f(){
var s=2
return s
}
console.log(a) // 1
console.log(f()) // 2
console.log(s) //error
1.2 未定义直接赋值的变量自动声明为拥有全局作用域
var a=1 //未定义直接赋值的变量自动声明为拥有全局作用域
function f(){
s=2
return s
}
console.log(a) // 1
console.log(f()) // 2
console.log(s) //2
1.3 windows对象的属性具有全局作用
局部作用域
局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所以在一些地方会把这种作用域成为函数作用域。
ES6引入了块级作用域,明确允许在块级作用域中声明函数,let和const命令都涉及块级作用域。
块级作用域允许声明函数只在使用大括号的情况下成立。
var a=2 //全局作用域
function f(){ //{}之间的是局部作用域
var a=3
var b=4
function f1(){
var c=1
// 找不到 a,b 向上查找
console.log(a,b,c) // 3 4 1
function f11(){
console.log(a) // 3
console.log(c) // 1
}
f11()
}
f1()
}
f()
var a=1
for (let i=0;i<4;i++){
console.log(i)
}
console.log(i) //error
2. 执行上下文o(´^`)o
不得不提的执行上下文 |ू・ω・` )
执行上下文就是为了提供数据的(变量和函数)
当运行了js代码,默认产生一个全局的执行上下文,当调用一个函数时,就会产生一个局部的执行上下文,局部的执行上下文会压在全局的执行上下文上面,压栈,当函数调用完后,局部的执行上下文,就会出栈,当js代码执行完后,全局的执行上下文也会出栈
举个栗子
//全局执行上下文开始
var a=2 //全局执行上下文 a=2 压栈
function f(){ //局部执行上下文 开始
var a=3 //局部执行上下文 a=3 压栈
var b=4 //局部执行上下文 b=4 压栈
function f1(){
var c=1 //局部执行上下文 c=1 压栈
// 找不到 a,b 向上查找
console.log(a,b,c) // 3 4 1
function f11(){
console.log(a) // 3
console.log(c) // 1
}
f11()
}
f1()// f1()执行完毕,c=1 出栈
}
f()// f1()执行完毕,a=2,b=4 出栈,局部执行上下文现在结束了
console.log(c) //全局执行上下文没有找到,error
3. 作用域链
全局执行上下文件包含全局变量,函数
局部执行上下文包含内部变量,函数,arguments,父级函数的执行上下文。
如果找一个数据,在一个局部执行上下文中,内部没有找到,就去父级函数的执行上下文中找,如果父里也没有,就是父的父中的执行上下文中找,直到找到全局的执行上下文,如果还找不到,就会报错。这个链就是作用域链。
举栗子
var m=7
var a=2
function f(){
var a=3
var b=4
function f1(){
var c=1
// 找不到 a,b 向上查找
console.log(a,b,c) // 3 4 1
function f11(){
// 向上找a,c
console.log(a) // 3
console.log(c) // 1
console.log(m) // 7
// f11()=>f1()=>f()=>全局,终于找到了(~ ̄▽ ̄)~
}
f11()
}
f1()
}
console.log(c) //error ,向上找不到,不能向下找 ̄へ ̄
f()
4. 闭包
提到作用域就不得不提到闭包,简单来讲,闭包外部函数能够读取内部函数的变量。
优点:闭包可以形成独立的空间,永久的保存局部变量。
缺点:保存中间值的状态缺点是容易造成内存泄漏,因为闭包中的局部变量永远不会被回收
ES6之前, 通常我们实现的模块就是利用了闭包. 闭包依赖的结构有个鲜明的特点, 即: 一个函数在词法作用域之外执行
5. 踩个作用域经典的坑
var div = document.getElementsByTagName("div")
for(var i=0,len=div.length;i<len;i++){
div[i].onclick = function(){
console.log(i)
}
}
上述代码的本意是每次点击div, 打印div的索引, 实际上打印的却是 len 的值. 我们来分析下原因.
点击div时, 将会执行 console.log(i) 语句, 显然 i 变量不在 click 事件的局部作用域内, 浏览器将沿着 scope chain 寻找 i 变量, 在 index1 的地方, 即 for循环开始的地方, 此处定义了一个 i 变量, 又 js 没有块作用域, 故 i 变量并不会在 for循环块执行完成后被销毁,又 i的最后一次自加使得 i = len, 于是浏览器在scope chain index=1索引的地方停下来了, 返回了i的值, 即len的值.
为了解决这个问题, 我们将根据症结, 对症下药, 从作用域入手, 改变click事件的局部作用域, 如下:
var div = document.getElementsByTagName("div")
for(var i=0,len=div.length;i<len;i++){
(function(n){
div[n].onclick = function(){
console.log(n)
}
})(i)
}
en,没问题了。
end
作用域和作用域链是很基础的内容,希望本文能给你带来帮助,感谢阅读(〃‘▽’〃)