当前的执行上下文,值和表达式在其中“可见”或可被访问。
作用域就是一个独立的地盘,让变量不会外泄、暴露出去。作用域最大的用处就是隔离变量,不同作用域下的同名变量不会有冲突。
ES6之前JavaScript,没有块级作用域,只有全局作用域和函数作用域。ES6的到来,为我们提供了“块级作用域”,可通过let和const来体现。
在代码中任何地方都能访问到的对象拥有全局作用域。
以下三种情况拥有全局作用域:
var outVariable = "我是最外层变量"; //最外层变量
function outFun() { //最外层函数
var inVariable = "内层变量";
function innerFun() { //内层函数
console.log(inVariable);
}
innerFun();
}
console.log(outVariable); //可以访问: 我是最外层变量
outFun(); //可以访问: 内层变量
console.log(inVariable); //无法访问: inVariable is not defined
innerFun(); //无法访问: innerFun is not defined
function outFun2() {
variable = "未定义直接赋值的变量";
var inVariable2 = "内层变量2";
}
outFun2();//要先执行这个函数,否则根本不知道里面是啥
console.log(variable); //未定义直接赋值的变量
console.log(inVariable2); //inVariable2 is not defined
全局作用域的弊端:如果我们写了很多行JS代码,变量定义都没有用函数包括,那么他们就全部在全局作用域中。这样就会污染全局命名空间,容易引起命名冲突。这就是为什么jQuery、Zepto等库的源码,所有的代码都会放在(function(){…})中。因为放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他库和JS脚本造成影响。这是函数作用域的一个体现。
指声明在函数内部的变量,和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到。
function doSomething(){
var stuName="zhangsan";
function innerSay(){
console.log(stuName);
}
innerSay();
}
console.log(stuName); // 无法访问:脚本错误
innerSay(); //无法访问: 脚本错误
注:块语句(大括号‘{}’中间的语句),如if 和switch条件语句,或for 和whlie循环语句,不像函数,他们不会创建一个新的作用域
if (true) {
// 'if' 条件语句块不会创建一个新的作用域
var name = 'Hammad'; // name 依然在全局作用域中
}
console.log(name); // 可以访问:'Hammad'
块级作用域可通过let和const声明,所声明的变量在指定块的作用域外无法被访问
{
var a=1
}
console.log(a)
{
let a=1
}
console.log(a)
当前作用域中没有定义的变量,称为自由变量。
访问变量时,自己的作用域中没有,一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃,这种一层一层的关系,就是作用域链。
var a = 100
function f1() {
var b = 200
function f2() {
var c = 300
console.log(a) // 100 自由变量,顺作用域链向父作用域找
console.log(b) // 200 自由变量,顺作用域链向父作用域找
console.log(c) // 300 本作用域的变量
}
f2()
}
f1()
例1:
var x = 10
function fn() {
console.log(x)
}
function show(f) {
var x = 20;
(function () {
f() // 10,而不是 20
})()
}
show(fn)
如上代码,取自由变量x的值时,无论fn函数在哪里调用,都要到创建fn函数的那个作用域中取。
自由变量的取值(静态作用域):要到创建爱你这个函数的那个域中取值。这里强调的是"创建",而不是“调用”。
例2:
const food = "rice";
const eat = function () {
console.log(`eat ${food}`);
};
(function () {
const food = "noodle";
eat(); // eat rice
})();
eat函数在创建时,它的父级上下文为全局上下文,所以food的值为rice。
若要打印为noodle,需做如下修改:
const food = "rice";
(function () {
const food = "noodle";
const eat = function () {
console.log(`eat ${food}`);
};
eat(); // eat noodle
})();
JavaScript属于解释型语言,执行分为解释和执行两个阶段:
解释阶段:
执行阶段
JavaScript解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了
执行上下文最明显的就是this的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。
总结: 执行上下文在运行时确定,随时可能改变,作用域在定义时就确定,并且不会改变。
什么是作业域 ?
ES5 中只存在两种作用域:全局作用域和函数作用域。
在 JavaScript 中,我们将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套子作用域中根据标识符名称进行变量(变量名或者函数名)查找。ES6 新增了块级作用域。
什么是作用域链 ?
当访问一个变量时,编译器在执行这段代码时,会首先从当前的作用域中查找是否有这个标识符,如果没有找到,就会去父作用域查找,如果父作用域还没找到继续向上查找,直到全局作用域为止。
而作用域链,就是有当前作用域与上层作用域的一系列变量对象组成,它保证了当前执行的作用域对符合访问权限的变量和函数的有序访问。
作用域链有一个非常重要的特性,那就是作用域中的值是在函数创建的时候,就已经被存储了,是静态的。
所谓静态,就是说作用域中的值一旦被确定了,永远不会变。**函数可以永远不被调用,但是作用域中的值在函数创建的时候就已经被写入了,**并且存储在函数作用域链对象里面。