面试官:说说JS作用域和作用域链,我是这样回答的

2022-10-06 08:07:58 浏览数 (1)

作用域(scope)

1.什么是作用域

概念:作用域是在程序运行时代码中的某些特定部分中变量、函数和对象的可访问性

从使用方面来解释,作用域就是变量的使用范围,也就是在代码的哪些部分可以访问这个变量,哪些部分无法访问到这个变量,换句话说就是这个变量在程序的哪些区域可见。代码演示:

代码语言:scss复制
function Fun() {
    var inVariable = "内部变量";
}
Fun();
console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined
//inVariable是在Fun函数内部被定义的,属于局部变量,在外部无法访问,于是会报错

从存储上来解释的话,作用域本质上是一个对象, 作用域中的变量可以理解为是该对象的成员

总结:作用域就是代码的执行环境,全局作用域就是全局执行环境,局部作用域就是函数的执行环境,它们都是栈内存

2.作用域分类

作用域又分为全局作用域局部作用域。在ES6之前,局部作用域只包含了函数作用域,ES6的到来为我们提供了 ‘块级作用域’(由一对花括号包裹),可以通过新增命令let和const来实现;而对于全局作用域这里有一个小细节需要注意一下:

在 Web 浏览器中,全局作用域被认为是 window 对象,因此所有全局变量和函数都是作为 window 对象的属性和方法创建的。 在 Node环境中,全局作用域是 global 对象。

全局作用域很好理解,现在我们再来解释一下局部作用域吧,先来看看函数作用域,所谓函数作用域,顾名思义就是由函数定义产生出来的作用域,代码示例:

代码语言:c#复制
function fun1(){
    var variable = 'abc'
}
function fun2(){
    var variable = 'cba'
}
fun1();
fun2();
//这里有两个函数,他们分别都有一个同名变量variable,在严格模式下,程序不会报错,
//这是因为这两个同名变量位于不同的函数内,也就是位于不同的作用域中,所以他们不会产生冲突。

我们再来看看块级作用域,ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数(局部)作用域。块语句( {} 中间的语句),如 ifswitch 条件语句, forwhile 循环语句,不同于函数,它们不会创建一个新的作用域;但是ES6及之后的版本,块语句也会创建一个新的作用域, 块级作用域可通过新增命令let和const声明,所声明的变量在指定块作用域外无法被访问。块级作用域在如下情况被创建:

在一个函数内部 在一个代码块(由一对花括号包裹)内部

let 声明的语法与 var 的语法一致。基本上可以用 let 来代替 var 进行变量声明,但会将变量的作用域限制在当前代码块中 (注意:块级作用域并不影响var声明的变量)。 但是使用let时有几点需要注意

声明变量不会提升到代码块顶部,即不存在变量提升 禁止重复声明同一变量 for循环语句中()内部,即圆括号之内会建立一个隐藏的作用域,该作用域不属于for后边的{}中,并且只有for后边的{}产生的块作用域能够访问这个隐藏的作用域,这就使循环中 绑定块作用域有了妙用

参考 前端进阶面试题详细解答

这里分别演示一下ES5和ES6版本的代码,ES5:

代码语言:css复制
if(true) {
    var a = 1
}
for(var i = 0; i < 10; i  ) {
    ...
}
console.log(a) // 1
console.log(i) // 10

ES6:

代码语言:javascript复制
for (let i = 0; i < 10; i  ) {
            console.log(i);//0,1,2,3,4,5,6,7,8,9
 }
console.log(i);// Uncaught ReferenceError: i is not defined
代码语言:javascript复制
if (true) {
     let i = 9;
}
console.log(i);// Uncaught ReferenceError: i is not defined

作用域链(scope chain)

概念:多个作用域对象连续引用形成的链式结构。

使用方面解释:当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域,如果在全局作用域里仍然找不到该变量,它就会直接报错。

存储方面解释:作用域链在JS内部中是以数组的形式存储的,数组的第一个索引对应的是函数本身的执行期上下文,也就是当前执行的代码所在环境的变量对象,下一个索引对应的空间存储的是该对象的外部执行环境,依次类推,一直到全局执行环境

代码示例:

代码语言:text复制
var a = 100
function fun() {
    var b = 200
    console.log(a) //100
// fun函数局部作用域中没有变量a,于是从它的上一级,也就是全局作用域中找,
//在全局中a被赋值为100,于是输出100
    console.log(b)//200 fun函数局部作用域中有变量b,并且它被赋值为了200,输出200
}
fun()

再来看个栗子:

代码语言:text复制
var a = 10
function fun() {
   console.log(a)
}
function show(f) {
   var a = 20
   (function() {
      f()   //10,而不是20; 函数的作用域是在函数定义的时候就被决定了,与函数在哪里被调用无关
   })()
}
show(fun)

由于变量的查找是沿着作用域链来实现的,所以也称作用域链为变量查找的机制。是不是很好理解,这里再来补充一点作用域的作用

作用域最为重要的一点是安全。变量只能在特定的区域内才能被访问,外部环境不能访问内部环境的任何变量和函数,即可以向上搜索,但不可以向下搜索, 有了作用域我们就可以避免在程序其它位置意外对某个变量做出修改导致程序发生事故。 作用域能够减轻命名的压力。我们可以在不同的作用域内定义相同的变量名,并且这些变量名不会产生冲突。

