ES6部新增语法

340 阅读6分钟

ES6入门

let和const命令

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10
//上面代码中,变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。

//如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。
for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc
// for循环的特别之处在于,设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域

不存在变量提升

暂时性死区

// 不存在变量提升
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

// 暂时性死区
var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}
//存在全局变量tmp,但是块级作用域又声明了一个局部变量tmp,导致后者会绑定这个块级作用域,所以在let之前声明的,对tmp赋值都会报错

// ES6规定,只要块级作用域中存在let,const的话,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域,凡在声明之前就使用这些变量,就会报错。

const

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
//const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

ES6声明变量的六种方法

var
function
let
const
import
class

顶层对象和全局对象

var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined

变量的解构

数组的解构

let [a, b, c] = [1, 2, 3]
// 如果解构不成功的话,变量的值就等于undefined
let [foo] = []
let [bar, foo] = [1]
//foo undefined
//如果等号的右边不是数组(或者严格地说,不是可遍历的结构,参见《Iterator》一章),那么将会报错。
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
默认值
let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
// 默认值严格等于undefined的时候默认值才会生效

对象的解构

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

// 解构也可以用于嵌套结构的对象。
let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

const node = {
  loc: {
    start: {
      line: 1,
      column: 5
    }
  }
};

let { loc, loc: { start }, loc: { start: { line }} } = node;
line // 1
loc  // Object {start: Object}
start // Object {line: 1, column: 5}

// 如果变量名和属性名不一致
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'

默认值

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"

如果要将一个已经声明的变量用于解构赋值,必须非常小心。

// 正确的写法

let x;
({x} = {x: 1});

由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。

let arr = [1, 2, 3];
let {0 : first, 1: second, [arr.length - 1] : last} = arr;
first // 1
last // 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

用途

交换变量的值

let x = 1;
let y = 2;

[x, y] = [y, x];

从函数返回多个值

// 返回一个数组

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

// 返回一个对象

function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();

函数参数的定义

// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});

提取json数据

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data:number } = jsonData;

console.log(id, status, number);
// 42, "OK", [867, 5309]

遍历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) {
    
}

字符串的扩展


//include() ,startsWith(), endsWith()
let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true

// repeat
// repeat方法返回一个新字符串,表示将原字符串重复n次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

// padStart(), padEnd()
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

// trim(), trimStart(), trimEnd()
const s = '  abc  ';
s.trim() // "abc"
s.trimStart() // "abc  "
s.trimEnd() // "  abc"

函数的扩展

箭头函数的扩展
// 箭头函数的扩展
// 箭头函数可以与变量解构结合使用。
const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person) {
  return person.first + ' ' + person.last;
}
<!--在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值:

如果是该函数是一个构造函数,this指针指向一个新的对象
在严格模式下的函数调用下,this指向undefined
如果是该函数是一个对象的方法,则它的this指针指向这个对象
等等-->
function Person() {
  // Person() 构造函数定义 `this`作为它自己的实例.
  this.age = 0;

  setInterval(function growUp() {
    // 在非严格模式, growUp()函数定义 `this`作为全局对象,
    // 与在 Person()构造函数中定义的 `this`并不相同.
    this.age++;
  }, 1000);
}

var p = new Person();

// 解决办法 通过将this值分配给封闭的变量,可以解决this问题。
function Person() {
  var that = this;
  that.age = 0;

  setInterval(function growUp() {
    // 回调引用的是`that`变量, 其值是预期的对象.
    that.age++;
  }, 1000);
}

箭头函数

1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
// 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。因此,在下面的代码中,传递给setInterval的函数内的this与封闭函数中的this值相同:

function Person(){
  this.age = 0;

  setInterval(() => {
    this.age++; // |this| 正确地指向 p 实例
  }, 1000);
}

var p = new Person();

使用箭头函数作为方法

'use strict';
var obj = {
  i: 10,
  b: () => console.log(this.i, this),
  c: function() {
    console.log( this.i, this)
  }
}
obj.b();
// undefined, Window{...}
obj.c();
// 10, Object {...}
function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数
  setInterval(() => this.s1++, 1000); // this指向的是Timer
  // 普通函数
  setInterval(function () {
    this.s2++; // this指向的是window
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0

数组的扩展

console.log(...[1, 2, 3])
// 1 2 3

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

// 函数调用
function push(array, ...items) {
  array.push(...items);
}

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

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

替代函数的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);
//下面是扩展运算符取代apply方法的一个实际的例子,应用Math.max方法,简化求出一个数组最大元素的写法。
// ES5 的写法
Math.max.apply(null, [14, 3, 77])

// ES6 的写法
Math.max(...[14, 3, 77])

// 等同于
Math.max(14, 3, 77);

扩展运算符的应用

// 复制数组
const a1 = [1, 2];
// ES5写法
const a2 = a1.concat()
a2[0] = 2;
a1 // [1, 2]
// ES6写法一
const a2 = [...a1];
// ES6写法二
const [...a2] = a1;

//合并数组
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' ]

Array.from()

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

Array.of()

// Array.of方法用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

arr.find() && arr.findIndex()

// find
let arr = [1,23,4,5]
arr.find((item, index, arr) => {
    return item > 20
})
// 23

// findIndex
let arr = [1, 5, 10, 15]
arr.findIndex(function(value, index, arr) {
  return value > 9;
}) // 2

arr.copyWithIn(target, start, end)

<!--target(必需):从该位置开始替换数据。如果为负值,表示倒数。-->
<!--start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。-->
<!--end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。-->

[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]

arr.fill(value, start, end)

// fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

let arr = [1,3,5,6]
arr.fill(9, 1, 2)

数组实例的entries(), keys(), values()

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, item] of ['a', 'b'].entries()) {
  console.log(index, item);
}
// 0 "a"
// 1 "b"

