使用ES6默认参数值,Spread和Rest的技术


默认行为

默认参数值让函数参数在未传递值或未定义时用默认值初始化。

function join(arr=[], sep=','){
    return arr.join(sep);
}

join();//""
join([1,2,3]); //"1,2,3"
join(["Javascript", "is", "awesome"], " "); //"Javascript is awesome" 


我们也可以指定一个函数作为默认值。

import rp from 'request-promise';

function jsonParser(body, response) {
    if (/^application\/json.*/i.test(response.headers['content-type'])){
        return JSON.parse(body);
    }
    return body;
}
function fetch(url, transform=jsonParser) {
    return rp({
        url: url,
        transform: jsonParser
    });
}


必需参数

另一种具有默认参数值的技术是允许函数声明所需的参数。在设计需要参数验证的API时,这确实很有用。

function required(param){
    throw new Error(`${param} is required`);
}
const Storage = {
    setItem: function setItem(key = required('key'), value=required('value')){
           //implentation code goes here
    },
    getItem: function getItem(key = required('key')){
    }    
}

Storage.setItem();//Uncaught Error: key is required
Storage.setItem('key1');//Uncaught Error: value is required
Storage.setItem('key1', 'value1'); //OK


复制数组并修改它们

在ES5中,我们可以使用Array#concat或Array#slice来制作数组的副本。

var arr = [1, 2, 3, 4, 5];
var arr2 = arr.slice(0); //1, 2, 3, 4, 5
var arr3 = [].concat(arr); //1, 2, 3, 4, 5


在ES6中,使用扩展运算符复制数组甚至更容易。

const arr = [1, 2, 3, 5, 6];
const arr2 = [...arr]; //1, 2, 3, 5, 6
const b = [...arr.slice(0, 3), 4, ...arr.slice(3)];//1, 2, 3, 4, 5, 6


复制对象并修改它们

在ES5中,我们可以借用一个实用函数,如jQuery#extend,_.assign来制作一个对象的副本:

var o = {
    name: 'John',
    age: 30,
    title: 'Software Engineer',
}
var o2 = _.assign({}, o);
var o3 = _.assign({}, o, {age: 25});


在ES6中,我们可以使用内置函数object.assign:

const o = {
    name: 'John',
    age: 30,
    title: 'Software Engineer',
}
const o2 = Object.assign({}, o);
const o3 = Object.assign({}, o, {age: 25});


另一种方法是使用扩展(…)运算符:

const o2 = {
    ...o
}
const o3 = {
    ...o,
    age: 25
}


注意:ES6不支持对象的扩展运算符。希望,这将包括在ES7。如果你用的是像巴贝尔这样的转译器,你就被保护了。

避免Function.Prototype.Apply

一些函数如Math.max,Math.min,Date等需要参数列表。

Math.max(1, 100, 90, 20);
new Date(2016, 7, 13);


如果我们有一个包含在数组中的参数值列表呢?解决方法是使用function.prototype.apply(thisArg,[])

var numbers = [1, 100, 90, 20];
Math.max.apply(null, numbers); // 100



在ES6中,使用扩展运算符:

var numbers = [1, 100, 90, 20];
Math.max(...numbers);

var parts = [2016, 7, 13];
var d = new Date(...parts);


忘记参数

arguments是一个类似数组的对象,可在函数调用中使用。它表示调用函数时传入的参数列表。有一些问题:

  • 参数不是数组。我们需要将其转换为数组,如果我们想使用数组方法,如slice,concat等。
  • 参数可能被函数参数或同名变量遮蔽。
function doSomething(arguments) {
    console.log(arguments);
}
doSomething(); //undefined
doSomething(1); //1

function doSomething2() {
    var arguments = 1;
    console.log(arguments);
}
doSomething2();// 1
doSomething2(2, 3, 4); // 1


在ES6中,我们完全可以忘记参数。使用rest(…)运算符,我们可以收集传递函数调用的所有参数:

function doSomething(...args) {
    console.log(args);
}

doSomething(1, 2, 3, 4); //[1, 2, 3, 4]


使用rest运算符,传递给doSomething的所有参数都被收集到args中。不仅如此,args是一个数组,因此我们不需要像参数那样额外地转换为数组。

合为一体

在这一部分中,我们将在一个复杂的案例中使用上述技术。让我们实现fetchAPI。为了简单起见,我们将API构建在request-promise模块。

function fetch(url, options){

}


第一步是参数检查:

//ES5
import rp from 'request-promise';
function fetch(url, options){
    var requestURL = url || '';
    var opts = options || {};
    ...
}
//ES6
import rp from 'request-promise';
function fetch(url='', options={}){
   ...
}


我们还需要检查options对象的一些属性:

function jsonParser(body, response) {
    if (/^application\/json.*/i.test(response.headers['content-type'])){
        return JSON.parse(body);
    }
    return body;
}
//ES5
import rp from 'request-promise';
function fetch(url, options){
    var requestURL = url || '';
    var opts = options || {};
    var method = options.method || 'get';
    var headers = opts.headers || {'content-type': 'application/json'};
    var transform = jsonParser;
    ...
}
//ES6
import rp from 'request-promise';
function fetch(url='', {method='get',
                        headers={'content-type': 'application/json'},
                        transform=jsonParser}){

}


在API的ES6版本中,我们使用析构来提取一些属性(method,headers和transform)并设置一些默认值。如果我们不传递options对象,这将不起作用,因为我们无法将模式与未定义的:

fetch();//TypeError: Cannot match against 'undefined' or 'null'


这可以通过默认值来修复:

//ES6
import rp from 'request-promise';
function fetch(url='', {method='get',
                        headers={'content-type': 'application/json'},
                        transform=jsonParser} = {}){
    return rp({
        url: url,
        method: method,
        headers: headers,
        transform: transform
    });
}


因为客户端代码可以传递除方法,标题,和转换时,我们需要复制所有剩余的属性:

//ES5
import rp from 'request-promise';
function fetch(url, options){
    var requestURL = url || '';
    var opts = options || {};
    var method = options.method || 'get';
    var headers = opts.headers || {'content-type': 'application/json'};
    var transform = jsonParser;
    //copy all properties and then overwrite some
    opts = _.assign({}, opts, {method: method, headers: headers, transform: transform})

    return rp(opts);
}


在ES6中,我们需要通过使用rest运算符来收集剩余的属性:

function fetch(url='', {method='get',
                        headers={'content-type': 'application/json'},
                        transform=jsonParser,
                        ...otherOptions} = {}){

}


。。。并使用扩展运算符将这些属性传递给目标函数:

function fetch(url='', {method='get',
                        headers={'content-type': 'application/json'},
                        transform=jsonParser,
                        ...otherOptions} = {}){
    return rp({
        url: url,
        method: method,
        headers: headers,
        transform: transform,
        ...otherOptions
    });   
}


最后,使用对象文字速记法,我们可以这样写:

function fetch(url='', {method='get',
                        headers={'content-type': 'application/json'},
                        transform=jsonParser,
                        ...otherOptions} = {}){
    return rp({
        url,
        method,
        headers,
        transform,
        ...otherOptions
    });   
}