Checkson / blog

Checkson个人博客
12 stars 3 forks source link

ES6 基础概述 #36

Open Checkson opened 5 years ago

Checkson commented 5 years ago

1. let 和 const 命令

在ES6之前,JavaScript 声明变量只能用 var 这个关键字;而在ES6中,则引入了其他两个关键字 letconst,那么,它们俩,到底与 var 有哪些异同呢?请看下表:

名称 作用 是否存在变量提升 是否存在块级作用域 是否能重复声明
var 变量声明
let 变量声明 不能
const 常量声明 不能

2. 变量的解构赋值

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

数据类型 常见解构形式 描述
数组 let [a, b, c] = [1, 2, 3]; 按照对应位置,对变量赋值
对象 let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; 变量必须与属性同名
字符串 const [a, b, c, d, e] = 'hello'; 字符串被转成类数组对象
数值 let {toString: s} = 123; 解构前转为对象
布尔值 let {toString: s} = true; 解构前转为对象
函数参数 let add = ([x, y]) => x + y; 类似数组解构
函数参数 let sub = ({x, y}) => x - y; 类似对象解构

3. 字符串扩展

3.1 模板字符串

模板字符串(template string)是增强版的字符串,用反引号(`)标识。

// es5
var name = 'Checkson';
var str = 'Hello ' + name + '!';

// es6
const name = 'Checkson';
const str = `Hello ${Checkson}!`;

3.2 includes() 实例方法

返回布尔值,表示是否找到了参数字符串(接受第二参数,代表开始搜索下标)。

// es5
var str = 'This is just test!';
if (str.indexOf('is') > -1) {
   // do something...
}

// es6
const str = 'This is just test!';
if (str.includes('is')) {
   // do something...
}

3.3 startsWith(), endsWith() 实例方法

两者都返回布尔值,前者表示参数字符串是否在原字符串的头部,后者表示参数字符串是否在原字符串的尾部(都接受第二参数,代表开始搜索下标)。

// es5
var str = 'Learn Once, Write Anywhere';
if (/^Learn/.test(str) || /Anywhere$/.test(str)) {
    // do something...
}

// es6
const str = 'Learn Once, Write Anywhere';
if (str.startsWith('Learn') || str.endsWith('Anywhere')) {
    // do something...
}

3.4 repeat() 实例方法

repeat方法返回一个新字符串,表示将原字符串重复n次,n为非负整数。

// es5
var str = '';
for (var i = 0; i < 6; i++) {
    str += 'template';
}

// es6
var str = 'template'.repeat(6);

3.5 padStart(), padEnd() 实例方法

ES2017 新增了 padStart()padEnd() 实例方法用来分别为字符串首、尾补全指定的字符。

// padStart
// es5
function prevZero (num) {
    return ('0' + num).substr(-2);
}

// es6
function prevZero (num) {
    return ('' + num).padStart(2, '0');
}

// padEnd 同理

3.6 trimStart(), trimEnd() 实例方法

ES2019 新增了 trimStart()trimEnd() 实例方法。它们的行为与trim()一致,前者消除字符串头部的空格,后者消除尾部的空格。

// trimStart
// es5
function trimStartSpace (str) {
    var res = '', isFirst = true ;
    for (var i = 0, len = str.length; i < len; i++) {
        if (isFirst && str[i] === ' ') {
            continue;
        }
        isFirst && (isFirst = !isFirst);
        res += str[i];
    }
    return res;
}

// es6
function trimStartSpace (str) {
    return str.trimStart();
}

// trimEnd 同理

4. 正则的扩展

字符串对象共有 4 个方法,可以使用正则表达式:match()replace()search()split()。ES6 将这 4 个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上。

5. 数值的扩展

5.1 二进制和八进制表示法

ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。

// 二进制
0b1000 === 8 // true
// 八进制
0o10 === 8   // true
// 转成十进制
Number(0b1000); // 8
Number(0o10);   // 8

5.2 Number.isFinite(), Number.isNaN()

ES6在Number对象上,新提供了Number.isFinite()Number.isNaN()两个方法。前者检查传入值是否有限,后者检查传入值是否为NaN。

// es5
isFinite(25) // true
isFinite("25") // true
isFinite(true) // true
// es6
Number.isFinite(25) // true
Number.isFinite("25") // false
Number.isFinite(true) // false

// es5
isNaN(NaN) // true
isNaN("NaN") // true
// es6
Number.isNaN(NaN) // true
Number.isNaN("NaN") // false
Number.isNaN(1) // false

它们与传统的全局方法isFinite()isNaN()的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false

5.3 Number.parseInt(), Number.parseFloat()

ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。

Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true

5.4 指数运算符

ES2016 新增了一个指数运算符(**)。

// es5
Math.pow(2, 2); // 4
Math.pow(2, 3); // 8

// es6
2 ** 2 // 4
2 ** 3 // 8

6. 函数的扩展

6.1 函数参数的默认值

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

// es5
function foo (x, y) {
    y = y || 'World';
    return x + y;
}

// es6
function foo (x, y = 'World') {
    return x + y;
}

6.2 解构 + 函数参数默认值

// es5
function foo (obj) {
    obj.y = obj.y || 2;
    return obj.x + obj.y;
}

// es6
function foo ({x, y = 2}) {
    return x + y;
}

6.3 rest 参数

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。

// es5
function add () {
    var args = [].slice.call(arguments);
    var sum = 0;
    args.forEach(function (val) {
         sum += val;
    });
    return sum;
}

// es6
function add(...args) {
  let sum = 0;
  for (let val of args) {
    sum += val;
  }
  return sum;
}

6.4 箭头函数

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

// es5
var foo = function () {
    return false;
}

// es6
const foo = () => false;

注意: 对于箭头函数,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象;不可以当作构造函数(不能被new);不可以使用arguments对象;不可以使用yield命令。

6.5 尾递归

函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

// 非尾递归 - 容易栈溢出
function factorial (n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

// 尾递归
function factorial (n, total) {
    if (n === 1) return total;
    return factorial(n - 1, total * n);
}

7. 数组的扩展

7.1 Array.from()

Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

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

7.2 Array.of()

Array.of方法用于将一组值,转换为数组。

//  es5
var arr = [];
[].push.call(arr, 1, 2, 3);

// es6
const arr = Array.of(1, 2, 3);

7.3 find() 实例方法

数组实例的find方法,用于找出第一个符合条件的数组成员。若果找到符合条件的成员,则返回第一个满足条件的成员;如果找不到满足条件的成员,返回undefined

var arr = [1, 2, 3, 4],  res = 'undefine';

// es5
for (var i = 0; i < arr.length; i++) {
      if (arr[i] > 2) {
           res = arr[i]; // 3
           break;
      }
}

// es6
res = arr.find(item => item > 2); // 3

7.4 findIndex() 实例方法

findIndex 方法与 indexOf 方法作用类似,但是findIndex能识别数组是否存在NaN

// es5
[1, NaN, 2, NaN].indexOf(NaN); // -1

// es6
[1, NaN, 2, NaN].findIndex(item => isNaN(item)); // 1

7.5 fill() 实例方法

fill方法使用给定值,填充一个数组。

// es5
new Array(6, 6, 6); // [6, 6, 6]

// es6
new Array(3).fill(6); // [6, 6, 6]

7.6 includes() 实例方法

ES2016引入Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。

// es5
[1, 2, 3].indexOf(2) > -1 // true 

// es6
[1, 2, 3].includes(2); // true
[1, 2, NaN].includes(NaN); // true

7.7 flat 实例方法

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

// es5
function flatten(arr) {
    return arr.reduce((a, b) => {
        return a.concat(Array.isArray(b) ? flatten(b) : b);
    }, []);
};
flatten([1, 2, [3, 4]]); // [1, 2, 3, 4]

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

8. 对象的扩展

8.1 属性的简洁表示法

ES6 允许直接写入变量和函数,作为对象的属性和方法。

// es5
function foo (x, y) {
    return { x: x, y: x };
}

// es6
function foo (x, y) {
    return {x, y};
}

8.2 属性名表达式

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

// es5
var obj = {
     foo: 1
};
obj['b' + 'ar'] = 2;

// es6
var obj = {
    foo: 1,
    ['b' + 'ar']: 2
}

8.3 属性遍历方法

ES6 一共有 5 种方法可以遍历对象的属性。

(1)for...in

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

(2)Object.keys(obj)

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

(3)Object.getOwnPropertyNames(obj)

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

(4)Object.getOwnPropertySymbols(obj)

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

(5)Reflect.ownKeys(obj)

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

8.4 扩展运算符

对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

var foo = { a: 1 };
var bar = { b: 2, c: 3 };

// es5
var baz = {};
for (var k in foo) baz[k] = foo[k];
for (var k in bar) baz[k] = bar[k];

// es6
var baz = {
    ...foo,
    ...bar
}

8.5 Object.is()

Object.is()方法用来判断两值是否一样

// es5
-0 === +0 // true
NaN === NaN // false

// es6
Object.is(-0, +0); // false
Object.is(NaN, NaN); // true;

8.6 Object.assign()

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

var foo = { a: 1 };
var bar = { b: 2 };
var baz = { c: 3 };

// es5 - 对象合并
for (var prop in bar) foo[prop] = bar[prop];
for (var prop in baz) foo[prop] = baz[prop];
console.log(foo);  // { a: 1, b: 2, c: 3 }

// es6 - 对象合并
Object.assign(foo, bar, baz); 
console.log(foo); // { a: 1, b: 2, c: 3};

8.7 Object.values()

Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。

var obj = { foo: 'bar', baz: 42 };
// es5
var arr = [];
for (var prop in obj) arr.push(obj[prop]); // ['bar', 42]

// es6
Object.values(obj); // ['bar', 42]

8.8 Object. entries()

Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

var obj = { foo: 'bar', baz: 42 };
// es5
var arr = [];
for (var prop in obj) arr.push([prop, obj[prop]]); // [["foo", "bar"], ["baz", 42]]

// es6
Object.entries(obj); // [["foo", "bar"], ["baz", 42]]

9. Symbol

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值,可以保证不会与其他属性名产生冲突。

var x = Symbol();
var y = Symbol();

x === y // false
typeof x; // "Symbol"

9.1 可以为Symbol添加描述

const sym = Symbol('foo');
sym.description // foo

9.2 作为属性名的 Symbol

let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

9.3 Symbol.for

该方法可以重用同一个 Symbol 值。

const s1 = Symbol.for('foo');
const s2 = Symbol.for('foo');

s1 === s2 // true

9.4 Symbol.keyFor

该方法返回一个已登记的 Symbol 类型值的key

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"

10. Set 和 Map 数据解构

10.1 Set 定义

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

Set 本身是一个构造函数,它可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。下面罗列几种常见的创建形式:

// 方式一
const set = new Set();
[2, 3, 3, 4, 5, 5, 6].forEach(x => set.add(x));
console.log(set); // Set(5) {2, 3, 4, 5, 6}

// 方式二
const set = new Set([2, 3, 3, 4, 5, 5, 6]);
console.log(set); // Set(5) {2, 3, 4, 5, 6}

// 方式三
const set = new Set(document.querySelectorAll('div'));

10.2 Set 实例的属性和方法

var set = new Set();

set.add(1).add(2).add(2);

set.size // 2

set.has(1); // true
set.has(2); // true
set.has('2'); // false
set.has(3); // false

set.delete(2); // true
set.has(2); // false

set.clear();
set.size: // 0

10.3 Set 遍历

Set 结构的实例有四个遍历方法,可以用于遍历成员。

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"]

10.4 Set 应用

[...new Set([1, 1, 2, 3, 3, 4, 4, 4, 5])]; // [1, 2, 3, 4, 5]
[...new Set('abababcd')].join(''); // abcd
let a = new Set([1, 2, 3]);
let b = new Set([2, 3, 4]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// Set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

10.5 Map 定义

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

var a = { foo: 1 }, b = {};
b[a] = 1;

console.log(b); // {[object Object]: 1} 自动将对象转化为字符串

为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

Map作为构造函数,也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。

const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

10.6 Map 实例属性和方法

const map = new Map();

map.set('name', 'checkson');
map.get('name'); // checkson
map.set('age', 23).set('sex', 'male'); // 链式用法

map.has('name');
map.size; // 3

map.delete('age');
map.size; // 2

map.clear();
map.size; // 0

10.7 Map遍历

Map 结构原生提供三个遍历器生成函数和一个遍历方法。

需要特别注意的是,Map 的遍历顺序就是插入顺序。

const map = new Map([
  ['F', 'no'],
  ['T',  'yes'],
]);

for (let key of map.keys()) {
  console.log(key);
}
// "F"
// "T"

for (let value of map.values()) {
  console.log(value);
}
// "no"
// "yes"

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

参考图书

《ECMAScript 6 入门》