原文发表于:2014-08-21
js作用域
为了更好的描述本文的核心:变量声明提升
,我们先来复习一下js的作用域。
js没有块级作用域,函数是js唯一拥有自身作用域的结构(注:指在ES6
出现以前,ES6
开始出现了对块级作用域
的支持)。
变量声明提升
什么是变量声明提升
这是一个比较老的话题,所谓变量声明提升hoisting
,是指JavaScript引擎在执行的时候,会把所有变量的声明都提升到当前作用域的最前面,包括普通变量和函数。
先看个经典例子
var str = 'Hello Liuxianan!';
(function()
{
console.log(str); // 输出 undefined
var str = 'Hello xmy!';
})();
运行结果是:undefined
为什么这样呢?前面说了,js引擎会把变量声明放到当前作用域的最前面,又由于函数是js唯一拥有自身作用域的结构,所以,提升的代码其实是这样的:
var str = 'Hello Liuxianan!';
(function()
{
var str;
console.log(str);
str = 'Hello xmy!';
})();
假如换成这样呢?不用说,这种情况下变量声明不会提升:
var str = 'Hello Liuxianan!';
if(true)
{
console.log(str); // 输出 'Hello Liuxianan!'
str = 'Hello xmy!';
})();
函数的提升
还是看段代码:
(function()
{
f1(); // 输出 我是f1
f2(); // 提示:f2 is not a function
function f1(){console.log('我是f1');}
var f2 = function(){};
})();
结论:
无论是普通变量,还是函数,声明都会被提升,但是,函数声明式
(比如这里的f1)会把函数内容也一起提升,而函数表达式
(比如这里的f2)只提升声明,不提升函数内容,这就是函数声明式和函数表达式的最大区别。
以上代码相当于:
(function()
{
function f1(){console.log('我是f1');}
var f2;
f1(); // 输出 我是f1
f2(); // 提示:f2 is not a function
f2 = function(){};
})();
函数表达式和函数声明式提升的顺序
(function()
{
console.log(foo); // 输出 function foo(){}
var foo;
function foo(){};
})();
(function()
{
console.log(foo); // 输出 function foo(){}
function foo(){};
var foo;
})();
可以看出,无论var foo;
是放在前面还是后面,它始终都被提升到了function foo(){}
的前面。以上代码均相当于:
(function()
{
var foo;
function foo(){};
console.log(foo); // 输出 function foo(){}
})();
但是由于只是声明提升,函数的内容并没有提升,所以如果是这的话:
(function()
{
function foo(){console.log('111')};
var foo = function(){console.log('222')};
foo(); // 输出 222
})();
(function()
{
var foo = function(){console.log('222')};
function foo(){console.log('111')};
foo(); // 还是输出 222
})();
函数声明表达式的提升
(function()
{
f(); // TypeError: f is not a function
foo(); // ReferenceError: foo is not defined
var f = function foo(){console.log(typeof foo);};
f(); // function
foo(); // ReferenceError: foo is not defined
})();
以上代码相当于:
(function()
{
var f;
f(); // TypeError: f is not a function
foo(); // ReferenceError: foo is not defined
f = function(){var foo = f; console.log(typeof foo);};
f(); // function
foo(); // ReferenceError: foo is not defined
})();
附:函数声明式和函数表达式
所谓函数声明式,就是类似这样的定义:
function foo(){}
所谓函数表达式,就是类似这样的定义:
var foo = function(){}
那如果一个function是这样定义的呢?
var f = function foo(){};
这样的function好像有一个名字叫函数声明表达式
,具体是不是专业名词俺没调查过,其实上面的代码相当于:
var f = function(){var foo = f;}
另外,突发奇想一个例子:
(function(foo)
{
function foo(){}
console.log(arguments); // 输出 [function(){}]
})('ss');
变量优先级
说明:下面的内容可能有严重错误,还没有完全研究透。
javascript中一个变量以四种方式进入作用域(scope),其优先级从低到高分别是(这里说的优先级的高低,是指优先级高的可以覆盖优先级低的):
- 语言内置:所有的作用域中都有 this 和 arguments 关键字
- 函数声明:形如function foo() {}
- 形式参数:函数的参数在函数作用域中都是有效的
- 变量声明:形如var bar;
以上内容属于转载,感觉有问题,有待继续参研。
看个例子:
(function(foo)
{
function foo(){};
var foo;
console.log(foo); // 输出function foo(){}
})('ss');
(function(foo)
{
var foo;
function foo(){};
console.log(foo); // 还是输出function foo(){}
})('ss');
(function(foo)
{
var foo;
console.log(foo); // 输出ss而不是undefined,说明形参的优先级比var声明高
})('ss');
(function(foo)
{
function foo(){};
console.log(foo); // 输出function foo(){},说明函数声明式覆盖了形参
})('ss');
以上例子我还没研究透,不过从上面可以大概看出,形参覆盖了var的声明,至于为何函数声明式覆盖了形参,还没弄明白。
再看个例子:
(function(foo)
{
console.log(foo, arguments[0]); // 输出 function foo(){}, function foo(){}
function foo(){};
console.log(foo, arguments[0]); // 输出 function foo(){}, function foo(){}
})('ss');
(function(foo)
{
console.log(foo, arguments[0]); // 输出 ss ss
var foo = function(){};
console.log(foo, arguments[0]); // 输出 function foo(){}, function foo(){}
})('ss');
可以看出arguments始终存放的是对形参的引用,形参内容变了arguments的内容也变了。
arguments可以被覆盖的:
(function(arguments)
{
console.log(arguments); // 输出ss而不是['ss'],说明arguments可以被覆盖
arguments = 111;
console.log(arguments); // 输出111
})('ss');
参考
http://openwares.net/js/javascript_declaration_hoisting.html