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']}

 

posted on 2020-01-21 13:40  FuYingju  阅读(65)  评论(0编辑  收藏  举报