ECMAScript6 - 学习笔记

let 和 const

  • Hoisting 机制
    在函数作用域或全局作用域中通过关键字 var 生命的变量,无论实际上是在哪里声明的,都会被当成在当前作用域顶部声明的变量,这就是我们常说的提升(Hoisting)机制。

  • 块级声明
    块级声明用于声明在指定块的作用域之外无法访问的变量。块级作用域(亦被称为词法作用域)存在于:

    1. 函数内部
    2. 块中(字符 { 和 } 之间的区域)
  • 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
        

字符串

  • 新增方法
    • 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)   // ""
      

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 是原始值,且 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.for() 方法首先在全局 Symbol 注册表中搜索键为 “uid” 的 Symbol 是否存在,如果存在,直接返回已有的 Symbol;否则,创建一个新的 Symbol,并使用这个键在 Symbol 全局注册表中注册,随即返回新创建的 Symbol。后续如果再传入同样的键调用 Symbol.for() 会返回相同的 Symbol。
    • Symbol.keyFor() 方法在 Symbol 全局注册表中检索与 Sumbol 有关的键
      let 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
      
      uid 和 uid2 都返回了 “uid” 这个键,而在 Symbol 全局注册表中不存在 uid3 这个 Symbol,也就是不存在与之有关系的键,所以最终返回 undefined

解构

数组解构
  • 在数组解构语法中,我们通过值在数组中的位置进行选取,且可以将其存储在任意变量中,未显式声明的元素都会直接被忽略。并且在这个过程中,数组本身不会发生任何变化。

    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)
})
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值