-
Notifications
You must be signed in to change notification settings - Fork 45
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
Comments
作用域是区间,区间内声明的所有内容都可以被该区间内的代码访问到,是控制变量与参数的可见性的区间;也可以理解为代码定义变量的区域 词法定义某个事物,比如创建表达式或变量的声明 词法作用域定义表达式并能被访问的区间,在函数声明时确定函数作用域 块级作用域块级作用域就是包含在花括号中的作用域 作用域链当前环境与上层环境的一系列作用域共同组成,保证当前执行环境对符合访问权限的变量和函数的有序访问;js在查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文的变量对象中查,一只查到全局对象。这个查找的路径就叫做作用于链 静态作用域词法作用域也叫静态作用域,静态作用域在词法分析阶段就确定作用返回,不会改变。 动态作用域函数的作用域是在函数调用的时候才决定的,被称为动态作用域 |
词法作用域词法作用域,其实也叫静态作用域,词法作用域是由我们写代码时将变量和块作用域写在哪里来决定的 function bar() {
console.log(myName)
}
function foo() {
var myName = "加菲"
bar()
}
var myName = "加菲吃饭"
foo() // "加菲吃饭" -> 由词法作用域有关(静态作用域) 块级作用域
Q: Javascript如何去支持块级作用域的? 块级作用域的暂时性死区在块级作用域内,let声明的变量被提升,但是变量只是创建被提升,初始化并没有被提升,在初始化之前使用变量就会形成一个暂时性死区。 结论
function test(){
console.log(a)
let a = 7;
}
test()
执行test的时候,编译阶段a已经在内存中,为什么提前访问不了?
这主要是因为V8虚拟机做了限制,
虽然a在内存中,但是当你在let a 之前访问a时,
根据ECMAScript定义,虚拟机会阻止的访问! 作用域链
动态作用域
|
词法作用域(lexical scope):词法作用域也被称为静态作用域((static scope)。使用词法作用域定义的函数中遇到既不是形参也不是函数内部定义的局部变量的变量时,去函数定义时的环境中查询。 动态作用域:动态作用域的函数中遇到既不是形参也不是函数内部定义的局部变量的变量时,到函数调用时的环境中查。 词法作用域是因为用的environment是定义时环境,只有唯一一个,而动态作用域用的environment是运行时环境,不确定会有多少个。 块级作用域:在 ES5 只有全局作用域和函数作用域,没有块级作用域。使用let声明的变量只能在块级作用域里访问,有“暂时性死区”的特性(也就是说声明前不可用)。块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。 作用域:作用域可以理解为一套规则, 它定义了变量和函数的可访问范围,控制着变量和函数的可见性与生命周期. |
词法作用域在变量定义的阶段决定变量的可访问性 块级作用域变量在声明之前不能使用,并且只能在声明的大括号内使用 作用域链每个作用域叠加在一起,会形成一条作用域链,他决定变量的访问顺序 静态作用域JS 是静态作用域,在定义阶段就决定了变量的访问方式,因此可以静态分析 |
词法作用域,也叫静态作用域,它的作用域是指在词法分析阶段就确定了,不会改变。 js 中默认使用的是词法作用域,当然也提供了使用动态作用域的某些方法如eval等。 作用域链可以联系我们之前讲过的执行上下文来解答,执行上下文是存在执行栈中的,嵌套函数可以访问多个执行上下文中的变量。如果当前执行上下文中找不到这个变量就往外层查找,知道访问到全局变量为止。返回最近一层的执行上下文中的变量。这样子就形成了作用域链。 |
首先,语言分为词法作用域和动态作用域两大类,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同时实现了动态作用域和静态作用域。
|
词法作用域 定义表达式并能被访问的区间,在函数声明时确定函数作用域 |
词法作用域顾名思义,词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪来决定的。 块级作用域{
let a = 1
}
console.log(1) // undefined
作用域链一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。 但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。 静态作用域简单来说就是在执行之前就确定了其可以应用哪些地方的作用域 但是通过eval和with都可以用来修改词法作用域。 动态作用域函数的作用域是在函数调用的时候才决定的 在调用某个变量时,会从当前作用域逐级向上查找。 如果在当前作用域找到,就调用该变量,如果没找到,就继续向父级作用域查找,以此类推。 如果一直查找到最外层的全局作用域,都没有找到该变量,那么就表明没有该变量。 |
词法作用域词法作用域就是静态作用域,词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的 块级作用域块级作用域就是包含在{...}中的作用域。在这个作用域中,用let,const定义的变量外部无法访问,和函数作用域效果差不多 作用域链js的主要分为全局作用域和函数作用域,作用域有上下级关系,上下级关系的确定就看函数是在哪个作用域下创建的。如:fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。 动态作用域词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
// 结果是 ???
假设javaScript采用静态作用域,让我们分析下执行过程: 执行foo函数,首先从 foo 函数内部查找是否有变量 value ,如果没有 假设javaScript采用动态作用域,让我们分析下执行过程: 执行foo函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有, |
词法作用域词法作用域也叫静态作用域,就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定 动态作用域动态作用域是在运行时根据程序的流程信息来动态确定的,是在函数调用的时候才决定的 块级作用域在es6中使用let和const声明的变量, 只在当前大阔号内生效,,外层作用域无法获取到内层作用域,非常安全明了。即使外层和内层都使用相同变量名,也都互不干扰 作用域链一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。 |
https://www.yuque.com/docs/share/ce74ac17-b2b7-4b06-bc34-fbb828e77cd2?# 《01-14、词法作用域、块级作用域、作用域链、静态动态作用域》 |
词法作用域,也叫静态作用域,在函数调用查找变量时,会根据此函数在定义时的环境来查找,js就是使用的此种作用域,所以也就有了闭包的产生,与之相反的就是动态作用域,就是函数调用查找变量时会根据调用时所在的环境来查找 |
词法作用域也成为静态作用域,定义在词法阶段的作用域,在我们编写代码时由我们的变量和块级作用域写在哪里决定的。 块级作用域将代码从在函数内隐藏信息拓展为在块内隐藏信息。这对保证变量不会被混乱的复用,以及提升代码的可维护性有很大的益处。 作用域链:在多级嵌套的作用域中,可以定义同名标识符,作用域的查找从运行时所在的最内层向外查找,找到第一个匹配的标识符为止。 动态作用域是在运行时根据程序的流程信息来动态确定的,是在函数调用的时候才决定的 |
JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。 什么是块级作用域? 作用域链,它保证对执行环境有权访问的所有变量和函数的有序访问;当js在读取一个变量时,它会沿作用域链逐级搜索这个名称,从作用域链的最前端开始,逐级往后查找 |
作用域可以访问到指定区域内所有代码的区域。 词法用来定义变量、表达式、函数等内容的语法。 词法作用域定义变量、表达式、函数等能被访问的区间。 块级作用域块级作用域就是包含在花括号中的作用域。 作用域链多个作用域存在嵌套关系时形成的关系网成为一条作用域链,当一个作用域中需要访问的内容在此作用域中无法找到,变回向上级作用域查找,不断向上查找知道找到或查到最外层全局作用域也未找到为止。 静态作用域词法作用域也叫静态作用域,静态作用域在词法分析阶段就确定作用域范围,不会改变。 动态作用域由函数在调用时才创建的函数作用域。 |
|
|
作用域作用域是程序源代码中定义变量访问权限的区域。 作用域规定了如何查找、在哪查找变量的规则,也就是确定了当前执行代码对变量的访问权限。 词法作用域js编译的第一个阶段就是词法阶段 块级作用域ES6 中规定了 let 和 const 来支持块级作用域。
i是声明在for循环头部的,通常我们这么写,是只需要在for循环内部的上下文中使用 i 变量,而忽略了 i 实际是绑定到外部作用域(函数或者全局作用域)中的事实。并且在相同作用域的任何位置都能访问并修改它。
这就是块级作用域的用处。变量应该距离使用的地方越近越好,并最大限度的本地化。 函数作用域变量在声明他们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。函数作用域是可以嵌套的,且子函数能访问父函数作用域。 作用域链函数在多层嵌套的时候,作用域也会发生嵌套,按照词法作用域查找的顺序,引擎在当前作用域没有查找到需要变量时,就会往外层嵌套作用域继续查找,直到找到该变量或者达到最外层作用域(全局作用域为止)。这就形成了作用域链。 动态作用域js中是使用的静态作用域,但动态作用域其实与js中另一套系统this,是表亲。它有以下特点
|
词法作用域 静态动态作用域 块级作用域 作用域链 |
静态作用域是在编译时期就确定的变量的可访问区域,以及作用域之间的嵌套关系 动态作用域是在执行时确定的变量可访问区域,典型代表是this,上下文环境 词法作用域词法作用域属于静态作用域,在编译时期会对程序进行词法分析,此时就会产生词法作用域 块作用域块作用域是 ES6 中为了与其他语言一致而产生的一种作用域,产生的条件是使用 let 或 const 定义变量 作用域链在不同层级的作用域中,内部作用域可以访问包含其本身在内的外部作用域的所有变量,而外部作用无法访问内部作用域的变量,内部作用域访问变量的顺序是由内到外,查到即止,不同层级的作用域之间就形成了作用域链 |
词法作用域又称静态作用域,js的函数作用域在词法分析时已经确定。 |
块级作用域(变量只在作用域内部有效)● es6中 let、const可以将变量绑定到所在的任意作用域中,形成一个块级作用域 词法作用域(静态作用域)函数的作用域在函数定义的时候就决定了,词法作用域只由函数被声明时所处的位置决定 动态作用域在 JavaScript 中每声明一个函数就会创建一个函数作用域,对于函数来说就是函数执行的位置决定这个函数所属的范围 作用域链作用域链实际就是变量对象的数组,第一个是当前函数的活动对象,第二个是当前活动函数的父亲上下文的活动对象,以此类推,最后一个是全局对象 定义:● 每个函数创建的时候,都会预先为该函数添加一个[[Scopes]]属性,该属性是父级(可以理解为包含该函数的函数执行空间)执行环境的作用域链 执行中需要读取的变量都会从该作用域链的前端查找(图中的Local对象),查找过程中如果没有找到需要的变量,就会一直找到作用链的最顶端window(图中的Global对象),这就是作用域链的查找 |
作用域指程序中定义变量的区域,它决定了我们查找变量的范围,也就是代码执行的时候对变量的访问权限。 作用域链我们在遇到变量的时候,会先去当前的作用域内寻找,如果没有,那么会去上级作用域内寻找,如果还没有,会去再上一级寻找,那么这一级一级的作用域就形成了作用域链。 词法作用域也就是静态作用域,是指作用域是在函数定义的时候决定的 动态作用域是指函数的作用域是在调用的时候才确定的 块级作用域是指用一对大括号包裹的一段代码的作用域,比如函数、判断语句、循环语句、甚至一对空的{} 要想使用块级作用域,需要使用let和const申明变量。 js引入块级作用域,是为了解决变量提升 那么什么是变量提升? 在执行上下文创建阶段,会先扫描变量,执行阶段,如果该变量是外部作用域内声明的,会去给它赋值,如果是当前作用域内申明,则不会,继续保持undefined,然后当我们执行代码的时候遇到变量且该变量是在后面申明并赋值的,那么这里该变量就会是undefined,而不会报错,这就是变量提升。 在执行上下文内,扫描变量只会扫描var申明的变量,不会扫描let和const申明的, 扫描到的var申明的变量会放到变量环境,let和const会放到词法环境,词法环境内的变量只会在块级作用域内使用,不会影响到会计外部,这样下来,使用let和const由于不会被扫描到,就不会有变量提升的问题。 |
词法作用域、静态动态作用域: 如果你是到定义这个函数的环境里面去找这个变量,就叫词法作用域。 为什么叫他动态作用域呢?因为调用这个函数,每次调用的位置不一样的话,就会导致你找到的变量,每次都可能是不一样的。因为这种不确定性/动态性,所以叫他动态作用域。其实this上下文机制,算是一种动态作用域。 而词法作用域,它是一种静态作用域。为什么它是静态的呢?因为你如果是根据词法去找这个外部变量的话,每次找到的变量肯定是同一个变量。 作用域链: 块级作用域: 有了块级作用域,其实就相当于JS里面有了生命周期更短的作用域。 |
作用域程序中存储变量以及访问变量的规则就是作用域。 也就是说作用域负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并有一套规则,确定当前执行的代码对这些标识符是否有访问权限。 词法作用域词法作用域简单来说就是定义在词法阶段的作用域。
就会被分解为 函数作用域属于这个函数的全部变量都可以在整个函数的范围内使用及复用,如果有嵌套的子函数,也可以在子函数中使用。这样属于函数的变量存储及访问的规则就是函数作用域。 我们把每一个函数的作用域想象成一个气泡,则内部的子函数的作用域对应的气泡就是当前气泡内的一个小气泡,同时当前函数作用域的气泡也被包含在一个更大的气泡内。 块级作用域块级作用域,顾名思义就是属于一个代码块的作用域,但是很遗憾,
这里我们在
动态作用域实际上 词法作用域关注函数在何处声明,而动态作用域关注函数在何处调用。所以 作用域链当一个代码块或者函数嵌套在另一个代码块或者函数中时,就发生了作用域的嵌套。因此,当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止。 |
The text was updated successfully, but these errors were encountered: