作用域 Scope
- 定义:在这个区域内声明的变量和函数的有效访问范围。即作用域控制着变量与函数的可见性和生命周期。
- 分类:全局作用域、局部作用域(函数作用域、块级作用域)
作用域只是一个“区域”。实际上:作用域只是划分你在这个作用域里面定义的变量和函数的有效范围,出了这个作用域就无效。
很多人会把执行上下文与作用域当做一个概念(包括我),今天就把它们区分开
执行环境 (执行上下文) 与作用域是不一样的。JavaScript
采用的是词法 (静态) 作用域,它规定作用域是在书写代码时函数声明的位置确定的,并不是函数执行时确定的。而执行上下文是函数执行时 (前) 创建的,执行上下文中的 this
关键词,就是由函数执行 (被调用) 时确定的。
全局作用域
全局作用域中的变量和函数在任何局部变量都能被访问到。其中的变量和函数被称之为全局变量和全局函数。
1). 最外层函数和最外层变量拥有全局作用域。
var x = 1;
function foo(){
var y = 2;
function bar(){var z = 3;}
}
console.log(x); // 1
console.log(foo); // function
console.log(y); // Uncaught ReferenceError: y is not defined
上述代码中 x 为全局变量,foo 为全局函数,y、bar、z 都是局部变量。
2). 所有未定义直接赋值的变量,默认都是全局变量。(非严格模式,严格模式下会报错)。
var x = 1;
function foo(){
y = 2;
function bar(){var z = 3;}
}
console.log(y); // 2
上述代码中 y 并没有是用关键词来声明它。所有它默认为全局变量。在外部也能访问它。
3. 在浏览器中,所有的 window 对象的属性都拥有全局作用域,也就是说 window 中的所有变量和方法,都是全局的。比如:window. location、window.name。
局部作用域
局部作用域和全局作用域相反,局部作用域中声明的变量和函数只能在当前作用域中有效。
var x = 1;
function foo(){
var y = 2;
function bar(){console.log(y); // 2
var z = 3;
}
bar();}
foo();
console.log(y); //Uncaught ReferenceError: y is not defined
上述代码中变量 y 就属于局部变量,只能在 foo 作用域内被访问(使用),在外部是访问不到的。
作用域链
作用域链保证了执行上下文中的变量和函数的有序访问。作用域链是由当前执行上下文以及上级执行上下文 (上级的上级,直到全局执行上下文) 的变量对象组成的集合。在作用域链的最前端始终都是当前执行上下文中的 变量对象 (VO),如果是函数,则称其为 活动对象 (AO),最底端总是全局执行上下文中的 变量对象(VO)。说白了就是变量和函数的查找规则。
函数也是对象,所以也可以有属性。当函数被创建时,默认就有一个 [[Scope]] 属性,它是一个内部属性仅供 JavaScript
引擎内部使用。这个属性里存放着就是当前执行上下文对应的作用域链。当当前执行上下文中访问一个属性或者函数时,就会从作用域链中自上而下进行查找。
var scope = 'scope';
function foo(){
var a = 1;
console.log(scope); // scope
}
foo();
上述代码中 foo 函数内部访问 scope 变量,但是当前作用域内并没有声明 scope 变量,所以就会去上级作用域中查找。最终在全局作用域中找到了变量 scope。这种链式的查找规则就是作用域链。
用图来表示函数 foo 创建时和执行时,作用域链的变化。
当 foo 函数创建时:
当 foo 函数执行时:
当联系着图再来看上述代码:当 foo 函数内部使用 scope 变量时,在作用域链的顶端 (foo 执行上下文的活动对象 AO) 中查找,如果找不到就往作用域链的下一级寻找,这里的下一级为全局作用域,所有就在全局执行上下文的变量对象上查找,刚好全局执行上下文的变量对象上有一个属性 scope,其值为“scope”。所以打印出“scope”。
再看一个例子:
var scope='scope';
function foo() {
var a=1;
function bar() {
var b=2;
console.log(a); // 1
console.log(b); // 2
console.log(scope); // scope
console.log(c); // Uncaught ReferenceError: c is not defined
}
bar();}
foo();
当 foo 被创建和执行时,作用域链的变化跟上述例子一致。但是这里当 foo 执行时,又遇到了 bar 函数的创建与执行。所以 bar 函数 执行上下文对应的作用域链为:
从上面的图我们可以分析上述代码的输出结果:
当 bar 访问属性 a 时,先从作用域链的最顶端查找,也就是 bar 执行上下文的活动对象 (AO),但是 AO 中并不存在 a 属性,所以就往作用域链的上层去查找,找到了 foo 执行上下文的活动对象(AO),碰巧 foo 执行上下文的活动对象(AO) 上有一个属性 a,其值为 1,因此打印出 1。当访问 b、scope、c 时跟访问 a 时查找规则一致。但是当访问 c 时,查找到作用域链的最底端,也就是全局执行上下文的变量对象上仍未查找到,这时认为该变量未定义,抛出 ReferenceError。
总结:
- 作用域分为全局作用域和局部作用域。
- 作用域其实就是规定了当前作用域中的变量和函数可被作用的范围。
- 作用域链其实就是规定了变量和函数的查找规则、是当前执行上下文的变量对象以及所有父级执行上下文的变量对象的集合。
- 当查找一个变量时,先从作用域的顶端查找,一直查找到作用域的底端,若查找完仍未找到,抛出错误。
如果有错误的地方,请务必给予指正,十分感谢。如果对你有帮助,点个赞给个关注呦~~。