深入学习JavaScript系列——作用域和作用域链

简介: 深入学习JavaScript系列——作用域和作用域链

大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库

1、js执行上下文和作用域链的关系?

作用域链和执行上下文是紧密相连的概念。

执行上下文是JavaScript代码执行时的环境,包括变量、函数、参数等信息。每个函数都有自己的执行上下文,而全局代码也有自己的执行上下文。

从执行上下文的角度来说: 作用域链是由当前执行上下文和所有外层执行上下文的变量对象组成的链式结构。当JavaScript代码在一个执行上下文中查找变量时,会先在当前执行上下文的变量对象中查找,如果没有找到,就会继续在外层执行上下文的变量对象中查找,直到找到该变量或者到达全局执行上下文。

因此,作用域链的形成是由执行上下文的嵌套关系决定的。在函数定义时,就已经确定了该函数的作用域链。当函数被调用时,会创建一个新的执行上下文,并将该执行上下文的变量对象添加到作用域链的顶端,从而形成新的作用域链。 函数销毁时,与之对应的作用域链节点也会销毁。

总之,作用域链和执行上下文是密不可分的,它们共同构成了JavaScript代码的作用域和变量查找机制。

2、什么是js作用域链和作用域?

在 JavaScript 中,每个函数都有一个作用域链,它是一个由当前函数和所有外层函数的变量对象组成的列表。当 JavaScript 引擎查找一个变量时,它会先在当前函数的变量对象中查找,如果找不到,就会在外层函数的变量对象中查找,直到找到该变量或者到达全局对象为止。

每个函数内部声明的变量和函数都只能在该函数内部访问,外部无法访问。但是,如果一个函数内部嵌套了另一个函数,那么内部函数可以访问外部函数的变量和函数,这就是作用域链的作用。

作用域链的创建是在函数定义时确定的,而不是在函数调用时确定的。当一个函数被调用时,它会创建一个新的执行上下文,并将其添加到调用栈中。在执行上下文中,会创建一个变量对象,该变量对象包含了当前函数的所有变量和函数声明。同时,该执行上下文的作用域链会指向当前函数的作用域链。

当 JavaScript 引擎在执行一个函数时,如果需要访问一个变量,它会先在当前函数的变量对象中查找,如果找不到,就会在外层函数的变量对象中查找,直到找到该变量或者到达全局对象为止。这个查找的过程就是作用域链的遍历过程。

通过上面两个问题的展开,其实已经能大致了解了作用域与作用域链,下面就展开讲一讲 在学习作用域链时需要注意哪些内容

3、作用域和作用域链的创建

js采用的是静态作用域,也就是说js函数的作用域是在函数定义的情况下就已经确定了,那么作用域链也是在函数定义的时候就创建,

举个静态作用域的小例子:

var value = 1;
function foo() {
    console.log(value);
}
function bar() {
    var value = 2;
    foo();
}
bar();
// 结果是 1 在函数定义的时候确定的作用value等于1 所以调用的时候也等于1 复制代码

如果上述的代码是动态作用域,那么value就是在执行时才生成 所以打印为2

动态作用域的语言:

  1. Bash:Bash 是一种常用的 Unix shell,它支持动态作用域。在 Bash 中,变量的作用域是在函数被调用时
  2. Perl:Perl 是一种通用的高级编程语言,它支持动态作用域。在 Perl 中,变量的作用域是在程序运行时确定
  3. Lisp:Lisp 是一种函数式编程语言,它也支持动态作用域。在 Lisp 中,变量的作用域是在函数被调用时确定
  4. Emacs Lisp:Emacs Lisp 是一种基于 Lisp 的编程语言,它是 Emacs 编辑器的扩展语言,也支持动态作用域。

4 作用域和作用域链的执行销毁

js有两种函数作用域:全局作用域和函数作用域,创建上面已经讲到了,那么执行呢,其实是在函数中的变量被使用时会执行,先在当前作用域中查找变量,然后再逐步往上查找父级作用域,直到找到为止, 如果最终都没找到,那么就会抛出一个 ReferenceError 异常。

作用域链及作用域链的销毁

当一个函数执行完毕之后,那么它的执行上下文也随之销毁,于此同时。对应的函数作用域和作用域链也会销毁(当前函数作用域节点),函数中声明的变量也会被销毁,这就是js中大垃圾回收机制(gc) 最常见的有标记清除法和计数算法

注:如果函数中使用了闭包,那可能包含外部变量,此时只有外部变量被销毁时才能销毁对应的闭包。这就是所谓的内存泄漏。

5 几个作用域练习题目

题目一:

var scope= "global scope";
function checkscope(){
    var scope= "local scope";
    functionscope(){
        returnscope;
    }
    returnscope();
}
checkscope();
//localscope复制代码
var scope= "global scope";
function checkscope(){
    var scope= "local scope";
    functionscope(){
        returnscope;
    }
    returnscope;
}
checkscope()();
//localscope复制代码

解释:函数的作用域基于函数创建的位置,

注:checkscope()()和checkscope()的区别:

题目一中,checkscope()是一个函数,它返回另一个函数scope()。这个内部函数scope()可以访问checkscope()函数的局部变量。它是一个闭包函数,因为它可以访问其外部函数checkscope()的词法环境。

checkscope()()是在调用checkscope()返回的函数scope()。它不是直接调用checkscope()函数本身。由于checkscope()函数返回了一个函数,因此需要在checkscope()后添加另一个括号来调用内部函数scope()。相当于》checkscope(). f()

