Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Day14 - 词法作用域、块级作用域、作用域链、静态动态作用域 #21

Open
su37josephxia opened this issue Jan 5, 2022 · 25 comments

Comments

@su37josephxia
Copy link
Owner

su37josephxia commented Jan 5, 2022

@su37josephxia su37josephxia changed the title 闭包产生的本质? 词法作用域、块级作用域、作用域链、静态动态作用域 Jan 8, 2022
@chunhuigao
Copy link

chunhuigao commented Jan 14, 2022

作用域

是区间,区间内声明的所有内容都可以被该区间内的代码访问到,是控制变量与参数的可见性的区间;也可以理解为代码定义变量的区域

词法

定义某个事物,比如创建表达式或变量的声明

词法作用域

定义表达式并能被访问的区间,在函数声明时确定函数作用域

块级作用域

块级作用域就是包含在花括号中的作用域

作用域链

当前环境与上层环境的一系列作用域共同组成,保证当前执行环境对符合访问权限的变量和函数的有序访问;js在查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文的变量对象中查,一只查到全局对象。这个查找的路径就叫做作用于链

静态作用域

词法作用域也叫静态作用域,静态作用域在词法分析阶段就确定作用返回,不会改变。

动态作用域

函数的作用域是在函数调用的时候才决定的,被称为动态作用域

@jiafei-cat
Copy link

jiafei-cat commented Jan 14, 2022

词法作用域

词法作用域,其实也叫静态作用域,词法作用域是由我们写代码时将变量和块作用域写在哪里来决定的

function bar() {
  console.log(myName)
}
function foo() {
  var myName = "加菲"
  bar()
}
var myName = "加菲吃饭"
foo() // "加菲吃饭" -> 由词法作用域有关(静态作用域)

块级作用域

ES6之前只支持全局作用域和函数作用域, ES6后才出现块级作用域, 没有块级作用域之前,变量提升会带来许多不可察觉的风险(变量被覆盖,函数内该销毁的变量没销毁等)。

Q: Javascript如何去支持块级作用域的?
A: Javascript通过在执行上下文中的词法环境中维护一个小型栈结构来存放let const等变量, 栈底是函数最外层的变量,进入到一个作用域块后,就会把该作用域内部变量压到栈顶,当作用域执行完后,弹出该作用域信息。

下图为变量a的查找过程
image

块级作用域的暂时性死区

在块级作用域内,let声明的变量被提升,但是变量只是创建被提升,初始化并没有被提升,在初始化之前使用变量就会形成一个暂时性死区。

结论

  • var的创建和初始化被提升,赋值不会被提升。
  • let的创建被提升,初始化和赋值不会被提升。
  • function的创建、初始化和赋值均会被提升。
function test(){
    console.log(a)
        let a = 7;
}
test()

执行test的时候,编译阶段a已经在内存中,为什么提前访问不了?

这主要是因为V8虚拟机做了限制,
虽然a在内存中,但是当你在let a 之前访问a时,
根据ECMAScript定义,虚拟机会阻止的访问!

作用域链

在 JavaScript 里面,函数、块、全局都可以形成作用域,他们之间可以相互嵌套,作用域之间会形成引用关系,这条链叫做作用域链。简单来说就是函数的执行上下文相连就是作用域链

动态作用域

也就是作用域的引用关系与嵌套关系无关,与执行顺序有关,

@BambooSword
Copy link

