javascript 的作用域

翻译自:http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html

如果以下代码执行,你知道出什么结果吗?

var foo = 1;

function bar(){
   if(!foo){
       var foo = 10;
   }
   alert(foo);
}
bar();

这运行结果是10. 如果你对结果感到惊讶,请看下面例子:

var a =1;

function b(){
   a = 10;
  return;
  function a(){}
}

b();
alert(a);

这段代码结果是1.那么到底发生了什么呢?虽然这可能看起来很奇怪,危险,令人困惑,但实际上这是语言的一个强大而富有表现力的特征。 我不知道这个具体行为是否有标准名称,但我已经想到“提升”一词。 本文将尝试阐明这一机制,但首先让我们进一步了解JavaScript的范围。

javascript的作用域

JavaScript初学者最为混乱的原因之一就是范围界定。 其实这不只是初学者。 我遇到了很多经验丰富的JavaScript程序员,他们不完全了解范围界定。 在JavaScript中如此混乱的原因是因为它看起来像一个C系列语言。 思考以下C程序:

#include <stdio.h>
int main() {
    int x = 1;
    printf("%d, ", x); // 1
    if (1) {
        int x = 2;
        printf("%d, ", x); // 2
    }
    printf("%d\n", x); // 1
}

该程序的输出将为1,2,1。这是因为C和C系列的其余部分具有块级范围。 当控件进入块(如if语句)时,可以在该范围内声明新变量,而不影响外部作用域。 JavaScript并非如此。 在Firebug中尝试以下操作:

var x = 1;
console.log(x); // 1
if (true) {
    var x = 2;
    console.log(x); // 2
}
console.log(x); // 2

在这种情况下,Firebug将显示1,2,2.这是因为JavaScript具有功能级的作用域。 这与C系列截然不同。 块(如if语句)不会创建新的范围。 只有函数创建一个新的范围。

对于习惯于C,C ++,C#或Java等语言的程序员来说,这是意想不到的,不受欢迎的。 幸运的是,由于JavaScript功能的灵活性,有一个解决方法。 如果必须在函数内创建临时作用域,请执行以下操作:

function foo() {
    var x = 1;
    if (x) {
        (function () {
            var x = 2;
            // some other code
        }());
    }
    // x is still 1.
}

这种方法实际上是非常灵活的,可以在任何需要临时作用域的地方使用,而不仅仅是在块语句中。 但是,我强烈建议您花点时间真正了解并欣赏JavaScript范围。 这是非常强大的,是我最喜欢的语言功能之一。 如果你明白范围界定,hosting将会更有意义。

hosting

在JavaScript中,一个名称以四种基本方式之一输入范围:

语言定义:默认情况下,所有作用域都给出了这个和参数的名称。
形式参数:函数可以具有命名形式参数,这些参数范围是该函数的正文。
函数声明:这些是函数foo(){}的形式。
变量声明:这些形式为var foo ;

函数声明和变量声明始终被JavaScript解释器移动(“hosting”)到其包含范围的顶部。 功能参数和语言定义的名称显然已经在那里。 这意味着这样的代码:

function foo() {
    bar();
    var x = 1;
}

事实上是这样:

function foo() {
    var x;
    bar();
    x = 1;
}

事实证明,包含声明的行是否将被执行并不重要。 以下两个功能是等效的:

function foo() {
    if (false) {
        var x = 1;
    }
    return;
    var y = 1;
}
function foo() {
    var x, y;
    if (false) {
        x = 1;
    }
    return;
    y = 1;
}

请注意,声明的转让部分未被吊起。 只有这个名字已经悬挂。 函数声明不是这样,整个函数体也将被挂起。 但请记住,有两种正常的方法来声明函数。 考虑以下JavaScript:

function test() {
    foo(); // TypeError "foo is not a function"
    bar(); // "this will run!"
    var foo = function () { // function expression assigned to local variable ‘foo‘
        alert("this won‘t run!");
    }
    function bar() { // function declaration, given the name ‘bar‘
        alert("this will run!");
    }
}
test();

