JavaScript 参数中的数组展开 [译]


Posted in Javascript onSeptember 21, 2012

译者注:本文要讲的是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 解决办法
第一步. 我们先让结果变的正确,稍候再考虑怎么用数组代替分割开的参数.

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)

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 相关文章推荐
JS中window.open全屏命令解析及使用示例
Dec 11 Javascript
js实现的牛顿摆效果
Mar 31 Javascript
jQuery中serializeArray()与serialize()的区别实例分析
Dec 09 Javascript
JavaScript自定义文本框光标
Mar 05 Javascript
从零开始学习Node.js系列教程四:多页面实现数学运算的client端和server端示例
Apr 13 Javascript
jquery实现tab选项卡切换效果(悬停、下方横线动画位移)
May 05 jQuery
Form表单上传文件(type="file")的使用
Aug 03 Javascript
微信小程序异步处理详解
Nov 10 Javascript
解决vue打包css文件中背景图片的路径问题
Sep 03 Javascript
基于vue实现圆形菜单栏组件
Jul 05 Javascript
微信小程序模板消息限制实现无限制主动推送的示例代码
Aug 27 Javascript
15个值得收藏的JavaScript函数
Sep 15 Javascript
JavaScript中将一个值转换为字符串的方法分析[译]
Sep 21 #Javascript
使用apply方法处理数组的三个技巧[译]
Sep 20 #Javascript
js DOM 元素ID就是全局变量
Sep 20 #Javascript
JavaScript NaN和Infinity特殊值 [译]
Sep 20 #Javascript
JavaScript 更严格的相等 [译]
Sep 20 #Javascript
JavaScript 反科里化 this [译]
Sep 20 #Javascript
Array.prototype.concat不是通用方法反驳[译]
Sep 20 #Javascript
You might like
那些年一起学习的PHP(一)
2012/03/21 PHP
php获取本地图片文件并生成xml文件输出具体思路
2013/04/27 PHP
smarty模板中拼接字符串的方法
2014/02/14 PHP
php实现设计模式中的单例模式详解
2014/10/11 PHP
简述php环境搭建与配置
2016/12/05 PHP
Zend Framework入门教程之Zend_Registry组件用法详解
2016/12/09 PHP
Ajax+Json 级联菜单实现代码
2009/10/27 Javascript
Jquery Ajax学习实例4 向WebService发出请求,返回实体对象的异步调用
2010/03/16 Javascript
Jquery代码实现图片轮播效果(一)
2015/08/12 Javascript
关于验证码在IE中不刷新的快速解决方法
2016/09/23 Javascript
浅谈regExp的test方法取得的值变化的原因及处理方法
2017/03/01 Javascript
详解webpack+angular2开发环境搭建
2017/06/28 Javascript
取消Bootstrap的dropdown-menu点击默认关闭事件方法
2018/08/10 Javascript
解决vue v-for 遍历循环时key值报错的问题
2018/09/06 Javascript
ng-repeat指令在迭代对象时的去重方法
2018/10/02 Javascript
node获取客户端ip功能简单示例
2019/08/24 Javascript
js实现坦克移动小游戏
2019/10/28 Javascript
javascript实现商品图片放大镜
2019/11/28 Javascript
Python语言编写电脑时间自动同步小工具
2013/03/08 Python
对python 多线程中的守护线程与join的用法详解
2019/02/18 Python
django之状态保持-使用redis存储session的例子
2019/07/28 Python
Python GUI自动化实现绕过验证码登录
2020/01/10 Python
Python中sys模块功能与用法实例详解
2020/02/26 Python
HTML5 localStorage使用总结
2017/02/22 HTML / CSS
html5使用Drag事件编辑器拖拽上传图片的示例代码
2017/08/22 HTML / CSS
全球知名巧克力品牌:Godiva
2016/07/22 全球购物
采用专利算法搜索最廉价的机票:CheapAir
2016/09/10 全球购物
Java程序开发中如何应用线程
2016/03/03 面试题
工程专业毕业生自荐信范文
2013/12/25 职场文书
七年级音乐教学反思
2014/01/26 职场文书
科技开发中心办公室主任岗位责任制
2014/02/10 职场文书
《童年的发现》教学反思
2014/02/14 职场文书
预备党员入党感想
2015/08/10 职场文书
人事任命书范本
2015/09/21 职场文书
五星级酒店宣传口号
2015/12/25 职场文书
如何做好工作总结!
2019/04/10 职场文书