ES5和ES6的区别以及ES6常用特性

ECMAScript是ECMA International定义的商标脚本语言规范。 创建它是为了标准化JavaScript。 ES脚本语言具有许多实现,流行的实现是JavaScript。 通常,ECMAScript用于万维网的客户端脚本。
ES5是ECMAScript 5的缩写,也被称为ECMAScript2009。ECMAScript标准的第六版是ES6或ECMAScript6。它也被称为ECMAScript2015。ES6是JavaScript语言的主要增强,允许我们编写程序。ES6适用于复杂的应用程序。尽管ES5和ES6在本质上有一些相似之处,但它们之间也有许多不同之处。

 ES5和ES6之间的比较列表如下:

ES5和ES6之间的区别

比较项ES5ES6
定义 ES5是ECMAScript(由ECMA International定义的商标脚本语言规范)的第五版。 ES6是ECMAScript(ECMA International定义的商标脚本语言规范)的第六版。
发布 它于2009年推出。 它于2015年推出。
数据类型 ES5支持原始数据类型,包括字符串,数字,布尔值,空值和未定义(undefined)。 在ES6中,对JavaScript数据类型进行了一些补充。 它引入了一种新的原始数据类型symbol以支持唯一值。
定义变量 在ES5中,只能使用var关键字定义变量。 在ES6中,有两种定义letconst变量的新方法。
性能 由于ES5早于ES6,因此某些功能不存在,因此其性能比ES6低。 由于具有新功能和速记存储实现,因此ES6具有比ES5更高的性能。
支持 许多社区都支持它。 它也有很多社区支持,但是比ES5小。
对象操纵 ES5比ES6耗时。 由于具有解构和速度运算符,因此可以在ES6中更平稳地处理对象操纵。
箭头函数 在ES5中,functionreturn关键字均用于定义函数。 箭头功能是ES6中引入的新功能,通过它不需要function关键字来定义函数。
循环 在ES5中,使用了for循环来遍历元素。 ES6引入了for?of循环的概念,以对可迭代对象的值执行迭代。

代码转换

到目前为止,还没有完全支持ES6功能的浏览器。 但是,我们可以使用转译将ES6代码转换为ES5代码。

有两个主要的编译器Babel和Traceur,用于在构建过程中将ES6代码转换为ES5代码。

点差运算符(…)

它在ES6中引入,使合并数组和对象变得容易。

 

ES6常用特性:

一、let和const命令

1、let:

(1)基本用法

ES6 新增了let命令,用来声明变量。类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

复制代码
{
let a = 10; var b = 1; } a // ReferenceError: a is not defined. b // 1

 

(2)let在for循环中使用

复制代码
var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[2](); // 10
a[6](); // 10

 

这个例子中,i 由 var 声明,在全局范围有效,a 的所有组员里面的 i 都是指向同一个 i,导致所有组员运行输出的都是最后一轮的 i 的值。

复制代码
var a = [];
for (let i = 0; i < 10; i++) {
    a[i] = function () {
        console.log(i);
    }
}
a[2](); // 2
a[6](); // 6

这个例子中, i 由 let 声明,当前的 i 只在本轮循环有效,所有每次循环的 i 其实都是一个新的变量。
Tips: 每轮循环的 i 都是重新声明的,JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的 i 时,就在上一轮循环的基础上进行计算

 

(3)for循环的特别之处

在 for 循环中,设置循环变量的那部分是一个父作用域,而循环内部是一个单独的子作用域

复制代码
for (let i = 0; i < 3; i++) {
    let i = 'abc';  // 和上一行for循环计数器的i不在同一作用域,所以可以声明成功。类比下面要讲的function(arg){let arg;}会报错,就是作用域问题
    console.log(i);
}
// abc
// abc
// abc

 

(4)不能重复声明

let 不允许在相同作用域内,重复声明同一个变量。

复制代码
function func() {
  var a = 10;
  let a = 1;
}
func(); //Identifier 'a' has already been declared

function func() {
  let a = 10;
  let a = 1;
}
func(); //Identifier 'a' has already been declared
 
function func(arg) {
  let arg; 
}
func(); //Identifier 'arg' has already been declared
 
function func(arg) {
  {
    let arg; 
  }
}
func(); //不报错

 

(5)不存在变量提升

var 命令会发生“变量提升”现象,即变量可以在声明之前使用,值为 undefined。 let 命令改变了语法行为,它所声明的变量一定要在声明后使用,否则会报错。

console.log(foo); //undefined
var foo = 2;
console.log(bar); // ReferenceError: bar is not defined
let bar =  2;

 

(6)暂时性死区

ES6规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前使用这些变量,就会报错。这在语法上称为 “暂时性死区”。

var tmp = 123;
if(true){
    tmp = 'abc'; // ReferenceError: tmp is not defined
    let tmp;
}

Tips:“暂时性死区”的本质就是:只要一进入当前作用域,所要使用的变量就已经存在了,但不可获取(否则报错),只有等到声明的那一行代码出现,才可以获取和使用。

 
2、const:

const命令的用法和let相似,最大不同点就是:const声明一个只读的常量。一旦声明,常量的值就不能改变。

Tips:这个不可改变的是指针,所以对于const声明的对象和数组还是可以改变的。如果真的想将对象冻结,应该使用Object.freeze方法。

 

3、let和const部分补充:

ES5 只有两种声明变量的方法:var命令和function命令。

ES6 除了添加let和const命令,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。

 

顶层对象:在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。

ES6为了保持兼容性,规定:a. var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性。b. let,const,class 命令声明的全局变量,不属于顶层对象的属性。

var a = 1;
window.a; // 1
let b = 2;
window.b; // undefined

二、变量的解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

 

1、数组的解构赋值

(1)基本用法

复制代码
// 之前,为变量赋值,可以直接指定值
let a = 1;
let b = 2;
let c = 3;

// ES6中允许这样
let [a, b, c] = [1, 2, 3];  // 可以从数组中提取值,按照对应位置,对变量赋值。

 

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。

复制代码
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
 
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
 
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
 
let [x, y, ...z] = ['a'];
x // "a"
y // undefined  - 如果解构不成功,变量的值就等于 undefined。
z // []

 如果解构不成功,变量的值就等于undefined

 

上面一种情况是解构不成功的;另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

上面两个例子,都属于不完全解构,但是可以成功。

 

如果等号的右边不是数组,那么将会报错。

复制代码
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

 

(2)默认值

解构赋值允许指定默认值。例如:

复制代码
let [foo = true] = [];
foo;// true
 
let [x, y = 'b'] = ['a'];
x;//'a'
y;//'b'

 

Tips:ES6 内部使用严格相等运算符(===)去判断一个位置是否有值所以,只有当一个数组成员严格等于 undefined ,默认值才会生效。例如:

let [x = 1] = [undefined];
x // 1

let [x = 1] = [null];
x // null - 默认值就不会生效,因为null不严格等于undefined

 

默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

复制代码
let [x = 1, y = x] = [1, 2];
x;//1
y;//2
 
let [x = y, y = 1] = [];// ReferenceError: y is not defined -- let的暂时性死区
let [x = y, y = 1] = [1, 2];

 

2、对象的解构赋值

(1)基本用法

复制代码
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

 

对象的解构与数组的解构,一个重要的不同点在于: 数组元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量名与属性同名,才能取到正确的值。

如果没有与变量名对应的属性名,导致取不到值,最后等于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 { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };

 也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

 

let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined

 上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo

 

(2)默认值

对象的解构也可以指定默认值。默认值生效的条件是对象的属性值严格等于 undefined。

复制代码
let {x, y = 5} = {x: 1};
x;// 1
y;// 5

let { message: msg = 'Something went wrong' } = {};
msg;//"Something went wrong"

 

Tips:如果要将一个已经声明的变量用于解构赋值,需要注意:

复制代码
let x;
{x} = {x:1};
//上面的代码会报错,因为JavaScript引擎会将 {x} 理解为一个代码块,从而发生语法错误。只有不将大括号写在行首,避免JavaScript将其解释为代码块,才能解决这个问题。
 
//正确的写法,将需要解构的部分用括号()包起来
let x;
({x} = {x:1});

 

3、字符串的解构赋值

字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

复制代码
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

 

类似数组的对象都有一个 length 属性,因此还可以对这个属性解构赋值。

let {length: len} = 'hello';
len;// 5

 

4、数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转化为对象:

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

上面代码中,数值和布尔值的包装对象都有 toString 属性,因此变量 s 都可以取到值。

 

解构赋值的规则是:只要等号右边的值不是对象或数组,就先将其转化为对象。 由于 undefined 和 null 无法转化为对象,所以对他们进行解构赋值都会报错。

 

