篇三:大前端基础之ES6+特性笔记

212 阅读8分钟

文章输出主要来源:拉勾大前端高新训练营(链接)。小哥哥小姐姐请不要嫌弃啰嗦,下面肯定都是干货。

1. 简介

ECMAScript是JavaScript(语言层面)的规范,JavaScript是ECMAScript的一种实现。ECMA-262规范

JavaScript的构成

  1. 浏览器端: ECMAScript,DOM,DOM
  2. Node: ECMAScript + Node API (fs、net、etc.)

2. 作用域与常量

2.1 块级作用域let

ES6之前的函数作用域与闭包

JavaScript最初只有全局作用域与函数作用域,变量通过var定义。在缺乏块级作用域时,使用for循环等易于出错,需借助闭包技术进行变量的临时保存。

闭包的产生也与函数作用域离不开关系,外层函数中定义的变量可以被内层函数所访问,外层函数返回一个新的函数,在内层函数中使用外层函数所定义的变量,执行外层函数后,其中定义的变量由于被使用并未被内存释放,因而再运行内层函数仍可访问该变量,通过函数作用域保存变量的技术也就是闭包的技术。

例:

// example1 闭包的普通使用
function outer() {
  var hello = 'hello ';
  return function(name) {
    console.log(hello + name);
  }
}

const sayHello = outer();
// outer已经运行过,再sayHello,仍可访问到outer中定义的hello变量
sayHello('world') // hello world
// example2 循环注册事件
function memoryIndex(index) { // index为外层函数隐式定义的变量
  return function() {
    console.log(index)
  }
}

var domList = []; // 模拟dom元素

// 使用闭包
for (var i = 0; i < 10; i++) {
  var click = memoryIndex(i);
  domList.push({click: click});
}

domList[5].click(); // 5
domList[2].click(); // 2
domList[8].click(); // 8

// 不使用闭包,变量没有被暂存,最终打印全局定义的i,全部为10
var domList2 = []
for (var i = 0; i < 10; i++) {
  var click = function() {
    console.log(i)
  };
  domList2.push({click: click});
}
domList2[5].click(); // 10
domList2[2].click(); // 10
domList2[8].click(); // 10

ES6带来的let

let可定义块级作用域变量,在一个{}块内可以有自己的作用域变量,如上方不使用闭包的代码改写为以下方式:

let domList2 = []
for (let i = 0; i < 10; i++) {
  var click = function() {
    console.log(i)
  };
  domList2.push({click: click});
}
domList2[5].click(); // 8
domList2[2].click(); // 2
domList2[8].click(); // 5

babel repl代码转换工具可以将ES6+语法转换为ES5, 通过转换过后的代码可以发现,转换过后的代码也是通过闭包进行变量的暂存。

for循环中的作用域

for循环中的作用域为两层。for (let i = 0; i < 3; i++)括号中的i为第一层,在{}循环体中再定义i变量,该变量为第二层作用域,不会与外层i有冲突

for(let i = 0; i < 3; i++) {
  let i = 100;
  console.log(i);
}
// 输出 100 100 100

变量提升

  1. var定义的变量存在变量提升,在声明变量前使用变量,变量值为undefined
  2. let定义的变量不存在变量提升,在生命变量之前使用变量会报错变量未定义

2.2 const常量定义

const在let的基础上增加了只读特性。通过const定义的变量值不能被修改(若为引用变量,不能修改其指针指向,内部属性仍可被修改)。

const定义变量必须初始化。

2 解构

解构包含数组解构对象结构, 通过解构赋值可以简化代码编写

2.1 数组解构

  1. 通过位置解构出在数组中对应位置的变量
const arr = [1,2,3,4];
const [first, , third] = arr;
console.log(first, third); // 1 3
  1. 剩余变量通过...rest语法结构出一个剩余变量的数组
const arr = [1,2,3,4];
const [first, ...rest] = arr;
console.log(first, rest); // 1 [ 2, 3, 4 ]
  1. 超出数组长度的解构变量会被赋值为undefined
const arr = [1,2];
const [first, second, third] = arr;
console.log(first, second, third); // 1 2 undefined
  1. 解构默认值,通过给解构变量赋值设置默认值
const arr = [1,2];
const [first, second, third = 'third'] = arr;
console.log(first, second, third); // 1 2 third
  1. 设置默认值后,对应位置为undefined的元素不会重新赋值给该变量