题目二:

var a = 1;
function foo() {
  console.log(a);
  var a = 2;
}
foo();
// undefined
复制代码

解释:因为在函数 foo() 中,先执行了 var a = 2 语句,此时变量 a 被重新定义并赋值为 2。所以,在函数 foo() 中,变量 a 的值是 2,而不是全局变量 a 的值。因此,执行 console.log(a) 语句时,输出的是 undefined,因为变量 a 被重新定义,但没有被初始化。 变量能提升 但是赋值不能提升

题目三:

var x = 1;
function outer() {
  var y = 2;
  function inner() {
    var z = 3;
    console.log(x + y + z);
  }
  inner();
}
outer();
// 6
复制代码

解释:在调用inner函数时,可以访问外层作用域中的变量 x 和 y。变量 x 的值是 1,变量 y 的值是 2,变量 z 的值是 3。所以,执行 console.log(x + y + z) 语句时,输出的是 6。

题目四:

function foo() {
  var a = 1;
  function bar() {
    console.log(a);
  }
  return bar;
}
var baz = foo();
baz();复制代码

答案:代码输出的是1。因为bar函数在定义时可以访问到其外层函数foo的变量a,并将其保存在函数作用域内部。当执行baz()时,实际上是执行了内部函数bar(),此时访问到的变量a是定义时保存在bar函数作用域内的变量,所以输出的是1

题目五:

var a = 1;
function foo() {
  console.log(a);
}
function bar() {
  var a = 2;
  foo();
}
bar();复制代码

答案:代码输出的是1。因为bar函数定义了一个名为a的局部变量,并将其赋值为2,但是在调用foo函数时,它会在全局作用域中查找变量a,因为在foo函数内部没有定义变量a。因此,foo函数输出的是全局变量a的值,即1。

关键点 foo的父级作用域不是bar 而是全局 只是foo()在bar()中执行。

题目六

var a = 1;
function foo() {
  var a = 2;
  function bar() {
    console.log(a);
  }
  return bar;
}
var baz = foo();
baz();复制代码

答案:代码输出的是2。在foo函数内部定义了一个局部变量a,并将其赋值为2,然后返回了内部定义的函数bar。bar函数可以访问到foo函数的作用域链,因此可以访问到变量a。当调用baz函数时,实际上是执行了内部函数bar,此时访问到的变量a是定义时保存在foo函数作用域内的变量,所以输出的是2。

和题目五对比,因为bar函数是定义在foo函数内的 ,所以bar的父级作用域是foo

看到这里的同学都很厉害,那就顺便给我点个赞吧!

大厂面试题分享 面试题库

前后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库

相关文章
|
19天前
|
存储 JavaScript 前端开发
解释 JavaScript 中的作用域和作用域链的概念。
【4月更文挑战第4天】JavaScript作用域定义了变量和函数的可见范围,静态决定于编码时。每个函数作为对象拥有`scope`属性,关联运行期上下文集合。执行上下文在函数执行时创建,定义执行环境,每次调用函数都会生成独特上下文。作用域链是按层级组织的作用域集合,自内向外查找变量。变量查找遵循从当前执行上下文到全局上下文的顺序,若找不到则抛出异常。
21 6
|
1月前
|
自然语言处理 JavaScript 前端开发
深入理解JS的执行上下文、词法作用域和闭包(中)
深入理解JS的执行上下文、词法作用域和闭包(中)
|
1月前
|
存储 自然语言处理 JavaScript
深入理解JS的执行上下文、词法作用域和闭包(上)
深入理解JS的执行上下文、词法作用域和闭包(上)
|
8天前
|
JavaScript 前端开发 应用服务中间件
node.js之第一天学习
node.js之第一天学习
|
1月前
|
JavaScript 前端开发
js开发:请解释什么是作用域(scope),并说明全局作用域、局部作用域和块级作用域的区别。
JavaScript中的作用域规定了变量和函数的可见性与生命周期。全局作用域适用于整个脚本,变量可通过全局对象访问,可能导致命名冲突和内存占用。局部作用域限于函数内部,每次调用创建新作用域,执行完毕后销毁。ES6引入的块级作用域通过`let`和`const`实现,变量仅在其代码块内有效,并有暂时性死区。作用域机制有助于代码组织和变量管理。
23 1
|
1月前
|
运维 JavaScript 前端开发
发现了一款宝藏学习项目,包含了Web全栈的知识体系,JS、Vue、React知识就靠它了!
发现了一款宝藏学习项目,包含了Web全栈的知识体系,JS、Vue、React知识就靠它了!
|
1月前
|
JavaScript
Vue.js学习详细课程系列--共32节(4 / 6)
Vue.js学习详细课程系列--共32节(4 / 6)
33 0
|
1月前
|
自然语言处理 前端开发 JavaScript
深入理解JavaScript中的闭包与作用域链
在JavaScript编程中,闭包和作用域链是两个非常重要的概念,它们对于理解代码的执行过程和解决一些特定问题至关重要。本文将深入探讨JavaScript中闭包和作用域链的原理和应用,帮助读者更好地理解这些概念并能够在实际项目中灵活运用。
|
1月前
|
JavaScript 前端开发
JS作用域与作用域链
JS作用域与作用域链
|
1月前
|
自然语言处理 JavaScript 前端开发
深入探索 JS 的提升机制、函数与块作用域以及函数表达式和声明(下)
深入探索 JS 的提升机制、函数与块作用域以及函数表达式和声明(下)