5、函数参数的解构赋值
复制代码
function add([x, y]){
   return x + y;
}
 
add([1, 2]); // 3
 
[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]

 上面代码中,函数 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 指定默认值。

 

6、解构的用途

(1)交换变量的值

let x = 1;
let y = 2;
[x, y] = [y, x];
x;// 2
y;// 1

 

(2)从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里面返回。有了解构赋值,取出这些值就非常方便。

复制代码
//返回一个数组
function example() {
    return [1, 2, 3];
}
let [a, b, c] = example();
a;//1
b;//2
c;//3
 
//返回一个对象
function example(){
    return {
        foo: 1,
        bar: 2
    }
}
let {foo, bar} = example();
foo;//1
bar;//2

 

(3)函数参数的定义
解构赋值可以方便的将一组参数与变量名对应起来。

复制代码
//参数是一组有序值
function f([x, y, z]) {}
f([1, 2, 3]);
 
//参数是一组无序值
function f({x, y, z}) {}
f({x: 1, y: 2, z: 3});

 

(4)提取JSON数据

复制代码
let data = {
    id: 1,
    status: 'ok',
    data: [867, 5612]
};
let {id, status, data:number} = data;
console.log(id, status, number);// 1 "ok"  [867, 5612]

 

 (5)遍历Map解构

复制代码
const map = new Map();
map.set('first','hello');
map.set('second','world');
for (let [key, value] of map) {
    console.log(key + ' is ' + value );
}
//first is hello
//second is world

// 获取键名
for (let [key] of map) {}
 
// 获取键值
for (let [,value] of map) {}

三、函数的默认参数

1、基本用法

ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。

复制代码
function log(x, y) {
  y = y || 'World';
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World

上面代码检查函数log的参数y有没有赋值,如果没有,则指定默认值为World。这种写法的缺点在于,如果参数y赋值了,但是对应的布尔值为false,则该赋值不起作用。就像上面代码的最后一行,参数y等于空字符,结果被改为默认值。


为了避免这个问题,通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值。

if (typeof y === 'undefined') {
  y = 'World';
}

 

ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

复制代码
function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello -- 上面说过,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于 undefined ,默认值才会生效。

 

2、与解构赋值默认值结合使用
复制代码
function foo({x, y = 5}) {
  console.log(x, y);
}

foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

上面代码只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数foo的参数是一个对象时,变量x和y才会通过解构赋值生成。

如果函数foo调用时没提供参数,变量x和y就不会生成,从而报错。

 

通过提供函数参数的默认值,就可以避免这种情况。

function foo({x, y = 5} = {}) {
  console.log(x, y);
}

foo() // undefined 5

 

下面是另一个解构赋值默认值的例子:

复制代码
function fetch(url, { body = '', method = 'GET', headers = {} }) {
  console.log(method);
}

fetch('http://example.com', {})
// "GET"

fetch('http://example.com')
// 报错

上面代码中,如果函数fetch的第二个参数是一个对象,就可以为它的三个属性设置默认值。


这种写法不能省略第二个参数,如果结合函数参数的默认值,就可以省略第二个参数。这时,就出现了双重默认值。

复制代码
function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
  console.log(method);
}

fetch('http://example.com')
// "GET"

上面代码中,函数fetch没有第二个参数时,函数参数的默认值就会生效,然后才是解构赋值的默认值生效,变量method才会取到默认值GET。

 

因此,与解构赋值默认值结合使用时,切记,函数要定义默认参数,防止函数不传参时报错。

 

四、模板字符串