1.什么是作用域

概念:作用域是在程序运行时代码中的某些特定部分中变量、函数和对象的可访问性

从使用方面来解释,作用域就是变量的使用范围,也就是在代码的哪些部分可以访问这个变量,哪些部分无法访问到这个变量,换句话说就是这个变量在程序的哪些区域可见。代码演示:

代码语言:scss复制
function Fun() {
    var inVariable = "内部变量";
}
Fun();
console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined
//inVariable是在Fun函数内部被定义的,属于局部变量,在外部无法访问,于是会报错

从存储上来解释的话,作用域本质上是一个对象, 作用域中的变量可以理解为是该对象的成员

总结:作用域就是代码的执行环境,全局作用域就是全局执行环境,局部作用域就是函数的执行环境,它们都是栈内存

2.作用域分类

作用域又分为全局作用域局部作用域。在ES6之前,局部作用域只包含了函数作用域,ES6的到来为我们提供了 ‘块级作用域’(由一对花括号包裹),可以通过新增命令let和const来实现;而对于全局作用域这里有一个小细节需要注意一下:

在 Web 浏览器中,全局作用域被认为是 window 对象,因此所有全局变量和函数都是作为 window 对象的属性和方法创建的。 在 Node环境中,全局作用域是 global 对象。

全局作用域很好理解,现在我们再来解释一下局部作用域吧,先来看看函数作用域,所谓函数作用域,顾名思义就是由函数定义产生出来的作用域,代码示例:

代码语言:c#复制
function fun1(){
    var variable = 'abc'
}
function fun2(){
    var variable = 'cba'
}
fun1();
fun2();
//这里有两个函数,他们分别都有一个同名变量variable,在严格模式下,程序不会报错,
//这是因为这两个同名变量位于不同的函数内,也就是位于不同的作用域中,所以他们不会产生冲突。

我们再来看看块级作用域,ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数(局部)作用域。块语句( {} 中间的语句),如 ifswitch 条件语句, forwhile 循环语句,不同于函数,它们不会创建一个新的作用域;但是ES6及之后的版本,块语句也会创建一个新的作用域, 块级作用域可通过新增命令let和const声明,所声明的变量在指定块作用域外无法被访问。块级作用域在如下情况被创建:

在一个函数内部 在一个代码块(由一对花括号包裹)内部

let 声明的语法与 var 的语法一致。基本上可以用 let 来代替 var 进行变量声明,但会将变量的作用域限制在当前代码块中 (注意:块级作用域并不影响var声明的变量)。 但是使用let时有几点需要注意

声明变量不会提升到代码块顶部,即不存在变量提升 禁止重复声明同一变量 for循环语句中()内部,即圆括号之内会建立一个隐藏的作用域,该作用域不属于for后边的{}中,并且只有for后边的{}产生的块作用域能够访问这个隐藏的作用域,这就使循环中 绑定块作用域有了妙用

参考 前端进阶面试题详细解答

这里分别演示一下ES5和ES6版本的代码,ES5:

代码语言:css复制
if(true) {
    var a = 1
}
for(var i = 0; i < 10; i  ) {
    ...
}
console.log(a) // 1
console.log(i) // 10

ES6:

代码语言:javascript复制
for (let i = 0; i < 10; i  ) {
            console.log(i);//0,1,2,3,4,5,6,7,8,9
 }
console.log(i);// Uncaught ReferenceError: i is not defined
代码语言:javascript复制
if (true) {
     let i = 9;
}
console.log(i);// Uncaught ReferenceError: i is not defined

作用域链(scope chain)

概念:多个作用域对象连续引用形成的链式结构。

使用方面解释:当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域,如果在全局作用域里仍然找不到该变量,它就会直接报错。

存储方面解释:作用域链在JS内部中是以数组的形式存储的,数组的第一个索引对应的是函数本身的执行期上下文,也就是当前执行的代码所在环境的变量对象,下一个索引对应的空间存储的是该对象的外部执行环境,依次类推,一直到全局执行环境

代码示例:

代码语言:text复制
var a = 100
function fun() {
    var b = 200
    console.log(a) //100
// fun函数局部作用域中没有变量a,于是从它的上一级,也就是全局作用域中找,
//在全局中a被赋值为100,于是输出100
    console.log(b)//200 fun函数局部作用域中有变量b,并且它被赋值为了200,输出200
}
fun()

再来看个栗子:

代码语言:text复制
var a = 10
function fun() {
   console.log(a)
}
function show(f) {
   var a = 20
   (function() {
      f()   //10,而不是20; 函数的作用域是在函数定义的时候就被决定了,与函数在哪里被调用无关
   })()
}
show(fun)

由于变量的查找是沿着作用域链来实现的,所以也称作用域链为变量查找的机制。是不是很好理解,这里再来补充一点作用域的作用

作用域最为重要的一点是安全。变量只能在特定的区域内才能被访问,外部环境不能访问内部环境的任何变量和函数,即可以向上搜索,但不可以向下搜索, 有了作用域我们就可以避免在程序其它位置意外对某个变量做出修改导致程序发生事故。 作用域能够减轻命名的压力。我们可以在不同的作用域内定义相同的变量名,并且这些变量名不会产生冲突。

0 人点赞