学习JavaScript函数

251 阅读5分钟

JavaScript中的函数

JavaScript中的函数使用function关键字,后跟一组参数以及函数体。例如:

function foo(arg0, arg1,...,arg) {
    ...
}

var foo = function () {
  ...
};

(function () {
  ...
})();

JavaScript中有三种函数类型:函数声明,函数表达式和函数构造器创建的函数。下面分别介绍这三种函数类型。

函数声明

函数声明由一系列的function关键字组成,依次为:

  • 有一个特定的名称
  • 函数参数列表,包围在括号中并由逗号分隔
  • 组成函数体的声明语句

例如:

function foo(arg0, arg1,...,arg) {
  var a = 1;
  console.log(a);
}

函数表达式

函数表达式和函数声明非常相似,依次为:

  • 可选的名称(当省略函数名的时候,该函数就成为了匿名函数。)
  • 函数参数列表,包围在括号中并由逗号分隔
  • 组成函数体的声明语句

例如:

var foo = function () {
  var b = 2;
  console.log(b);
};

var foo = function bar() {
  var b = 2;
  console.log(b);
};

(function() {
    var c = 3;
    console.log(c);
})();

Function构造函数

可以通过Function对象使用new操作符创建一个构造函数。例如:

new Function('alert('hello'); alert('world');');

函数声明和函数表达式

这里重点学习函数声明和函数表达式。创建函数的最常用的两个方法是函数表达式和函数声明。

函数声明:

function 函数名称 (参数:可选){ 函数体 }

函数表达式:

function 函数名称(可选)(参数:可选){ 函数体 }

所以,创建一个函数如果不声明函数名称,它肯定是表达式,可如果声明了函数名称的话,如何判断是函数声明还是函数表达式呢?

区分函数声明和表达式最简单的方法是看 function 关键字出现在声明中的位 置(不仅仅是一行代码,而是整个声明中的位置)。如果 function 是声明中 的第一个词,那么就是一个函数声明,否则就是一个函数表达式。或者这么理解function foo(){}是作为赋值表达式的一部分的话,那它就是一个函数表达式,如果function foo(){}被包含在一个函数体内,或者位于程序的最顶部的话,那它就是一个函数声明。

例如:

function foo(){} // 声明,因为它是程序的一部分

var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分

new function bar(){}; // 表达式,因为它是new表达式

(function(){
    function bar(){} // 声明,因为它是函数体的一部分
})();

(function foo(){}); // 表达式 包含在分组操作符内

匿名和具名函数

对于函数表达式,最熟悉的场景就是回调,例如:

setTimeout(function(){
    console.log("hello world!");
},1000);

这是一个匿名函数表达式,在日常开发和工具库中这种模式很常见。但是它也有几个缺点需要考虑。

  1. 匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难。
  2. 如果没有函数名,当函数需要引用自身时只能使用已经过期的arguments.callee引用, 比如在递归中。另一个函数需要引用自身的例子,是在事件触发后事件监听器需要解绑 自身。
  3. 匿名函数省略了对于代码可读性/可理解性很重要的函数名。一个描述性的名称可以让 代码不言自明。

例如:

具名函数

function foo(){
    return bar();
}
function bar(){
    return baz();
}
function baz(){
    debugger;
}
foo();  //当调试器debugger的调用栈,栈中显示了baz,bar,foo。
  
  
// 这是一个自执行的函数,函数内部执行自身,递归
function self() { 
    self(); 
}

匿名函数

function foo(){
    return bar();
  }
var bar = (function(){
    return function(){
      return baz();
    };
})();
function baz(){
    debugger;
}
foo();//当调试器debugger的调用栈,栈中显示了baz,(anonymous),foo。


// 这是一个自执行的匿名函数,因为没有标示名称
// 必须使用arguments.callee属性来执行自己
var self = function () { 
    arguments.callee(); //在严格模式下,ES5禁止使用 arguments.callee()。
};

给函数表达式指定一个函数名,不会影响其功能,反而是一个比较好的实现方式,例如:

setTimeout(function handle(){
    console.log("hello world!");
},1000);

立即执行函数表达式

(function foo() { 
    var a = 1;
    console.log(1);
})();

(function () { /* code */ } ()); // 推荐使用这个

(function () { /* code */ })(); // 但是这个也是可以用的

由于函数被包含在一对 ( ) 括号内部,因此成为了一个表达式,通过在末尾加上另外一个 ( ) 可以立即执行这个函数,比如(function foo(){ .. })()。第一个 ( ) 将函数变成表 达式,第二个 ( ) 执行了这个函数。这种模式也叫立即执行函数表达式(IIFE)。

除了( ) 括号外,还有很多其他的方式也能让一个函数变成函数表达式,例如:

//&&,异或,逗号等操作符
true && function () { /* code */ } ();
1, function () { /* code */ } ();
//一元操作符号
!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();

但是依然使用( ) 括号,括号内部本来期望的就是函数表达式,主要是为了方便开发人员阅读。

IIFE一些常见应用,例如:

  • 把它们当作函数调用并传递参数进去
var a = 1;
(function ( global ) {
    var a = 2;
    console.log( a ); // 2 
    console.log( global.a ); // 1
})( window );
console.log( a ); // 1

  • 模块方法
var module = (function () {
    var i = 0;
    return {
        get: function () {
            return i;
        },
        set: function (val) {
            i = val;
        },
        add: function () {
            return ++i;
        }
    };
} ());
module.get(); // 0
module.set(1);
module.add(); // 1
module.i // undefined 因为i不是返回对象的属性

结尾

这一节是对学习JavaScript作用域中的函数作用域的一个补充。这样把琐碎的知识串联起来,更容易形成体系。