模板字符串是增强版的字符串,用反引号 (`) 标识。它可以当普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

 

1、基本用法
复制代码
let greeting = `\`Yo\` World!`;  // `Yo` World -- 使用反斜杠转义反引号

// 多行字符串
$('#result').append(`
  There are <b>${basket.count}</b> items
   in your basket, <em>${basket.onSale}</em>
  are on sale!
`);

Tips:
a. 如果在模板字符串中需要使用反引号,则前面需要使用反斜杠转义。
b. 如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。

 

2、在模板字符串使用变量

(1) 在模板字符串中嵌入变量,需要将变量名写在 ${} 之中。
(2) 大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。
(3) 模板字符串之中还能调用函数。
(4) 如果大括号中的值不是字符串,将按照一般的规则转为字符串。如: 如果大括号中是一个对象,将默认调用对象的 toString 方法。
(5) 如果模板字符串中的变量没有声明将会报错。
(6) 如果大括号内部是一个字符串,将原样输出。

复制代码
//普通字符串
`In JavaScript '\n' is a line-feed.`;
 `Hello ${'World'}`; // "Hello World"

//字符串中嵌入变量
let name = "Bob", time = "today";
console.log(`Hello ${name}, how are you ${time}?`); //  Hello Bob, how are you today?

//字符串中嵌入 JavaScript 表达式
`${x} + ${y} = ${x + y}`;// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`;// "1 + 4 = 5"

let obj = {x: 1, y: 2};
`${obj.x + obj.y}`;// "3"

// 字符串中嵌入函数
function fn(){
    return "hello world"
}
`foo ${fn()} bar`;// "foo hello world bar"

// 字符串中嵌入对象
`${obj}`;// "[object Object]"

// 字符串中嵌入未声明的变量
 `Hello, ${place}`;// ReferenceError: place is not defined 

五、箭头函数

 ES6 允许使用 “箭头” (=>)定义函数。

1、基本用法
var f = v => v;
//等同于
var f = function (v) {
    return v;
}

=> 前面的部分是函数的参数,=> 后面的部分是函数的代码块。

 

(1)如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

复制代码
var f = () => 5;
//等同于
var f = function (v) {
    return 5
};

var sum = (num1, num2) =>  num1 + num2;
//等同于
var sum = function(num1, num2){
    return num1 + num2;
}

 

(2)如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用 return 语句返回。

var sum = (num1,num2) => { return num1 + num2; }

 

(3)由于大括号会被解释为代码块,所以如果箭头函数直接返回一个对象,就必须在对象外面加上括号,否则会报错。

复制代码
// 报错
let getTempItem = id => { id: id, name: "Temp" };

// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
getTempItem(1);//{id: 1, name: "Temp"}

let getTempItem = id => {
    return { id: id, name: "Temp" };
}
getTempItem(1);// {id: 1, name: "Temp"}

 

(4)箭头函数内部,可以嵌套箭头函数

复制代码
function insert (value) {
    return {into: function (array){
            return {after: function (afterValue) {
                        array.splice(array.indexOf(ahterValue)+1, 0, value);
                        return array;
                    }
               }
        }    
    }
}
insert(2).into([1, 3]).after(1); //[1, 2, 3]

//使用箭头函数改写
let insert = (value) => ({into: (array) => ({after: (afterValue) => {
            array.splice(array.indexOf(afterValue)+1, 0, value);
            return array;
        }
    })
});

 

2、箭头函数与变量解构结合使用
复制代码
const full = ({ first, last }) => first + ' ' + last;
full({first: 1, last: 2});// "1 2"
// 等同于 function full(person) { return person.first + ' ' + person.last; } full({first: 1, last: 2});// "1 2"

 

3、rest 参数与箭头函数结合
const numbers = (...nums) => nums;
numbers(1,2,3,4);//  [1, 2, 3, 4]

const headAndTail = (head, ...tail) => [head, tail];
headAndTail(1,2,3,4);// [1,[2,3,4]]

 

4、箭头函数的优点

(1)简化代码

const isEven = n => n%2 == 0;
isEven(3);// false
isEven(4);// true

 

(2)简化回调函数

[1, 2, 3].map( x => x*x ); //[1, 4, 9]
[3,2,5].sort((a, b) => a - b); // [2, 3, 5]

 

5、箭头函数的注意点——this

ES5中 this 的指向是可变的,但是箭头函数中 this 指向固定化。

复制代码
var  handler = {
    id: '123',
    init: function () {
        document.addEventListener('click', 
            event => this.doSomethisng(event.type), false);
    },
    doSomethisng: function (type) {
        console.log('Handling ' + type + ' for ' + this.id);
    }
};
handler.init();  //Handling click for 123  -- this指向handle  -- this是属于函数的一个对象,谁调用指向谁(es5中)

 

 this 指向的固定化,并不是因为箭头函数内部有绑定 this 的机制,实际原因是箭头函数内部根本没有自己的this,导致内部的this 就是外层代码块的 this 。

 

 箭头函数的this是比较难理解的,但只要能想象到他是如何从ES5转化来的,就可以完全理解它的作用对象。 箭头函数转成 ES5 的代码如下:

复制代码
// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}

 

Tips:

(1) 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
(2) 不可以当构造函数,即不可以使用 new 命令,因为它没有 this,否则会抛出一个错误。
(3) 箭头函数没有自己的 this,所以不能使用 call()、apply()、bind() 这些方法去改变 this 指向。
(4) 不可以使用arguments 对象,该对象在函数体内不存在。如果要用,可以使用rest参数代替。

六、数组的扩展(扩展运算符) 

 1、扩展运算符

(1)基本用法

  扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

console.log(...[1, 2, 3])  // 1 2 3
console.log(1, ...[2, 3, 4], 5)  // 1 2 3 4 5
[...document.querySelectorAll('div')]  // [<div>, <div>, <div>]

 

  该运算符主要用于函数调用。扩展运算符与正常的函数参数可以结合使用,非常灵活。

复制代码
function push(array, ...items) {
  array.push(...items);
}

function add(x, y) {
  return x + y;
}

const numbers = [4, 38];
add(...numbers) // 42

function f(v, w, x, y, z) { }
const args = [0, 1];
f(-1, ...args, 2, ...[3]);

 

如果扩展运算符后面是一个空数组,则不产生任何效果。 [...[], 1] // [1] 

 

注意,扩展运算符如果放在括号中,JavaScript 引擎就会认为这是函数调用,否则就会报错。

复制代码
(...[1,2])
// Uncaught SyntaxError: Unexpected number

console.log((...[1,2]))
// Uncaught SyntaxError: Unexpected number

console.log(...[1,2])  // 不会报错 

 

2、替代函数的apply方法

由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。

复制代码
// ES5 的写法
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f.apply(null, args);

// ES6的写法
function f(x, y, z) {
  // ...
}
let args = [0, 1, 2];
f(...args);

 

3、扩展运算符引用

(1)复制数组

数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针(浅拷贝),而不是克隆一个全新的数组。

如何实现深拷贝呢?下面看看ES5和ES6的实现:

复制代码
// ES5
const a1 = [1, 2];
const a2 = a1.concat();

// ES6
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;

a2[0] = 2;
a1 // [1, 2]

 

(2)合并数组

复制代码
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

 

不过,这两种方法都是浅拷贝,使用的时候需要注意。如果修改了原数组的成员,会同步反映到新数组。

复制代码
const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];

