在这篇文章中,我会试图讲解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!"。

JavaScript变量作用域(Variable Scope)和闭包(closure)的基础知识的更多相关文章

  1. 聊一下JS中的作用域scope和闭包closure

    聊一下JS中的作用域scope和闭包closure scope和closure是javascript中两个非常关键的概念,前者JS用多了还比较好理解,closure就不一样了.我就被这个概念困扰了很久 ...

  2. 【转】javascript变量作用域、匿名函数及闭包

    下面这段话为摘抄,看到网上大多数人使用的是变量在使用的时候声明而不是在顶端声明,也可能考虑到js查找变量影响性能的问题,哪里用就在哪里声明,也很好. 在Javascript中,我们在写函数的时候往往需 ...

  3. JavaScript的作用域(Scope)和上下文(Context)

    JavaScript对于作用域(Scope)和上下文(Context)的实现是这门语言的一个非常独到的地方,部分归功于其独特的灵活性. 函数可以接收不同的的上下文和作用域.这些概念为JavaScrip ...

  4. 第一百零六节,JavaScript变量作用域及内存

    JavaScript变量作用域及内存 学习要点: 1.变量及作用域 2.内存问题 JavaScript的变量与其他语言的变量有很大区别.JavaScript变量是松散型的(不强制类型)本质,决定了它只 ...

  5. JavaScript变量作用域

    全部变量拥有全局作用域,局部变量拥有局部作用域(这里注意函数的参数也是局部变量) 1.在函数体内,局部变量的优先级高于同名的全局变量. 我的理解就是当你同时定义了同名的局部变量和全局变量时,函数体内返 ...

  6. JavaScript 进阶(四)解密闭包closure

    闭包(closure)是什么东西 我面试前端基本都会问一个问题"请描述一下闭包".相当多的应聘者的反应都是断断续续的词,“子函数”“父函数”“变量”,支支吾吾的说不清楚.我提示说如 ...

  7. 关于javascript变量作用域的研究。

    开始 一个变量的作用域(scope)是程序源代码中定义这个变量的区域.全局变量具有全局作用域,在javascript中的任何地方都是有定义的.然而在函数内申明的变量只在函数体内有定义.他们是局部变量, ...

  8. JavaScript 变量作用域和声明提升

    一.变量作用域 说到这个概念,不有自主的想到this,scope 这两个关键字. JavaScript的this总是指向一个明确的对象,这个对象是在执行的时候动态绑定的.通俗的说就是谁调用我,我的th ...

  9. JavaScript 变量作用域

    一. 变量声明 变量用var关键字来声明,如下所示: 变量在未声明的情况下被初始化,会被添加到全局环境. JavaScript执行代码时,会创建一个上下文执行环境,全局环境是最外围的环境.每个函数在被 ...

随机推荐

  1. C++ code:向量操作之添加元素

    读入一个文件aaa.txt的数据到向量中,文件中是一些整数(个数未知).要判断向量中的元素有多少个两两相等的数对. 代码如下: #include<iostream> #include< ...

  2. Spring IOC 低级容器解析

    1.IOC是什么 IOC-Inversion of Control,即"控制反转",不是什么技术,而是一种设计思想.在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不 ...

  3. composer卸载重装

    $ composer remove phpunit/phpunit --dev $ composer require phpunit/phpunit --dev

  4. javah命令的使用

    背景 java开发中如果使用到JNI,则难免需要使用javah来生成C++或C的头文件信息,下面小编就直接说说javah的命令: 第一种:直接cd到当前程序的target/class目录下(maven ...

  5. Codeforces Round #369 (Div. 2)-D Directed Roads

    题目大意:给你n个点n条边的有向图,你可以任意地反转一条边的方向,也可以一条都不反转,问你有多少种反转的方法 使图中没有环. 思路:我们先把有向边全部变成无向边,每个连通图中肯定有且只有一个环,如果这 ...

  6. URAL - 1078 Segments

    URAL - 1078 题目大意:有n条线段,一个线段a 完全覆盖另一个线段b 当且仅当,a.l < b.l && a.r>b.r.问你 一个线段覆盖一个线段再覆盖一个线段 ...

  7. C++雾中风景12:聊聊C++中的Mutex,以及拯救生产力的Boost

    笔者近期在工作之中编程实现一个Cache结构的封装,需要使用到C++之中的互斥量Mutex,于是花了一些时间进行了调研.(结果对C++标准库很是绝望....)最终还是通过利用了Boost库的share ...

  8. Leaf:美团分布式ID生成服务开源

    Leaf是美团基础研发平台推出的一个分布式ID生成服务,名字取自德国哲学家.数学家莱布尼茨的一句话:“There are no two identical leaves in the world.”L ...

  9. XenServer日志清理方法

    服务器使用时间长了,XenServer产生了很多日志,甚至有些人因为日志占满了空间, 导致系统出现问题:xapi崩溃,或者系统卡死,重启也无效. 所以我们要时常看看日志是否占的空间的,清理下日志先查看 ...

  10. unity3d 给游戏添加音源 Unity3d adds a sound source to the game

    unity3d 给游戏添加音源 Unity3d adds a sound source to the game   作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱:3131345 ...