JavaScript拾遗(3) | 青训营笔记

94 阅读9分钟

这是我参与「第四届青训营 」笔记创作活动的的第17天

前言:昨天我们介绍了ES5的新特性,今天我们将介绍ES6的新特性。

ES6

ES2015是改动最大的一个版本,基本上对ES2015之前的所有的内容都做了扩展,大体如下图所示:

es6.png

let、const关键字

在ES6之前只有一种声明变量的方式,就是使用var关键字,在ES2015中新增了letconst关键字来声明变量与常量。推荐所有变量声明都使用let或const

使用letconst关键字声明的变量或者常量是具有块级作用域的,同时let或者const关键字声明的变量不具有变量提升的特性,且存在暂时性死区的特性。

块级作用域:

{
    val a = 1;
} {
    let b = 2;
}
console.log(a);
console.log(b); // b not defined

箭头函数

function a() {
    // do something
}

等价于

const a = () => {
    // do something
}

注意

  1. 箭头函数的this是根据执行上下文决定(指向上级作用域的this),内部并不会绑定this
  2. 箭头函数中不存在arguments,所以箭头函数不可以使用arguments。
  3. 箭头函数不存在预解析,所以我们使用箭头函数必须先定义、再使用。
// 1. this指针
let obj = {
    uname : '张三',
    fei : function () {
        // console.log(this);
        // setInterval( function () {
        // 	console.log( this );
        // }, 1000 );
        setInterval( () => {
                console.log( this );
        }, 1000 );
    }
};
obj.fei();

// 2. arguments参数
const foo = (...args) => {
    console.log(arguments)  // ReferenceError: arguments is not defined`
    console.log(args)  // args 是一个数组
}; 
foo(1, 2, 3, 4)  // [ 1, 2, 3, 4 ]

// 3. 函数提升
c(); function c () {console.log('c')}; // OK
d(); const d = () => {console.log('d')}; // d is not defined

数值

在ES2015中对数值的扩展主要时为MathNumber两个对象增加一些方法,以及二进制和八进制的表示方法。在ES2015中使用0b或者0B表示二进制,使用0o或者0O表示八进制。

Number扩展

属性/方法名描述
Number.EPSILON数值最小精度:2.220446049250313e-16
Number.MIN_SAFE_INTEGER最小安全数(-2^53)
Number.MAX_SAFE_INTEGER最大安全数(2^53)
Number.parseInt()把参数解析为整数并返回
Number.parseFloat()把参数解析为浮点数并返回
Number.isFinite()判断是否为有限数值
Number.isNaN()判断是否为NaN
Number.isInteger()判断是否为整数
Number.isSafeInteger()判断数值是否在安全范围内

Math扩展

方法名描述
Math.trunc()返回数值整数部分
Math.sign()返回数值类型(正数1、负数-1、零0)

字符串

模板字符串

const val1 = `a string`; // 但一般开发中固定字符串依旧使用''

// 模板字符串可以保留格式
let val2 = `<html>
string
format
</html>
`;
console.log(`${val2}`) // 使用 ${} 进行包裹

String扩展

方法名描述
String.fromCodePoint()用于从 Unicode 码点返回对应字符
String.raw()返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。
String.prototype.codePointAt()返回字符对应码点(String.fromCodePoint()的逆操作)
String.prototype.normalize()把字符的不同表示方法统一为同样形式,返回新字符串(Unicode正规化)
String.prototype.repeat()把字符串重复n次,返回处理后的字符串
String.prototype.includes()判断是否存在指定字符串
String.prototype.startsWith()判断字符串是否存在原始字符串的头部
String.prototype.endsWith()判断字符串是否存在原始字符串的尾部

Iterator

在介绍数组前,我们先了解ES6中的迭代器(Iterator)

迭代器是一种接口,为各种不同的数据结构提供了统一的访问机制,换句话说,只要有任何数据结构部署了迭代接口,就可以使用统一的方式的来遍历它。

实现可迭代接口的数据结构,一般都自身实现或继承了以Symbol.iterator属性的,就属于可迭代对象。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。

