《JavaScript应用程序设计》一一2.2 函数声明

简介:

本节书摘来华章计算机出版社《JavaScript应用程序设计》一书中的第2章,第2.2节,作者:Eric Elliott 更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.2 函数声明

在JavaScript中有多种定义函数的方法,不同方法各有优缺点。

function foo() {

  /* Warning: arguments.callee is deprecated.
     Use with caution. Used here strictly for
     illustration. */

  return arguments.callee;
}

foo(); //=> [Function: foo]

在这段代码中,foo()是一个函数声明。正如在“变量提升”一节中所提到的,你不能在条件语句中进行函数声明,这点一定要注意,下面的代码中,函数声明将无效:

var score = 6;

if (score > 5) {
  function grade() {
    return 'pass';
  }
} else {
  function grade() {
    return 'fail';
  }
}

module('Pass or Fail');

test('Conditional function declaration.', function () {

  // Firefox: Pass
  // Chrome, Safari, IE, Opera: Fail
  equal(grade(), 'pass',
    'Grade should pass.');
});

更为糟糕的是,不同浏览器对这段代码的解读会有差异,所以,尽量避免在条件语句下进行函数声明,详情请参见“变量提升”一节。
过度使用函数声明会导致模块中出现大量无关联函数,因为函数声明没有明确定义函数的作用范围、功能职责,以及互相间的协作方式。

var bar = function () {
    return arguments.callee;
};

bar(); //=> [Function](Note: It's anonymous.)

在上述例子中我们将一个函数体赋值给变量bar,这种声明方式我们称之为函数表达式。
函数表达式的优势在于,你可以像变量赋值操作那样将函数体赋值给变量,它遵循应用正常的流程控制逻辑,这意味着你可以在条件语句中声明函数表达式。
函数表达式的不足之处在于,你始终需要为函数体指派一个名称,否则所声明的函数将变为匿名函数。匿名函数在JavaScript中很容易被滥用,假设模块中所有的函数都是匿名函数,而且彼此间互有嵌套(这在事件驱动的应用中非常常见),当嵌套层级达到12层时,恰巧某个环节出了问题,经调试发现调用栈的输出呈现:

(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)
(Anonymous function)

很显然,调用栈没有提供给我们任何线索:

var baz = {
    f: function () {
        return arguments.callee;
    }
  };

baz.f(); // => [Function](Note: Also anonymous.)

这是函数表达式的另外一种声明方式,将匿名函数作为属性赋值给对象字面量,此时匿名函数被称为“方法字面量”,方法是指与对象绑定的函数。
方法的优势在于,可以使用对象字面量将有关联的函数归为一组。举例来说,假设你有一组控制灯泡状态的函数:

var lightBulbAPI = {
    toggle: function () {},
    getState: function () {},
    off: function () {},
    on: function () {},
    blink: function () {}
  };

将函数归类的好处是显而易见的,代码变得易读且富有条理,模块变得易于理解和维护。
另外,当模块愈加庞大时,由方法字面量所构成的对象能够很容易地被拆解并重新排列。举例来说,假设你负责维护一个控制家中照明、电视、音乐和车库门API的智能家居模块,当有新设备接入进来时,如果家居模块的API组织采用了方法字面量,那么将整个模块拆解为独立的文件或子模块会变得非常简单。
警告: 尽量不要使用Function()构造函数进行函数声明,这等于做了一次隐式的eval()调用,从而给程序带来性能损耗与安全隐患等问题,更多内容参见附录A。
命名函数表达式
如你所见,以上每一种函数声明方法都有其不足之处。不过有一种函数声明既可以让代码易于组织,又能解决调用栈被匿名函数污染的问题,同时还可以在条件语句中使用。来看看灯泡API的另外一种声明方式:

var lightbulbAPI = {
    toggle: function toggle() {},
    getState: function getState() {},
    off: function off() {},
    on: function on() {},
    blink: function blink() {}
  };

