当前位置: 首页 > news >正文

JavaScript变量作用域(Variable Scope)和闭包(closure)的基础知识

  在这篇文章中,我会试图讲解JavaScript变量的作用域和声明提升,以及许多隐隐藏的陷阱。为了确保我们不会碰到不可预见的问题,我们必须真正理解这些概念。

  基本定义

  作用范围是个“木桶”,里面装着变量。变量可以是局部或者全局性的,但在子范围中定义的变量是可以访问父范围的,这一点可能会造成一些困扰。

  在JavaScript中使用"var"关键字声明变量。一旦在父范围宣声明,就会作为各自子范围的一部分。即在本地范围内有效,但本地定义的变量不可在全局范围内访问。

  让我们来看一个例子。执行下面的代码,你会发现,你能打印出全局范围定义的变量,而全局范围无法访问局部范围定义的变量。

1
2
3
4
5
6
7
var agloballydefinedvariable = 'Global' ;
function someFunction() {
   var alocallydefinedvariable = 'Local' ;
   console.log(agloballydefinedvariable); // Global
}
console.log(alocallydefinedvariable);
// Uncaught ReferenceError: alocallydefinedvariable is not defined

  作用域链(Scope Chain)

  如果你忘记使用“var”的关键字来定义局部变量,事情可能会变得非常糟糕。为什么会这样呢?因为JavaScript会首先在父作用域内搜索一个未定义的变量,然后再到全局范围进行搜索。在下面的例子中,JavaScript知道变量“a”是someFunction()的一个局部变量,在anotherFunction()中它会寻找它父作用域内的变量。

1
2
3
4
5
6
7
var a = 1;
function someFunction() {
   var a = 2;
   function anotherFunction() {
     console.log(a); // 2
   }
}

  更复杂的情况是,在下面的例子中,一个变量没有在函数中进行作用域的限定。

  在someFunction()中调用了一个没有在函数范围内定义的变量 a=2; 这个分配将覆盖全局变量的值。

  后续引用将指向全局变量的值。

1
2
3
4
5
6
7
8
9
10
var a = 1;
function someFunction() {
   a = 2;
   function anotherFunction() {
     console.log(a); // 2
   }
   anotherFunction();
}
someFunction();
console.log(a); //2

  声明提升(Hoisting)

  Hoisting会将在函数或全局范围内的变量“提升”到顶部声明的过程。请记住,只有量声明被提升了,初始化或值分配等等没有变化,在下面的代码的情况下,第一个输出将不确定...但它不会抛出任何错误。

1
2
3
console.log(a); //undefined
var a = 1;
console.log(a); //1

  Window范围

  在基于浏览器的JavaScript中,定义为全局范围内的一部分变量实际上是所谓的“Window”对象的属性。这里的Window是指“容器”。换句话说,当你想从一个局部范围修改全局定义的变量,你也可以通过修改Window对象的相应的属性来做到这一点。

1
2
3
4
5
6
var myVariable = 'Global Scope' ;
function myFunction() {
   window.myVariable = 'Something Else' ;
}
myFunction();
console.log(myVariable); // Something Else

  可能的陷阱

  如果在函数内部分配一个以前没有被定义的变量的值,它会自动成为全局范围的一部分。

1
2
3
4
5
function myFunction() {
   myVariable = 'JavaScript' ;
}
myFunction();
console.log(myVariable); //JavaScript

  如果你不小心忘记定义了一个局部变量,你的整个脚本可能会运行混乱。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var city= "LA" ;
var team= "Lakers" ;
function showTeam () {
     console.log (city + " " + team);
}
function showCity () {
     city = "Moscow" ;
     console.log (city);
}
showTeam(); // LA Lakers
showCity(); // Moscow
/*
因为上面的 showCity 中定义的变量 "city" 没有使用 "var" 声明,全局范围内的变量被覆盖了。因此会导致下面的问题 :)
*/
showTeam(); // Moscow Lakers

  内部函数依然会存储局部变量即使它的外部函数已经执行完毕

  这听起来可能有点怪异,看一个例子,就会更容易理解。解释这一点的最好办法是使用一个简单的“Hello World”的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
function greet(who) {
     var iterations = 0;
     return function () {
         console.log(++iterations);
         return 'Hello ' + who + '!' ;
     };
}
var greeting = greet( 'World' );
console.log( typeof greeting); //function
console.log( typeof greeting()); //string & iterations=1
console.log(greeting()); //Hello World! & iterations=2
console.log(greeting( "Universe" )); //Hello World! & iterations=3
//输出不是 Hello Universe. world 被闭包封闭保存了起来

  注*  在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

  闭包的概念出现于60年代,最早实现闭包的程序语言是Scheme。之后,闭包被广泛使用于函数式编程语言如ML语言和LISP。很多命令式程序语言也开始支持闭包。  引自:Wiki

  正如你上面看到的那样,greet() 返回一个被称为“闭包”的内部函数。闭包除了会储存他们自己本地作用域内部的封闭起来的函数和变量外,还会存储外部引用的参数。参看我们的具体例子,参数 who 和 iterations 就是被闭包封闭起来的局部变量。

  这意味着,greeting已成为一个包含who和iterations在内的函数(直接返回的匿名函数)。- 它不会再次执行greet,它只会执行闭包而且返回结果永远是 "Hello World!"。

相关文章:

  • Spring Data Redis—Pub/Sub(附Web项目源码)
  • 用面对对象方式定tab标签
  • Android 开发小知识点收集(随时更新)
  • 第二阶段冲刺8
  • 用shell脚本写的一个9*9乘法表
  • 导入项目后遇到页面报错如何解决
  • Ubuntu: how to md5sum (适用macOS)
  • Linux 磁盘配额 quota
  • crontab笔记
  • 互联网一线大厂都在用的Java架构师知识体系
  • linux 安全
  • centos中mysql,和配置ansible遇到的错误
  • 博客版权问题
  • Graph database_neo4j 底层存储结构分析(1)
  • python_bomb----数据类型总结
  • 「译」Node.js Streams 基础
  • 【5+】跨webview多页面 触发事件(二)
  • const let
  • es6--symbol
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • iOS仿今日头条、壁纸应用、筛选分类、三方微博、颜色填充等源码
  • js继承的实现方法
  • magento2项目上线注意事项
  • 那些被忽略的 JavaScript 数组方法细节
  • 线性表及其算法(java实现)
  • 想写好前端,先练好内功
  • 原生Ajax
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • 【运维趟坑回忆录 开篇】初入初创, 一脸懵
  • MPAndroidChart 教程:Y轴 YAxis
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • 进程与线程(三)——进程/线程间通信
  • ​无人机石油管道巡检方案新亮点:灵活准确又高效
  • #NOIP 2014#Day.2 T3 解方程
  • #微信小程序(布局、渲染层基础知识)
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • (2)STL算法之元素计数
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (力扣)循环队列的实现与详解(C语言)
  • (原)Matlab的svmtrain和svmclassify
  • (转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库
  • (转)清华学霸演讲稿:永远不要说你已经尽力了
  • (转载)Linux 多线程条件变量同步
  • (轉)JSON.stringify 语法实例讲解
  • . NET自动找可写目录
  • .NET的数据绑定
  • .Net环境下的缓存技术介绍
  • .NET开发人员必知的八个网站
  • @ 代码随想录算法训练营第8周(C语言)|Day53(动态规划)
  • @autowired注解作用_Spring Boot进阶教程——注解大全(建议收藏!)
  • [120_移动开发Android]008_android开发之Pull操作xml文件
  • [ABC294Ex] K-Coloring
  • [AIGC] 开源流程引擎哪个好,如何选型?