const a3 = a1.concat(a2);
const a4 = [...a1, ...a2];

a3[0] === a1[0] // true
a4[0] === a1[0] // true

 

(3)与解构赋值结合

复制代码
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

const [first, ...rest] = [];
first // undefined
rest  // []

const [first, ...rest] = ["foo"];
first  // "foo"
rest   // []

 

注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

const [...butLast, last] = [1, 2, 3, 4, 5]; // 报错

const [first, ...middle, last] = [1, 2, 3, 4, 5]; // 报错

 

(4)字符串

扩展运算符可以将字符串和函数参数arguments转成真正的数组

[...'hello']  // [ "h", "e", "l", "l", "o" ]

function (){
   let arr = [...arguments];
}

七、对象的扩展

 

1、属性的简洁表示法

ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

复制代码
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}

// 等同于
const baz = {foo: foo};

 

上面代码表明,ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值

 

属性简写:

复制代码
function f(x, y) {
  return {x, y};
}

// 等同于
function f(x, y) {
  return {x: x, y: y};
}

f(1, 2) // Object {x: 1, y: 2}

 

方法简写:

复制代码
const o = {
  method() {
    return "Hello!";
  }
};

// 等同于
const o = {
  method: function() {
    return "Hello!";
  }
};

 

 CommonJS 模块输出一组变量,就非常合适使用简洁写法。

复制代码
let ms = {};

function getItem (key) {
  return key in ms ? ms[key] : null;
}

function setItem (key, value) {
  ms[key] = value;
}

function clear () {
  ms = {};
}

module.exports = { getItem, setItem, clear };
// 等同于
module.exports = {
  getItem: getItem,
  setItem: setItem,
  clear: clear
};

 

 

2、属性名表达式

JavaScript 定义对象的属性,有两种方法。 

// 方法一
obj.foo = true;

// 方法二
obj['a' + 'bc'] = 123;

上面代码的方法一是直接用标识符作为属性名,方法二是用表达式作为属性名,这时要将表达式放在方括号之内。