在这种情况下,只有函数声明的主体已经悬挂在顶部。 “foo”的名字已经悬挂,但身体被遗弃,在执行过程中被分配。

这涵盖了起重机的基础知识,并不像看起来那么复杂或混乱。 当然,这是JavaScript,在某些特殊情况下还有一点复杂性。

名称解析顺序

要记住的最重要的特殊情况是名称解析顺序。请记住,名称输入给定范围有四种方式。我上面列出的顺序是它们解决的顺序。一般来说,如果一个名称已经被定义,它不会被同名的另一个属性所覆盖。这意味着函数声明优先于变量声明。这并不意味着对该名称的分配将不起作用,只是声明部分将被忽略。还有一些例外:

内置的名称参数的行为奇怪。它似乎是在形式参数之后声明的,但在函数声明之前。这意味着具有name参数的形式参数将优先于内置,即使未定义。这是一个不好的功能。不要使用name参数作为形式参数。
尝试使用名称作为任何位置的标识符将导致一个SyntaxError。这是一个很好的功能。
如果多个形式参数具有相同的名称,则列表中最新出现的一个参数将优先,即使未定义。

命名函数表达式 

您可以给函数表达式中定义的函数命名,其语法类似函数声明。 这不是一个函数声明,并且这个名字没有被引入到范围内,而且这个名字也没有被提起来。 这里有一些代码来说明我的意思:

foo(); // TypeError "foo is not a function"
bar(); // valid
baz(); // TypeError "baz is not a function"
spam(); // ReferenceError "spam is not defined"

var foo = function () {}; // anonymous function expression (‘foo‘ gets hoisted)
function bar() {}; // function declaration (‘bar‘ and the function body get hoisted)
var baz = function spam() {}; // named function expression (only ‘baz‘ gets hoisted)

foo(); // valid
bar(); // valid
baz(); // valid
spam(); // ReferenceError "spam is not defined"

如何在代码中应用

现在您已经了解了范围和提升情况,这对JavaScript编程意味着什么? 最重要的是始终使用var语句声明变量。 我强烈建议您每个范围只有一个var语句,它位于顶部。 如果你强迫自己这样做,你永远不会有起重的混乱。但是,这样做可能难以跟踪在当前范围内实际声明了哪些变量。 我建议使用JSLint与onevar选项来强制执行。 如果你完成了所有这些,你的代码应该是这样的:
/*jslint onevar: true [...] */
function foo(a, b, c) {
    var x = 1,
    	bar,
    	baz = "something";
}

 标准是什么呢?

我发现直接咨询ECMAScript标准(pdf)通常是有用的,以了解这些事情如何工作。 以下是对变量声明和范围的说明(旧版本中的第12.2.2节):

如果变量语句发生在FunctionDeclaration内,则在该函数中使用函数局部作用域定义变量,如第10.1.3节所述。 否则,使用属性属性{DontDelete}定义全局范围(即,它们被创建为全局对象的成员,如第10.1.3节所述)。 在输入执行范围时创建变量。 块不定义新的执行范围。 只有程序和函数声明才能产生一个新的范围。 变量在创建时被初始化为未定义。 当执行VariableStatement时,具有Initialiser的变量将分配给其AssignmentExpression的值,而不是创建变量时。

我希望这篇文章揭示了JavaScript程序员最常见的困惑之一。 我试图尽可能的彻底,避免造成更多的混乱。 如果我有任何错误或遗漏,请通知我。


时间: 2024-12-06 18:47:38

javascript 的作用域的相关文章

深入浅出JavaScript变量作用域

在学习JavaScript的变量作用域之前,我们应当明确几点: JavaScript的变量作用域是基于其特有的作用域链的. JavaScript没有块级作用域.赌王娱乐城 函数中声明的变量在整个函数中都有定义. 1.JavaScript的作用域链 首先看下下面这段代码: <script type="text/javascript"> var rain = 1;     function rainman(){     var man = 2;     function inn

JavaScript从作用域到闭包

作用域(scope) 全局作用域和局部作用域 通常来讲这块是全局变量与局部变量的区分. 参考引文:JavaScript 开发进阶:理解 JavaScript 作用域和作用域链 全局作用域:最外层函数和在最外层函数外面定义的变量拥有全局作用域. 1)最外层函数和在最外层函数外面定义的变量拥有全局作用域 2)所有末定义直接赋值的变量自动声明为拥有全局作用域,即没有用var声明的变量都是全局变量,而且是顶层对象的属性. 3)所有window对象的属性拥有全局作用域 局部作用域:和全局作用域相反,局部作

