let、const块作用域ES6引入块作用域考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。letlet声明的变量只在它所在的代码块有效。let不允许在相同作用域内重复声明同一个变量let声明不存在变量提升,会产生暂时性死区const声明并赋值一个只读的变量const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动,因此值类型变量不可变,而引用类型变量可以更改实体数据内容。其他特性和let一致顶层对象let、const、class命令声明的全局变量,不属于顶层对象的属性解构ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构实质是模式匹配等号右边的对象需要具备 Iterator 接口// 基本模式匹配let [a, b, , c] = [1, 2, 3]; // a = 1; b = 2; c = undefined;// 复杂模式匹配[a, b, c] = [1, [2], 3]; // a = 1; b = [2]; c = 3;[a, [b], c] = [1, [2], 3]; // a = 1; b = 2; c = 3;[a, [b], c] = [1, [2, 3], 4]; // a = 1; b = 2; c = 4;[a, [b], c] = [1, 2, 3]; // Uncaught TypeError: undefined is not a function// 与rest参数结合使用let [head, …tail] = [1, 2, 3, 4]; // head = 1; tail = [2, 3, 4]// 设置默认值,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。let [foo = 1] = []; // foo = 1;let [foo = 1] = [null]; // foo = null;// 默认值是表达式的情况,表达式惰性求值function f() { console.log(‘aaa’);}let [x = f()] = [1];// 默认值为变量的情况,变量必须已声明let [x = 1, y = x] = []; let [x = y, y = 1] = []; // ReferenceError: y is not defined// 解构字符串const [a, b, c, d, e] = ‘hello’; // a = ‘h’; b = ’e’; c = ’l’; d = ’l’; e = ‘o’let { foo, bar } = { foo: “aaa”, bar: “bbb” }; // foo = ‘aaa’; bar = ‘bbb’;// 匹配的模式: 变量let { foo: f, bar: b } = { foo: “aaa”, bar: “bbb” }; // foo = undefined; f = “aaa”; b = “bbb”// 默认值let { baz = {} } = { foo: “aaa”, bar: “bbb” }; // baz = {};let {x: y = 3} = {x: 5}; // y = 5;// 混合解构let { items: [{name}] } = { id: ‘1’, items: [{name: ‘joke’}, {name: ’tony’}] }; // name = ‘joke’let [{name, habbits: [habbit]}] = [{id: ‘1’, name: ‘kelly’, habbits: [‘piano’]}, {id: ‘2’, name: ’tony’}]; // name = ‘kelly’; habbit = ‘piano’// 嵌套赋值let obj = {};let arr = [];({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });obj // {prop:123}arr // [true]// 在直接使用赋值表达式时,需要使用圆括号包住赋值表达式,大括号写在行首会被解释为代码块let x;{x} = {x: 1}; // SyntaxError: syntax error({x} = {x: 1}); // x = 1;const arr = [1,2,3,4]let {0 : first, [arr.length - 1] : last, length} = arr; // first = 1; last = 4; length = 4// 函数参数的解构赋值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]字符串模板字符串${name}扩展方法startsWithlet s = ‘Hello world!’;s.startsWith(‘world’) // trues.startsWith(‘world’, 6) // true, 第二个参数针对第n个位置直到结束endsWithlet s = ‘Hello world!’;s.endsWith(‘world’) // trues.endsWith(‘Hello’, 5) // true, 第二参数针对前n个字符includeslet s = ‘Hello world!’;s.includes(‘world’) // trues.includes(‘Hello’, 6) // false, 第二参数针对第n个位置直到结束repeat’hello’.repeat(2) // hellohello,repeat重复字符串操作padStart, padEnd// 补全字符串方法,接受两个参数,第一个参数指定字符串长度,第二个参数指定补全内容’x’.padStart(5, ‘ab’) // ababx’x’.padEnd(3, ‘a’) // xaa数值在Number原型上新增了isFinite(), isNaN(), parseInt(), parseFloat(), isInteger()方法,用来代替全局方法扩展方法Math.trunc - 去除小数部分Math.sign - 判断一个数到底是正数、负数、还是零Math.cbrt - 计算立方根函数默认值参数全等于undefined时使用默认值默认值为函数时惰性执行默认值为表达式时惰性执行,并不会缓存下计算值rest参数 - 获取函数的多余参数, 代替arguments使用name属性箭头函数箭头函数本身不存在上下文this在定义时确定不能用作构造函数没有arguments,用rest代替不能使用yield命令数组解构扩展运算符扩展方法Array.from - 将类数组对象或可遍历对象转换为数组Array.of - 将一组值转换为数组copyWithinfindfindIndexfillentrieskeysvaluesincludes - 代替indexOf,更加直观,避免NaN判断的问题flat[1, 2, [3, [4, 5]]].flat() // [1, 2, 3, [4, 5]],单层拉平[1, 2, [3, [4, 5]]].flat(2) // [1, 2, 3, 4, 5],两层拉平[1, [2, [3]]].flat(Infinity) // [1, 2, 3]flatMap - flat和map的结合对象属性简写属性名表达式let userName = ‘joe’;let firstName = ‘curly’;let lastName = ‘water’; [userName]: 23, [firstName + lastName]: 25};- 方法的name属性- 属性遍历- for…in - 遍历原型链,遍历自身和继承的可枚举属性(不含Symbol属性)- Object.keys - 返回一个数组,包括自身的所有可枚举属性(不含 Symbol 属性)的键名- Object.getOwnPropertyNames - 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性)的键名- Object.getOwnPropertySymbols - 返回一个数组,包含对象自身的所有Symbol属性的键名- Reflect.ownKeys - 返回一个数组,包含对象自身的所有键名- super关键字 - 指向当前对象的原型对象,只能用在简写的对象方法中- 解构- 扩展运算符- 扩展方法- Object.is() - 严格相等,解决全等运算符NaN
不等于自身,以及+0
等于-0
等问题- Object.assign() - 用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象,浅拷贝- Object.getOwnPropertyDescriptors() - 返回对象属性的描述对象- Object.setPrototypeOf() - 设置原型对象- Object.getPrototypeOf() - 读取原型对象- Object.keys() - 返回一个数组,包括自身的所有可枚举属性(不含 Symbol 属性)的键名- Object.values() - 返回一个数组,包括自身的所有可枚举属性(不含 Symbol 属性)的键值- Object.entries() - 方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组- Object.fromEntries() - Object.entries的逆操作# SymbolES6新加入的类型值,表示独一无二的值let s = Symbol();- Symbol
函数的参数只是表示对当前 Symbol 值的描述- 不能与其他类型的值进行运算- 作为属性名,不能使用点运算符let mySymbol = Symbol();// 第一种写法 let a = {}; a[mySymbol] = ‘Hello!’;// 第二种写法 let a = {[mySymbol]: ‘Hello!’};// 第三种写法 let a = {}; Object.defineProperty(a, mySymbol, { value: ‘Hello!’ });// 以上写法都得到同样结果 a[mySymbol] // “Hello!”- 可以用于定义一组常量,保证这组常量的值都是不相等的const log = {};log.levels = {DEBUG: Symbol(‘debug’),INFO: Symbol(‘info’),WARN: Symbol(‘warn’)}; console.log(log.levels.DEBUG, ‘debug message’); console.log(log.levels.INFO, ‘info message’);# Set 和 MapSetES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set
本身是一个构造函数,用来生成 Set 数据结构。const s = new Set([1, 2, 3]);s.add(4);s.delete(4);s.has(4);s.clear();s.size;s.keys();s.values();s.entries();s.forEach((value, key) => console.log(key + “:” + value));WeakSetWeakSet 结构与 Set 类似,也是不重复的值的集合。WeakSet 的成员只能是对象。WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。MapES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。const map = new Map();map.set(‘foo’, true);map.set(‘bar’, false);map.set([1], false);map.size // 2map.get(‘foo’);map.has(‘foo’);map.delete(‘foo’);map.clear();for (let key of map.keys()) { console.log(key);}for (let value of map.values()) { console.log(value);}for (let item of map.entries()) { console.log(item[0], item[1]);}// 或者for (let [key, value] of map.entries()) { console.log(key, value);}// 等同于使用map.entries()for (let [key, value] of map) { console.log(key, value);}WeakMapWeakMap
结构与Map
结构类似,也是用于生成键值对的集合。WeakMap
只接受对象作为键名(null
除外)WeakMap
的键名所指向的对象,不计入垃圾回收机制。# ProxyProxy 是一个构造函数,可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。# Reflect保存Object对象的一些属于语言内部的方法,比如说defineProperty
/get
/apply
好处在于:让Object
操作都变成函数行为,在Proxy中可以获取对象的默认行为> - Reflect.apply(target, thisArg, args)> - Reflect.construct(target, args)> - Reflect.get(target, name, receiver)> - Reflect.set(target, name, value, receiver)> - Reflect.defineProperty(target, name, desc)> - Reflect.deleteProperty(target, name)> - Reflect.has(target, name)> - Reflect.ownKeys(target)> - Reflect.isExtensible(target)> - Reflect.preventExtensions(target)> - Reflect.getOwnPropertyDescriptor(target, name)> - Reflect.getPrototypeOf(target)> - Reflect.setPrototypeOf(target, prototype)# Promise所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。Promise
对象是一个构造函数,用来生成Promise
实例。状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)- Promise
新建后就会立即执行。- 调用resolve
或reject
并不会终结 Promise
的参数函数的执行。- then
方法返回的是一个新的Promise
实例- catch
方法会捕获状态确定前的所有错误,包括在then回调函数中的错误- Promise
会吃掉错误,不会对后续代码执行产生影响- finally
方法,不管 Promise
对象最后状态如何,都会执行的操作- Promise.all
方法用于将多个 Promise
实例,包装成一个新的 Promise
实例。多个Promise
实例都改变状态,才会调用新Promise
实例的回调- 如果作为参数的 Promise
实例,自己定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法。- Promise.race
方法用于将多个 Promise
实例,包装成一个新的 Promise
实例。第一个Promise
实例都改变状态,进入新Promise
实例的回调- Promise.resolve(reason)
方法也会返回一个新的 Promise
实例,该实例的状态为fulfilled
。- Promise.reject(reason)
方法也会返回一个新的 Promise
实例,该实例的状态为rejected
。const promise = new Promise(function(resolve, reject) { // … some codeif (/ 异步操作成功 /){resolve(value);} else {reject(error);}});promise .then(function(value) { console.log(value) }, function (err) {console.log(err);}) .catch(function(error) { console.log(error) });# IteratorIterator(遍历器)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。var it = makeIterator([‘a’, ‘b’]);it.next() // { value: “a”, done: false }it.next() // { value: “b”, done: false }it.next() // { value: undefined, done: true }function makeIterator(array) { var nextIndex = 0; return {next: function() { return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {value: undefined, done: true};}};}ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator
属性,或者说,一个数据结构只要具有Symbol.iterator
属性,就可以认为是“可遍历的”(iterable)原生具备Iterator接口的数据结构- Array- Map- Set- String- TypedArray- 函数的 arguments 对象- NodeList 对象for...of
循环调用遍历器接口,作为遍历所有数据结构的统一的方法。for...in
循环主要是为遍历对象而设计的。forEach
无法跳出循环for...of
可跳出循环,严格按照顺序遍历# GeneratorGenerator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。function* helloWorldGenerator() { yield ‘hello’; yield ‘world’; return ’ending’;}var hw = helloWorldGenerator();hw.next()// { value: ‘hello’, done: false }hw.next()// { value: ‘world’, done: false }hw.next()// { value: ’ending’, done: true }hw.next()// { value: undefined, done: true }- yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行- 只有调用next
方法时,Genarator函数才会执行- yield
只能被Genarator函数包裹,普通函数不行- yield*
表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数# asyncGenerator 函数的语法糖async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。await
命令后面可以是Promise
对象async
函数的返回值是 Promise 对象# Classclass MyClass { constructor() {this.name = name;} get prop() {return ‘getter’;} set prop(value) {console.log(‘setter: ‘+value);}}- 类和模块的内部,默认就是严格模式,所以不需要使用use strict
指定运行模式- 类不存在变量提升new Foo(); // Error class Foo {print () { console.log(‘Hello’); }}- name 属性- 可以使用Generator实现Symbol.iterator遍历器- 类的方法内部如果含有this
,它默认指向类的实例。但是如果单独提取方法出来用,容易报错class Logger {printName(name = ’there’) { this.print(Hello ${name}
);}print(text) { console.log(text);}}const logger = new Logger(); const { printName } = logger; printName(); // TypeError: Cannot read property ‘print’ of undefined解决方法class Logger {constructor() { this.printName = this.printName.bind(this);}// …}class Logger {prinitName () { this.print(Hello ${name}
) }// …}- 如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。class Foo {static classMethod() { return ‘hello’;}}Foo.classMethod() // ‘hello’var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function如果静态方法包含this
关键字,这个this
指的是类,而不是实例。父类的静态方法,可以被子类继承。- 实例属性除了在constructor()
方法里面定义,也可以直接写在类的最顶层。class IncreasingCounter {_count = 0;get value() { console.log(‘Getting the current value!’); return this._count;}increment() { this._count++;}}- 静态属性class Foo {static prop = 1;}- 私有属性- 使用_约定- 结合Symbol使用避免被覆盖- 使用#代表私有属性class IncreasingCounter { #count = 0; get value() { console.log(‘Getting the current value!’); return this.#count; } increment() { this.#count++; }}```new.target - 该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。类继承 - 子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。Decorator修饰器是一个对类进行处理的函数@testableclass MyTestableClass { // …}function testable(target) { target.isTestable = true;}MyTestableClass.isTestable // truefunction testable(isTestable) { return function(target) { target.isTestable = isTestable; }}@testable(true)class MyTestableClass {}MyTestableClass.isTestable // true@testable(false)class MyClass {}MyClass.isTestable // false修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数。@connect(mapStateToProps, mapDispatchToProps)export default class MyReactComponent extends React.Component {}修饰对象属性class Person { @readonly name() { return ${this.first} ${this.last}
}}function readonly(target, name, descriptor){ // target要修改的对象 // name要修饰的属性名 // descriptor要修饰的属性的描述对象 descriptor.writable = false; return descriptor;}修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。ModuleCommonJS规范始于nodejs特点:同步加载模块,运行时加载接口:// moduleA.jsmodule.exports = function( value ){ return value * 2;}// moduleB.jsvar multiplyBy2 = require(’./moduleA’);var result = multiplyBy2(4);原理:通过require读取并执行一个JS文件返回该模块的exports对象的拷贝,一旦输出一个值,模块内部的变化就影响不到这个值。CommonJS 的一个模块,就是一个脚本文件。require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象缓存。AMD规范为浏览器设计,常用requirejs实现特点:异步加载模块,运行时加载接口:define(‘myModule’, [‘jquery’], function($) { // $ 是 jquery 模块的输出 $(‘body’).text(‘hello world’);});require([‘myModule’], function(myModule) {});// 未使用模块名,类CommonJS使用define(function(require, exports, module) {})ES6 模块浏览器与服务器通用特点:ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。原理:通过export命令显式指定输出的代码(并非输出对象),再通过import动态引用。接口:export命令用于规定模块的对外接口import命令用于输入接口export default规定模块默认接口,本质是输出一个叫default的变量,所以在模块中只能唯一存在,并且不可更改,不能跟声明语句export 其他模块,export和import的复合写法,实际上并没有导入当前模块,只是转发import命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行,import和export命令只能在模块的顶层,不能在代码块之中import()函数完成动态加载,返回一个Promise对象// Module1var m = 1;export { m n as N};// 输出一个default变量,将变量m的值赋给变量defaultexport default m;// Module2import {m, N} from “Module1”; // 导入m和N接口import { m as M } from “Module1”; // 导入m接口,重命名为Mimport module1 from “Module1”; // 导入默认接口import * as module1 from “Module1”; // 导入所有接口export * from “Module1”; // 再输出,export *命令会忽略Module1模块的default方法。加载规则浏览器对于带有type=“module"的<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。外部ES6模块脚本特性:代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。模块脚本自动采用严格模式,不管有没有声明use strict。模块之中,可以使用import命令加载其他模块(.js后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用export命令输出对外接口。模块之中,顶层的this关键字返回undefined,而不是指向window。也就是说,在模块顶层使用this关键字,是无意义的。同一个模块如果加载多次,将只执行一次。解决循环加载问题:使用函数声明做提升编程风格在let和const之间,建议优先使用const常量表示,便于理解有利于未来多线程编写JavaScript 编译器会对const进行优化,所以多使用const,有利于提高程序的运行效率静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。const a = ‘apple’;let b = “banana”;b = “batman”;优先使用解构赋值如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。对象单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。使用属性表达式定义动态属性名对象的属性和方法尽量使用简写数组使用扩展运算符拷贝数组使用Array.from将类数组对象转为数组函数所有配置项都应该集中在一个对象,放在最后一个参数rest运算符代替arguments变量使用默认值语法设置函数参数的默认值。Map注意区分 Object 和 Map,如果只是需要key: value的数据结构,使用 Map 结构。因为 Map 有内建的遍历机制。模块如果模块只有一个输出值,就使用export default,如果模块有多个输出值,就不使用export default,export default与普通的export不要同时使用。如果模块默认输出一个函数,函数名的首字母应该小写。function makeStyleGuide() {}export default makeStyleGuide;如果模块默认输出一个对象,对象名的首字母应该大写。const StyleGuide = { es6: { }};export default StyleGuide;