作用域&作用域链
作用域的概念
- 变量作用域的概念:变量作用域就是一个变量可以使用的范围。
- JS中首先有一个最外层的作用域:称之为全局作用域。
- JS中还可以通过函数创建出一个独立的作用域,其中函数可以嵌套,所以作用域也可以嵌套。
var age = 18; // age是在全局作用域中声明的变量,即为全局变量
function f1(){
console.log(name); // 此处可以访问到name变量,输出undefined
// 这里为何输出undefined,是由于变量提升造成的,见附录1
// name是f1函数内部声明的变量,所以name变量的作用域就是在f1函数内部
var name = "hello";
console.log(name); // 可以访问到name变量
console.log(age); // age是全局作用域中声明的变量,所以在f1函数内部也可以访问
}
console.log(age); // 此处可以访问
console.log(age); // 此处不可以访问name,因为name变量在函数f1内部声明的,属于局部变量,所以在全局作用域中无法访问。
上面就是一个简单的两级作用域栗子,下面我们写一个多级的作用域来引出作用域链的概念
作用域链
首先看一个多级作用域的栗子:
//多级作用域
//======>此处是1级作用域
var gender = "男";
function fn(){ // ======>从这里开始是2级作用域
//gender 可以访问 gender是全局作用域的变量,任何地方都可以访问
//age 可以访问
//height 不能访问
return function(){ // ======>从这里开始是3级作用域
//gender 可以访问
//age 可以访问
//height 可以访问
var height = 180;
}
var age = 5;
}
-
由于作用域是相对于变量而言的,而如果存在多级作用域,这个变量又来自于哪里?这个问题就需要好好地探究一下了,我们把这个变量的查找过程称之为变量的作用域链
-
作用域链的意义:查找变量(确定变量来自于哪里,变量是否可以访问)
-
简单来说,查找一个变量来自哪里,能否被访问,需要以下四步:
- 1.查看当前作用域,如果当前作用域声明了这个变量,可以直接访问
- 2.查找当前作用域的上级作用域,也就是当前函数的上级函数,看看上级函数中有没有声明,有就返回变量,没有继续下一步
- 3.再查找上级函数的上级函数,直到全局作用域为止,有则返回,无则继续
- 4.如果全局作用域中也没有,我们就认为这个变量未声明(xxx is not defined)
这四步操作就描述了整个作用域链及作用域链如何查找变量的过程。
下面我们来举几个特殊的栗子:
- 栗子1:
function fn(callback){
var age = 20;
callback();
}
fn(function(){
console.log(age);//报错
//1.在当前作用域没有查找到age
//2.查找上一级作用域:全局作用域
//为何是全局作用域?因为看上一级作用域,不是看函数在哪调用,而是看函数在哪编写的。
//这种特别的作用域,叫做“词法作用域”
})
这个栗子比较特殊,可能很多人会认为输出20,因为函数调用的地方是fn函数内部,恰巧age又是声明在这个函数内部的,理所应当输出20。这是错误的!
现在分析下作用域链如何查找变量的:
- console.log(age)的时候,在当前作用域并没有查询到age变量。所以查找上一级作用域。
- 上级作用域是谁?这里需要引出一个概念,查找函数上级作用域,不是看函数在哪调用,而是看函数在哪编写。所以这样来看,上级作用域就是全局作用域。
- 在全局作用域中并没有声明age变量,所以console.log(age);就会报错。
- 栗子2:
var name="张三";
function f1(){
var name = "abc";
console.log(name);
}
f1(); // abc
如果查找一个变量时,在当前作用域找到变量,不管上级、上上级有没有同名变量都不会再去寻找。
- 栗子3
var name="张三";
function f1(){
console.log(name);
var name = "abc";
}
f1(); // undefined
如果这个栗子能看懂说明已经了解变量提升,如果不懂请参考附录1
- 栗子4
var name = "张三";
function f1(){
var name = "abc";
return function(){
console.log(name);
console.log(age);
}
var age = 18;
}
var fn = f1();
fn();
//abc
//undefined
这个栗子咋一看可能有点懵,但是记住之前一句很重要的话,查找函数上级作用域,不是看函数在哪调用,而是看函数在哪编写。
- 栗子5
var name="张三";
function f1(){
return {
say:function(){
console.log(name);
var name="abc";
}
}
}
var fn=f1();
fn.say();//undefined
当前作用域查到了变量,则不会再继续寻找,直接返回该变量的值,这里打印的时候变量声明但是未赋值,所以输出undefined。
- 如果以上几个栗子都能看懂,说明你已经掌握了作用域&作用域链,学好作用域和作用域链可以为理解闭包打下很好的基础。
==附录1== 变量提升
变量提升的概念:函数和变量的声明会被js的解释器放到最上面。
function fn1() {
a = 1;
console.log(a);
console.log(window.a);
var a = 5;
console.log(a);
}
fn1();
输出结果依次为:1 undefined 5,因为代码在解析的时候相当于
var a;//函数的声明会被解释器放到头部,预先声明,但没有赋值,所以在此刻a是undefined
a = 1;
console.log(a);
console.log(window.a); //此处打印的是全局变量的a,但是并没有声明全局变量a
a = 5;
console.log(a);
- 函数的提升
js中我们定义函数有两种方式:- 函数表达式 var fn=function fn(){}
- 函数声明方式 function fn(){}
当函数是通过函数声明的方式定义时,就会有函数提升,
特别注意的是:
- 变量提升,变量赋值并没有被提升,只是声明被提升了。
- 但是,函数的声明有点不一样,函数体也会一同被提升。
test();
function test(){
console.log(1);
}
所以会出现上面的情况,先执行test函数,然后声明,但是实际上函数确实被执行了,这就是与变量提升不同的点,函数提升不止声明提升,函数体也会一同被提升。
由于只是讲解作用域&作用域链的前置知识,简单介绍,不再赘述。想了解更多的关于变量提升的知识请查看我写的一篇专门讲解变量提升的文章。
网友评论