词法作用域(lexical scope):词法作用域也被称为静态作用域((static scope)。使用词法作用域定义的函数中遇到既不是形参也不是函数内部定义的局部变量的变量时,去函数定义时的环境中查询。

动态作用域:动态作用域的函数中遇到既不是形参也不是函数内部定义的局部变量的变量时,到函数调用时的环境中查。

词法作用域是因为用的environment是定义时环境,只有唯一一个,而动态作用域用的environment是运行时环境,不确定会有多少个。

块级作用域:在 ES5 只有全局作用域和函数作用域,没有块级作用域。使用let声明的变量只能在块级作用域里访问,有“暂时性死区”的特性(也就是说声明前不可用)。块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。

作用域:作用域可以理解为一套规则, 它定义了变量和函数的可访问范围,控制着变量和函数的可见性与生命周期.
js 的执行分为编译期和执行期,而js的作用域是在编译期确定的。

@bianzheCN
Copy link

词法作用域

在变量定义的阶段决定变量的可访问性

块级作用域

变量在声明之前不能使用,并且只能在声明的大括号内使用

作用域链

每个作用域叠加在一起,会形成一条作用域链,他决定变量的访问顺序

静态作用域

JS 是静态作用域,在定义阶段就决定了变量的访问方式,因此可以静态分析

@attackam
Copy link

词法作用域,也叫静态作用域,它的作用域是指在词法分析阶段就确定了,不会改变。
动态作用域是在运行时根据程序的流程信息来动态确定的,而不是在写代码时进行静态确定的。

js 中默认使用的是词法作用域,当然也提供了使用动态作用域的某些方法如eval等。
es5之前作用域只有两个概念,函数作用域和全局作用域,es6新推出了一个块级作用域,如if、for等。
使用let定义的变量都是默认在块级作用域范围内。

作用域链可以联系我们之前讲过的执行上下文来解答,执行上下文是存在执行栈中的,嵌套函数可以访问多个执行上下文中的变量。如果当前执行上下文中找不到这个变量就往外层查找,知道访问到全局变量为止。返回最近一层的执行上下文中的变量。这样子就形成了作用域链。

@zcma11
Copy link

zcma11 commented Jan 14, 2022

首先,语言分为词法作用域和动态作用域两大类,JavaScript是词法作用域。意思就是函数的外层作用域不是函数执行的地方作为外部作用域,而是函数声明的地方作为外部作用域。就是字面意思,做个不恰当的比喻,词法作用域是如果把代码比作词的话就是代码写在哪,哪就是外部作用域。

然后块级作用域是 es6 新出的,在大括号内会被视为块级作用域,实际上是由 let 和 const 这两个新的声明变量的关键字实现的。块级作用域外的无法访问块级作用域内声明的变量。一定程度上代替了 iife 解决变量污染的问题,例如 for 循环结束了,但 变量 i 依然存在着。

静态作用域和词法作用域差不多意思,是根据函数声明的位置决定外部所在作用域。他们的反面就是动态作用域,是将函数执行的地方作为外部作用域。

讲作用域之前先要从作用域开始。在浏览器是在 window 内,所以全局作用域是window,也是最外层的作用域。JavaScript代码解析的时候所扫描的最外层。假如这时候收集声明的函数 b,这里收集到的函数 b 处在全局作用域下。然后b调用的时候会扫描内部,再生成 b 的函数作用域,收集里面的变量或函数,然后根据作用域收集的东西,创建函数上下文,同时还有一个集合,里面第一个指向的是函数 b 生成的函数作用域,然后下一个指向 b 所在的外部作用域,在这个例子里就是全局作用域。这时候作用域链就产生了。作用域链就是自己生成的作用域和词法作用域下的作用域。

作用域决定了能访问哪些变量,作用域链决定了能访问哪些外部变量,所以在作用域生成的时候,闭包也生成了。

再继续,假如 b 里面声明了一个函数 c,然后函数 c return 出去,在全局调用。这时候函数 c 执行,首先扫描函数 c 内部,生成作用域,然后由 c 的词法作用域决定它的外部作用域的 函数 b 的作用域。然后产生一个集合 第一个位置指向 c 自己的作用域,第二个位置指向 b 的作用域,然后由于 b 的作用域链里有全局作用域,所以伪代码就是 c.b.window 顺着作用域链拿到了全局作用域。

顺着作用域链也没找到变量就会报错。

这样来看 JavaScript 并不会是动态作用域,而是使用静态作用域。但是 JavaScript 中有 this,谁调用函数, this 就是谁。默认this 是全局作用域。我们可以通过改绑 this 实现访问不是在作用域链上的变量。例如全局声明的函数 a 理论上他只能拿得到全局作用域的变量。如果在函数 b 内执行 a.call({ count: 1}),就实现了 a 读取非作用域链上的变量。

再描述一次动态作用域和静态作用域,静态作用域是 函数声明的地方作为外部作用域。 动态作用域是函数执行的地方作为外部作用域。

我们可以将 b 作用域内的变量,保存在一个对象里,然后将 a 的 this 改绑成这个对象。就实现了 a 可以得到 执行 a 的地方的作用域内的变量。

当然作为参数传进 a 也是可以的。

上下文的生成一部分是依据作用域,一部分是依据上下文栈,在上下文栈中,我们可以根据函数调用顺序,得到一部分前面的上下文传递进来的参数。

上下文和作用域使得JavaScript同时实现了动态作用域和静态作用域。

// 全局作用域
function a(k) {
  // a 作用域内
  this.name
  this.age
  k
}
function b () {
  // b作用域内
  let name = 'hello'
  let age = 12
  let k = 0
  let _this = { name, age }
  
  function c() {
    
    // c作用域内
  }
  a.call(_this)
  a(k)
  return c
}

let c = b()
c()

@alec1815
Copy link

词法作用域 定义表达式并能被访问的区间,在函数声明时确定函数作用域
块级作用域 es6 let cont 声明时产生的块级作用域
词法作用域也叫静态作用域
动态作用域函数调用的时候产生的 eval

@792472461
Copy link

词法作用域

顾名思义,词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪来决定的。

块级作用域

{
  let a = 1
}
console.log(1) // undefined
  1. with:with也是块作用域的一个例子。
  2. try/catch: try/catch的catch分句也会创建一个块作用域,其中声明的变量仅在catch内部有效。
  3. let:let关键字可以将变量绑定到所在的任意作用域中,换句话说,let为其声明的变量隐式地劫持了所在的块作用域。用let将变量附加在一个已经存在的块作用域的行为是隐式的。如果没有密切关注哪些块作用域中有绑定的变量,并且习惯性移动这些块或者包含在其他块中,就会导致代码混乱。但是,用let进行的声明不会在块作用域中进行提升。声明的代码被运行之前,声明并不存在。
  4. const: ES6引入了const,同样可以用来创建块作用域变量,但是其值是固定的。之后试图修改值的操作都会引起错误。
  5. 垃圾收集

作用域链

一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。

  但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

静态作用域

简单来说就是在执行之前就确定了其可以应用哪些地方的作用域

但是通过eval和with都可以用来修改词法作用域。

动态作用域

函数的作用域是在函数调用的时候才决定的

在调用某个变量时,会从当前作用域逐级向上查找。

如果在当前作用域找到,就调用该变量,如果没找到,就继续向父级作用域查找,以此类推。

如果一直查找到最外层的全局作用域,都没有找到该变量,那么就表明没有该变量。

@yanzefeng
Copy link

词法作用域

词法作用域就是静态作用域,词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的

块级作用域

块级作用域就是包含在{...}中的作用域。在这个作用域中,用let,const定义的变量外部无法访问,和函数作用域效果差不多

作用域链

js的主要分为全局作用域和函数作用域,作用域有上下级关系,上下级关系的确定就看函数是在哪个作用域下创建的。如:fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。
一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。
但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

动态作用域

词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
让我们看一个例子来理解词法作用域和动态作用域之间的区别:

 
var value = 1;
 
function foo() {
  console.log(value);
}
 
function bar() {
  var value = 2;
  foo();
}
 
bar();
// 结果是 ???
  • 1.我们首先定义了一个value,并赋值为1;
  • 2.声明一个函数foo,函数的功能是打印 value 这个变量的值;
  • 3.声明一个函数bar,函数内部重新创建了一个变量 value 这个变量赋值为2;在函数内部执行了 foo() 这个函数;
  • 4.执行 bar() 这个函数

假设javaScript采用静态作用域,让我们分析下执行过程:

执行foo函数,首先从 foo 函数内部查找是否有变量 value ,如果没有
就根据书写的位置,查找上面一层的代码,我们发现value等于1,所以结果会打印 1。

假设javaScript采用动态作用域,让我们分析下执行过程:

执行foo函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有,
就从调用函数的作用域,也就是 bar 函数内部查找 value 变量,所以结果会打印 2。

@zzzz-bang
Copy link

词法作用域

词法作用域也叫静态作用域,就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定

动态作用域

动态作用域是在运行时根据程序的流程信息来动态确定的,是在函数调用的时候才决定的

块级作用域

在es6中使用let和const声明的变量, 只在当前大阔号内生效,,外层作用域无法获取到内层作用域,非常安全明了。即使外层和内层都使用相同变量名,也都互不干扰

作用域链

一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。
但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链

@QbjGKNick
Copy link

https://www.yuque.com/docs/share/ce74ac17-b2b7-4b06-bc34-fbb828e77cd2?# 《01-14、词法作用域、块级作用域、作用域链、静态动态作用域》

@yaoqq632319345
Copy link

词法作用域,也叫静态作用域,在函数调用查找变量时,会根据此函数在定义时的环境来查找,js就是使用的此种作用域,所以也就有了闭包的产生,与之相反的就是动态作用域,就是函数调用查找变量时会根据调用时所在的环境来查找
块级作用域在es6之前是没有块级作用域的,es6才有了块级作用域,由一对花括号包括的范围形成,
当作用域形成嵌套时,变量的查找会由小到大,由内到外去嵌套的作用域中查找,这种嵌套的作用域称为作用域链

@qytayh
Copy link

qytayh commented Jan 14, 2022

词法作用域也成为静态作用域,定义在词法阶段的作用域,在我们编写代码时由我们的变量和块级作用域写在哪里决定的。

块级作用域将代码从在函数内隐藏信息拓展为在块内隐藏信息。这对保证变量不会被混乱的复用,以及提升代码的可维护性有很大的益处。

作用域链:在多级嵌套的作用域中,可以定义同名标识符,作用域的查找从运行时所在的最内层向外查找,找到第一个匹配的标识符为止。

动态作用域是在运行时根据程序的流程信息来动态确定的,是在函数调用的时候才决定的

@Limeijuan
Copy link

JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。
词法作用域:函数的作用域在函数定义的时候就决定了。
动态作用域:函数的作用域是在函数调用的时候才决定的。

什么是块级作用域?
简单的说,{} 这个花括号里面的作用域就是块级作用域。
这是es6新增的一个概念;
es6中提供了let 与 count 关键字,它们声明的范围就是块级作用域

作用域链,它保证对执行环境有权访问的所有变量和函数的有序访问;当js在读取一个变量时,它会沿作用域链逐级搜索这个名称,从作用域链的最前端开始,逐级往后查找

@wzl624
Copy link

wzl624 commented Jan 14, 2022

作用域

可以访问到指定区域内所有代码的区域。

词法

用来定义变量、表达式、函数等内容的语法。

词法作用域

定义变量、表达式、函数等能被访问的区间。

块级作用域

块级作用域就是包含在花括号中的作用域。

作用域链

多个作用域存在嵌套关系时形成的关系网成为一条作用域链,当一个作用域中需要访问的内容在此作用域中无法找到,变回向上级作用域查找,不断向上查找知道找到或查到最外层全局作用域也未找到为止。

静态作用域

词法作用域也叫静态作用域,静态作用域在词法分析阶段就确定作用域范围,不会改变。

动态作用域

由函数在调用时才创建的函数作用域。

@superjunjin
Copy link

  • 动态作用域

    动态作用域就是在函数调用的时候才决定的作用域,例如使用call,apply方法指定函数调用时的作用域

  • 作用域链

    函数可以被多层嵌套。例如,函数A可以包含函数B,函数B可以再包含函数C。B和C都形成了闭包,所以B可以访问A,C可以访问B和A。因此,闭包可以包含多个作用域;他们递归式的包含了所有包含它的函数作用域。这个称之为作用域链

    当同一个闭包作用域下两个参数或者变量同名时,就会产生命名冲突。更近的作用域有更高的优先权,所以最近的优先级最高,最远的优先级最低。这就是作用域链。链的第一个元素就是最里面的作用域,最后一个元素便是最外层的作用域。

  • 词法作用域

    词法作用域也叫静态作用域,就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定

    词法(lexical)一词指的是,词法作用域根据源代码中声明变量的位置来确定该变量在何处可用。

  • 块级作用域

    通过var声明的变量或者非严格模式下(non-strict mode)创建的函数声明没有块级作用域。

    相比之下,使用 letconst声明的变量是块级作用域的。

    块级作用域是独立的作用域空间,不同的块级作用域声明相同变量互不干扰

@aiuluna
Copy link

aiuluna commented Jan 14, 2022

  • 词法作用域又称静态作用域,js的函数作用域在词法分析时已经确定。
  • 块级作用域,定义在{}内的作用域。在es6之后才出现块级作用域,let和const的变量不可提升,且只能在当前和下级块级作用域中使用。
  • 作用域链[[scope]]
    • 作用域链是指由当前上下文和上层上下文的一系列变量对象组成的层级链
    • 函数声明时会创建上层作用域链,当创建新的执行上下文,即函数入栈时,会将当前执行上下文中的变量对象放入作用域链的最顶端
  • 静态作用域:即词法作用域,js使用的是词法作用域
  • 动态作用域:函数的作用域由调用时决定

@JanusJiang1
Copy link

JanusJiang1 commented Jan 14, 2022

作用域

作用域是程序源代码中定义变量访问权限的区域。 作用域规定了如何查找、在哪查找变量的规则,也就是确定了当前执行代码对变量的访问权限。
作用域共有两种工作模型,词法作用域(js和别的大多数语言都是),动态作用域(Bash脚本)。js用的就是词法作用域!

词法作用域

js编译的第一个阶段就是词法阶段
JavaScript 是基于词法作用域的语言,通过阅读包含变量定义在内的源码就能知道变量的作用域,也就是说在写出来的时候已经确定了变量的作用域。
作用域嵌套是气泡结构,查找变量时会先从内层作用域查找,引擎无法在当前作用域找到变量,会继续在上一级嵌套的作用域找,并会在找到第一个标识符时候停止。这是遮蔽效应,即不同层级作用域有同名变量会以内层为准。
另外可以通过,evel函数,with方法来欺骗词法

块级作用域

ES6 中规定了 let 和 const 来支持块级作用域。
在js之中原本只有函数作用域没有块级作用域,这就导致在非函数的代码块{ }内声明的变量,实际是全都暴露在外部的函数作用域或全局作用域之内的。如下

for(var i=0;i<10;i++){
console.log(i); //0...9
}
cosole.log(i) //10

i是声明在for循环头部的,通常我们这么写,是只需要在for循环内部的上下文中使用 i 变量,而忽略了 i 实际是绑定到外部作用域(函数或者全局作用域)中的事实。并且在相同作用域的任何位置都能访问并修改它。
但改成如下,let声明 i 变量则只能在for{}中使用变量 i 。

for(let i=0;i<10;i++){
console.log(i); //0...9
}
cosole.log(i) // x is not defined

这就是块级作用域的用处。变量应该距离使用的地方越近越好,并最大限度的本地化。

函数作用域

变量在声明他们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。函数作用域是可以嵌套的,且子函数能访问父函数作用域。

作用域链

函数在多层嵌套的时候,作用域也会发生嵌套,按照词法作用域查找的顺序,引擎在当前作用域没有查找到需要变量时,就会往外层嵌套作用域继续查找,直到找到该变量或者达到最外层作用域(全局作用域为止)。这就形成了作用域链。

动态作用域

js中是使用的静态作用域,但动态作用域其实与js中另一套系统this,是表亲。它有以下特点

  • 词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定的。

  • 动态作用域不关心函数和变量是在何处声明的,只关心它们是从何处调用的;

  • 动态作用域是基于调用栈 的,而不是代码中的作用域嵌套。

@guoshukun1994
Copy link

guoshukun1994 commented Jan 14, 2022

词法作用域
作用域是指程序源代码中定义变量的区域。作用域规定了如何查找变量,确定当前代码对变量的访问权限,在Javascript中叫做词法作用域,确立了标识符和变量的映射关系;

静态动态作用域
函数定义时它的词法作用域就确定了,就是所谓的函数作用域,也叫静态作用域,在函数调用时还会创建一个动态作用域,也就是函数局部变量构成的动态作用域,在非闭包情况下动态作用域只存在于调用期间;

块级作用域
块级作用域是指变量在声明的代码段之外是不可见的,比如Es6的 let,const,Es6之前时没有块级作用域的,用var会造成变量提升为全局变量,污染变量环境,而let和const定义的变量只存在与其最近的花括号定义的块级作用域内,成为一个独立作用域;

作用域链
作用域链是指层层嵌套的作用域之间的关系链,内层作用域在查找变量时,会先从自身作用域查找,如果没有就从上层作用域找,直至找到最外层。

@rachern
Copy link

rachern commented Jan 14, 2022

静态作用域

是在编译时期就确定的变量的可访问区域,以及作用域之间的嵌套关系

动态作用域

是在执行时确定的变量可访问区域,典型代表是this,上下文环境

词法作用域

词法作用域属于静态作用域,在编译时期会对程序进行词法分析,此时就会产生词法作用域

块作用域

块作用域是 ES6 中为了与其他语言一致而产生的一种作用域,产生的条件是使用 let 或 const 定义变量

作用域链

在不同层级的作用域中,内部作用域可以访问包含其本身在内的外部作用域的所有变量,而外部作用无法访问内部作用域的变量,内部作用域访问变量的顺序是由内到外,查到即止,不同层级的作用域之间就形成了作用域链

@partiallove
Copy link

词法作用域又称静态作用域,js的函数作用域在词法分析时已经确定。
静态作用域:即词法作用域,js使用的是词法作用域
动态作用域:函数的作用域由调用时决定
块级作用域
定义在{}内的作用域。在es6之后才出现块级作用域,let和const的变量不可提升,且只能在当前和下级块级作用域中使用。
作用域链
作用域链是指由当前上下文和上层上下文的一系列变量对象组成的层级链
函数声明时会创建上层作用域链,当创建新的执行上下文,即函数入栈时,会将当前执行上下文中的变量对象放入作用域链的最顶端

@alienRidingCat
Copy link

alienRidingCat commented Jan 14, 2022

块级作用域(变量只在作用域内部有效)

● es6中 let、const可以将变量绑定到所在的任意作用域中,形成一个块级作用域
● try/catch 中的catch分句也会创建一个块作用域
● with 语句的作用是将代码的作用域设置到一个特定的对象中,这也是块级作用域的一种体现

词法作用域(静态作用域)

函数的作用域在函数定义的时候就决定了,词法作用域只由函数被声明时所处的位置决定

动态作用域

在 JavaScript 中每声明一个函数就会创建一个函数作用域,对于函数来说就是函数执行的位置决定这个函数所属的范围

作用域链

作用域链实际就是变量对象的数组,第一个是当前函数的活动对象,第二个是当前活动函数的父亲上下文的活动对象,以此类推,最后一个是全局对象

定义:

● 每个函数创建的时候,都会预先为该函数添加一个[[Scopes]]属性,该属性是父级(可以理解为包含该函数的函数执行空间)执行环境的作用域链
● 每个(函数)执行环境都会取该函数的[[Scopes]]属性,并copy一份作为当前的作用域链 Scope
● 并把该函数执行环境的变量对象,推入到当前作用域的最前端

执行中需要读取的变量都会从该作用域链的前端查找(图中的Local对象),查找过程中如果没有找到需要的变量,就会一直找到作用链的最顶端window(图中的Global对象),这就是作用域链的查找

@crazyyoung1020
Copy link

作用域

指程序中定义变量的区域,它决定了我们查找变量的范围,也就是代码执行的时候对变量的访问权限。

作用域链

我们在遇到变量的时候,会先去当前的作用域内寻找,如果没有,那么会去上级作用域内寻找,如果还没有,会去再上一级寻找,那么这一级一级的作用域就形成了作用域链。

词法作用域

也就是静态作用域,是指作用域是在函数定义的时候决定的

动态作用域

是指函数的作用域是在调用的时候才确定的

块级作用域

是指用一对大括号包裹的一段代码的作用域,比如函数、判断语句、循环语句、甚至一对空的{}

要想使用块级作用域,需要使用let和const申明变量。

js引入块级作用域,是为了解决变量提升

那么什么是变量提升?

在执行上下文创建阶段,会先扫描变量,执行阶段,如果该变量是外部作用域内声明的,会去给它赋值,如果是当前作用域内申明,则不会,继续保持undefined,然后当我们执行代码的时候遇到变量且该变量是在后面申明并赋值的,那么这里该变量就会是undefined,而不会报错,这就是变量提升。

在执行上下文内,扫描变量只会扫描var申明的变量,不会扫描let和const申明的,

扫描到的var申明的变量会放到变量环境,let和const会放到词法环境,词法环境内的变量只会在块级作用域内使用,不会影响到会计外部,这样下来,使用let和const由于不会被扫描到,就不会有变量提升的问题。

@rhythm022
Copy link

词法作用域、静态动态作用域:
作用域,其实它是一种规则,就是当你一个函数里面要去访问非本地声明的变量的时候,我该怎么去找这个变量。

如果你是到定义这个函数的环境里面去找这个变量,就叫词法作用域。
如果你是到调用这个函数的环境里面去找这个变量,就叫动态作用域。

为什么叫他动态作用域呢?因为调用这个函数,每次调用的位置不一样的话,就会导致你找到的变量,每次都可能是不一样的。因为这种不确定性/动态性,所以叫他动态作用域。其实this上下文机制,算是一种动态作用域。

而词法作用域,它是一种静态作用域。为什么它是静态的呢?因为你如果是根据词法去找这个外部变量的话,每次找到的变量肯定是同一个变量。

作用域链:
作用域练其实也是一种规则,其实无论你是函数套函数的去调用,还是函数套函数的定义,最里面的那个函数,如果里面它要去访问非本地声明的变量的时候,它是由近及远的去找这个变量,这就是作用域链的规则。

块级作用域:
块级作用域是ES6之后新增的作用域,其实块一直都有,像if-else的花括号,或者说for循环的花括号,但是呢,在ES6之前我们在这些块里面声明的变量,其实是声明到了这个块所在的函数的作用域里面。但是现在有了块级作用域之后呢,我们才能真正的把花括号里面声明的变量放在块级作用域里面。

有了块级作用域,其实就相当于JS里面有了生命周期更短的作用域。

@zhenyuWang
Copy link

作用域

程序中存储变量以及访问变量的规则就是作用域。

也就是说作用域负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并有一套规则,确定当前执行的代码对这些标识符是否有访问权限。

词法作用域

词法作用域简单来说就是定义在词法阶段的作用域。
那什么是词法阶段呢?
它就是 JavaScript 引擎进行编译的一个步骤,在这个阶段,编译器会把字符串分解成一个个词法单元。
词法单元就是对于编程语言来说有意义的代码块。例如:

var name = "snail"

就会被分解为 varnamesnail

函数作用域

属于这个函数的全部变量都可以在整个函数的范围内使用及复用,如果有嵌套的子函数,也可以在子函数中使用。这样属于函数的变量存储及访问的规则就是函数作用域。

我们把每一个函数的作用域想象成一个气泡,则内部的子函数的作用域对应的气泡就是当前气泡内的一个小气泡,同时当前函数作用域的气泡也被包含在一个更大的气泡内。

块级作用域

块级作用域,顾名思义就是属于一个代码块的作用域,但是很遗憾,ES6 之前,JavaScript 中其实并没有块级作用域。

console.log('1',i) // undefined
for(var i = 0;i<3;i++){}
console.log('2',i) // 3

这里我们在 for 循环中定义变量 i,其实是只想在 for 循环内部的上下文中使用 i,但是在 JavaScript 中,var 声明的变量此时会被绑定到外部作用域中。
基于这样的一个问题,ES6 中引入了 letconst 声明变量的方式,通过letconst 声明的变量只在当前代码块内有效。
所以它们实际上为 JavaScript 新增了块级作用域。

console.log('1',i) // ReferenceError: i is not defined
for(let i = 0;i<3;i++){}

动态作用域

实际上 JavaScript 并不具有动态作用域,它只有词法作用域。
但是 this 机制很像动态作用域。
词法作用域和动态作用域的区别是:
词法作用域是在写代码或者说变量定义时确定的;
动态作用域是在运行时确定的;

词法作用域关注函数在何处声明,而动态作用域关注函数在何处调用。所以 this 的机制很像动态作用域。

作用域链

当一个代码块或者函数嵌套在另一个代码块或者函数中时,就发生了作用域的嵌套。因此,当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止。
我们把这之中的每一个作用域看作一环,作用域之间层层嵌套就形成了一个链,这就是作用域链。

@su37josephxia su37josephxia changed the title 词法作用域、块级作用域、作用域链、静态动态作用域 Day14 - 词法作用域、块级作用域、作用域链、静态动态作用域 Jan 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests