函数属性、方法和构造函数


length 属性

在函数体中,arguments.length 表示传入函数的实参的个数,而函数本身的 length 属性则有着不同的含义,函数的 length 属性是只读属性,它代表函数形参的数量(期望传入实参的个数):

注:我们在代码使用使用 arguments.callee 代表当前正在执行的函数对象。

prototype属性

每个函数都包含 prototype 属性,它指向一个对象的引用,这个对象叫做「原型对象」。当函数用作构造函数时,新创建的对象会从原型对象上继承属性。

call() 和 apply()

可以将这两个方法看作是某个对象的方法,通过调用方法的形式来间接调用函数。call()apply() 的第一个实参是要调用函数的母对象,它是调用上下文,在函数体内通过 this 来获取对它的引用。要想以对象 o 的方法来调用函数 f,可以这么做:

f.call(o);
f.apply(o);

在 ECMAScript 5 严格模式中,call()apply()的第一个实参都会变为 this 的值,哪怕传入的实参是原始值甚至是 null 或 undefined。而在 ECMAScript 3 和 ECMAScript 5 非严格模式中,传入的 nullundefined 都会被全局对象代替,而其他原始值则会被相应的包装对象所替代。

要调用带参数的对象方法,可以这么做:

f.call(o, 1, 2);  // 等价于 o.f(1, 2);
f.apply(o, [1, 2]);  // 和上面的操作等价

通过这个例子,也可以看出两个方法的区别,就是在于传参的形式,一个是以参数列表的形式,一个是以参数数组的形式。

需要注意的是,传递给 apply() 的参数数组可以是类数组也可以是真实数组。事实上,可以将 arguments 直接传进 apply() 方法来调用函数。

bind() 方法

这是在 ECMAScript 5 中新增的方法,这个方法的主要作用就是将函数绑定到某个对象,具体实现如下:

要兼容 ECMAScript 3 代码,可以这么定义 bind 方法:

function bind(f, o) {
  if (f.bind)
    return f.bind(o);
  else 
    return function () {
      return f.apply(o, arguments);
    };
}

ECMAScript 5 的 bind() 方法不仅将函数绑定至一个对象,还附带一些其他应用:除了第一个实参之外,传入 bind() 的实参也会绑定至 this,这是一种常见的函数式编程技术,有时也称作「柯里化」:

在 ECMAScript 3 中,要模拟该实现,可以将这个方法另存为 Function.prototype.bind,以便所有的函数对象都能继承它:

if (!Function.prototype.bind) {
  Function.prototype.bind = function (o/*, args*/) {
    var self = this, boundArgs = arguments;
    return function () {
      var args = [], i;
      for (i = 1; i < boundArgs.length; i++) args.push(boundArgs[i]);
      for (i = 0; i < arguments.length; i++) args.push(arguments[i]);
      return self.apply(o, args);
    }
  };
}

ECMAScript 5 定义的 bind() 方法也有一些特性是 ECMAScript 3 无法模拟的,首先真正的 bind() 方法返回的函数对象 length 属性是绑定函数的形参个数减去绑定实参的个数(不能小于0),再者,ECMAScript 5 的 bind() 方法返回的函数可用作构造函数,当用作构造函数时,将忽略传入到 bind() 方法的 this。最后,由 bind() 方法返回的函数并不包含 prototype 属性,并且将这些绑定的函数用作构造函数时所创建的对象从原始的未绑定的构造函数中继承 prototype

toString() 方法

该方法返回一个字符串,这个字符串和函数声明语句的语法有关。实际上,大多数 toString() 方法的实现都返回函数的完整源码,而内置函数则返回「[native code]」字符串作为函数体:

Function() 构造函数

除了函数定义语句和函数直接量表达式之外,还可以通过 Function() 构造函数来定义函数:

var f = new Function("x", "y", "return x*y;");

上述代码创建的函数和下面代码定义的函数等价:

var f = function(x, y) { return x * y; };

如上所示,Function() 函数支持传入任意数量的字符串实参(没有参数则留空),最后一个参数表示的文本就是函数体,它可以包含任意的 JavaScript 语句,每条语句以分号结尾。Function() 构造函数创建的是一个匿名函数。

使用 Function() 构造函数的注意事项:

  • 可以通过 Function() 构造函数在运行时动态定义并编译函数
  • 每次调用 Function() 都会解析函数体并创建新的函数对象,在循环中使用会影响效率
  • 由 Function() 创建的函数并不使用词法作用域,相反,函数体代码的编译总是会在顶层函数执行

下面看一个具体的示例:

我们可以把 Function() 构造函数认为是在全局作用域中执行 eval(),在实际编程中很少用到,不仅是性能方面的考量还有可读性、可维护性也很差。

可调用的对象

和「类数组」类似,「可调用的对象」是一个对象,可以在函数调用表达式中调用这个对象,所有的函数都是可调用对象,但不是所有的可调用对象都是函数。下面列举两个可调用对象的例子。

在 IE 8 及之前版本中,客户端方法如 window.alert()Document.getElementsById() 使用了可调用的宿主对象,而不是内置函数对象,IE 中的这些方法在其他浏览器中也都存在,但它们本质上不是 Function 对象。

另外一个常见的可调用对象是 RegExp 对象,这是一个彻头彻尾的非标准特性,最开始被网景(Netscape)提出,后来被其他浏览器复制,这个特性在未来可能被废弃并删除。在不同浏览器中,对 RegExp 执行 typeof 运算的结果并不统一,有些返回「function」,有些返回「object」。

判断一个对象是否是函数对象和判断数组类似:

function isFunction(o) {
  return Object.prototype.toString.call(o) == '[object Function]';
}

点赞 取消点赞 收藏 取消收藏

<< 上一篇: 作用域和闭包

>> 下一篇: 函数式编程