javascript中变量的范围是什么?它们在函数内部的作用域与在函数外部的作用域相同吗?或者这有关系吗?此外,如果变量是全局定义的,那么变量存储在哪里?
当前回答
为了补充其他答案,范围是所有声明的标识符(变量)的查找列表,并强制执行一组严格的规则,以确定当前执行的代码如何访问这些标识符。该查找可能用于分配变量,该变量是LHS(左侧)引用,也可能用于检索其值,该值是RHS(右侧)引用。这些查找是JavaScript引擎在编译和执行代码时在内部执行的操作。
因此,从这个角度来看,我认为我在凯尔·辛普森(Kyle Simpson)的《范围与关闭》(Scopes and Closures)电子书中找到的一张图片会有所帮助:
引用他的电子书:
该构建表示程序的嵌套范围规则集。第一个建筑物的楼层代表您当前执行的范围,无论你在哪里。建筑的顶层是全球范围。通过查看当前楼层来解析LHS和RHS参考,如果你找不到,就乘电梯到下一层,看那里,然后看下一个,以此类推。一旦你到达顶层(全球范围),你要么找到你想要的,要么不要。但你必须停止不管。
值得一提的是,“范围查找一旦找到第一个匹配项就停止”。
“作用域级别”的概念解释了如果在嵌套函数中查找“This”,为什么可以使用新创建的作用域来更改“This”。这里有一个链接,介绍了所有这些细节,您想了解的关于javascript范围的一切
其他回答
内联处理程序
前端代码编写者经常遇到的一个尚未描述的非常常见的问题是HTML中内联事件处理程序可见的范围,例如
<button onclick="foo()"></button>
on*属性可以引用的变量范围必须是:
全局(工作内联处理程序几乎总是引用全局变量)文档的属性(例如,作为独立变量的querySelector将指向document.querySelector;罕见)处理程序附加到的元素的属性(如上所述;罕见)
否则,当调用处理程序时,将得到ReferenceError。因此,例如,如果内联处理程序引用了在window.onload或$(function){中定义的函数,则引用将失败,因为内联处理程序只能引用全局范围内的变量,而函数不是全局的:
window.addEventListener('DOMContentLoaded',()=>{函数foo(){console.log('运行');}});<button onclick=“foo()”>单击</button>
文档的财产和处理程序附加到的元素的财产也可以作为内联处理程序中的独立变量引用,因为内联处理函数是在两个块中调用的,一个用于文档,另一个用于元素。这些处理程序中变量的作用域链非常不直观,并且一个工作的事件处理程序可能需要一个函数是全局的(并且应该避免不必要的全局污染)。
由于内联处理程序内部的作用域链非常奇怪,而且内联处理程序需要全局污染才能工作,并且内联处理程序在传递参数时有时需要丑陋的字符串转义,因此可能更容易避免它们。相反,使用Javascript(如addEventListener)而不是HTML标记附加事件处理程序。
函数foo(){console.log('运行');}document.querySelector('.my button').addEventListener('click',foo);<button class=“my button”>单击</button>
模块(<script type=“module”>)
另一方面,与在顶层运行的普通<script>标记不同,ES6模块内的代码在其自己的私有范围内运行。在普通<script>标记顶部定义的变量是全局的,因此您可以在其他<script>标签中引用它,如下所示:
<脚本>const foo=“foo”;</script><脚本>console.log(foo);</script>
但是ES6模块的顶层不是全局的。在ES6模块顶部声明的变量仅在该模块内部可见,除非该变量被显式导出,或者除非它被分配给全局对象的属性。
<script type=“module”>const foo=“foo”;</script><脚本>//无法在此处访问foo,因为其他脚本是模块console.log(foo类型);</script>
ES6模块的顶层与普通<script>中顶层IIFE内部的顶层类似。模块可以引用任何全局变量,除非模块为其明确设计,否则任何内容都不能引用模块内部的任何内容。
几乎只有两种类型的JavaScript作用域:
每个var声明的范围都与最直接的封闭函数相关联如果var声明没有封闭函数,则为全局范围
因此,除函数之外的任何块都不会创建新的作用域。这解释了for循环覆盖外部范围变量的原因:
var i = 10, v = 10;
for (var i = 0; i < 5; i++) { var v = 5; }
console.log(i, v);
// output 5 5
改用函数:
var i = 10, v = 10;
$.each([0, 1, 2, 3, 4], function(i) { var v = 5; });
console.log(i,v);
// output 10 10
在第一个示例中,没有块范围,因此最初声明的变量被覆盖。在第二个示例中,由于函数的原因,出现了一个新的作用域,因此最初声明的变量是SHADOWED,而不是重写。
就JavaScript范围而言,这几乎是您需要了解的全部内容,除了:
try/catch仅为异常变量本身引入新范围,其他变量没有新范围with子句显然是另一个例外,但不鼓励使用with子句(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with)
因此,您可以看到JavaScript作用域实际上非常简单,尽管并不总是直观的。需要注意的几点:
var声明被提到了范围的顶端。这意味着无论var声明发生在哪里,对于编译器来说,就像var本身发生在顶部一样同一范围内的多个var声明组合在一起
因此,此代码:
var i = 1;
function abc() {
i = 2;
var i = 3;
}
console.log(i); // outputs 1
相当于:
var i = 1;
function abc() {
var i; // var declaration moved to the top of the scope
i = 2;
i = 3; // the assignment stays where it is
}
console.log(i);
这似乎与直觉相悖,但从命令式语言设计者的角度来看,这是有道理的。
为了补充其他答案,范围是所有声明的标识符(变量)的查找列表,并强制执行一组严格的规则,以确定当前执行的代码如何访问这些标识符。该查找可能用于分配变量,该变量是LHS(左侧)引用,也可能用于检索其值,该值是RHS(右侧)引用。这些查找是JavaScript引擎在编译和执行代码时在内部执行的操作。
因此,从这个角度来看,我认为我在凯尔·辛普森(Kyle Simpson)的《范围与关闭》(Scopes and Closures)电子书中找到的一张图片会有所帮助:
引用他的电子书:
该构建表示程序的嵌套范围规则集。第一个建筑物的楼层代表您当前执行的范围,无论你在哪里。建筑的顶层是全球范围。通过查看当前楼层来解析LHS和RHS参考,如果你找不到,就乘电梯到下一层,看那里,然后看下一个,以此类推。一旦你到达顶层(全球范围),你要么找到你想要的,要么不要。但你必须停止不管。
值得一提的是,“范围查找一旦找到第一个匹配项就停止”。
“作用域级别”的概念解释了如果在嵌套函数中查找“This”,为什么可以使用新创建的作用域来更改“This”。这里有一个链接,介绍了所有这些细节,您想了解的关于javascript范围的一切
我的理解是有三个范围:全球范围,全球可用;局部范围,无论块如何,整个功能都可用;和块范围,仅对使用它的块、语句或表达式可用。全局和局部作用域用关键字“var”表示,无论是在函数内还是在函数外,块作用域都用关键字“let”表示。
对于那些认为只有全局和局部范围的人,请解释为什么Mozilla会有一个完整的页面来描述JS中块范围的细微差别。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
JavaScript只有两种类型的作用域:
全局作用域:全局只是一个窗口级作用域。在这里,整个应用程序中都存在变量。Functional Scope:在带有var关键字的函数中声明的变量具有函数作用域。
每当调用一个函数时,就会创建一个变量范围对象(并包含在范围链中),然后是JavaScript中的变量。
a = "global";
function outer(){
b = "local";
console.log(a+b); //"globallocal"
}
outer();
作用域链-->
窗口级别-a和外部函数位于范围链的顶层。当外部函数调用一个新的变量作用域对象(并包含在作用域链中)时,在其内部添加了变量b。
现在,当一个变量a需要时,它首先搜索最近的变量范围,如果变量不在,则将其移动到变量范围链的下一个对象。在这种情况下,这是窗口级别。
推荐文章
- Babel 6改变了它导出默认值的方式
- 如何配置历史记录?
- ES6模板文字可以在运行时被替换(或重用)吗?
- [Vue警告]:找不到元素
- 可以在setInterval()内部调用clearInterval()吗?
- AngularJS控制器的生命周期是什么?
- 无法读取未定义的属性“msie”- jQuery工具
- 我的蛋蛋怎么不见了?
- JavaScript中的排列?
- JavaScript中有睡眠/暂停/等待功能吗?
- 如何禁用文本选择使用jQuery?
- 如何停止事件冒泡复选框点击
- 如何在PHP中截断字符串最接近于一定数量的字符?
- 向对象数组添加属性
- 如何在Redux应用程序中动态加载代码分割的减速器?