但是,如果使用字面量方式定义对象(使用大括号),在 ES5 中只能使用方法一(标识符)定义属性。

var obj = {
  foo: true,
  abc: 123
};

 

ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内。

复制代码
let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};

 

表达式还可以用于定义方法名。比如我们熟悉的Vuex定义Mutation就是推荐这种方式:

复制代码
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'

// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
    [SOME_MUTATION] (state) {
      // mutate state
    }
  }
})

 

复制代码
var test = {            
  'attr0' : 'attr1',            
  'attr1' : 'attr2',            
  'attr2' : 'attr3',            
  'attr3' : 'I'm here',          
}                  

// 输出: I'm here  , 方括号可以接受任何JS语句,最后都被转化为字符串。利用这个字符串在key-value集合中寻址。
console.log(test[test[test[test['attr0']]]]);

  

八、Array 的扩展方法

 扩展运算符(展开语法):扩展运算符可以将数组或者对象转为用逗号分隔的参数序列

let ary = [1, 2, 3];
...ary // 1, 2, 3
console.log(...ary); // 1 2 3,相当于下面的代码
console.log(1,2,3);

扩展运算符可以应用于合并数组

// 方法一 
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];
// 方法二 
ary1.push(...ary2);

 

构造函数方法:Array.from()

将伪数组或可遍历对象转换为真正的数组

//定义一个集合
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
}; 
//转成数组
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

方法还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组

let arrayLike = { 
"0": 1,
"1": 2,
"length": 2
}
let newAry = Array.from(arrayLike, item => item *2)//[2,4]

注意:如果是对象,那么属性需要写对应的索引

实例方法:find()

用于找出第一个符合条件的数组成员,如果没有找到返回undefined

let ary = [{
id: 1,
name: '张三'
}, { 
id: 2,
name: '李四'
}]; 
let target = ary.find((item, index) => item.id == 2);//找数组里面符合条件的值,当数组中元素id等于2的查找出来,注意,只会匹配第一个

实例方法:findIndex()

用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1

let ary = [1, 5, 10, 15];
let index = ary.findIndex((value, index) => value > 9); 
console.log(index); // 2

 实例方法:includes()

判断某个数组是否包含给定的值,返回布尔值。

[1, 2, 3].includes(2) // true 
[1, 2, 3].includes(4) // false

arr.splice(index,len,[item])

表示从数组中删除或者添加元素,返回新数组保存所有删除的元素
index表示要操作的索引位置,len表示要删除的个数,如果len的值为0表示不删除,item表示要添加的元素,可以是多个

let ary = [10, 20, 50];
 // 把20给删除
 // ary.splice(1, 1);
 // console.log(ary);
 // 把20删除,替换成30
 // ary.splice(1, 1, 30);
 // console.log(ary);
 // 在20前面加上15
 // ary.splice(1, 0, 15);
 // console.log(ary);
 // 在50前面加上30,40
ary.splice(2, 0, 30, 40);
console.log(ary);

arr.filter()

返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组

// 返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组
 // let result = ary.filter(item => item > 150);
 // console.log(result)

String 的扩展方法

实例方法:repeat()

repeat方法表示将原字符串重复n次,返回一个新字符串

'x'.repeat(3) // "xxx" 
'hello'.repeat(2) // "hellohello"

 

Set 数据结构

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

Set本身是一个构造函数,用来生成 Set 数据结构

const s = new Set();

Set函数可以接受一个数组作为参数,用来初始化。

const set = new Set([1, 2, 3, 4, 4]);//{1, 2, 3, 4}

 实例方法

add(value):添加某个值,返回 Set 结构本身

delete(value):删除某个值,返回一个布尔值,表示删除是否成功

has(value):返回一个布尔值,表示该值是否为 Set 的成员

clear():清除所有成员,没有返回值

const s = new Set();
s.add(1).add(2).add(3); // 向 set 结构中添加值 
s.delete(2) // 删除 set 结构中的2值 
s.has(1) // 表示 set 结构中是否有1这个值 返回布尔值 
s.clear() // 清除 set 结构中的所有值
//注意:删除的是元素的值,不是代表的索引

 遍历

Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。

s.forEach(value => console.log(value))

 

 

 

 



posted @ 2020-07-06 19:30  ℳℓ马温柔  阅读(10516)  评论(0编辑  收藏  举报