arr.flat() && arr.flatMap()

//数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

[1, 2, [3, 4]].flat()
// [1, 2, 3, 4]

[1, 2, [3, [4, 5]]].flat()
// [1, 2, 3, [4, 5]]

[1, 2, [3, [4, 5]]].flat(2)
// [1, 2, 3, 4, 5]

//如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。
[1, [2, [3]]].flat(Infinity)
// [1, 2, 3]

let arr = [1,3,4,6,8,10]
arr.flatMap((item, index, arr) => {
})

对象的扩展

属性简洁表示法

const foo = 'bar';
const baz = {foo};
console.log(baz) // {foo: "bar"}


let birth = '2000/01/01';
const Person = {

  name: '张三',

  //等同于birth: birth
  birth,

  // 等同于hello: function ()...
  hello() { console.log('我的名字是', this.name); }

};

// 这种写法用于函数的返回值,将会非常方便。
function getPoint() {
  const x = 1;
  const y = 10;
  return {x, y};
}

getPoint()
// {x:1, y:10}

对象的遍历

1for...in

for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

(2Object.keys(obj)

Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

(3Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

(4Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

(5Reflect.ownKeys(obj)

Reflect.ownKeys返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。

扩展运算符

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

let {...x} = {a:1, b:2}
console.log(x)
let obj = {a: {b: 1}, name: 'bob'} // 是一个浅拷贝只能深拷贝一层,多层的话就是浅拷贝
let {...y} = obj
obj.name = 'marry'
console.log(y) // { a: { b: 1 }, name: 'bob' }


// 由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}

// 创建对象的方式
// new Object() 方式创建
var a = {  rep : 'apple' }
var b = new Object(a)
console.log(b) // {rep: "apple"}
console.log(b.__proto__) // {}
console.log(b.rep) // {rep: "apple"}

// Object.create() 方式创建 继承了一个对象
var a = { rep: 'apple' }
var b = Object.create(a)
console.log(b)  // {}
console.log(b.__proto__) // {rep: "apple"}
console.log(b.rep) // {rep: "apple"}

对象新增方法


//比较两个值是否相等
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false


// 用于对象的合并,将源对象(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()直接返回该参数
typeof Object.assign(2) // "object"

// object.assign()用途
// 1.为对象添加属性
// 上面方法通过Object.assign()方法,将x属性和y属性添加到Point类的对象实例
class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}

// Object.getOwnPropertyDescriptors() 方法会返回对象所有自身属性的描述对象(非继承属性)
const obj = {
  foo: 123,
  get bar() { return 'abc' }
};

const obj1 = Object.getOwnPropertyDescriptors(obj)
// 返回的是一个新的对象
console.log(obj1)

// Object.setPrototypeOf 设置一个对象的原型对象
// 格式
Object.setPrototypeOf(object, prototype)

// 用法
const o = Object.setPrototypeOf({}, null);

// Object.getPrototypeOf(obj); 读取一个对象的原型对象
Object.getPrototypeOf(obj);

// 等同于 Object.getPrototypeOf(Number(1))
Object.getPrototypeOf(1)
// Number {[[PrimitiveValue]]: 0}

// 等同于 Object.getPrototypeOf(String('foo'))
Object.getPrototypeOf('foo')
// String {length: 0, [[PrimitiveValue]]: ""}

// 等同于 Object.getPrototypeOf(Boolean(true))
Object.getPrototypeOf(true)
// Boolean {[[PrimitiveValue]]: false}

Object.getPrototypeOf(1) === Number.prototype // true
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true

Object.keys(); Object.values(); Object.entries();

// Object.keys() 可以返回一个数组都是key
// Object.values() 可以返回一个数组都是value
var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}

for (let value of values(obj)) {
  console.log(value); // 1, 2, 3
}

for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}

Object.fromEntries() 用于将键值对数组转换为对象

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }

运算符扩展

1.链判断运算符
上面代码使用了?.运算符,直接在链式调用的时候判断,左侧的对象是否为nullundefined。如果是的,就不再往下运算,而是返回undefinedconst firstName = message?.body?.user?.firstName || 'default';
2.Null判断运算符
ES2020 引入了一个新的 Null来判断运算符??。它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。
const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;