一个包含next()方法的对象,才可以称为一个迭代对象。next()对象的会有返回一个对象,对象中包含两个值:

  • value:迭代器返回的任何JavaScript值。donetrue时可省略。
  • done:一个布尔值,为false时表示迭代未停止,为true时立即停止迭代器,且可以省略value的值。

image.png

数组

展开运算符...

const arr = [1, 2, 3, 4, 5, 6]
const newArr = [...arr] // 复制数组
console.log(Math.max.call(null, ...arr)) // 6

Array扩展

Array.from()

将类数组对象或者可迭代对象创建为一个新的数组

function foo() {
  return Array.from(arguments) // 将 arguments 转换为数组
}
console.log(foo(1, 2, 3, 4, 5, 6)) // [ 1, 2, 3, 4, 5, 6 ]
Array.prototype.find()

根据给定的回调函数,找到匹配的第一个元素,找不到返回undefined

const arr = [1, 2, 3, 4]
arr.find(item => item === 2) // 2(表示元素)
Array.prototype.findIndex()

根据给定的回调函数,找到匹配的第一个元素的索引,找不到返回-1

const arr = [1, 2, 3, 4]
arr.findIndex(item => item === 2)  // 1 (表示索引)
Array.prototype.fill()

将给定值填充数组

const arr = [1, 2, 3, 4]
// 将给定值填充索引1-3
arr.fill('999', 1, 3) // [ 1, '999', '999', 4 ]
Array.prototype.keys()

返回一个可迭代的对象(Array Iterator),其内容为数组的key

const arr = ['aaa', true, 123]
const keys = arr.keys()
for (const i of keys) {
  console.log(i) // 遍历结果 0 1 2
}
Array.prototype.values()

返回一个可迭代的对象,其内容为数组的value

const arr = ['aaa', true, 123]
const values = arr.values()
for (const i of values) {
  console.log(i) // 遍历结果 'aaa', true, 123
}
Array.prototype.entries()

返回一个可迭代的对象,其内容是一个数组[key, value],索引0为原数组的元素,1为原数组该位置的值

const arr = ['aaa', true, 123]
const entries = arr.entries()
console.log(Array.from(entries)) // [[0, 'aaa'], [1, true], [2, 123]]
for (const i of entries) {console.log(i)} // [0, 'aaa'] [1, true] [2, 123]

对象

对象的属性名和属性值一致时可以只写属性名

// es6前
function a (event) {
    // ...
    return {
        event: event,
        // ...
    }
}

// es6后
const a = (event) => {
    // ...
    return {
        event
        // ...
    }
}

[]包裹作为属性名

const myName = 'name'
const age = 20
const person = {
  myName,
  ['a' + 'g' + 'e']: age,
}
console.log(person) // { myName: 'name', age: 20 }

Object.is()

用于比较两个值是否相等,用于解决NaN ≠= NaN,+0 === -0的问题

console.log(NaN === NaN) // false
console.log(+0 === -0) // true
 
console.log(Object.is(NaN, NaN)) // true
console.log(Object.is(+0, -0)) // false

Object.assign()

常用!将所有可枚举属性的值从一个或多个源对象复制到目标对象,并返回目标对象

const person = Object.assign({}, { name: 'name' }, { age: 20 })
console.log(person) // { name: 'name', age: 20 }

类(Class)

ES6 最大的改进之一就是提出了类的概念,在语法层面上有了类。

即使JavaScript的类的概念和其他OOP语言有很大不同,这些年也有很多人诟病JS的类,因为JS中的类从设计之初就是残疾的。但不可否认的是,ES6提出类的概念是对原型链语法的一次重大改进。

  • class是语法糖
  • 让其他oop语言学习者更容易理解js
  • 良好IDE支持