// example1
const arr = [1,null, undefined, false];
const [first, second, third = 'third', forth] = arr;
console.log(first, second, third, forth);  // 1 null third false

2.2 对象结构

  1. 通过属性字面量结构对应属性的值
const obj = {
  hello: 'hello',
  world: 'world'
}

const { hello, world } = obj;
console.log(hello, world) // hello world
  1. 剩余变量...rest语法结构剩余的属性拼出新对象

  2. 未定义的属性被结构出来为undefined

  3. 结构默认值,与数组使用方式一致

  4. 重命名,可以通过重命名对解构的变量重新命名

    const obj = {
      hello: 'hello',
      world: 'world'
    }
    
    const { hello: h, world: w } = obj;
    console.log(h, w)  // hello world
    

3 字符串扩展

3.1 模板字符串

基础使用

通过**** 反引号定义模板字符串,其中可以使用换行,单引号,双引号等。通过${variable}`插值表达式的方式可嵌入合法js语句

高级使用

带标签的模板字符串: 使用带标签的模板字符串,需要先定义一个函数(标签函数)

// 参数列表为:第一个是通过模板字符串中变量对模板字符串进行分割,产生的字符串数组,其余参数依次为模板字符串中的变量
function tagFunction(strings, name, age) {
  console.log(strings, name, age);
  const sex = gender ? 'man' : 'woman';
  return strings[0] + name + strings[1] + sex + strings[2];
}

const name = 'tom', gender = true;
// 使用方式,标签函数+模板字符串。结果为标签函数的返回值。
const result = tagFunction`hello, ${name} is a ${gender}.`

console.log('result:', result) 
// [ 'hello, ', ' is a ', '.' ] tom true
// result: hello, tom is a man.

3.2 字符串扩展方法

  1. includes(subStr): 字符串是否包含subStr
  2. startsWith(subStr): 字符串是否已subStr开头
  3. endsWith(subStr): 字符串是否已subStr结尾

4 函数扩展

4.1 参数默认值

参数默认值与结构默认值的使用方法类似,。

注意:

在使用函数默认值时候,该参数必须在参数列表末尾

function sayHello (name = 'Tom') {
  console.log('hello ' + name);
}
sayHello(); // hello Tom
sayHello('Jerry'); // hello Jerry

4.2 剩余参数

剩余参数的使用与数组解构中的剩余变量结构用法一致

function test(first, ...rest) {
  console.log(first, rest);
}

test(1,2,3); // 1 [2, 3]

4.3 展开运算符

...不仅可以用于剩余参数,也可以进行展开操作。

可以展开数组,对象。

对于函数参数可以将数组展开作为函数的参数,将数组内的元素依次展开放入函数参数列表、

function test(first, second, ...rest) {
  console.log(first, second, rest);
}
const params = [1,2,3,4,5,6]
test(...params) // 1 2 [3, 4, 5, 6]

4.4 箭头函数

函数体不使用花括号

函数体不使用花括号时,只能是个JavaScript表达式,其值为函数最终返回值

const add = (a, b) => a + b;

函数体使用花括号

函数体使用花括号时, 返回值要用return 自己写

const add = (a, b) => {
  return a + b;
}

箭头函数不会改变this的指向

箭头函数中没有this机制,不会改变this指向。

const person = {
  name: 'tom',
  sayHello: function() {
    console.log(`hello ${this.name}`)
  },
  sayHello2: () => {
    console.log(`hello ${this.name}`)
  }
}
person.sayHello() // hello tom
person.sayHello2() // hello undefined

5 对象扩展

5.1 对象字面量增强

1. 属性名与变量名相同可进行省略

普通属性

const name = 'tom', age = 18;
const person = {
  name,
  age
}
console.log(person) // { name: "tom", age: 18 }

方法

const name = 'tom', age = 18;
const person = {
  name,
  age,
  sayHello (name) {
    console.log(`hello ${name}`);
  }
}
person.sayHello('jerry') // hello jerry

2. 计算属性

通过[expression]动态添加对象属性名,表达式计算的结果即为对象的属性名

const obj = {
  ['hello_' + 'world']: 'hello world'
}
obj.hello_world // "hello world"

5.2 Object.assign

Object.asign可以对对象进行浅拷贝,与...展开运算符的作用一致。

const obj1 = { hello: 'hello' }
const obj2 = { world: 'world' }
const result1 = Object.assign({}, obj1, obj2);
const result2 = {...obj1, ...obj2};
console.log(result1, result2) // {hello: "hello", world: "world"} {hello: "hello", world: "world"}

5.3 Object.is

Object.is()判断两个变量是否相等。

相等符号不能解决的问题:

  1. +0 === -0: true
  2. NaN === NaN: false

Object.is()添加了这些值的比较问题

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

6 Proxy代理

vue2.x使用的响应式原理底层使用了Object.defineProperty对对象进行代理,该方法不能实现对对象方法的代理,也无法监听数组的变化。

ES6中提供的Proxy可以更好的对对象进行代理,vue3的响应式系统也采用了Proxy。不过Proxy无法被polyfill,导致其对于低版本浏览器支持性不好。

6.1 基本使用方法:

通过get,set拦截对属性的访问与设置,可以在操作前做一些自定义的操作

const person = {
  name: 'tom',
  age: 18
}

// Proxy第一个值传入被代理对象,第二个值为拦截的配置项
const personProxy = new Proxy(person, {
  get(target, property) {
    return property in target ? target[property] : 'default';
  },
  set(target, property, value) {
    if (property === 'age' && !Number.isInteger(value)) {
      throw new TypeError(`${value} is not an int`);
    }
    target[property] = value;
  }
})

console.log(personProxy.name); // tom
console.log(personProxy.age); // 18
personProxy.age = '18'; // TypeError: 18 is not an int

6.2 Proxy对比Object.defineProperty的优势

Object.defineProperty只能监听属性的读写,无法监听到delete操作,对对象方法的调用等

Proxy对对象的代理更加强大。

删除对象属性的代理

const person = {
  name: 'tom',
  age: 18
}

const personProxy = new Proxy(person, {
  deleteProperty(target, property) {
    console.log('delete', property);
    delete target[property];
  }
})

delete personProxy.age; // delete age
console.log(person) // { name: 'tom' }

Proxy可代理的所有操作

Handler触发方式
get读取某个属性
set写入某属性
hasin操作符
deletePropertydelete操作符
getPrototypeOfObject.getPrototypeOf()
setPrototypeOfObject.setPrototypeOf()
isExtensibleObject.isExtensible()
preventExtensionsObject.preventExtensions()
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor()
definePropertyObject.defineProperty()
ownKeysObject.getOwnPropertyNames()、Object.getOwnPropertySymbols()
apply调用一个函数
construct用new 调用一个函数

监听数组对象操作

const list = [];

const listProxy = new Proxy(list, {
  set(target, property, value) {
    console.log('set', property, value);
    target[property] = value;
    return true;
  }
})

listProxy.push(100) 
// set 0 100
// set length 1

Proxy可以通过非侵入式的方式完成对对象的操作,Object.defineProperty需要操作原对象

7 Reflect 反射

Reflect是一个静态类,无法通过new的方式创建新实例,只能调用其静态方法

Reflect内部定义了对对象的13中操作(之前14种,废弃了一种),与Proxy处理对象的13中操作方法对应,且为Proxy处理对象的默认操作。

Reflect的价值:

统一了一套用于操作对象的API

例:

const obj = {
  name: 'tom',
  age: 18
}

// 传统做法
console.log('name' in obj);
console.log(delete obj['age']);
console.log(Object.keys(obj));
// Reflect做法
console.log(Reflect.has(obj, 'name'))
console.log(Reflect.deleteProperty(obj, 'age'))
console.log(Reflect.ownKeys(obj))

8 Promise

通过链式调用解决异步编程回调函数嵌套过深的问题。

9 Class

类的定义

传统方式:通过构造函数进行定义,通过prototype显式原型添加对象实例的共享属性

function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = functoin(name) {
  console.log(`hello ${name}`)
}

ES6之后新方法:通过class关键词定义类

class Person {
  constructor(name) {
    this.name = name;
  }
  
  sayHello(name) {
    console.log(`hello ${name}`)
  }
}

注意:

class定义类为严格模式,严格模式下函数中的this为undefined

静态方法

静态方法也叫类方法,由类进行调用。实例方法通过new出一个实例,通过实例进行方法的调用。

class Person {
  constructor(name) {
    this.name = name;
  }
  
  sayHello(name) {
    console.log(`hello ${name}`)
  }
  
  create(name) {
    return new Person(name);
  }
}

const tom = Person.create('tom');

类的继承

ES6中通过extends关键词实现继承,super可以调用父类的构造函数。

class Student extends Person {
  constructor(name) {
    super(name)
  }
}

10 新增数据结构

10.1 Set 集合

集合中不会存在重复的元素,可用于数组去重。

使用方法:

  • set.add()添加元素

  • set.forEach()遍历

  • set.size获取集合长度

  • set.has(item)判断集合中是否存在某个值

  • set.delete(iten)删除集合中元素

  • set.clear()清空集合

const set = new Set();
s.add(1).add(2); // 集合的add方法会返回集合本身,可实现链式调用

// forEach遍历
set.forEach(item => console.log(item))
// for of 遍历
for (let item of set) {
  console.log(item);
}

console.log(set.size); // 获取集合长度
console.log(set.has(1)); // 查看集合中是否存在1
set.delete(1); // 删除集合中1
set.clear(); // 清空集合

10.2 Map

字面量对象的key无法使用复杂数据结构作为key,使用复杂数据结构作为key,key会通过toString()进行转换。

例:

const obj = {};
obj[true] = 'value';
obj[123] = 'value';
obj[{a: 1}] = 'value';

console.log(Object.keys(obj)); // [ '123', 'true', '[object Object]' ]

map数据结构真正实现了key - value键值对映射,key也可用于存储复杂数据结构。

const map = new Map();
const tom = { name: 'tom' };
map.set(tom, 'this is tom');
console.log(map); // Map { { name: 'tom' } => 'this is tom' }
console.log(map.get(tom)); // this is tom

map支持的操作方法:

  • map.set(key, value)添加键值对映射
  • map.get(key)通过key获取value
  • map.has(key)通过key查看是否存在某个映射
  • map.delete(key)通过key删除映射关系
  • map.clear()清空map
  • map.forEach((val, key ) => {})通过forEach进行遍历

10.3 Symbol 原始类型

Symbol是ES6新增的基础类型,通过Symbol类型可以创建一个独一无二的符号。

const s = Symbol();
const s1 = Symbol()
console.log(s); // Symbol()
console.log(typeof s) // symbol
console.log(s === s1) // false

通过给Symbol传递一个参数,描述Symbol

const tom = Symbol('tom');
const jerry = Symbol('jerry');
console.log(tom, jerry); // Symbol(tom) Symbol(jerry)

Symbol可以作为对象的key

const tom = Symbol('tom');
const person = {
  age: 18
}
person[tom] = 'tom';
console.log(person); // {age: 18, Symbol(tom): "tom"}

借助Symbol实现私有成员

// a.js
const name = Symbol();
const person = {
  [name] = 'tom';
  sayHello() {
    console.log(`hello ${tom}`);
  }
}


// b.js
// 无法创建相同的Symbol,无法直接访问私有成员
person.sayHello();

Symbol.for(key)

Symbol.for(key)静态方法在全局维护了一个字符串Symbol的映射关系,其中的key如果不为字符串,则会被转为字符串,由于维护的是字符串Symbol的映射关系,Symbol.for('hello') === Symbol.for('hello');Symbol.for('true') === Symbol.for(true)(布尔true会被转换为字符串'true')

Symbol常量

Symbol内部还维护着许多Symbol常量,用于作为内部方法的标识,可以让自定义对象实现js内置的接口。

例:自定义toString标签

const obj = {
  [Symbol.toStringTag]: 'TomObject'
}
console.log(obj.toString()) // [object TomObject]

注意:

通过Symbol定义的对象属性,使用for in 循环,Object,keys()时候都无法获取到。使用JSON.stringify(obj)也会忽略Symbol属性

通过Object.getOwnPropertySymbols(obj)可获取到对象中通过Symbol定义的属性名。

11 循环与遍历

11.1 for of循环

通过for of进行的循环可以使用break关键词结束循环。for of 循环可对实现了迭代器的数据结构进行遍历。

其他遍历方法:

  1. array.forEach()无法使用break
  2. for in对象专用遍历
  3. array.map()遍历并生成新数组
  4. array.filter遍历并挑选符合条件的元素生成新数组
  5. array.every遍历,所有元素满足条件返回true,否则返回false
  6. array.some遍历,有元素满足条件返回true,否则返回false

for of遍历map

通过for of 遍历map,item为[key, value]的数, 可通过数组解构方式获取key,value

const map = new Map();
map.set('a', 'aa');
map.set('b', 'bb');

for(let [key, value] of map) {
  console.log(key, value);
}

for of无法直接遍历普通对象,只能遍历实现了迭代器接接口的数据结构。

12 Iterator 迭代器

迭代器接口的实现是for of 循环的前提条件

12.1 Iterable可迭代接口

const obj = {
  [Symbol.iterator]: function() {
    return {
      next: function() {
        return {
          value: 'tom',
          done: true
        }
      }
    }
  }
}
  1. obj实现了可迭代接口(Iterable),其约定对象必须有Symbol.iterator属性用于返回迭代器的方 法
  2. [Symbol.iterator]属性对应的函数返回值实现了Iterator接口,其约定对象内部必须有用于迭代的next()方法
  3. next()方法中返回的结果实现了IterationResult接口,内部必须有value表示被迭代到的数据,done属性(布尔值)表示迭代是否完成。

例:可迭代的object

const obj = {
  store: ['foo', 'bar', 'baz'],
  [Symbol.iterator]: function() {
    let index = 0;
    const self = this;
    return {
      next: function() {
        const result = {
          value: self.store[index],
          done: index >= self.store.length
        }
        index++;
        return result;
      }
    }
  }
}

for(let item of obj) {
  console.log(item);
}
// fop bar baz

13 Generator生成器

函数名前加*定义生成器函数。

生成器函数执行后会返回一个生成器,生成器函数也实现了迭代器接口。

function *foo() {
  console.log('tom');
  return 100;
}
const result = foo(); // result为生成器对象,函数并不会立即执行。
console.log(result.next()) // {value: 100, done: true}

yield关键词

yield会暂停函数的执行,yield的值会作为next的结果中的value值。

function *foo() {
  console.log('1111');
  yield 100;
  console.log('2222');
  yield 200;
  console.log('3333');
  yield 300;
}

const g = foo();
console.log(g.next()) // 1111 {value: 100, done: false}
console.log(g.next()) // 2222 {value: 200, done: false}
console.log(g.next()) // 3333 {value: 300, done: false}
console.log(g.next()) // {value: undefined, done: true}

14 ES 2016

数组实例的includes方法

array.indexOf无法查找NaN,array.includes()可以查找

const arr = ['foo', 'bar', 'baz', NaN]
console.log(arr.includes('foo')) // true
console.log(arr.includes(NaN)) // true

指数运算符

// 2的10次方
console.log(2 ** 10) // 1024

15 ES 2017

1. 对象的Object.values(obj)方法:返回对象的值的数组

2. 对象的Object.entries(obj)方法: 返回对象的[key,valye]数组

3. Object.getOwnPropertyDescriptors(obj):获取对象属性的完整描述信息 通过Object.assign复制的对象无法正确复制getter,setter属性。可借助以上方法与Object.defineProperties实现对象的完整复制

const obj = {
  firstName: 'Tom',
  lastName: 'Wang',
  get fullName() {
    return this.firstName + ' ' + this.lastName;
  }
}
console.log(obj.fullName); // Tom Wang
const obj1 = Object.assign({}, obj);
obj1.firstName = 'Jerry';
console.log(obj1.fullName);// Tom Wang 与预期不符,因为fullNmae是个getter属性,没能正确复制过来

解决方法:

const obj = {
  firstName: 'Tom',
  lastName: 'Wang',
  get fullName() {
    return this.firstName + ' ' + this.lastName;
  }
}
console.log(obj.fullName); // Tom Wang
const propertyDescriptors = Object.getOwnPropertyDescriptors(obj);
console.log(propertyDescriptors); 
const obj1 = Object.defineProperties({}, propertyDescriptors);
obj1.firstName = 'Jerry';
console.log(obj1.fullName); // Jerry Wang

4. 字符串填充方法: String.prototype.padStart(length, padStr),String.prototype.padEnd(length, padStr)

5. 允许函数最后一个元素后面增加一个尾逗号: function(a,b,){}

6. Async/Await语法