ES6+中对函数、对象、字符串、数组、正则以及数值等对象都作了特性的扩展,本篇简要介绍ES6中对函数和数组的扩展特性。
一、函数的扩展
1.函数的默认值
从ES6开始,允许为函数参数设置默认值,即直接写在参数定义的后面。这样做使代码变得简洁自然,另外还有两个好处:
- 可以方便明确的知道哪些参数有默认值是可以省略的
- 有利于将来的代码优化,即使去掉这个参数也不会导致以前的代码无法运行
另外,参数变量是默认声明的,因此不能再使用let和const命令再次声明。定义了默认值的参数应该是函数的尾参数,这样就可以很容易的看出到底省略了哪些参数。如果尾部参数没有设置默认值那么是无法省略的。另外也无法直接省略处在中间的有默认值的参数,除非显示传入undefined
与解构赋值结合使用
参数的默认值可以和解构赋值结合使用,代码示例:
function({x, y = 5}){
console.log(x, y);
}
f({});//undefined, 5
f({x:1,y:2});//1,2
f({x:1});//1,5
另外《ES6标准入门》中还举了两个有趣的例子,如下:
function m1({x:0,y:0} = {}){
return [x, y];
}
function m2({x,y} = {x:0,y:0}){
return[x, y];
}
m1()//[0,0]
m2();//[0,0]
m1({x:3, y:8});//[3,8]
m2({x:3,y:8});//[3,8]
m1({x:3});//[3,0]
m2({x:3});//[3, undefined]
m1函数参数设置了默认值为空对象,但是设置了解构赋值的默认值,m2函数的参数默认值是一个有具体属性的对象,但是没有设置解构赋值的默认值。
length属性
指定默认值以后函数的length属性返回的是没有指定默认值参数的个数,因为length属性的含义就是函数预期传入参数的个数,当某个参数指定了默认值以后预期的传入参数就不再包括已经指定默认值的参数。
2.rest参数与扩展运算符
ES6引入rest参数(形式为’…变量名’),用于获取函数的多余参数,该功能类似于Java中的不确定数量的参数,rest参数将不定个数的参数转换为名称为指定变量名的数组,然后我们可以通过众多的数组函数对其进行操作
注意一点,rest函数必须是函数的最后一个参数否则会报错
与rest参数相反,扩展运算符 … 将一个数组转为用逗号分隔的参数序列。扩展运算符提供了众多的用途
替代数组的apply方法
下面是求数组最大值的不同写法:
//ES5
Math.max.apply(null,[14,2,4]);
//ES6
Math.max(...[14,2,4]);
合并数组
var arr1 = ['a', 'b'];
var arr2 = ['c', 'd'];
var arr3 = ['e'];
//ES5
arr1.contact(arr2,arr3);
//ES6
[...arr1, ...arr2, ...arr3];//['a', 'b', 'c', 'd', 'e']
与解构赋值结合
两者可以结合使用生成数组
const [first, ...rest] = [1, 2, 3, 4, 5];
first //1
rest //[2,3,4,5]
将字符串转为数组
[..."hello"]// ['h', 'e', 'l', 'l', '0']
使用扩展运算符可以正确的识别32位的Unicode字符
转换类似数组的对象、Set、Map以及Generator函数
//转换对象
var nodelist = document.querySelectorAll('div');
var array = [...nodelist]
//转换Map。Map和Set都是具有Iterator的对象, 只要是具有Iterator接口的对象都可以使用扩展运算符
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let arr = [...map.keys()];//[1, 2, 3]
//转换Generator函数,Generator函数运行后返回一个遍历器对象,因此也可以使用扩展运算符
var go = function*(){
yield 1;
yield 2;
yield 3;
};
3.name属性
函数的name属性返回函数名,如果是匿名函数,ES5返回空字符串,而ES6返回实际的函数名。Function构造函数返回的函数示例name属性值为’anonymous’,bind返回的函数name属性值会加上’bound’前缀
var func1 = function(){};
func1.name;//ES5:"" ES6:func1
//如果将一个具名函数返回给一个变量,ES5/ES6都会返回这个具名函数原本的名字而不是变量名
const bar = function baz(){};
bar.name();//baz
4.箭头函数
ES6允许使用* 箭头 => *定义函数,箭头左边为参数,右边为方法体。如果不需要参数则用圆括号代表参数部分,如果方法体多于一行则用大括号括起来并用return表示返回。
var f = v => v;
//等价于
var f = function(v){ return v};
var f = ()=>5;
//等价于
var f = function(){
return 5;
}
箭头函数的几点注意:
* 函数体内的this总是该函数定义时所在的对象。因为箭头函数根本没有自己的this,因此其内部的this就是外层代码块的this
* 不可以当做构造函数,即不可以使用new命令
* 不可以使用arguments对象,该对象在函数体内不存在, 可以用rest代替
* 不可以使用yield命令,因此箭头函数无法用作Generator函数
5.函数绑定、尾逗号以及尾调用优化
ES7提出了函数绑定用来取代call、apply、bind调用,运算符是双冒号* :: *, 其坐标是一个对象,右边是一个函数,它将自动将左边的对象作为上下文环境(即this对象)绑定到右边的函数中。其返回的还是原对象因此可以使用链式调用
let {find, html} = jake;
document.querySelectorAll("div.myClass")
::find("p")
::html("hahaha");
尾调用优化
尾调用是函数式编程的一个重要概念, 指某个函数的最后一步是调用另一个函数。尾调用可以不出现在尾部,只要是函数操作的最后一步即可。
在函数的调用过程中,会形成调用栈,尾调用与其他的调用不同在于因为尾调用是函数的最后一步操作,因此不需要外层函数的调用栈,这样就只保留了内层的调用栈。因此如果所有的函数都是尾调用那么完全可以做到每次执行调用时只有一帧,将大大的节省内存,这就是所谓的尾调用优化
尾调用可以对递归进行优化。函数调用自身成为递归,如果尾调用自身就成为尾递归。因为可能需要保存大量的调用栈因此非常消耗内存。尾递归很好的解决了这一问题。
尾递归的实现往往需要改写递归函数,确保其最后只调用自身。方法就是:把所有用到的内部变量变为函数的参数。但这么做会导致函数的可读性降低,这里有两个方法解决:
一是在尾递归函数之外再提供一个正常形式的函数来调用尾递归函数,另一个就是柯里化,是函数式编程的一个概念,是将多参数的函数转换为单参数的形式。
尾逗号
从ES6开始允许函数的最后一个参数有尾逗号
function func(
foo,
bar,){
}
二、数组的扩展
1.ES6针对数组的方法扩展
Array.from()
该方法用于将两类对象转换为真正的数组:类似数组的对象和可遍历对象(包括ES6新增的数据结构Set和Map)。只要是部署了Iterator接口的数据结构,Array.from()都能将其转为数组,另外还支持类似数组的对象, 简单来说就是必须有length属性,任何有length属性的对象都可以通过Array.from()方法转为数组。
let arraryList = {
'0':'a',
'1':'b',
'2':'c',
length:3
};
//ES5写法
var arr1 = [].slice.call(arrayLike);//['a', 'b', 'c']
//ES6写法
let arr = Arrary.from(arrayLike);//['a', 'b', 'c']
//还可以接受第二个参数,作用类似于map方法用来对每个元素进行处理
Array.from([1,2,3], (x)=>x*x);//[1,4,9]
//扩展运算符** ... **也可以将部署Iterator接口的数据结构转化为数组,但是无法转化对象。
通过该方法, 我们可以将各种值转化为真正的数组并提供map功能。这意味着只要有一个原始的数据结构,就可以先对它进行值的处理,然后转换为数组结构,进而可以使用数量众多的数组方法
另外可以通过Array.from()方法将字符串转换为数组,然后返回字符串的长度.因为它能正确处理各种Unicode字符,可以避免JavaScript将大于\uFFFF的字符算作两个字符的bug.
Array.of()
用于将一组值转换为数组, 该方法主要是为了弥补弥补数组构造函数Array()的不足。
Array()方法只有一个参数时代表指定的数组长度,只有不少于两个参数时才会返回由参数组成的数组。而Array.of()方法总是返回参数值组成的数组,如果没有参数就返回
一个空数组。
Array();//[]
Array(3);//[ , , ,]
Array(3,11,8);//[3,11,8]
Array.of();//[]
Array.of(1);//[1]
Array.of(1,2)//[1,2]
Arrar.of(undefined);//[undefined]
copyWithin()
在当前数组内部将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法会修改当前数组。
//三个参数
//target(必需): 从该位置其替换数据
//start(可选): 从该位置开始读取数据,默认为0,如果是负数表示倒数
//end(可选): 到该位置前停止读取数据,默认等于数组长度,负数表示倒数
[1,2,3,4,5].copyWithin(0,3,4);//[4,2,3,4,5]
[1,2,3,4,5].copyWithin(0,-2,-1);//[4,2,3,4,5]
find()与findIndex()方法
find()参数是一个回调函数,用来找出符合第一个条件的数组成员。所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员则返回undefined。
findIndex()方法返回第一个符合条件的数组成员的位置,如果所有成员都不符合则返回-1.
另外这两个方法还可以接受第二个参数用来绑定回调函数的this对象,还可以识别数组的NaN成员,弥补了indexOf方法的不足。
fill()
使用给定值填充数组
//数组中的元素会被全部抹去,可以非常方便的初始化空数组
['a','b','c'].fill(7);//[7, 7, 7]
//还可以接受第二、三个参数,用于指定填充的起始和结束位置
['a', 'b', 'c'].fill(7,1,2);//['a', 7, 'c']
entires()、keys()和values()
用来遍历数组,返回一个遍历器对象可用for…of循环遍历。keys()是对键名的遍历, value()是对键值的遍历,entires()是对键值对的遍历。
let keys = [1,2,3].keys();
for(let index of keys){
console.log(index);//0,1
}
let kv = ['a','b'].entires();
for(let [index, elem] of kv){
console.log(index,elem);
}
//0,'a'
//1, 'b'
includes()
判断某个数组是否包含给定的值,类似于字符串的includes方法,该方法属于ES7, 但是Babel转码器已经支持。可以接受第二个参数表示搜索的起始位置,默认从0开始,如果为负数则表示倒数的位置,如果超过数组长度则重置从0开始。另外该方法不会对NaN产生误判
[1,2,3].includes(3,3);//false
[1,2,3].includes(2)//true
[NaN].includes(NaN);//true
注意和Mao、Set数据结构的has方法区分:
* Map结构的has方法是用来查找键名的
* Set结构的has方法是用来查找值的
2.数组的空位与数组推导
数组的空位
数组的空位指数组的某一个位置没有值任何值。注意是没有任何值,包括undefined。一个位置的值等于undefined依然是有值的。
ES5中对空位处理并不一致,在ES6中明确将空位转为undefined。
ES5的处理方式:
- forEach()、filter()、every()、some()都会跳过空位
- map()会跳过空位但是会保留这个值
- join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
由于空位的处理规则非常不统一,所以应该避免出现空位
数组推导
数组推导允许我们通过现有的数组生成新数组。该项功能推迟到了ES7, Babel转码器已经支持该功能。
数组推导中for…of结构总是卸载最前面,返回的表达式写在最后面
var a1 = [1,2,3,4];
var a2 = [for(i of a1) i*2];//2,4,6,8
var years = [1954, 1974, 1990, 2006, 2010, 2014];
[for(year of years) if(year > 2000) if(year<2010)year];//[2006]
注意两点:
- 数组推导的方括号构成了一个单独的作用域,在其中声明的变量类似于使用let命令声明的变量
- 新数组会立即在内存中生成,因此如果原数组很大将非常耗内存