首先看一个小问题:
1 | var a = 'Hello'; |
猜猜弹框中会输出 ‘Hello’ 还是 ‘World’。揭晓答案: ‘undefined’。这里是一个 JavaScript 的小陷阱–JavaScript 变量提升(Hoisting)。
JavaScript Scoping
在 ES6 之前,JavaScript 没有块状作用域(block-level scope),只有函数级作用域(function-level scope)。
1 | // 块级作用域 |
如果在声明一个变量的时候没有使用 var 关键字,那么变量将成为一个全局变量。
1 | (function() { |
在 setTimeout 中的函数是在全局作用域中执行的。
1 | var a = 1 |
为了避免对全局作用域的污染, 所以一般情况下我们尽可能少的声明全局变量。
关于 ES6 中 使用 let 和 const 声明块级作用域的内容,可以参考 JavaScript 中的 let 和 const。
关于 ES5 中严格模式的内容可以参考 JavaScript 严格模式。
关于 JavaScript 中 this 的详细用法可以参考 JavaScript 中 的this。
JavaScript Hoisting
在 JavaScript 中,函数、变量的声明都会被提升(hoisting)到该函数或变量所在的 scope 的顶部。
1 | var a |
在 JavaScript 中,如果声明一个变量,但是为对其进行赋值,那么 JS 引擎会默认让其等于 undefined。所以上述例子中可以看到变量 b 在声明后,被提升到作用域顶部,和 a 一样,获得了 undefined 的值。
除了变量声明会提升,函数声明也会提升。
1 | console.log(add(1, 2, 3)) // 6 |
值得注意的是:函数声明可以提升,但是函数表达式不能提升。
函数声明: function fun(arguments) {}
函数表达式: var fun = function (arguments) {}
1 | add(1, 2) // 报错:Uncaught TypeError: add is not a function |
函数声明会覆盖变量声明。
1 | var test |
如果变量已经赋值,则无法别覆盖:
1 | var test = 'test' |
优先级
在 JavaScript 中,一个变量以四种方式进入作用域 scope:
- 语言内置:所有的作用域中都有 this 和 arguments 关键字(global 没有 arguments);
- 形式参数:函数的参数在函数作用域中都是有效的;
- 函数声明:形如
function foo() {}; - 变量声明:形如
var bar;
函数声明和变量声明总是会被移动(即 hoisting)到它们所在的作用域的顶部。而变量的解析顺序(优先级),与变量进入作用域的 4 种方式的顺序一致,如果一个变量的名字与函数的名字相同,那么函数的名字会覆盖变量的名字,无论其在代码中的顺序如何,但是名字的初始化却是按其在代码中书写的顺序进行的,不受以上优先级的影响。
而变量的解析顺序(优先级),与变量进入作用域的 4 种方式的顺序一致。
1 | // 1. var 声明并且赋值高于函数声明 |
变量声明(赋值) > 形参 > 语言内置变量 > 变量声明不赋值 > 函数外部作用域的其他所有声明
总结变量优先级正好验证了作用域链式查找,局部作用域 -> 上一级局部作用域 -> 全局作用域 -> TypeError。
最后看一个例子:
1 | function test(arguments) { |