一、作用域
作用域决定了变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期
1. 理解
就是一块"地盘", 一个代码段所在的区域
它是静态的(相对于上下文对象), 在编写代码时就确定了
2. 分类
(1)全局作用域
拥有全局作用域的对象可以在代码的任何地方访问到,在js中一般有以下几种情形拥有全局作用域:
- 最外层函数以及最外层变量
- 未定义直接赋值的变量(由于变量提升使之成为全局变量)
function fn(){ a=123 } fn() console.log(a)//123
例子中:函数中定义的变量a,不添加var关键字会造成变量提升,这个变量会成为一个全局变量
- 所有window对象的属性拥有全局作用域
弊端:如果写了很多 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中,会污染全局命名空间, 容易引起命名冲突。
(2) 函数作用域
局部作用域一般只能在固定的代码片段中才能访问。
定义在函数中的变量就在函数作用域中,函数内部访问,函数每次调用都有一个不同的作用域,意味着同名变量可以在不同的函数中。因为这些变量绑定在不同的函数中,拥有不同作用域,彼此之间不能访问。
(3)ES6有块作用域(在函数内部,由{}包裹)
ES6新增let和const命令,用来创建块级作用域变量,使用let命令声明的变量只能在let命令所在的代码块内有效
1.变量不会提升到代码块顶部且不允许从外部访问块级作用域内部变量
2.不允许反复声明
(4)动态作用域
只能在执行阶段才能决定变量的作用域 ,就是动态作用域
动态作用域规则依赖的是程序执行时的函数调用顺序
3. 作用
隔离变量,不同作用域下同名变量不会有冲突
/* //没块作用域
if(true) { var c = 3 }
console.log(c)
*/
var a = 10,b = 20
function fn(x) {
var a = 100, c = 300;
console.log('fn()', a, b, c, x) //100 20 300 10
function bar(x) {
var a = 1000, d = 400
console.log('bar()', a, b, c, d, x)
}
bar(100)//1000 20 300 400 100
bar(200)//1000 20 300 400 200
}
fn(10)
4.总结
"作用"就是生效,"域"就是范围,作用域,变量与函数生效的范围,可以理解成一个代码段所在的区域,作用域决定了变量与函数的可访问范围,它最大的作用就是隔离变量,在不同的作用于下同名变量不会冲突。
分类:
全局作用域
函数作用域,每个函数都会创建自己的作用域,作用域在函数定义的时候已经确定好了,而不是在调用的时候,只有执行上下文才需要调用。
ES6里面的块作用域,用let、const声明的变量就在块作用域内,变量不能提升到代码块的顶部,并且外部也不可以访问到块作用域内的变量,变量也不可以重复声明。
二、作用域链
1. 理解
(1)多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
(2)查找变量时就是沿着作用域链来查找的
2. 查找一个变量的查找规则
(1)在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
(2)在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
(3)再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常
var a = 1
function fn1() {
var b = 2
function fn2() {
var c = 3
console.log(c)
console.log(b)
console.log(a)
console.log(d)
}
fn2()
}
fn1()
3.总结
当所需要的变量在所在的作用域中查找不到的时候,它会一层一层向上查找,直到找到全局作用域还没有找到的时候,就会放弃查找。这多个上下级关系的作用域形成的链,就是作用域链,它的方向是从下向上的。
三、相关面试题
1. 作用域在函数定义时就已经确定了,而不是在函数调用时
var x = 10;
function fn() {
console.log(x);
}
function show(f) {
var x = 20;
f();//执行fn函数,内部没有x,然后在全局作用域找到了x=10
}
show(fn); //输出10
2. 对象变量不能产生局部作用域
var fn = function () {
console.log(fn)
}
fn()
var obj = { //对象变量不能产生局部作用域,所以会找到全局去,导致报错
fn2: function () {
console.log(fn2)
//console.log(this.fn2)
}
}
obj.fn2()