命名函数表达式是一种具有名称的特殊匿名函数,它的名称不仅可以从函数内部获取(例如递归),还可以在调试时,显示在调用栈中。
与匿名函数一样,方法字面量仅仅只是命名函数表达式存在的一种形式,你可以在程序的任意处通过对变量赋值来使用命名函数表达式。命名函数表达式与函数声明的区别在于,命名函数表达式的函数名称仅能在函数内部被访问。在函数体之外,你仍然只能通过被函数赋值的变量或形参来获得函数引用。

test('Named function expressions.', function () {
  var a = function x () {
    ok(x, 'x() is usable inside the function.');
  };

  a();

  try {
    x(); // Error
  } catch (e) {
    ok(true, 'x() is undefined outside the function.');
  }
});

警告: IE8会将命名函数表达式解析为函数声明,所以在同一作用域内,命名函数表达式会与其他变量或者函数存在同名冲突。这个问题已经在IE9中修复,而且没有在市面上其他浏览器中出现过。

这个问题其实很容易规避,只要你为命名函数表达式与被赋值变量使用相同的名称,再将其声明语句放置在函数体顶部即可。
test('Function Scope', function () {
  var testDeclaration = false,
    foo;

  // This function gets erroneously overridden in IE8.
  function bar(arg1, bleed) {
    if (bleed) {

      ok(false, 
       'Declaration bar() should NOT be callable from'
       + ' inside the expression.');

    } else {

      ok(true, 
        'Declaration bar() should be called outside the'
        + ' expression.');

    }
    testDeclaration = true;
  }

  foo = function bar(declaration, recurse) {
    if (recurse) {

      ok(true,
        'Expression bar() should support scope safe'
        + ' recursion');

    } else if (declaration === true) {

      ok(true,
        'Expression bar() should be callable via foo()');
        bar(false, true);

    } else {

      // Fails in IE8 and older
      ok(false, 
      'Expression bar() should NOT be callable outside'
      + ' the expression');

    }
  };

  bar();
  foo(true);

  // Fails in IE8 and older
  ok(testDeclaration, 
    'The bar() declaration should NOT get overridden by'
    + ' the expression bar()');
});
相关文章
|
16天前
|
JavaScript
变量和函数提升(js的问题)
变量和函数提升(js的问题)
|
16天前
|
JavaScript
常见函数的4种类型(js的问题)
常见函数的4种类型(js的问题)
10 0
|
16天前
|
JavaScript
写一个函数将N组<>(包含开始和结束),进行组合,并输出组合结果 (js)
写一个函数将N组<>(包含开始和结束),进行组合,并输出组合结果 (js)
9 0
|
27天前
|
自然语言处理 JavaScript 网络架构
js开发:请解释什么是ES6的箭头函数,以及它与传统函数的区别。
ES6的箭头函数以`=>`定义,简化了函数写法,具有简洁语法和词法作用域的`this`。它无`arguments`对象,不能用作构造函数,不支持`Generator`,且不改变`this`、`super`、`new.target`绑定。适用于简短表达式,常用于异步编程和高阶函数。
17 5
|
1月前
|
JavaScript 前端开发 网络架构
JavaScript 谈谈对箭头函数的理解及其与普通函数的区别。
JavaScript 谈谈对箭头函数的理解及其与普通函数的区别。
17 1
|
1月前
|
前端开发 JavaScript 数据处理
在JavaScript中,什么是异步函数执行的例子
在JavaScript中,什么是异步函数执行的例子
10 0
|
1月前
|
JavaScript
JS封装节流函数
JS封装节流函数
15 0
|
1月前
|
JavaScript 前端开发
javascript箭头函数
javascript箭头函数
|
1月前
|
JavaScript 小程序
微信小程序 wxml 中使用 js函数
微信小程序 wxml 中使用 js函数
67 0
|
1月前
|
JavaScript 前端开发
JavaScript函数科里化
JavaScript函数科里化