class Person {
  constructor(age) {
    // 属性
    this.myName = 'name'
    this.age = age
  }
  // 静态方法
  static print() {
    console.log()
  }
  // 访问器
  get myName() {
    console.log('getter')
    return 'abc'
  }
  set myName(v) {
    console.log('setter' + v)
  }
  setName(v) {
    this.myName = v
  }
}
const person = new Person(18)
person.setName('ywanzhou') // 触发 setter 访问器
console.log(person.myName) // 触发 getter 访问器

解构赋值

允许我们使用按照一定的模式,在数组或者对象中提取指定的值

// swap a with b
let a = 1;
let b = 2;
[a, b] = [b, a]

Promise

Promise是ES6中提供的一个异步解决方案,解决了回调地狱的问题。

通过Promise()构造函数可以创建一个promise对象,每一个Promise对象都具有以下几种状态:

  • pending: 初始状态,既不是成功,也不是失败状态。
  • resolved: 意味着操作成功完成。
  • rejected: 意味着操作失败。

状态的切换只有两种,分别是:

  • pending → resolved
  • pending → reject

一旦状态发生改变,就不会再次改变。

Promise实例中存在then方法,允许我们在Promise实例中链式调用。每个then方法还会返回一个Promise实例。

new Promise((resolve, reject) => {
    console.log('我是第一个Promise中的log')
    const a = 'aaa'
    resolve(a)
})
    .then((a) => {
        console.log('我是第一个then中的log,携带:', a)
        resolve(a)
    })
    .then((a) => {
        console.log('我是第二个then中的log,但是我出现了异常,携带:', a)
        throw new Error('Error')
    })
    .then(() => {
        console.log('我是第三个then中的第一个回调的log,但是我不会执行,因为我上面出现了异常')
    }, (err) => {
        console.log('我是第三个then中的第二个回调的log,捕捉到错误:', err)
    })
    .then(() => {
        console.log('我是第四个then中的log,我可以正常执行')
    })
    
/*
我是第一个Promise中的log
我是第一个then中的log,携带: aaa
我是第三个then中的第二个回调的log,捕捉到错误: ReferenceError: resolve is not defined at <anonymous>:8:9
我是第四个then中的log,我可以正常执行
*/

有关Promise的一些方法如下:

  • Promise.prototype.then():它最多需要有两个参数:Promise的成功和失败情况的回调函数;
  • Promise.prototype.catch():等于then方法的第二个参数;
  • Promise.all():将多个实例包装成一个新实例,返回全部实例状态变更后的结果数组(齐变更再返回)
  • Promise.race():将多个实例包装成一个新实例,返回全部实例状态优先变更后的结果(先变更先返回)
  • Promise.resolve():将对象转为Promise对象(等价于new Promise(resolve => resolve()))
  • Promise.reject():将对象转为状态为rejected的Promise对象(等价于new Promise((resolve, reject) => reject()))

Set、Map、WeakSet、WeakMap

  • SetWeakSet与数组类似,准确的它他们是集合,这两者的区别就是Set可以存储任何数据类型,而WeakSet只能存储对象的引用,而且是弱引用;
  • MapWeakMap与对象类似,存储方式是键值对形式的,这两者的区别Map的键值对都是可以是任意的而WeakMap键必须是对象的引用而值可以是任意类型的。

Set对象在实际开发中最常见的就是实现数组去重

const arr = [1, 2, 2, 3, 4, 3, 5]
const set = new Set(arr)
// set对象可以使用 ... 展开 所有项
console.log([...set]) // [ 1, 2, 3, 4, 5 ]

Generator

Generator是ES6中提供的一种异步编程解决方案,定义Generator函数在function关键字和函数名中间使用*星号,函数内部使用yield关键字定义不同的状态。

function* testGenerator() {
  // yield定义一个状态
  yield 'a'
  yield 'b'
  return 'c' // 结束Generator,后面即使有yield关键字也无效
  yield 'd'
}
const g = testGenerator() // 返回 Generator 对象,通过next()方法移动状态
 
g.next()
/* { value: 'a', done: false } */
 
g.next()
/* { value: 'b', done: false } */
 
g.next()
/* { value: 'c', done: true } */

g.next()
/* {value: undefined, done: true} */