let 和 const
Hoisting 机制
在函数作用域或全局作用域中通过关键字 var 生命的变量,无论实际上是在哪里声明的,都会被当成在当前作用域顶部声明的变量,这就是我们常说的提升(Hoisting)机制。-
块级声明
块级声明用于声明在指定块的作用域之外无法访问的变量。块级作用域(亦被称为词法作用域)存在于:- 函数内部
- 块中(字符 { 和 } 之间的区域)
-
let:
- let 类似于 var,用来声明变量
- 通过 let 声明的变量不同于 var,只在 let 命令所在的代码块内有效(块级作用域)
- let 声明的变量不存在变量提升
- let不允许在相同作用域内,重复声明同一个变量
-
const:
- const声明一个只读的常量。一旦声明,常量的值就不能改变
- const 声明必须初始化
- const的作用域与let命令相同:只在声明所在的块级作用域内有效
- const命令声明的常量也是不提升,必须先声明后使用
- const声明的常量,也与let一样不可重复声明
变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
-
数组解构:
let [a, b, c] = [123, 456, 789] console.log(a, b, c) 123 456 789
-
对象解构:
let { name, age } = { name: 'Jack', age: 18 } console.log(name, age) Jack 18
-
函数参数解构:
function f (p1, { p2 = 'aa', p3 = 'bb' }) { console.log(p1, p2, p3) } f('p1', { p2: 'p2' }) p1 p2 bb
-
字符串解构:
let [a, b, c, d, e] = 'hello' console.log(a, b, c, d, e) h e l l o
字符串
-
实用方法:
includes(String):返回布尔值,表示是否找到了参数字符串。 startsWith(String):返回布尔值,表示参数字符串是否在源字符串的头部。 endsWith(String):返回布尔值,表示参数字符串是否在源字符串的尾部。 repeat(Number):repeat方法需要指定一个数值,然后返回一个新字符串,表示将原字符串重复Number次。
-
模板字符串:
let basket = { count: 5, onSale: true } $('#result').append(` There are <b>${basket.count}</b> items in your basket, <em>${basket.onSale}</em> are on sale! `);
模板字符串(template string)是增强版的字符串,用反引号(`)标识
它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量
如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中
-
模板字符串中嵌入变量,需要将变量名写在
${}
之中- 大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性
- 大括号内部还可以调用函数
数组
-
方法:
Array.from() 将一个伪数组转为一个真正的数组 实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合, 以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。 Array.of() Array.of方法用于将一组值,转换为数组 这个方法的主要目的,是弥补数组构造函数Array()的不足。 因为参数个数的不同,会导致Array()的行为有差异。 find() 查找数组中某个元素 findIndex() 查找数组中某个元素的索引下标 includes() 返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似
-
实例方法:
ES6提供三个新的方法——entries(),keys()和values()——用于遍历数组。可以用
for...of
循环进行遍历,唯一的区别是keys()
是对键名的遍历、values()
是对键值的遍历,entries()
是对键值对的遍历。entries() keys() values()
箭头函数
- 箭头函数是一种使用(=>)定义函数的新语法,但是它与传统的 JavaScript 函数有些许不同,主要集中在以下方面:
- 没有 this、super、arguments 和 new.target 绑定
-
不能通过 new 关键字调用 箭头函数没有
[[Construct]]
方法,所以不能被当做构造函数 - 没有原型 由于不能通过 new 关键字调用箭头函数,因而没有构建原型的需求,所以箭头函数不存在 prototype 这个属性
- 不可以改变 this 的绑定 函数内部的this值不可被改变,在函数的生命周期内式中保持一致
- 不支持 arguments 对象 箭头函数没有 arguments 绑定,所以你必须通过命名参数和不定参数这两种形式访问函数的参数
- 不支持重复的命名参数 无论在严格模式还是非严格模式下,箭头函数都不支持重复的命名参数;而在传统函数的规定中,只有在严格模式下才不能有重复的命名参数。
- 箭头函数语法
let reflect = value => value; // 实际上相当于: let reflext = function(value) { return value; };
- 箭头函数中的 this 值取决于该函数外部箭头函数的 this 值,且不能通过 call()、apply() 或 bind() 方法来改变 this 的值
扩展对象
- 对象类别
- 普通(Ordinary)对象 具有 JavaScript 对象所有的默认内部行为。
- 特异(Exotic)对象 具有某些与默认行为不符的内部行为。
- 标准(Standard)对象 ES6 规范中定义的对象,例如 Array、Date 等。标准对象既可以是普通对象,也可以是特异对象。
- 内建对象 脚本开始执行时存在于 JavaScript 执行环境中的对象,所以标准对象都是内建对象。
- 新增方法
- Object.is() 方法
- ES6 引入了 Object.is() 方法来弥补全等运算符的不准确运算。这个方法接收两个参数,如果这两个参数类型相同且具有相同的值,则返回出 true。
- 对于 Object.is() 方法来说,其运行结果在大部分情况中与 === 运算符相等,唯一的区别在于 NaN 和 -0被识别为不相等并且 NaN 与 NaN 等价。
console.log(+0 == -0) // true console.log(+0 === -0) // true console.log(Object.is(+0, -0)) // false console.log(NaN == NaN) // false console.log(NaN === NaN) // false console.log(Object.is(NaN, NaN)) // true
- Object.is() 方法
字符串
- 新增方法
-
String.raw() 该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。
String.raw`Hi\n${2+3}!`; // 返回 "Hi\\n5!" String.raw`Hi\\n` === "Hi\\\\n" // true
- includes() 方法 如果在字符串中检测到指定文本则返回 true ,否则返回 false。
- startsWidth() 方法 如果在字符串的起始部分检测到指定文本则返回 true,否则返回 false。
-
endsWidth() 方法 如果在字符串的结束部分检测到指定文本则返回 true,否则返回 false。
以上三个方法都接受三个参数:第一个参数指定要搜索的文本;第二个参数是可选的,指定一个开始搜索的位置的索引值。let msg = "Hello World!" console.log(msg.startsWidth("Hello")) // true console.log(msg.endsWidth("!")) // true console.log(msg.includes("o")) // true
-
repeat() 方法 该方法接受一个 number 类型的参数,表示该字符串的重复次数,返回值是当前字符串重复一定次数后的新字符串。
'x'.repeat(3) // "xxx" 'na'.repeat(NaN) // ""
-
String.raw() 该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。
Symbol
在 ES5 及早期版本中,语言包含5中原始类型:string、number、boolean、null 和 undefined。ES6 引入了第6种原始类型:Symbol。
- 创建 Symbol
- 可以通过全局的 Symbol 函数创建一个 Symbol
let firstName = Symbol() let person = {} person[firstName] = "Nico" console.log(person[firstName]) // "Nico"
- 由于 Symbol 是原始值,因此调用 new Symbol() 会导致程序抛出错误。
- Symbol 函数接受一个可选参数,其可以让你添加一段文本描述即将创建的 Symbol,这段描述不可用于属性访问,但是建议在每次创建 Symbol 时都添加这样一段描述。
let firstName = Symbol("first name") let person = {} person[firstName] = "Nico" console.log("first name" in person) // false console.log(person[firstName]) // "Nico" console.log(firstName) // "Symbol(first name)"
- 可以通过全局的 Symbol 函数创建一个 Symbol
- Symbol 的辨识方法
Symbol 是原始值,且 ES6 同时扩展了 typeof 操作符,支持返回 "Symbol",所以可以用 typeof 来检测变量是否为 Symbol 类型。let symbol = Symbol("test symbol") console.log(typeof symbol) // symbol
- Symbol 的使用方法
所有使用可计算属性名的地方,都可以使用 Symbol。Symbol 也可以用域计算对象字面量属性名、ObjectdefineProperty() 方法和 Object.defineProperties() 方法的调用过程中。// 使用一个可计算对象字面量属性 let person = { [firstName]: "Nico" } // 将属性设置为只读 Object.defineProperty(person, firstName, { writable: false }) let lastName = Symbol("last name") Object.defineProperties(person, { [lastName]: { value: "Zakas", writable: false } }) console.log(person[firstName]) console.log(person[lastName]) // 修改失败,只读 person[lastName] = "jack" console.log(person[lastName]) // Zakas
- Symbol 共享体系
- 如果想要创建一个可共享的 Symbol,要使用 Symbol.for() 方法。它只接受一个参数,也就是即将创建的 Symbol 的字符串标识符,这个参数同样也被用作 Symbol 的描述。
let uid = Symbol.for("uid") let object = {} object[uid] = "12345" console.log(object[uid]) // 123456 console.log(uid) // Symbol(uid)
- Symbol.keyFor() 方法在 Symbol 全局注册表中检索与 Sumbol 有关的键
uid 和 uid2 都返回了 "uid" 这个键,而在 Symbol 全局注册表中不存在 uid3 这个 Symbol,也就是不存在与之有关系的键,所以最终返回 undefinedlet uid = Symbol.for("uid") console.log(Symbol.keyFor(uid)) // "uid" let uid2 = Symbol.for("uid") console.log(Symbol.keyFor(uid2)) // "uid" let uid3 = Symbol("uid") console.log(Symbol.keyFor(uid3)) // undefined
- 如果想要创建一个可共享的 Symbol,要使用 Symbol.for() 方法。它只接受一个参数,也就是即将创建的 Symbol 的字符串标识符,这个参数同样也被用作 Symbol 的描述。
解构
数组解构
-
在数组解构语法中,我们通过值在数组中的位置进行选取,且可以将其存储在任意变量中,未显式声明的元素都会直接被忽略。并且在这个过程中,数组本身不会发生任何变化。
let colors = ["green", "red", "blue"] let [ firstColor, secondColor ] = colors console.log(firstColor) // green console.log(secondColor) // red
-
在解构模式中,也可以直接省略元素,只为感兴趣的元素提供变量名。
let colors = ["green", "red", "blue"] let [ , , thirdColor ] = colors console.log(thirdColor) // blue
-
数组解构也可用于赋值上下文,但不需要用小括号包裹表达式,这一点与对象结构的约定不同
let colors = ["green", "red", "blue"], firstColor = "black", secondColor = "purple" ;[ firstColor, secondColor ] = colors console.log(firstColor) // green
对象解构
- 对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let node = { type: "Identifer", name: "foo" } let { type, name } = node console.log(type) // Identifer console.log(name) // foo
- 使用 var、let 或 const解构声明变量,则必须要提供初始化程序(也就是等号右侧的值),否则程序会抛出语法错误
// 语法错误! var { type, name } // 语法错误! let { type, name } // 语法错误! const { type, name }
混合结构
let node = {
type: "Identifier",
name: "foo",
loc: {
start: {
line: 1,
column: 1
},
end: {
line: 1,
column: 4
}
},
range: [0, 3]
}
let {
loc: { start },
range: [ startIndex ]
} = node
console.log(start.line) // 1
console.log(start.column) //1
console.log(startIndex) // 0
- 这段代码分别将 node.loc.start 和 node.rang[0] 提取到变量 start 和 startIndex 中。请记住,结构模式中的 loc: 和 range:仅代表它们在 node 对象中所处的位置(也就是该对象的属性)。当你使用混合结构的语法时,则可以从 node 提取任意想要的信息。
Class 类
类的声明
- 要声明一个类,首先要编写 class 关键字,紧跟着的是类的名字,其他部分的语法类似于对象字面量方法的简写形式,但不需要在类的个元素之间使用逗号分割。
class Person { // 等价于 Person 的构造函数 constructor(name) { this.name = name } // 等价于 Person.prototype.sayName sayName() { console.log(this.name) } } let person = new Person("Nico") person.sayName() // "Nico" console.log(person instanceof Person) // true console.log(person instanceof Object) // true console.log(typeof Person) // "function" console.log(typeof Person.prototype.sayName) // "function"
- 自有属性是实例中的属性,不会出现在原型上,且只能在类的构造函数或方法中创建,此例中的 name 就是一个自有属性
- 类声明仅仅是基于已有自定义类型声明的语法糖
- 与函数不同的是,类属性不可被赋予新值,与 Person.prototype.sayName 一样就是一个只可读的类属性
类与自定义类型的差异
函数声明可以被提升,而类声明与 let 声明类似,不能被提升;真正执行声明语句之前,他们会一直存在与临时死区中。
类声明中的所有代码将自动运行在严格模式下,而且无法强行让代码脱离严格模式执行。
-
在自定义类型中,需要通过 Object.defineProperty() 方法手工指定某个方法为不可枚举;而在类中,所有的方法都是不可枚举的。
每个类都有一个名为 [[Construct]] 的内部方法,通过关键字 new 调用那些不含 [[Construct]] 的方法会导致程序抛出错误。
使用除关键字 new 以外的方式调用类的构造函数会导致程序抛出错误。
-
在类中修改类名会导致程序报错
// 等价于 Person let Person2 = (function() { "use strict" const Person2 = function (name) { // 确保通过关键字 new 调用该函数 if (typeof new.target === "undefined") { throw new Error("必须通过关键字 new 调用构造函数") } this.name = name } Object.defineProperty(Person2.prototype, "sayName", { value: function() { // 确保不会通过关键字 new 调用该方法 if (typeof new.target !== "undefined") { throw new Error("不可使用关键字 new 调用该方法") } console.log(this.name) }, // enumerable 可枚举的; configurable 可配置的 enumerable: false, writable: true, configurable: true }) return Person2 }())
- 首先请注意,这段代码中有两处 Person2 声明:一处是外部作用域中的 let 声明,一处是立即执行表达式(IIFE)中的 const 声明,这也从侧面说明了为什么可以在外部修改类名而内部却不可修改。在构造函数中,先检查 new.target 是否通过 new 调用,如果不是则抛出错误;紧接着,将 sayName() 方法定义为不可枚举,并在此检查 new.target 是否通过 new 调用,如果是则抛出错误;最后,返回这个构造函数。
常量类名
- 类的名称之在类中为常量,所以尽管不能在类的方法中修改类名,但是可以在外部修改:
class Foo { constructor() { Foo = "bar" // 执行时会抛出错误 } } const foo = new Foo() // 报错啦 console.log(Foo) // [Function: Foo] // 但是类声明结束后就可以修改 Foo = "baz" console.log(Foo) // "baz"
类表达式
- 类和函数都有两种存在形式:声明形式和表达式形式。
- 声明形式的函数和类都是由关键字(分别为 function 和 class)进行定义,随后紧跟一个标识符
- 表达式形式的函数和类与声明形式类似,只是不需要在关键字后添加标识符。
- 类表达式的设计初衷是为了声明响应变量或传入函数作为参数
- 基本的类表达式语法
let Person = class {}
- 命名类表达式
let Person = class Person2 {} console.log(typeof Person) // function console.log(typeof Person2) // undefined
- 在此示例中,类表达式被命名为 Person2,由于标识符 Person2 只存在于类定义中,因此它可被用在像 sayName()这样的方法中。而在类的外部,由于不存在一个名为 Person2 的绑定,因而 typeof Person2 的值为 "undefined"。
// 等价于 Person let Person = (function() { "use strict" const Person2 = function (name) { // 确保通过关键字 new 调用该函数 if (typeof new.target === "undefined") { throw new Error("必须通过关键字 new 调用构造函数") } this.name = name } Object.defineProperty(Person2.prototype, "sayName", { value: function() { // 确保不会通过关键字 new 调用该方法 if (typeof new.target !== "undefined") { throw new Error("不可使用关键字 new 调用该方法") } console.log(this.name) }, // enumerable 可枚举的; configurable 可配置的 enumerable: false, writable: true, configurable: true }) return Person2 }())
- 在 JavaScript 引擎中,类表达式的实现与类声明稍有不同。对于类声明来说,通过 let 定义的外部绑定与通过 const 定义的内部绑定具有相同名称;而命名类表达式通过 const 定义名称,从而 Person2 只能在类的内部使用。
Map 集合
Es6 中的 Map 类型是一种存储着许多键值对的有序列表,其中的简明和对象的值支持所有的数据类型。
- 如果要向 Map 集合中添加新的元素,可以调用 set() 方法并分别传入键名和对应值作为两个参数;如果要从集合中获取信息,可以调用 get() 方法。
let map = new Map() map.set("title", "Understanding ES6") map.set("year", 2019) console.log(map.get("title")) // Understanding ES6 console.log(map.get("year")) // 2019
Map 集合支持的方法
- has(key) 检测指定的键名在 Map 集合中是否已经存在。
- delete(key) 从 Map 集合中移除指定键名及对应的值。
- clear() 移除 Map 集合中的所有键值对
let map = new Map() map.set("title", "Understanding ES6") map.set("year", 2019) console.log(map.get("title")) // Understanding ES6 console.log(map.get("year")) // 2019 console.log(map.size) // 2 console.log(map.has("title")) // true map.clear() console.log(map.size) // 0
Map 集合的 forEach() 方法
- forEach() 方法的回调函数接受以下 3 个参数:
- Map 集合中下一次索引的位置
- 与第一个参数一样的值
- 被遍历的 Map 集合本身
let map = new Map([["name", "Nichlas"], ["age", 25]]) map.forEach(function(value, key, ownerMap) { console.log(key + '--' + value) // name--Nichlas console.log(ownerMap) // Map { 'name' => 'Nichlas', 'age' => 25 } console.log(ownerMap === map) // true })
Weak Map 集合
ES6 中的 Weak Map 类型是一种存储着许多键值对的无序列表,列表的键名必须是非 null 类型的对象,键名对应的值则可以是任意类型。
let map = new WeakMap(),
element = document.querySelector(".element")
map.set(element, "cyan")
let value = map.get(element)
console.log(value) // "cyan"
// 移除 element 元素
element.parentNode.removeChild(element)
element = null
Promise 与异步编程
Promise 的生命周期
var fs = require("fs")
var p1 = new Promise(function(resolve, reject) {
fs.readFile('./demo.txt', 'utf8', function(err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
p1.then(function(contents) {
console.log(contents)
})
p1.then(null, function (err) {
console.log(err)
})
- 每个 Promise 都会经历一个短暂的生命周期:线是处于进行中(pending)的状态,此时操作尚未完成,所以它是未处理的(unsettled)的;一旦异步操作执行结束,Promise则变成已处理(settled)的状态。在上例中,当 readFile() 函数返回 Promise 时它变为 pending 状态,操作结束后,Promise 可能会进入到以下两个状态中的其中之一:
- Fulfilled Promise 异步操作成功完成
- Rejected 由于程序错误或一些其他原因,Promise 异步操作未能成功完成
- 内部属性 [PromiseState] 被用来表示 Promise 的3种状态:"pending"、"fulfilled"及"reject"。这个属性不暴露在 Promise 对象上,所以不能以编程的方式检测 Promise 的状态,只有当 Promise 的状态改变时,通过 then() 方法来踩去特定的行动。
- 所有的 Promise 都有 then() 方法,它接受两个参数:第一个是当 Promise 状态变为 fulfilled 时要调用的函数,与异步操作相关的附加数据都会传递给这个完成函数 (fulfillment function);第二个是当 Promise 的状态变为 reject 时候要调用的函数,其与完成调用的函数类似,所有与失败状态相关的附加数据都会传递给这个拒绝函数 (rejection function) 。
- Promise 还有一个 catch() 方法,相当于只给其传入拒绝处理程序的 then() 方法。
promise.catch(function(err) {
// 拒绝
console.error(err.message)
})
// 与下调用相同
promise.then(null, function(err) {
// 拒绝
console.log(err.message)
})