JavaScript 中数组展开与构造函数调用的替代方案
本文探讨在ES6之前如何利用apply和bind实现数组展开为函数参数。重点解决new运算符无法直接使用apply的问题,通过Function.prototype.bind.apply或Object.create模拟构造函数调用,并分析内置构造函数如Date、Number的行为差异及返回值覆盖规则。
译者注:本文涉及ECMAScript 6中的知识点。若您尚未掌握ES5,建议尽快学习,因为CSS4、HTML6乃至ES7、ES8等标准已在规划中,停滞不前将面临淘汰风险。
展开操作基础
有时我们需要将数组展开为多个元素,作为函数调用的参数。在JavaScript中,可以使用Function.prototype.apply来实现这种展开操作,但它无法用于执行构造函数的情况。本文旨在解释什么是展开操作,以及如何在使用new运算符时进行展开操作。 1. 展开(Spreading)
展开是指在函数调用、方法调用或执行构造函数时,通过数组提供所需参数。在Python中,这种操作称为unpacking。ECMAScript.next已引入展开操作符(spread operator,表示为前缀...)来执行此操作。在当前JavaScript中,你可以通过Function.prototype.apply方法实现相同效果。
译者注:展开操作符不仅可用于实参位置展开数组,还可用于形参位置表示剩余参数。请参阅我翻译的MDN文档关于剩余参数的部分。 2. 展开函数参数 Math.max()方法返回其0到若干个数值类型参数中的最大值。使用展开操作符,你可以直接将数组作为参数传入: Math.max(...[13, 7, 30]) 这等同于以下写法
代码如下:
Math.max(13, 7, 30) 在当前JavaScript中,你可以使用apply()方法。
代码如下:
> Math.max.apply(null, [13, 7, 30])
30 apply方法的作用是使用以下调用方式:
代码如下:
func.apply(thisValue, [param1, param2, ...]) 来替代这种
代码如下:
thisValue.func(param1, param2, ...) 需要注意的是,func不一定是thisValue的方法,apply可以让它临时拥有该方法。
3. 展开构造函数的参数
展开构造函数的参数
Date构造函数接受几个数值类型的参数,生成一个Date对象。通过展开操作符,你可以直接传入一个数组。
代码如下:
new Date(...[2011, 11, 24]) // 2011年的圣诞夜 然而,这次我们不能使用apply方法来实现展开操作,因为它无法与new一起工作:
代码如下:
> new Date.apply(null, [2011, 11, 24])
TypeError: function apply() { [native code] } is not a constructor new运算符期望Date.apply是一个构造函数。即使你用括号将该表达式括起来,根本问题依然存在:apply执行的是函数调用,它无法将参数传递给new运算符。
3.1 解决办法
使用bind实现展开
第一步:我们先让结果正确,稍后再考虑如何用数组替代分割开的参数。
代码如下:
new (Date.bind(null, 2011, 11, 24)) 我们首先使用bind()创建一个无参数的函数(参数已绑定在该绑定函数的内部),然后使用new调用它,就像调用普通构造函数一样。bind的函数签名如下:
代码如下:
func.bind(thisValue, arg1, arg2, ...) bind函数将原函数
func转变成一个全新的函数,这个全新函数的this值永远是参数thisValue指定的值,并且它的初始参数包含了从arg1开始到最后的所有参数.当调用这个新函数时,新添加的参数会跟随在那些已有的通过
bind绑定的参数后面.MDN上有更详细的资料。注意上面的例子中,第一个参数是
null,因为
Date函数并不需要一个thisValue:在作为构造函数调用时,
new运算符会覆盖掉通过bind指定的thisValue.第二步:我们想把数组传给bind。因此再次使用了apply,将一个数组转换为展开的参数传递给bind函数。
代码如下:
new (Function.prototype.bind.apply(
Date, [null].concat([2011, 11, 24])))我们在函数Function.prototype.bind上调用apply方法,带有两个参数:
• 第一个参数:this的值指定为Date,也就相当于上面写的Date.bind(...).
• 第二个参数:传给bind方法的参数,null和后面的数组[2011, 11, 24]连接成的新数组。
3.2 一个库函数
封装库方法
Mozilla建议将上述工作封装成一个库方法。下面的代码正是在他们的建议之上稍微修改了一下:
代码如下:
if (!Function.prototype.construct) {
Function.prototype.construct = function(argArray) {
if (! Array.isArray(argArray)) {
throw new TypeError("Argument must be an array");
}
var constr = this;
var nullaryFunc = Function.prototype.bind.apply(
constr, [null].concat(argArray));
return new nullaryFunc();
};
} 运行一下:
代码如下:
> Date.construct([2011, 11, 24])
Sat Dec 24 2011 00:00:00 GMT+0100 (CET) 手动实现new运算符
3.3 一个看似更简单的解决方案
你可以手动实现new运算符的操作。例如:
代码如下:
var foo = new Foo("abc"); 实际上等同于:
代码如下:
var foo = Object.create(Foo.prototype);
Foo.call(foo, "abc"); 根据这个原理,我们可以写一个简单的库方法:
代码如下:
Function.prototype.construct = function(argArray) {
var constr = this;
var inst = Object.create(constr.prototype);
constr.apply(inst, argArray);
return inst;
}; 唉!Date作为一个普通函数来调用和作为一个构造函数来调用是一样的:它会忽略掉call()和apply()方法中第一个参数指定的this值,总会生成并返回一个新的实例。
译者注:这里作者理解有误,Date作为普通函数调用和作为构造函数来调用是完全不一样的。不加new的情况下,无论有没有参数,Date()只会返回当前时间的字符串,也就是(new Date()).toString()
代码如下:
> Date.construct([2011, 11, 24])
{} 译者注:内置的构造函数中,Array(),Function(),RegExp(),Error()等构造函数在调用时,加new或不加几乎一样。比如Array(10)也是生成一个数组,但Number(),String(),Boolean()就不一样了。不加new它们是类型转换函数,返回的是原始值,加new是构造函数,返回的是对象值。
代码如下:
>typeof Number("1")
"number"
>typeof new Number("1")
"object" 正如你所看到的,在操作Date()方法时,我们所写的这个construct()方法并不能如期工作,而且还有一些其他的内置构造函数也表现的和Date一样。不过如果是在操作一个库中自定义的构造函数的时候,这个方法基本可以正常工作(少部分构造函数返回了自己指定的对象值,而不是返回了默认的自动生成的实例this)。
译者注:一个构造函数的return语句只要返回的是个对象值,就会覆盖掉默认的this值。比如:
代码如下:
function Func1(){
this.value = "this"; return {}
}
function Func2(){
this.value = "this"; return 1}function Func3(){ this.value = "this"} >new Func1() //返回的{}是个对象值,覆盖了默认的this.{} >new Func2() //返回的1是个原始值,所以仍然返回默认的this.{value:"this"} >new Func3() //没有return语句,默认返回了undefined,是个原始值,所以仍然返回默认的this.{value:"this"} >new Func3 //没有参数时,小括号可以省略.{value:"this"} JavaScript入门书籍避坑:这些书不适合初学者
2026-11-05 16:31:15
JS使用Math.round实现保留两位小数的原理与代码
2026-11-05 15:35:55
JavaScript 原生 Date 对象怎么实现日期加减
2026-11-05 14:30:00
JavaScript函数定义的三种方式:声明、表达式与箭头函数
2026-11-05 13:50:55
JavaScript中escape、encodeURI和encodeURIComponent对空格的处理差异
2026-11-05 12:47:23
