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

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()');
});

时间: 2025-01-01 00:44:37

《JavaScript应用程序设计》一一2.2 函数声明的相关文章

跟我学习javascript的var预解析与函数声明提升_javascript技巧

1.var 变量预编译 JavaScript 的语法和 C .Java.C# 类似,统称为 C 类语法.有过 C 或 Java 编程经验的同学应该对"先声明.后使用"的规则很熟悉,如果使用未经声明的变量或函数,在编译阶段就会报错.然而,JavaScript 却能够在变量和函数被声明之前使用它们.下面我们就深入了解一下其中的玄机. 先来看一段代码: (function() { console.log(noSuchVariable);//ReferenceError: noSuchVari

《JavaScript应用程序设计》一一2.4 立即执行函数表达式

2.4 立即执行函数表达式 在JavaScript中,我们可以在函数声明后立即将其执行,这个技术曾在社区中被称为"自执行匿名函数",不过冠以这个名称让人们觉得函数能够被递归调用,显然有点不太妥.Ben Alman曾发表了一篇名为"自执行函数表达式"(IIFE)的博文(http://bit.ly/i-ife/),相比之下,人们觉得缩写IIFE显得更为有趣且清晰易懂,现在IEIF一词已经在JavaScript社区中广为使用.IIFE所创建出的临时作用域常常用来作模块封装

《JavaScript应用程序设计》一一2.7 变量提升

2.7 变量提升 变量提升是指函数中的所有变量声明会在函数执行时被"提升"至函数体顶端,这仅仅是从非技术角度来阐述的,至少从开发者看来实际效果如此. JavaScript的执行环境构建分为声明阶段和执行阶段.在声明阶段JavaScript引擎为所有变量与函数声明创建标识符,可以将此阶段看作是对运行环境的前期配置.到了执行阶段,函数均已被定义,但所有变量的值均未定义,例如: var x = 1; (function () { console.log(x); var x = 2; }())

javascript-Function构造函数 vs 函数声明的区别是什么?

问题描述 Function构造函数 vs 函数声明的区别是什么? 首先提供一个让我产生疑惑的链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions#Function构造函数_vs_函数声明_vs_函数表达式 图片中这句话 让我无比疑惑,因为在我理解里,构造函数应该是: var name=new Function("x","y","return x*y&qu

《JavaScript应用程序设计》一一3.8 工厂函数

3.8 工厂函数 使用对象字面量带来的便捷是显而易见的,不过它们无法封装私有数据.封装的概念之所以在编程中具有价值,是因为它将程序内部的实现细节对使用者做了隐藏.回忆一下"四人帮"在面向对象设计模式一书中首章的描述,"面向接口编程,而不是面向实现编程",封装将这一编码原则在代码中贯彻,即对使用者隐藏实现细节.不过,经过前面几节的介绍,你已经对构造函数的弊病有所了解,并知晓如何去规避.下面介绍一种构造函数的替代方案:工厂函数.工厂函数被用来构建并实例化对象,使用它的目

JavaScript高级程序设计(第3版)学习笔记8 js函数(中)_基础知识

6.执行环境和作用域 (1)执行环境(execution context):所有的JavaScript代码都运行在一个执行环境中,当控制权转移至JavaScript的可执行代码时,就进入了一个执行环境.活动的执行环境从逻辑上形成了一个栈,全局执行环境永远是这个栈的栈底元素,栈顶元素就是当前正在运行的执行环境.每一个函数都有自己的执行环境,当执行流进入一个函数时,会将这个函数的执行环境压入栈顶,函数执行完之后再将这个执行环境弹出,控制权返回给之前的执行环境. (2)变量对象(variable ob

JavaScript高级程序设计(第3版)学习笔记7 js函数(上)_基础知识

变量类型 在说函数之前,先来说说变量类型. 1.变量:变量在本质上就是命名的内存空间. 2.变量的数据类型:就是指变量可以存储的值的数据类型,比如Number类型.Boolean类型.Object类型等,在ECMAScript中,变量的数据类型是动态的,可以在运行时改变变量的数据类型. 3.变量类型:是指变量本身的类型,在ECMAScript中,变量类型就只有两种:值类型和引用类型.当变量的数据类型是简单数据类型时,变量类型就是值类型,当变量的数据类型是对象类型时,变量类型就是引用类型.在不引起

JavaScript函数声明与表达式小结

函数声明与表达式 函数是JavaScript中的一等对象,这意味着可以把函数像其它值一样传递. 一个常见的用法是把匿名函数作为回调函数传递对异步函数中. 函数声明 function foo() {} 上面的方法会在执行前被 解析(hoisted),因此它存在于当前上下文的任意一个地方, 即使在函数定义体的上面被调用也是对的. foo(); // 正常运行,因为foo在代码运行前已经被创建 function foo() {} 函数赋值表达式 var foo = function() {}; 这个例

Javascript中函数声明与函数表达式的区别

Js中的函数声明是指下面的形式: function functionName(){ } 这样的方式来声明一个函数,而函数表达式则是类似表达式那样来声明一个函数,如: var functionName = function(){ } 可能很多朋友在看到这两一种写法时会产生疑惑,这两种写法差不多,在应用中貌似也都是可行的,那他们有什么差别呢? 事实上,js的解析器对函数声明与函数表达式并不是一视同仁地对待的.对于函数声明,js解析器会优先读取,确保在所有代码执行之前声明已经被解析,而函数表达式,如同