JavaScript 函数作用域的“提升”现象

在JavaScript当中,定义变量通过var操作符+变量名.但是不加 var 操作符,直接赋值也是可以的.例如 : message = "hello JavaScript ! " 即定义了一个全局变量message,并赋值 "Hello JavaScript!"--<JavaScript高级程序第三版> 如同往日一般,一群人在所谓的技术交流群里面相互斗图着.突然老王莫名的正经起来,在群里发了一道JavaScript的题目,让大家猜一猜这道题的答案. v

浅谈JavaScript的作用域

前段时间学了下JavaScript作用域,这个东西在JavaScript非常重要,也是JavaScript很基础的东西,正如少林里面基础武功,有了基础,才能学绝世武功. 作用域的作用是啥?一套设计良好的规则来存储变量,并且之后可以方便的找到这些变量. 就JavaScript里面的作用域来说,我总结有这么几个关键词: 1. 词法作用域 2. 函数作用域 3. 块级作用域 4. 闭包 5. 提升 好吧,我脑海里面能想到的就这么多了,不够的,也可以有朋友指出来,接下来,我一个一个过下这些词. 一.词法

JavaScript变量作用域

全部变量拥有全局作用域,局部变量拥有局部作用域(这里注意函数的参数也是局部变量) 1.在函数体内,局部变量的优先级高于同名的全局变量. 我的理解就是当你同时定义了同名的局部变量和全局变量时,函数体内返回的将是局部变量的值. 例如: var scope="我是全局变量"; function checkscope(){ var scope="我是局部变量"; console.log(scope); } checkscope(); 输出:我是局部变量 这里需要注意的是,在

JavaScript的作用域

JavaScript的作用域主要是指函数的作用域,在进行结果判断的时候十分重要,如果不清楚作用域,便很有可能导致拿不到预期的结果,也就无法顺利的进行程序的编写,在经历了一系列的学习和了解之后,对相关知识进行一个汇总,认识比较浅显,希望可以帮助到有需要的人. 首先引入一个概念:词法分析 JavaScript在创建的时候会对function进行词法分析,函数会在创建时形成一个活动对象,ActiveObject,简称AO,先举一个实际的例子进行分析: function t1(age) { consol

JavaScript中作用域链和闭包

一.匿名函数 1.1 匿名函数的概念 ? 声明一个没有函数名的函数,就是匿名函数. ? 有函数名的函数就是具名函数. 看下面的代码: <script type="text/javascript"> /* //这里定义了一个函数,而且没有函数名.这样写语法是错误的,如果允许这样定义,那么根本就没有办法调用. //所以,我们可以用一个变量来存储一下 function(){ } */ // 声明了一个匿名函数,并把匿名函数赋值给变量f. 注意这个时候这个匿名函数并没有执行. va

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

JavaScript变量作用域及内存 学习要点: 1.变量及作用域 2.内存问题 JavaScript的变量与其他语言的变量有很大区别.JavaScript变量是松散型的(不强制类型)本质,决定了它只是在特定时间用于保存特定值的一个名字而已.由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的生命周期内改变. 一.变量及作用域 1.基本类型和引用类型的值 ECMAScript变量可能包含两种不同的数据类型的值:基本类型值和引用类型值.基本类型值指的是那些保存在栈

JavaScript的作用域和提升机制

你知道下面的JavaScript代码执行时会输出什么吗? var foo = 1; function bar() { if (!foo) { var foo = 10; } alert(foo); } bar(); 答案是"10",吃惊吗?那么下面的可能会真的让你大吃一惊: var a = 1; function b() { a = 10; return; function a() {} } b(); alert(a); 这里浏览器会弹出"1".怎么回事?这似乎看起