当前位置:Gxlcms > JavaScript > JS作用域与作用域链详解_基础知识

JS作用域与作用域链详解_基础知识

时间:2021-07-01 10:21:17 帮助过:8人阅读

(1)作用域

一个变量的作用域(scope)是程序源代码中定义的这个变量的区域。

1. 在JS中使用的是词法作用域(lexical scope)

不在任何函数内声明的变量(函数内省略var的也算全局)称作全局变量(global scope)

在函数内声明的变量具有函数作用域(function scope),属于局部变量

局部变量优先级高于全局变量

代码如下:

var name="one";
function test(){
var name="two";
console.log(name); //two
}
test();

函数内省略var的,会影响全局变量,因为它实际上已经被重写成了全局变量

代码如下:

var name="one";
function test(){
name="two";
}
test();
console.log(name); //two

函数作用域,就是说函数是一个作用域的基本单位,js不像c/c++那样具有块级作用域 比如 if for 等

代码如下:

function test(){
for(var i=0;i<10;i++){
if(i==5){
var name = "one";
}
}
console.log(name); //one
}
test(); //因为是函数级作用域,所以可以访问到name="one"

当然了,js里边还使用到了高阶函数,其实可以理解成嵌套函数

代码如下:

function test1(){
var name = "one";
return function (){
console.log(name);
}
}
test1()();

test1()之后将调用外层函数,返回了一个内层函数,再继续(),就相应调用执行了内层函数,所以就输出 ”one"

嵌套函数涉及到了闭包,后面再谈..这里内层函数可以访问到外层函数中声明的变量name,这就涉及到了作用域链机制

2. JS中的声明提前

js中的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。并且,变量在声明之前就可以使用了,这种情况就叫做声明提前(hoisting)

tip:声明提前是在js引擎预编译时就进行了,在代码被执行之前已经有声明提前的现象产生了

比如

代码如下:

var name="one";
function test(){
console.log(name); //undefined
var name="two";
console.log(name); //two
}
test();

上边就达到了下面的效果

代码如下:

var name="one";
function test(){
var name;
console.log(name); //undefined
name="two";
console.log(name); //two
}
test();

再试试把var去掉?这是函数内的name已经变成了全局变量,所以不再是undefined

代码如下:

var name="one";
function test(){
console.log(name); //one
name="two";
console.log(name); //two
}
test();

3. 值得注意的是,上面提到的都没有传参数,如果test有参数,又如何呢?

代码如下:

function test(name){
console.log(name); //one
name="two";
console.log(name); //two
}
var name = "one";
test(name);
console.log(name); // one

之前说过,基本类型是按值传递的,所以传进test里面的name实际上只是一个副本,函数返回之后这个副本就被清除了。
千万不要以为函数里边的name="two"把全局name修改了,因为它们是两个独立的name

(2)作用域链

上面提到的高级函数就涉及到了作用域链

代码如下:

function test1(){
var name = "one";
return function (){
console.log(name);
}
}
test1()();

1. 引入一大段话来解释:
每一段js代码(全局代码或函数)都有一个与之关联的作用域链(scope chain)。

这个作用域链是一个对象列表或者链表,这组对象定义了这段代码中“作用域中”的变量。

当js需要查找变量x的值的时候(这个过程称为变量解析(variable resolution)),它会从链的第一个对象开始查找,如果这个对象有一个名为x的属性,则会直接使用这个属性的值,如果第一个对象中没有名为x的属性,js会继续查找链上的下一个对象。如果第二个对象依然没有名为x的属性,则会继续查找下一个,以此类推。如果作用域链上没有任何一个对象含有属性x,那么就认为这段代码的作用域链上不存在x,并最终抛出一个引用错误(ReferenceError)异常。

2. 作用域链举例:

在js最顶层代码中(也就是不包括任何函数定义内的代码),作用域链由一个全局对象组成。

在不包含嵌套的函数体内,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。

在一个嵌套的函数体内,作用域上至少有三个对象。

3. 作用域链创建规则:

当定义一个函数时(注意,是定义的时候就开始了),它实际上保存一个作用域链。

当调用这个函数时,它创建一个新的对象来储存它的参数或局部变量,并将这个对象添加保存至那个作用域链上,同时创建一个新的更长的表示函数调用作用域的“链”。

对于嵌套函数来说,情况又有所变化:每次调用外部函数的时候,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的。内部函数在每次定义的时候都要微妙的差别---在每次调用外部函数时,内部函数的代码都是相同的,而且关联这段代码的作用域链也不相同。

(tip: 把上面三点理解好,记住了,最好还要能用自己的话说出来,不然就背下来,因为面试官就直接问你:请描述一下作用域链...)

举个作用域链的实用例子:

代码如下:

var name="one";
function test(){
var name="two";
function test1(){
var name="three";
console.log(name); //three
}
function test2(){
console.log(name); // two
}
test1();
test2();
}
test();

上边是个嵌套函数,相应的应该是作用域链上有三个对象
那么在调用的时候,需要查找name的值,就在作用域链上查找

当成功调用test1()的时候,顺序为 test1()->test()->全局对象window 因为在test1()上就找到了name的值three,所以完成搜索返回

当成功调用test1()的时候,顺序为 test2()->test()->全局对象window 因为在test2()上没找到name的值,所以找test()中的,找到了name的值two,就完成搜索返回

还有一个例子有时候我们会犯错的,面试的时候也经常被骗到。

代码如下: