这篇文章还是对基础的复习,对面试经历的一个总结。
之前的面试中遇到过一道面试题
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 () {};
- 给右侧的匿名函数增加函数名
- 以右侧函数名来执行函数
-
能成功吗?
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
说被覆盖了),一旦一个变量名已经声明了,那么它就不可能被其他更低优先级的变量声明形式所覆盖。
相关推荐
在实际运行之前,JavaScript会扫描整个脚本,处理变量声明。这就是为什么在`f2`函数中,尽管全局变量`x`已经赋值为1,但在函数内部,`x`首先被声明为局部变量,导致它在函数开始时的值为undefined。预编译使得函数...
- 变量声明:`var`, `let`, `const` - 数据类型:基本类型(如number、string、boolean等)与引用类型(如object、array等) 2. **控制结构** - 条件语句:`if...else`, `switch...case` - 循环语句:`for`, `...
JavaScript变量作用域问题 JavaScript作为一门动态脚本语言,在变量作用域上与其他语言存在显著差异。变量的作用域(Scope)指的是变量可以被访问的代码区域,它决定了哪些部分的代码可以访问该变量。理解变量作用...
此外,关于变量提升,JavaScript解释器会在执行代码前先扫描函数体,将所有的var声明移动到函数顶部,这种行为被称为变量提升(hoisting)。这意味着无论var声明在函数体中的位置如何,变量的声明都会被提升到函数的...
函数声明提升高于变量声明 //同时声明变量a和函数a var a; function a() {} alert(typeof a); //显示的是"function",初步证明function的优先级高于var。 //先声明函数后声明变量,证明上边的例子不是...
2. **变量声明提升**:JavaScript会预编译变量声明,将它们提升到其所在作用域的顶部。 3. **局部变量覆盖全局变量**:在函数内部声明的同名变量会遮盖全局变量,形成新的局部作用域。 4. **访问全局变量**:在局部...
1. **基础语法**:JavaScript的基本结构,如变量声明(var、let、const)、注释、语句(if-else、for、while)等,这些都是编写任何代码的基石。 2. **数据类型**:JavaScript有七种数据类型,包括基本类型...
"ppk谈JavaScript.part02.zip"可能是一个系列教程或讲座的第二部分,由ppk(Peter-Paul Koch)分享。ppk是一位知名的前端开发者,以其对浏览器兼容性和移动Web开发的深入研究而闻名。在这个部分中,他可能会继续探讨...
本文将探讨几个关键的编码规范方面,包括文件引用、代码排版、命名规则、变量声明、作用域以及特殊符号的使用。 首先,JavaScript文件应独立为.js文件,然后通过HTML的`<script src="filename.js">`标签引入,以...
声明提升是指JavaScript会将`var`变量声明和函数声明移动到它们所在的作用域顶部,无论这些声明在代码中的实际位置在哪里。这在理解代码执行顺序时尤为重要。 1. **引例及基本原理** 在JavaScript中,当遇到`var a...
变量声明 在JavaScript中,变量用于存储数据值。可以使用`var`、`let`或`const`关键字来声明变量。例如: ```javascript let x = 10; const y = "Hello"; ``` #### 2. 数据类型 JavaScript支持多种数据类型,包括...
始终使用`let`和`const`代替`var`进行变量声明,以防止作用域污染;使用单引号(' ')或双引号(" ")来包围字符串,但保持一致性;以及遵循一定的命名约定,如驼峰式命名(camelCase)或下划线分隔(snake_case)。 ...
预解析是JavaScript引擎在代码实际执行前进行的一种优化行为,它会将函数声明和变量声明提升到它们所在的作用域顶部。但值得注意的是,虽然声明被提升了,赋值操作则仍然保持在原位置。 1. 函数声明会被置顶:这...
通过以上介绍,我们可以看到《ppk谈JavaScript》这本书为初学者提供了一个很好的起点,不仅涵盖了语言的基础知识,还涉及了一些高级主题。对于想要深入学习JavaScript的人来说,这本书是一个宝贵的学习资源。