ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
ECMAScript 和 JavaScript 的关系
一个常见的问题是,ECMAScript 和 JavaScript 到底是什么关系?
要讲清楚这个问题,需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。
该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。
因此,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的。
let 和 const 命令
首先理解一下变量提升的定义:所有变量的声明语句都会被提升到代码头部,这就是变量提升。
例如:
console.log(a); var a =1;
以上语句并不会报错,只是提示undefined
。实际在js引擎中的运行过程是:
var a; console.log(a); a =1;
实际运行表示变量a已声明,但还未赋值。即变量可以在声明之前使用,值为undefined。
let命令
ES6 新增了let命令,用来声明变量。let命令改变了语法行为,只在let命令所在的代码块内有效,它所声明的变量一定要在声明后使用,否则报错。
{ let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1
for循环有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) { let i = 'abc'; console.log(i); } // abc // abc // abc
上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域。
const 命令
const声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable.
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
数组的解构赋值
let [a, b, c] = [1, 2, 3]; console.log(a) // 1 console.log(b) // 2 console.log(c) // 3 let [x, , y] = [1, 2, 3]; console.log(x) // 1 console.log(y) // 3
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果解构不成功,变量的值就等于undefined。
另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
let [x, y] = [1, 2, 3]; x // 1 y // 2 let [a, [b], d] = [1, [2, 3], 4]; a // 1 b // 2 d // 4
默认值
解构赋值允许指定默认值。
let [foo = true] = []; foo // true let [x = 1] = [undefined]; x // 1 let [x, y = 'b'] = ['a']; // x='a', y='b' let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
上面代码中,如果一个数组成员是 null,默认值就不会生效,因为null不严格等于 undefined。
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f() { console.log('aaa'); } let [x = f()] = [1];
默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
let [x = 1, y = x] = []; // x=1; y=1 let [x = 1, y = x] = [2]; // x=2; y=2 let [x = 1, y = x] = [1, 2]; // x=1; y=2 let [x = y, y = 1] = []; // ReferenceError: y is not defined
上面最后一个表达式之所以会报错,是因为x用y做默认值时,y还没有声明。
对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。如果解构失败,变量的值等于undefined。
let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb" let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb" let { baz } = { foo: 'aaa', bar: 'bbb' }; baz // undefined
如果变量名与属性名不一致,必须写成下面这样。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' }; baz // "aaa" let obj = { first: 'hello', last: 'world' }; let { first: f, last: l } = obj; f // 'hello' l // 'world'
与数组一样,解构也可以用于对象数组。
let data = [ { "key": 1, "payPeriods": "2", "planPayTime": "2020-03-12 03:46:34", "playMoney": "3", "percent": "23", "remark": "测试", "editable": true } ] let [{ payPeriods, planPayTime, playMoney, percent, remark }] = data console.log(payPeriods, planPayTime, playMoney, percent, remark)
下面例子中的 p 是模式,不是变量,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。
let obj = { p: [ 'Hello', { y: 'World' } ] }; let { p, p: [x, { y }] } = obj; x // "Hello" y // "World" p // ["Hello", {y: "World"}]
默认值
对象的解构也可以指定默认值。
var {x = 3} = {}; x // 3 var {x, y = 5} = {x: 1}; x // 1 y // 5 var {x: y = 3} = {}; y // 3 var {x: y = 3} = {x: 5}; y // 5 var { message: msg = 'Something went wrong' } = {}; msg // "Something went wrong"
默认值生效的条件是,对象的属性值严格等于undefined。
var {x = 3} = {x: undefined}; x // 3 var {x = 3} = {x: null}; x // null
如果要将一个已经声明的变量用于解构赋值,必须非常小心。
// 错误的写法 let x; {x} = {x: 1}; // SyntaxError: syntax error
上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。
// 正确的写法 let x; ({x} = {x: 1});
上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。
字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o"
函数参数的解构赋值
函数的参数也可以使用解构赋值。
function add([x, y]){ return x + y; } add([1, 2]); // 3
上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。
函数参数的解构也可以使用默认值。
function move({x = 0, y = 0} = {}) { return [x, y]; } move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, 0] move({}); // [0, 0] move(); // [0, 0]
上面代码中,函数move的参数是一个对象,通过对这个对象进行解构,得到变量x和y的值。如果解构失败,x和y等于默认值。
注意,下面的写法会得到不一样的结果。
function move({x, y} = { x: 0, y: 0 }) { return [x, y]; } move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, undefined] move({}); // [undefined, undefined] move(); // [0, 0]
上面代码是为函数move的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。
数组的扩展
扩展运算符
扩展运算符(spread)是三个点(...
)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
可以这么理解,数组是存放元素集合的一个容器,而使用拓展运算符可以将这个容器拆开展示,这样就只剩下元素集合,你可以把这些元素集合放到一个数组里面。
将一个数组,变为参数序列:
let add = (x, y) => x + y; let numbers = [3, 45]; console.log(add(...numbers))//48
使用push将一个数组添加到另一个数组的尾部:
let arr1 = [1, 2, 3]; let arr2 = [4, 5, 6]; arr1.push(...arr2);
字符串转换成数组:
扩展运算符还可以将字符串转为真正的数组。
[...'hello'] // [ "h", "e", "l", "l", "o" ]
合并数组
var arr1 = ['a', 'b']; var arr2 = ['c']; var arr3 = ['d', 'e']; // ES5 的合并数组 arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] // ES6 的合并数组 [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]
复制数组
数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
const a1 = [1, 2]; const a2 = a1; a2[0] = 2; a1 // [2, 2]
上面代码中,a2并不是a1的克隆,而是指向同一份数据的另一个指针。修改a2,会直接导致a1的变化。
ES5 只能用变通方法来复制数组。
const a1 = [1, 2]; const a2 = a1.concat(); a2[0] = 2; a1 // [1, 2]
扩展运算符提供了复制数组的简便写法。
const a1 = [1, 2]; // 写法一 const a2 = [...a1]; // 写法二 const [...a2] = a1;
forEach()
遍历数组全部元素,利用回调函数对数组进行操作,自动遍历整个数组,且无法break中途跳出循环,不可控,不支持return操作输出,return只用于控制循环是否跳出当前循环。
回调有三个参数:第一个参数是遍历的数组内容,第二个参数是对应的数组索引,第三个参数是数组本身。这个方法是没有返回值的,仅仅是遍历数组中的每一项,不对原来数组进行修改。
注意: forEach() 对于空数组是不会执行回调函数的。
var ary = [12,23,24,42,1]; ary.forEach((item,index,arr) { arr[index] = item*10; })
map()
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。map() 方法按照原始数组元素顺序依次处理元素。
注意: map() 不会对空数组进行检测。
注意: map() 不会改变原始数组。
function(currentValue, index,arr){ // currentValue 必须。当前元素的值 // index 可选。当前元素的索引值 //arr 可选。当前元素属于的数组对象 }
利用 map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
this.arrList.map(item => ({ ...item })) var data = [1, 2, 3, 4]; var arrayOfSquares = data.map(function (item) { //接收新数组 return item * item; }); alert(arrayOfSquares); // [1, 4, 9, 16]
js 纯数组转换为数组对象
let arr = ["刘备","关羽","张飞"] let obj = {}; // 将数组转化为对象 for (let key in arr) { obj[key] = arr[key] } newObj = Object.keys(obj).map(val => ({ label: obj[val], value: obj[val] })) console.log(newObj) //[{label:"刘备",value:"刘备"},{label:"关羽",value:"关羽"},{label:"张飞",value:"张飞"}]
filter()
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
filter 为数组中的每个元素调用一次 callback 函数,并利用所有使得 callback 返回 true 或 等价于 true 的值 的元素创建一个新数组。那些没有通过 callback 测试的元素会被跳过,不会被包含在新数组中。filter 不会改变原数组。
注意: filter() 不会对空数组进行检测。
注意: filter() 不会改变原始数组。
function(currentValue, index,arr){ // currentValue 必须。当前元素的值 // index 可选。当前元素的索引值 // arr 可选。当前元素属于的数组对象 } var arr = [10, 20, 30, 40, 50] var newArr = arr.filter(item => item > 30); console.log(newArr); //[40, 50] // 遍历一个id数组并判断是否存在于对应对象数组中 var arr = this.selectedRowKeys //Id数组 var purProdList = [...this.purProdList] arr.forEach(id => { var record = newData.filter(item => id === item.id)[0] record.projectBugetId = param.projectBugetId record.expenseTypeName = param.expenseTypeName record.expenseItemName = param.expenseItemName }) //根据对象属性拆分数组 //根据对象中tag属性的值,把tag属性值为优的数组赋值给youList,把tag属性值为良的数组赋值给LiangList getHeziMarketRanking(this.requestParams).then(res => { this.heziMarketRa0nkingResp = res.data this.youList = this.heziMarketRankingResp.filter(item => item.tag === '优'); this.LiangList = this.heziMarketRankingResp.filter(item => item.tag === '良'); }).catch(error => { console.log(error) reject(error) }) // 数组去重 var arr = [1, 2, 3, 1, 2, 3, 4, 5, 5]; var resultArr; resultArr = arr.filter(function (item, index, self) { return self.indexOf(item) == index; }); console.log(resultArr);
from()
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
下面是一个类似数组的对象, Array.from 将它转为真正的数组。
let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 }; // ES5的写法 var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c'] // ES6的写法 let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
实际应用中,常见的类似数组的对象是 DOM 操作返回的 NodeList 集合,以及函数内部的 arguments 对象。Array.from 都可以将它们转为真正的数组。
Array.of()
Array.of 方法总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
Array.of(3, 11, 8) // [3,11,8] Array.of(3) // [3] Array.of(3).length // 1
find() 和 findIndex()
数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
[1,4,-5,10].find((n) => n>0) // 1 [1, 5, 10, 15].find(function(value, index, arr) { return value > 9; }) // 10
上面代码中,find 方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组。
数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
[1, 5, 10, 15].findIndex(function(value, index, arr) { return value > 9; }) // 2
这两个方法都可以接受第二个参数,用来绑定回调函数的 this 对象。
function f(v){ return v > this.age; } let person = {name: 'John', age: 20}; [10, 12, 26, 15].find(f, person); // 26
上面的代码中,find函数接收了第二个参数person对象,回调函数中的this对象指向person对象。
includes()
返回一个布尔值,表示某个数组是否包含给定的值。
[1, 2, 3].includes(2) // true [1, 2, 3].includes(4) // false [1, 2, NaN].includes(NaN) // true
该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。
some()
some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。
some() 方法会依次执行数组的每个元素:
- 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
- 如果没有满足条件的元素,则返回false。
array.some(function(currentValue,index,arr){ // currentValue 必须。当前元素的值 // index 可选。当前元素的索引值 // arr 可选。当前元素属于的数组对象 }
slice()
slice() 方法可从已有的数组中返回选定的元素。
slice()方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。
注意: slice() 方法不会改变原始数组。
var fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"]; var citrus = fruits.slice(1,3); // Orange,Lemon
splice()
splice(index,len,[item]) 向/从数组中添加/删除项目,然后返回被删除的项目。 注释:该方法会改变原始数组。
splice有3个参数,它也可以用来替换/删除/添加数组内某一个或者几个值。
index:数组开始下标 len: 替换/删除的长度 item:替换的值,删除操作的话 item为空。
如:arr = ['a','b','c','d']
删除 ---- item不设置
ary.splice(1) //[ 'b','c','d' ] 删除数组中的第一个元素 arr.splice(1,1) //['a','c','d'] 删除起始下标为1,长度为1的一个值,len设置的1,如果为0,则数组不变 arr.splice(1,2) //['a','d'] 删除起始下标为1,长度为2的一个值,len设置的2
替换 ---- item为替换的值
arr.splice(1,1,'ttt') //['a','ttt','c','d'] 替换起始下标为1,长度为1的一个值为‘ttt’,len设置的1 arr.splice(1,2,'ttt') //['a','ttt','d'] 替换起始下标为1,长度为2的两个值为‘ttt’,len设置的1
添加 ---- len设置为0,item为添加的值
arr.splice(1,0,'ttt') //['a','ttt','b','c','d'] 表示在下标为1处添加一项‘ttt’
字符串扩展
字符串的遍历器接口
ES6 为字符串添加了遍历器接口,使得字符串可以被for...of循环遍历。
for (let codePoint of 'foo') { console.log(codePoint) } // "f" // "o" // "o"
ES6 字符串模板引擎
ES5中的字符串缺乏多行字符串、字符串格式化、HTML转义等特性。
而ES6通过模板字面量的方式进行了填补,模板字面量试着跳出JS已有的字符串体系,通过一些全新的方法来解决问题。
es6 使用 ` (windows键盘英文输入法下tab键上面那个键)来定义一个字符串模板。
① 多行字符串
传统的JavaScript语言,输出模板通常是这样写的:
var name = '黑子'; var age = 8; $('#result').append( '我的宠物狗叫 <b>' + name + '</b>\n' + '今年\n' + '<em>' + age+ '</em>岁,\n'+ '十分可爱!' );
但是在ES6中,要获得同样效果的多行字符串,只需使用如下代码:
let name = '黑子'; let age = 8; $('#result').append( `我的宠物狗叫 <b>${name}</b> 今年 <em>${age}</em>岁, 十分可爱!` );
对比两段拼接的代码,模板字符串使得我们不再需要反复使用双引号(或者单引号)了;而是改用反引号标识符(`),插入变量的时候也不需要再使用加号(+)了,而是把变量放入${ }即可。
也不用再通过写 n 进行换行了,ES6 的模板字面量使多行字符串更易创建,因为它不需要特殊的语法,只需在想
要的位置直接换行即可,此处的换行会同步出现在结果中。
② 字符串中嵌入变量
ES5写法:
const age = 8; const message = '我的宠物狗叫黑子,今年' + age*2 + '岁了' ; //我的宠物狗叫黑子,今年16岁了
ES6写法:
const age = 8; const message = `我的宠物狗叫黑子,今年 ${age*2} 岁了` ; //我的宠物狗叫黑子,今年16岁了
变量占位符允许将任何有效的JS表达式嵌入到模板字面量中,并将其结果输出为字符串的一部分。
如上面的例子,占位符 ${age} 会访问变量 age,并将其值插入到 message 字符串中。
既然占位符是JS表达式,还可以轻易嵌入运算符、函数调用等。
const age = 8; const message = `我的宠物狗叫黑子,今年 ${(age*2).toFixed(2)} 岁了`; //"我的宠物狗叫黑子,今年 16.00 岁了" function fn() { return "小黄"; } `我朋友家的宠物叫${fn()}` //"我朋友家的宠物叫小黄"
对象的扩展
ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
Object.is
Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
Object.is('foo', 'foo') // true Object.is({}, {}) // false
不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
+0 === -0 //true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true
Object.assign
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 }; const source1 = { b: 2 }; const source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}
Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。
注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
如果只有一个参数,Object.assign会直接返回该参数。
const obj = {a: 1}; Object.assign(obj) === obj // true
如果该参数不是对象,则会先转成对象,然后返回。
typeof Object.assign(2) // "object"
注意点:
1. 由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。这意味着,如果undefined和null不在首参数,就不会报错。
2. Object.assign 方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
常见用途:
(1)为对象添加属性
Object.assign(this, {x, y});
(2)为对象添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
(3)克隆对象
Object.assign({}, origin); // 将原始对象拷贝到一个空对象,就得到了原始对象的克隆。
不过,采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它继承的值。如果想要保持继承链,可以采用下面的代码。
function clone(origin) { let originProto = Object.getPrototypeOf(origin); return Object.assign(Object.create(originProto), origin); }
Object.keys()
ES5 引入了 Object.keys 方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
var obj = { foo: 'bar', baz: 42 }; Object.keys(obj) // ["foo", "baz"]
Object.values()
ES2017 引入了跟Object.keys配套的Object.values和Object.entries,作为遍历一个对象的补充手段,供for...of循环使用。Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
const obj = { foo: 'bar', baz: 42 }; Object.values(obj) // ["bar", 42]
Object.entries()
Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
const obj = { foo: 'bar', baz: 42 }; Object.entries(obj) // [ ["foo", "bar"], ["baz", 42] ]
Object.fromEntries()
Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。
Object.fromEntries([ ['foo', 'bar'], ['baz', 42] ]) // { foo: "bar", baz: 42 }
函数的扩展
函数参数的默认值
ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
var remark = null || "World"; console.log(remark); //World var remark = undefined || "World"; console.log(remark); //World var remark = "" || "World"; console.log(remark); //World
上面代码检查函数log的参数y有没有赋值,如果没有,则指定默认值为World。
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
function Point(x = 0, y = 0) { this.x = x; this.y = y; } const p = new Point(); p // { x: 0, y: 0 }
通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
隐式参数 arguments
JavaScript 有一个免费赠送的关键字 arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。
利用arguments,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值:
// 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null: function foo(a, b, c) { if (arguments.length === 2) { // 实际拿到的参数是a和b,c为undefined c = b; // 可以通过下标获取参数b并把b赋给c b = null; // b变为默认值 } }
rest参数
ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function foo(a, b, ...rest) { console.log('a = ' + a); console.log('b = ' + b); console.log(rest); } foo(1, 2, 3, 4, 5); // 结果: // a = 1 // b = 2 // Array [ 3, 4, 5 ] foo(1); // 结果: // a = 1 // b = undefined // Array []
rest参数只能写在最后,前面用 ... 标识,从运行结果可知,传入的参数先绑定a、b,多余的参数以数组形式交给变量rest,所以,不再需要 arguments 我们就获取了全部参数。如果传入的参数连正常定义的参数都没填满,也不要紧,rest 参数会接收一个空数组(注意不是undefined)。
箭头函数
ES6 允许使用“箭头”(=>)定义函数:(参数列表)=>{业务逻辑}。
var f = v => v; // 等同于 var f = function (v) { return v; };
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
var f = () => 5; // 等同于 var f = function () { return 5 }; var sum = (num1, num2) => num1 + num2; // 等同于 var sum = function(num1, num2) { return num1 + num2; };
箭头函数里的 this 作用域
普通函数的this指向:
首先,this 总是返回一个对象,简单说,就是返回属性或方法“当前”所在的对象。
this.property
上面代码中,this 就代表 property 属性当前所在的对象。
由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,即 this 的指向是可变的。
function f() { return '姓名:'+ this.name; } var A = { name: '张三', describe: f }; var B = { name: '李四', describe: f }; A.describe() // "姓名:张三" B.describe() // "姓名:李四"
上面代码中,函数f
内部使用了 this 关键字,随着f
所在的对象不同,this 的指向也不同。
只要函数被赋给另一个变量,this 的指向就会变。
总结:
1. this总是代表它的直接调用者(js的this是执行上下文), 例如 obj.func ,那么func中的this就是obj
2. 在默认情况(非严格模式下,未使用 'use strict'),没找到直接调用者,则this指的是 window (约定俗成)
3. 在严格模式下,没有直接调用者的函数中的this是 undefined
4. 使用call,apply,bind(ES5新增)绑定的,this指的是 绑定的对象
箭头函数的 this 指向:
1. 箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window; 箭头函数可以方便地让我们在 setTimeout ,setInterval中方便的使用this
2. 箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。
例如下面这段函数中,this指向的是Person对象,可以看到控制台打印结果,此时的箭头函数所处的宿主对象是Person,所以this指向的是person。
下面看这样一段代码,非箭头函数。
可以看到这里的this指向的是window对象,这是由于setInterval跟setTimeout调用的代码运行在与所在函数完全分离的执行环境上。这会导致这些代码中包含的 this 关键字会指向 window (或全局)对象。
下面我们做一个修改,将this存为一个变量,此时的this指向Person1,也可以使用bind函数来绑定this实现以下效果
Set 数据结构
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
const s = new Set(); [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x)); for (let i of s) { console.log(i); } // 2 3 5 4
上面代码通过add()方法向 Set 结构加入成员,结果表明 Set 结构不会添加重复的值。
Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
// 例一 const set = new Set([1, 2, 3, 4, 4]); [...set] // [1, 2, 3, 4] // 例二 const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]); items.size // 5 // 例三 const set = new Set(document.querySelectorAll('div')); set.size // 56
Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。
下面先介绍四个操作方法:
Set.prototype.add(value) //添加某个值,返回 Set 结构本身。 Set.prototype.delete(value) //删除某个值,返回一个布尔值,表示删除是否成功。 Set.prototype.has(value) //返回一个布尔值,表示该值是否为Set的成员。 Set.prototype.clear() //清除所有成员,没有返回值。
代码:
s.add(1).add(2).add(2); // 注意2被加入了两次 s.size // 2 s.has(1) // true s.has(2) // true s.has(3) // false s.delete(2); s.has(2) // false
遍历方法:
Set.prototype.keys() //返回键名的遍历器 Set.prototype.values() //返回键值的遍历器 Set.prototype.entries() //返回键值对的遍历器 Set.prototype.forEach() //使用回调函数遍历每个成员
代码:
let set = new Set(['red', 'green', 'blue']); for (let item of set.keys()) { console.log(item); } // red // green // blue for (let item of set.values()) { console.log(item); } // red // green // blue for (let item of set.entries()) { console.log(item); } // ["red", "red"] // ["green", "green"] // ["blue", "blue"] let set = new Set([1, 4, 9]); set.forEach((value, key) => console.log(key + ' : ' + value)) // 1 : 1 // 4 : 4 // 9 : 9
Map 数据结构
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
操作方法
const m = new Map(); const o = {p: 'Hello World'}; m.set(o, 'content') // 设置成员 key 和 value m.get(o) // 获取成员属性值 "content" m.size // 获取成员的数量 1 m.has(o) // 判断成员是否存在 true m.delete(o) // 删除成员 true m.has(o) // 判断成员是否存在 false m.clear() // 清空所有
作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
const map = new Map([ ['name', '张三'], ['title', 'Author'] ])
遍历方法
Map.prototype.keys() //返回键名的遍历器。 Map.prototype.values() //返回键值的遍历器。 Map.prototype.entries() //返回所有成员的遍历器。 Map.prototype.forEach() //遍历 Map 的所有成员。
利用 Map 返回一个新数组
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
this.arrList.map(item => ({ ...item })) var data = [1, 2, 3, 4]; var arrayOfSquares = data.map(function (item) { //接收新数组 return item * item; }); alert(arrayOfSquares); // [1, 4, 9, 16]
与其他数据结构的互相转换
(1)Map 转为数组
前面已经提过,Map 转为数组最方便的方法,就是使用扩展运算符(...
)。
let myMap = new Map([[true, 7], [{foo: 3}, ['abc']]]); [...myMap] console.log([...myMap]);
(2)数组 转为 Map
将数组传入 Map 构造函数,就可以转为 Map。
new Map([[true, 7], [{foo: 3}, ['abc']]])
(3)Map 转为对象
如果所有 Map 的键都是字符串,它可以无损地转为对象。
const myMap = new Map().set('yes', true).set('no', false); strMapToObj(myMap) // { yes: true, no: false }
如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。
(4)对象转为 Map
const mapItem = new Map(Object.entries(obj)) var id = mapItem.get('id')
(5)Map 转为 JSON
Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。
function strMapToJson(strMap) { return JSON.stringify(strMapToObj(strMap)); } let myMap = new Map().set('yes', true).set('no', false); strMapToJson(myMap) // '{"yes":true,"no":false}'
另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']); mapToArrayJson(myMap) // '[[true,7],[{"foo":3},["abc"]]]'
(6)JSON 转为 Map
JSON 转为 Map,正常情况下,所有键名都是字符串。
jsonToStrMap('{"yes": true, "no": false}') // Map {'yes' => true, 'no' => false}
但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。
jsonToMap('[[true,7],[{"foo":3},["abc"]]]') // Map {true => 7, Object {foo: 3} => ['abc']}