《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()');
});
相关文章
|
1月前
|
JavaScript 前端开发 Java
[JS]同事:这次就算了,下班回去赶紧补补内置函数,再犯肯定被主管骂
本文介绍了JavaScript中常用的函数和方法,包括通用函数、Global对象函数以及数组相关函数。详细列出了每个函数的参数、返回值及使用说明,并提供了示例代码。文章强调了函数的学习应结合源码和实践,适合JavaScript初学者和进阶开发者参考。
43 2
[JS]同事:这次就算了,下班回去赶紧补补内置函数,再犯肯定被主管骂
|
1月前
|
前端开发 JavaScript 开发者
除了 Generator 函数,还有哪些 JavaScript 异步编程解决方案?
【10月更文挑战第30天】开发者可以根据具体的项目情况选择合适的方式来处理异步操作,以实现高效、可读和易于维护的代码。
|
2月前
|
JavaScript 前端开发
JavaScript 函数语法
JavaScript 函数是使用 `function` 关键词定义的代码块,可在调用时执行特定任务。函数可以无参或带参,参数用于传递值并在函数内部使用。函数调用可在事件触发时进行,如用户点击按钮。JavaScript 对大小写敏感,函数名和关键词必须严格匹配。示例中展示了如何通过不同参数调用函数以生成不同的输出。
|
2月前
|
存储 JavaScript 前端开发
JS函数提升 变量提升
【10月更文挑战第6天】函数提升和变量提升是 JavaScript 语言的重要特性,但它们也可能带来一些困惑和潜在的问题。通过深入理解和掌握它们的原理和表现,开发者可以更好地编写和维护 JavaScript 代码,避免因不了解这些机制而导致的错误和不一致。同时,不断提高对执行上下文等相关概念的认识,将有助于提升对 JavaScript 语言的整体理解和运用能力。
|
3月前
|
JavaScript 前端开发 安全
JavaScript函数详解
JavaScript函数的详细解析,包括函数的定义和调用方式(如一般格式、匿名函数、构造函数、自调用函数、箭头函数和严格模式)、函数参数(arguments对象、可变参数、默认参数值)、闭包的概念和应用实例。
JavaScript函数详解
|
2月前
|
JavaScript 前端开发
js教程——函数
js教程——函数
52 4
|
2月前
|
存储 JavaScript 前端开发
js中函数、方法、对象的区别
js中函数、方法、对象的区别
23 2
|
2月前
|
JavaScript 前端开发 Java
【javaScript数组,函数】的基础知识点
【javaScript数组,函数】的基础知识点
32 5
|
2月前
|
JavaScript 前端开发
Node.js 函数
10月更文挑战第5天
26 3
|
2月前
|
前端开发 JavaScript
探索JavaScript函数基础
探索JavaScript函数基础
23 3