`

谈 javascript 变量声明

 
阅读更多

这篇文章还是对基础的复习,对面试经历的一个总结。

之前的面试中遇到过一道面试题

var a = 10;
(function () {
    console.log(a); 
    var a = 20;
})()
  • 短短 5 行代码 console.log(a) 的结果是什么?
  • 如果把 var a = 20; 和 console.log(a) 语句顺序对调呢?

这道题目的答案是 undefined。不是 10。

关键在于 javascript 的变量声明有一个 hoisting 机制,变量声明永远都会被提升至作用域的最顶端(注意测试还只是声明,还没有赋值)。 其实上面的语句相当于:

var a = 10;
(function () {
    var a; // 在这里对变量hoisting,先声明
    console.log(a); 
    a = 20; // 再赋值
})()

再精简一点:

bla = 2
var bla;

// 这是分割线,上下代码的效果其实是一样的

var bla;
bla = 2;

也就是先使用,再声明(注意是声明,还没有赋值),这样一来,声明和赋值就被分开来了。 所以最佳实践都推荐最好在函数的顶端把需要使用的变量首先声明一遍。

同理,我们可以理解下面的代码也是会报错的

f() // 明显这里有错,因为 f 还没有被赋一个函数
var f = function () {
    console.log("Hello");
}

但有一个问题,如果将上例 f 的函数声明修改一下,还会报错吗

f() // 可以运行吗?
function f() {
    console.log("Hello");
}

这里我其实想强调的是两种函数声明的 var f = function () {} 和 function f() {} 差别。

事实上,javascript 中所有的函数声明(function declarations)变量声明(variable declarations)都会被提升(hoisted)至它们所在作用域的最顶端。 需要注意的是函数声明只有一种,也就是 function f() {} 的形式。 而var f = function () {} 是什么? 你可以理解为它是将一个匿名函数(当然也可以取函数名,下面会解释)赋值给了一个变量。

就哪上面两个例子来说,同样是想实现先使用再定义的效果。 只有第二种有用,虽然函数f在使用之后才定义,但是在 javascript 解释器中,它仍然是先于执行语句被定义的。

而第一个例子,执行的效果是这样的

var f;
f() // 没有定义任何函数,当然无法执行
f = function () {
    console.log("Hello");
}

这么看来,虽然 javascript 是允许先执行再声明,但切勿这么做,请遵循先声明再使用的好习惯

再看看另一种情况,如果我把之前的函数定义

var f = function () {};
  1. 给右侧的匿名函数增加函数名
  2. 以右侧函数名来执行函数
  3. 能成功吗?

     var f = function ab() {};
     ab();
    

答案是否定的,因为上面的代码对f函数的定义是以命名函数表达式(Named Function Expressions),而并非真正的函数声明,注意该函数名只在该函数的作用域内有用。 下面这段代码充分说明了它的意义:

var f = function foo(){
    return typeof foo;
};
typeof foo; // "undefined"
f(); // "function"

那么如此声明还有什么意义呢?好吧,就我目前找到的资料而言,这样做的好处就是便于调试。

接下来考虑一些意想不到的边缘,虽然我觉得一个程序员写出下面的代码有点自找苦吃,而且应该是在实战中避免的,但作为考试的题目来说是值得一说的。 比如对比下面两段代码:

function value(){
    return 1;
}
var value;
alert(typeof value);    //"function"


function value(){
    return 1;
}
var value = 1;
alert(typeof value);    //"number"

第一段代码想说明的是函数声明会覆盖变量声明,注意是声明,还没有赋值。 如代码中,虽然同名变量在函数后再次声明,但是 typeof 的结果仍然是 function

第二段代码想说明的是函数声明不会覆盖变量赋值或者说初始化,如代码所示

Name Resolution Order

为什么会有上面的结果,为什么函数的声明会覆盖变量的声明。 就是因为 name resolution order。 我不知道怎么翻译这个名词,暂且就翻译为名称解析顺序吧。

在 javascript 中,一个变量名(name)有四种方式进入作用域(scope)中

  • 语言内置,所有的作用域中都有 this 和 arguments 关键字
  • 形式参数,函数的参数在整个作用域中都是有效的
  • 函数声明
  • 变量声明

上面列出的四种顺序也正是由高到底的优先级的顺序(关于这点我有所保留,我测试的结果是参数和函数的优先级都会比语言内置的优先级高,你可以把形式参数取名为 arguments,或者定义一个函数名为arguments,结果内置的 argument 说被覆盖了),一旦一个变量名已经声明了,那么它就不可能被其他更低优先级的变量声明形式所覆盖。

参考文章:

  1. 命名函数表达式探秘
  2. function-declarations-vs-function-expressions (墙)
  3. Javascript 作用域和变量提升
  4. Zakas 解答 Baranovskiy 的 JavaScript 小测试
11
11
分享到:
评论
2 楼 求求你帮帮我 2013-06-28  
完全看不懂写的什么意思。
1 楼 zhukewen_java 2013-06-14  
js果然好无聊。其实只要好好写代码,起变量名起好一点,完全不用在乎这些小玩意

相关推荐

    浅谈JavaScript中变量和函数声明的提升

    函数声明提升高于变量声明 //同时声明变量a和函数a var a; function a() {} alert(typeof a); //显示的是"function",初步证明function的优先级高于var。 //先声明函数后声明变量,证明上边的例子不是...

    浅谈JavaScript中定义变量时有无var声明的区别

    主要介绍了JavaScript中定义变量时有无var声明的区别分析以及示例分享,需要的朋友可以参考下

    浅谈JavaScript的全局变量与局部变量

    使用未声明变量或函数会导致致命错误从而中断脚本执行) //此时i值为undefined for(var i=0; i<3;i++){ alert("in for scope:"+i);} //i的值是0,1,2 alert(“after for scope:”+1); //i的值是...

    浅谈JavaScript 声明提升

    在学习JavaScript声明提升之前,我们先看下面这个例子: console.log(a); var a=2; 运行结果会是什么?你可能会有以下的猜测: 1.报错ReferenceError: a is not defined; 2.打印2; 3.打印undefined。 正确的结果...

    JavaScript网页特效应用开发手册

    5-3 声明变量 5-4 变量的类型 5-5 数值变量 5-6 字符串变量 5-7 不同类型变量转换方式 第6章 数组 6-1 什么是数组 6-2 建立与使用数组 第7章 基本运算 7-1 基本运算概念 7-2 数学运算符 7-3 数学运算符的优先顺序 7-...

    浅谈JavaScript作用域和闭包

    作用域和闭包在JavaScript里非常重要。但是在我最初学习JavaScript的时候,却很难理解。这篇文章会用一些例子帮你理解它们。 我们先从作用域开始。 作用域 JavaScript的作用域...一旦你声明了一个全局变量,那么你在

    浅谈JavaScript中的作用域和闭包问题

    函数内部可以声明和访问全局变量,也可以声明局部变量(使用var关键字,函数的参数也是局部变量),但函数外部无法访问内部的局部变量: function test() { var a = 0; // 局部变量 b = 1; // 全局变量 } a = ?, b...

    浅谈[removed]两种注释,声明变量,定义函数

    JavaScript:单行注释用//呵呵呵呵;多行注释用/*hdhdhdh*/ javascript中区别大小写,定义变量使用关键字...以上就是小编为大家带来的浅谈[removed]两种注释,声明变量,定义函数全部内容了,希望大家多多支持软件开发网~

    浅谈JavaScript中null和undefined

    先说null,它表示一个特殊值,...undefined出现有4种情况:①变量声明但没有初始化时②要查询的对象属性或数组的元素不存在时③如果函数没有任何返回值,则返回undefined④引用没有提供实参的函数形参的值也只会得到und

    浅谈JavaScript 函数参数传递到底是值传递还是引用传递

    在传统的观念里,都认为JavaScript函数...对于这里的输出20,10,按照JS的官方解释就是在基本类型参数传递的时候,做了一件复制栈帧的拷贝动作,这样外部声明的变量num和函数参数的num,拥有完全相同的值,但拥有完全不

    浅谈js基本数据类型和typeof

    当声明变量却没有赋值时会显示该值。可以为变量赋值为undefined •number:数值。最原始的数据类型,表达式计算的载体 •string:字符串。最抽象的数据类型,信息传播的载体 •boolean:布尔值。最机械的数据类型,...

    浅谈js的解析顺序 作用域 严格模式

    一、javascript的解析顺序 我们大家所理解的代码的执行顺序都是从上到下的,但是实际上确不是这样的。我们看一下下面的代码。 alert(...function也有声明变量的的作用。 2.解析顺序 1.找声明 var

    浅谈js script标签中的预解析

    细节问题:在多对的script标签中如果有相同的函数,那它们相互之间是不会受影响的,在第二对script标签中声明变量或者是创建函数,在第一对script标签中是无法访问到的,这就说明了,javaScript的预解析只会在各自的...

    asp.net知识库

    也谈 ASP.NET 1.1 中 QueryString 的安全获取写法 ASP.NET运行模式:PageHandlerFactory 利用搜索引擎引用来高亮页面关键字 网站首页的自动语言切换 应用系统的多语言支持 (一) 应用系统的多语言支持 (二) 自动...

Global site tag (gtag.js) - Google Analytics