在《JavaScript权威指南》第六版的4.3节和5.3节中分别讲了函数的两种定义方式:
函数声明方式: function foo(){}
函数表达式方式:var foo= function(){}
两者的区别在于函数声明的方式会有一个“定义提升”(hoisting),即将函数声明提升到顶部,这样之后的代码也都可以访问到它。函数表达式的方式则不存在定义提升。通过下面两个例子展示两者的区别:
例子1: foo();//调用成功: function foo(){ console.log('ok');}
例子2: foo();//调用失败,"uncaught TypeError: Property 'foo' of object [object Object] is not a function" var foo = function(){ console.log('ok'); }
例子1中虽然是先调用foo,然后才声明的foo函数,但是由于定义提升,foo的声明被提高到顶部,因此foo调用成功。例子1的代码等价于:
function foo(){ console.log('ok'); } foo();//调用成功
例子2中,foo是函数表达式,不存在定义提升,所以先调用后定义的方式是行不通的。和函数声明同样有定义提升的效果的还有var关键字定义的变量,请看下面例子:
例子3: console.log(i);//undefined var i = 1; console.log(i);//1var i =1;可以看成变量定义和初始化两部分,var i; i =1;其中变量定义提升,放在了最开始的部分,而初始化还是留在原地,所以第一次输出的是undefined,第二次输出的是1;
因此之前的例子2等价于下面的代码:
var foo; foo();//调用失败 foo = function(){ console.log('ok'); }这也就说明了为什么报错的内容是“foo不是一个函数”。 由于有定义提升,在同一作用域下所以要特别注意函数同名覆盖的问题。由于多次用var声明一个变量是无所谓的(《JavaScript权威指南》第六版的5.3.1节),这里只需要讨论函数之间同名覆盖、函数与变量同名覆盖两个问题。请看下面的例子:
例子4: function fun1(){alert('A');} fun1(); //结果为 B function fun1(){ alert('B');}可以看到函数声明的提升,会按照出现的顺序进行覆盖。
例子5: (function(a){ console.log(a);//输出函数a的内容 function a(){}; var a=10; }(100))可以看到函数声明和变量声明的提升,会以函数声明为主。因为在函数执行上下文中,变量声明不会干扰已经存在的同名变量,形参和函数声明。说到执行上下文(EC)又是一个比较大的话题,我把它放到下一篇,这里就略过。
最后再看一个比较综合的例子:
例子6: function Foo() { getName = function () { alert (1); }; return this; } Foo.getName = function () { alert (2);}; var getName = function () { alert (4);}; function getName() { alert (5);} 问: Foo.getName(); getName(); Foo().getName(); getName();
答案:Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
参考文章:
1、JavaScript函数声明和函数表达式的区别:函数声明的提升
2、一道常被人轻视的JS前端面试题