系统化学习前端之JavaScript(ES6)

前言

ES6 同样是 ECMAScript 的拓展,发布于 2015年,目前多数浏览器对于部分 ES6 更新内容不支持,通常需要借助 bable 编译器编译成 ES5 或者 ECMAScript 早期版本语法去执行。

ES6 的学习推荐阮一峰老师的 ES6 教程

ES6

ES6 是 ECMAScript 最近一次较大版本的更新,更新内容主要是一些优化,包括开发效率的优化,以及之前版本问题的优化等。

let 和 const

  1. let

    let 关键字用于定义变量,优化掉了 var 关键字。

    let 关键字定义变量的特点:

    • {} 内部定义变量,会生成块级作用域。

      {
          var a = 1
          let b = 1
      }
      console.log(a) // 1
      console.log(b) // ReferenceError
      

      注意:块级作用域内重复声明全局变量,则该变量绑定块级作用域。

    • 抛出变量提升引起的问题。

      console.log(a) // undefined
      var a = 1
      
      console.log(b) // ReferenceError
      let b = 1
      
    • 抛出重复声明的问题。

      // 重复声明变量
      function fun1() {
          var a = 1
          var a = 2
          console.log(a) // 2
      }
      fun1()
      
      function fun2() {
          let a = 1
          var a = 2
          console.log(a) // SyntaxError:already been declared
      }
      fun2()
      
      // 重复声明参数
      function fun3(x) {
          var x = 1
          console.log(x) // 1
      }
      fun3()
      
      function fun4(x) {
          let x = 1
          console.log(x) // SyntaxError:already been declared
      }
      fun4()
      
  2. const

    const 关键字用于定义常量,常量一旦声明,必须立即赋值。

    const 定义常量的特点:

    • {} 内部定义常量,会生成块级作用域。

      {
          const A = 1
      }
      console.log(A) // ReferenceError
      
    • 定义常量无法修改。

      const A = 1
      A = 2 // TypeError: Assignment to constant variable
      

    注意:const 也具备 let 相同的特点。

解构赋值

  1. 数组解构

    数组解构分为两种:常规解构和默认值解构

    • 常规解构

      let [a, b, c] = [1, 2, 3]
      console.log(a, b, c) // 1, 2, 3
      
      let [x, , y, z] = [1, 2, 3]
      console.log(x, y, z) // 1, 3, undefined
      
      let [i] = [1, 2, 3]
      console.log(i) // 1
      
      let [u, ...v] = [1, 2, 3]
      console.log(u, v) // 1, [2, 3]
      
    • 默认值解构

      let [a = 4, b, c, d = 3] = [1, 2]
      console.log(a, b, c, d) // 1, 2, undefined, 3
      

      注意:解构有值,则默认值无效,解构无值,则默认值有效。

  2. 对象解构

    对象解构分为三种:常规解构,换名解构,默认值解构。

    • 常规解构

      let obj = {
          name: 'jim',
          age: 20
      }
      
      let { name, age, gender } = obj
      console.log(name, age, gender) // jim, 20, undefined
      
    • 换名解构

      let obj = {
          name: 'jim',
          age: 20
      }
      
      let { name: call } = obj
      console.log(call) // jim
      

      注意:换名解构相当于定义一个变量,通过解构出来的值进行赋值操作。

    • 默认值解构

      let obj = {
          name: 'jim',
          age: 20
      }
      
      let { name: call = 'jack', gender: sex = 'male' } = obj
      console.log(call, sex) // jim, male
      

      注意:解构有值,则默认值无效,解构无值,则默认值有效。

  3. 字符串解构

    字符串解构分为两种:字符解构和长度解构

    • 字符解构

      let [a, b, , c, d] = 'hello'
      console.log(a, b, c, d) // h, e, l, o
      
    • 长度解构

      let { length: len } = 'hello'
      console.log(len) // 5
      
  4. 函数参数解构

    函数参数解构分为两种:数组参数解构和对象参数解构

    • 数组参数解构

      function sum(x, y) {
          return x + y
      }
      
      sum(...[1,2]) // 3
      
    • 对象参数解构

      function sum({ x = 0, y = 1 } = {}) {
          return x + y
      }
      
      sum() // 1
      sum({ x: 1 }) // 2
      sum({ x: 2, y: 2 }) // 4
      

模板字符串

模板字符串是一种支持内置表达式的字符串,简化字符串拼接操作。

let price = 12.5
let count = 5
console.log(`
单价:¥${price.toFixed(2)}
数量:${count}
=====================
总价:¥${(price * count).toFixed(2)}
`)

模板字符串的特点:

  1. 模板字符串使用 `` 反引号包裹。

  2. 模板字符串支持使用 js 表达式,使用 ${} 插值,插值内为 js 环境。

  3. 模板字符串支持换行。

BigInt

JavaScript 在数值表示存在两个问题:

  1. 大于或等于 2^53 的数值无法保证精度。

    Math.pow(2, 53) === Math.pow(2, 53) + 1 // true
    
  2. 大于或等于 2^1024 的数值无法表示,会返回 Infinity

    Math.pow(2, 1024) // Infinity
    

    注意:可以使用 isInfinity() 判断是否为 Infinity

BigInt 类型用于解决上述问题,没有位数限制,任何位数的整数都可以精确表示。

注意:

a. BigInt 类型与普通整数数值相等,类型不同。如 2n == 2 返回 true,2n === 2 返回false。

b. BigInt 类型不能使用 + 表示正数,会编译成隐式转换,而 BigInt 类型无法转换 Number 类型。

c. BigInt 类型可以正常进行相关运算。

定义 BigInt 类型的方式:

  1. 字面量定义

    let x = 2n // 整数后缀 n,表示 bigint 类型
    
    typeof x // bigint
    
  2. 转换函数定义

    let x = BigInt(2)
    
    typeof x // bigint
    

    注意:BigInt() 只能转换整型、整型字符串以及 boolean 值,其他无法转换,抛出错误。

运算符拓展

  1. 指数运算符

    let a = 2 ** 3 // 2^3
    a **= 2 // 64
    
  2. 链判断运算符

    链判断运算符用于判断对象的属性和方法是否存在。

    let obj = {
        a: '1',
        b: {
            c: 2
        },
        run() {
            console.log('running')
    	}
    }
    
    // ES6 之前
    let x = obj && obj.b && obj.b.c || undefined
    
    // ES6 之后
    let x = obj?.b?.c // 属性存在则赋属性值,不存在则赋值 undefined
    
    obj.run?.() // 方法存在则执行方法,不存在则不执行
    
  3. Null 判断运算符

    变量赋值默认值的方法

    let x = y || 300 // y 为变量
    

    上例中当 y 存在则赋值给 x,当 y 不存在则赋值 x 为 300。y 不存在具体含义:y 为 undefined 或者 null 时,当上例中 y 为 0 或 false 也满足不存在的条件。

    // ES6
    let x = y ?? 300
    

    只有当 y 为 undefined 或者 null 时,才会给 x 赋值 300。

  4. 逻辑赋值运算符

    逻辑赋值运算符有: &&=, ||=, ??=

    x &&= y 等同于 x = x && y

函数拓展

  1. 参数拓展

    • 函数参数默认值

      // ES6 之前
      function sum(x, y) {
          y = y ?? 0
          return x + y
      }
      
      // ES6
      function sum(x, y = 0) {
          return x + y
      }
      
    • rest 参数

      // ES6 之前
      function sum() {
          console.log(arguments) // Arguments[1,2] 类数组对象
      }
      
      // ES6
      function sum(...params) {
          console.log(params) // [1,2] 数组
      }
      
      sum(1,2)
      

      注意:...params 参数之后不能再出现任何形参,否则报错。之前出现形参,则为形参解构实参。

  2. 箭头函数

    箭头函数是对匿名函数的简写方式。

    let log = function (info) {
        console.log(info)
    }
    
    let log = info => {
        console.log(info)
    }
    
    // 函数只有一个参数,则可以不使用 `()` 包括。
    
    let sum = function (x, y) {
        return x + y
    }
    
    let sum = (x, y) => x + y
    
    // 函数只有返回值一句,则可以省略 `{}`。
    
    let co = function (a, b) {
       return { name: a, age: b }
    }
    
    let co = (a, b) => ({ name: a, age: b })
    
    // 返回值是一个对象,则需要使用 `()` 包括。
    

    注意:

    a. 箭头函数内部的 this 始终指向其上层作用域的 this,不可改变,即 call, apply, bind 无效。

    let obj = {
       name: 'json',
       age: 20
    }
    
    function outer() {
       let inner = () => {
           console.log('inner',this)
       }
       inner()
       console.log('outer', this)
    }
    
    outer() // inner window outer window
    outer.call(obj) // inner {name: 'json', age: 20}, outer {name: 'json', age: 20}
    

    b. 箭头函数内部不可以使用 arguments 获取参数,会抛出错误。

    c. 不能使用 newyield 命令,即不能作为构造函数和 Generator 函数。

数组拓展

  1. 扩展运算符

    扩展运算符指 ... 运算符,作用:

    • 数组打散

      let arr = [1, 2, 3]
      console.log(...arr) // 1 2 3
      
    • 数组解构

      let arr = [1, 2, 3]
      let [ a, ...b ] = arr
      console.log(a, b) // 1, [2, 3]
      
    • 对象解构

      let obj = { a: 1, b: 2, c: 3 }
      let { a, ...rest } = obj
      console.log(a, rest) // 1, { b: 2, c: 3 }
      
  2. Array.from()

    可将类数组对象和可迭代对象(如 Set 和 Map)转换为真正的数组。

    let arr = Array.from(arr-like) 
    
  3. Array.of()

    将一组参数转换为数组。

    let arr = Array.of(1,2,3) // [1, 2, 3]
    

对象拓展

  1. 属性和方法简化

    let name = 'jim'
    let age = 21
    
    // ES6 之前
    let obj = {
    	name: name,
    	age: age,
    	run: function() {
    	    return `${this.name} is running`
    	}
    }
    
    // ES6
    let obj = {
    	name, // 同名属性可以简写
    	age,
    	run() {
    	    return `${this.name} is running` // 方法可以简写
    	}
    }
    
  2. 遍历对象属性

    对象属性中的 enumerable 特性设置为 true,表示当前对象属性可以遍历。遍历对象属性的方法有:

    • for...in

      let obj = {
          name: 'jason',
          age: 18
      }
      
      for(let key in obj) {
          console.log(key, obj[key]) // name jason, age 18
      }
      
    • Object.keys(obj)

      let obj = {
          name: 'jason',
          age: 18
      }
      
      let res = Object.keys(obj)
      console.log(res) // ['name', 'age']
      
    • Object.getOwnPropertyNames(obj)

      let obj = {
          name: 'jason',
          age: 18
      }
      
      let res = Object.getOwnPropertyNames(obj)
      console.log(res) // ['name', 'age']
      
    • Reflect.ownKeys(obj)

      let obj = {
          name: 'jason',
          age: 18
      }
      
      let res = Reflect.ownKeys(obj)
      console.log(res) // ['name', 'age']
      
  3. 新增对象方法

    • Object.is()

      0 === -0 // true
      NaN === NaN // false
      
      Object.is(0, -0) // false
      Object.is(NaN, NaN) // true
      
    • Object.assign()

      Object.assign() 接收多个参数对象,第一个参数是目标对象,后面参数都是源对象,该方法是将源对象的所有属性复制到目标对象,返回一个合并后的对象。如果存在同名属性,则后面的属性会覆盖前面的属性。

      let obj1 = { a: 1, b: 2, c: 3 }
      let obj2 = { d: 4, e: 5, a: 6 }
      
      Object.assign({}, obj1, obj2) // {a: 6, b: 2, c: 3, d: 4, e: 5}
      

      目标对象参数不为对象,则报错,源对象参数不为对象,会进行对象转化,转化后处理,只有 String 和 Array 类型可以转换。

      Object.assign({}, 1.5) // {}
      Object.assign({}, true) // {}
      Object.assign({}, null) // {}
      Object.assign({}, undefined) // {}
      Object.assign({}, 'hello') // {0: 'h', 1: 'e', 2: 'l', 3: 'l', 4: 'o'}
      Object.assign({}, [1,2,3]) // {0: 1, 1: 2, 2: 3}
      

      Object.assign()拷贝对象属于浅拷贝,参数对象的属性为引用类型,实际拷贝的是属性的引用。

      let obj = {
          a: '1',
          b: {
              c: 2
          }
      }
      
      let cobj = Object.assign({}, obj)
      
      obj.b.c = 20
      
      console.log(cobj) // { a: '1', b: { c: 20 } }
      

      实现深拷贝的两种方式:

      // 方式一
      let cobj = JSON.parse(JSON.stringify(obj))
      

      注意:方式一中,对象属性值如果是 undefined,Symbol 类型,Function 类型会被过滤,因此只适合符合 JSON 风格的对象。

      // 方式二
      function deepCopy(arg) {
          if(typeof arg === "object") {
              var result = arg.constructor === Array ? [] : {};
              for(let i in arg) {
                  result[i] = typeof arg[i] === 'object' ? deepCopy(arg[i]) : arg[i];
              }
          }
          else {
              var result = arg;
          }
          return result
      }
      
    • Object.hasOwn()

      Object.hasOwn(obj, 'attr') 接收两个参数,分别为对象和属性,用于判断 attr是否为 obj 自有属性。同 obj.hasOwnProperty() 一致。

正则拓展

正则拓展是指拓展 JavaScript 的正则语法,未增加新的 API 方法。

  1. 先行断言

    /x(?=y)/ 表示匹配在 y 之前的 x

  2. 先行否定断言

    /x(?!y)/ 表示匹配在不是 y 之前的 x

  3. 后行断言

    /(?<=y)x/ 表示匹配在 y 之后的 x

  4. 后行否定断言

    /(?<!y)x/ 表四匹配不是 y 之后的 x

    /(?<=(o)d\1)r/.exec('hodor')  // null
    
    /(?<=\1d(o))r/.exec('hodor')  // ["r", "o"]
    

    注意:后行断言的分组引用需要将分组引用前置。

  5. 分组命名

    通过 (?<name>)可以为分组命名,使用 reg.exec() 返回的类数组对象可以获取对应分组匹配结果。

    let reg = /(?<year>\d{4})-(?<month>\d{1,2})-(?<day>\d{1,2})/
    
    let res = reg.exec('2023-3-31')
    
    console.log(res.groups.year) // 2023
    console.log(res.groups.month) // 3
    console.log(res.groups.day) // 31
    

    分组命名以后,可以获取分组名字,则可以通过解构获取对应分组匹配结果。

    let reg = /(?<year>\d{4})-(?<month>\d{1,2})-(?<day>\d{1,2})/
    
    let { groups: { year, month, day } } = reg.exec('2023-3-31')
    
    console.log(year) // 2023
    console.log(month) // 3
    console.log(day) // 31
    

    注意:分组命名,可以通过 \k<name> 引用分组。

Set 和 Map

  1. Set

    Set 是类似于数组的数据结构,其成员都是唯一的。

    创建 Set 类型数据

    // 方式一:实例化传入数组参数
    let set = new Set([1,2,3,4,3]) // Set(4) {1, 2, 3, 4}
    
    // 方式二:实例方法添加成员
    let set = new Set() // Set(4) {1, 2, 3, 4}
    set.add(1)
    set.add(2)
    set.add(3)
    set.add(4)
    set.add(3)
    

    Set 类型属性

    • set.size

      Set 类型数据长度。

      let set = new Set([1,2,3,4,3])
      console.log(set.size) // 4
      

    Set 类型方法

    • set.add

      接收一个参数,向 set 中添加成员,成员可以为任意类型,返回添加后的 Set ,如 set.add(1)

    • set.delete

      接收一个参数,从 set 中删除成员,若删除存在成员则返回 true,删除不存在成员则返回 false,如 set.delete(3)

    • set.has

      接收一个参数,判断参数是否为 set 成员,是则返回 true,否则返回 false,如 set.has(2)

    • set.clear

      清空 set 中所有成员。

    • set.forEach

      遍历 set 中的成员,如 set.forEach((item, index) => { ... }), 其中 itemindex 是相同的,均为 set 中的成员。

    注意: Set 常用简单数组去重, 如 [...new Set(arr)]

  2. WeakSet

    WeakSet 是特殊的 Set 数据类型,其成员须是对象或者类数组对象,成员是唯一的,WeakSet 不具有长度属性。

    创建 WeakSet 类型数据

    // 方式一: 实例化传入数组参数
    let weakSet = new WeakSet([{}, [1,2,3]])
    
    // 方式二:实例方法添加成员
    let weakSet = new WeakSet()
    weakSet.add({})
    weakSet.add([1,2,3])
    

    WeakSet 方法

    • weakSet.add

      接收一个参数,参数必须为引用类型属性,返回添加后的 weakSet ,如 weakSet.add({})

    • weakSet.delete

      接收一个参数,删除 weakSet 中成员。weakSet 均为引用类型数据,删除只是删除对应引用,无法真正删除,一直返回 false。

    • weakSet.has

      接收一个参数,判断参数是否在 weakSet 中,一直返回 false,因为 {} !== {}

  3. Map

    Map 是类似于对象的数据结构,其键值对中的键可以是任意类型数据,区别对象的键只能是字符串。

    创建 Map 类型数据

    // 方式一:接收数组作为参数,数组中元素须是键值对组成的数组
    let map = new Map([['name', '张三'], ['age', 18]]) // {'name' => '张三', 'age' => 18}
    
    let map = new Map() // Map(3) {'name' => '张三', 'age' => 18, {…} => 'object'}
    let obj = { a: 1 }
    map.set('name', '张三')
    map.set('age', 18)
    map.set(obj, 'object')
    

    Map 类型属性

    • map.size

      map 的长度。

    Map 类型方法

    • map.set

      接收两个参数,第一个参数为键,第二个参数为值,为 map 添加成员,如 map.set('age', 18)

    • map.get

      接收一个参数,参数为键,用于查找当前 map 对应键的值,未找到返回 undefined,如 map.get('name')

    • map.has

      接收一个参数,参数为键,判断参数是否为 map 中的键,是则返回 true,否则返回 false,如 map.has(obj) 返回 true。

    • map.delete

      接收一个参数,参数为键,删除当前 map 中的键值对,如 map.delete(obj)

    • map.clear

      清空当前 map 中所有成员。

    • map.forEach

      遍历 map 中的成员,如 map.forEach((value, key, map) => { ... }),其中回调函数的第一个参数为value,第二参数为 key。

  4. WeakMap

    WeakMap 是特殊的 Map 类型数据,其键必须为对象,不能使用 null,WeakMap 不具有长度属性。

    创建 WeakMap 数据

    let obj = {}
    let weakMap = new WeakMap()
    weakMap.set(obj, 1)
    

    WeakMap 类型方法

    • weakMap.set

      接收两个参数,第一个参数为键,第二个参数为值,为 map 添加成员,其键必须为对象,如 weakMap.set(obj, 1)

    • weakMap.get

      接收一个参数,参数为键,用于查找当前 map 对应键的值,未找到返回 undefined,如 weakMap.get(obj)

    • weakMap.has

      接收一个参数,参数为键,判断参数是否为 map 中的键,是则返回 true,否则返回 false,如 weakMap.has(obj) 返回 true。

Proxy

Proxy 是指对象的代理,对象属性的保护,在 ES5 中可以通过 Object.defineProperty 设置属性特性进行保护,ES6 中通过代理对象 Proxy 进行保护。PS:说是保护,更多是拦截操作,通过重写方法的方式进行保护或者禁止等其他操作。

  1. 创建代理对象

    通过构造函数法可以创建代理对象

    let proxy = new Proxy(target, handler) // target 为被代理对象,即目标对象,handler 为拦截方法对象,proxy 为代理对象。
    
  2. 拦截方法对象

    拦截方法对象是指由多个 proxy 实例方法组成的对象,通过重写方法可以实现对拦截对象的保护或者其他操作。如 { get() { ... }, set() { ... }, ... }

    proxy 实例方法有:

    • get

      该方法用于拦截指定属性的读取操作,接收三个参数,分别对应目标对象,指定属性以及代理对象。

      let obj = { name: 'jason', age: 18 }
      let proxy = new Proxy(obj, {
          get(target, key, proxy) {
              if(age in target) {
                  return target[key] + 1
              } else {
                  throw new ReferenceError('属性不存在!')
              }
          }
      })
      
      console.log(proxy.age) // 19
      console.log(proxy.gender) // ReferenceError
      
    • set

      该方法用于拦截指定属性的写入操作,接受四个参数,分别对应目标对象,指定属性,写入值以及代理对象。

      let obj = { name: 'jason', age: 18 }
      let proxy = new Proxy(obj, {
          set(target, key, value, proxy) {
              target[key] = value + 1
          }
      })
      
      proxy.age = 20
      console.log(proxy.age) // 21
      

      注意:getset 方法中不能使用 proxy[key] 的方式读取或写入属性,回发生循环引用问题,导致栈溢出。

    proxy 还有许多其他实例方法,如 apply, has, construct, defineProperty, deleteProperty, getOwnPropertyDescriptor, getPrototypeOf, setPrototypeOf, ownKeys, isExtensible, preventExtensions, isSealed, seal, isFrozen, freeze 等。

注意:proxy 代理 target,target 对象的 this 是指向 proxy 对象的,可以通过 bind 的更改。外次,实例方法的 this 指向 拦截方法对象,即 handler 对象。

Reflect

Reflect 对象是为了抽离部分 Object 静态方法的 API 封装,部分 Object 静态方法应用不局限于 Object,这类方法可以提取在新对象中使用。

  1. get

    var obj = {
        foo: 1,
        bar: 2,
        get baz() {
            return this.foo + this.bar;
        },
    }
    Reflect.get(obj, 'foo') // 1
    Reflect.get(obj, 'bar') // 2
    Reflect.get(obj, 'baz') // 3
    
  2. set

    var obj = {
        foo: 1,
        set bar(value) {
            return this.foo = value;
        },
    }
    
    obj.foo // 1
    Reflect.set(obj, 'foo', 2);
    obj.foo // 2
    
  3. apply

    function fun(x, y) {
        console.log(this, x, y)
    }
    fun(1,2) // window 1 2
    
    let obj = { name: 'jim' }
    
    Reflect.apply(fun, obj, [1,2]) // obj 1 2
    
  4. construct

    function Greeting(name) {
        this.name = name;
    }
    
    // new 的写法
    const instance = new Greeting('张三');
    
    // Reflect.construct 的写法
    const instance = Reflect.construct(Greeting, ['张三']);
    
  5. defineProperty

    let obj = {
        foo: 'bar',
    }
    
    Reflect.defineProperty(obj, 'foo', {...})
    

    注意: Reflect.defineProperty(obj, 'foo', {...}) 等效于 Object.defineProperty(obj, 'foo', {...})

  6. deleteProperty

    const obj = { foo: 'bar' }
    
    Reflect.deleteProperty(obj, 'foo')
    

    注意:Reflect.deleteProperty(obj, 'foo') 等效于 delete myObj.foo

  7. getOwnPropertyDescriptor

    var obj = {};
    Object.defineProperty(obj, 'hidden', {
        value: true,
        enumerable: false,
    });
    
    // 旧写法
    var theDescriptor = Object.getOwnPropertyDescriptor(obj, 'hidden');
    
    // 新写法
    var theDescriptor = Reflect.getOwnPropertyDescriptor(obj, 'hidden');
    
  8. getPrototypeOf

    const obj = new Animal();
    
    // 旧写法
    Object.getPrototypeOf(obj) === Animal.prototype;
    
    // 新写法
    Reflect.getPrototypeOf(obj) === Animal.prototype;
    
  9. setPrototypeOf

    const obj = {};
    
    // 旧写法
    Object.setPrototypeOf(obj, Array.prototype);
    
    // 新写法
    Reflect.setPrototypeOf(obj, Array.prototype);
    
  10. has

    var obj = {
        foo: 1,
    }
    
    Reflect.has(obj, 'foo') // true
    

    注意:Reflect.has(obj, 'foo') 等效于 'foo' in obj

  11. ownKeys

    var obj = {
        foo: 1,
        bar: 2,
        [Symbol.for('baz')]: 3,
        [Symbol.for('bing')]: 4,
    };
    
    // 旧写法
    Object.getOwnPropertyNames(obj) // ['foo', 'bar']
    Object.getOwnPropertySymbols(obj) //[Symbol(baz), Symbol(bing)]
    
    // 新写法
    Reflect.ownKeys(obj) // ['foo', 'bar', Symbol(baz), Symbol(bing)]
    
  12. isExtensible

    const obj = {};
    
    // 旧写法
    Object.isExtensible(obj) // true
    
    // 新写法
    Reflect.isExtensible(obj) // true
    
  13. preventExtensions

    var obj = {};
    
    // 旧写法
    Object.preventExtensions(obj) // Object {}
    
    // 新写法
    Reflect.preventExtensions(obj) // true
    

for..in 和 for...of

  1. for...in

    for (let key in iteratableObj) {
        // key 为 iteratableObj 可迭代对象的键,字符串表示。
    }
    

    注意:for...in 可迭代对象包括:Array,Map,Set,String,对象以及类数组对象。

  2. for...of

    for (let value in iteratableObj) {
        // value 为 iteratableObj 可迭代对象的值。
    }
    

    注意:for...of 可迭代对象包括:Array,Map,Set,String 以及类数组对象,不包含对象。

Class

Class 类是 ES6 引入的一个语法糖,其本身也是通过原型链模拟的,并不是如 C++java 等面向对象语言实现了类。

  1. 实例属性

    // Class 之前
    function Animal(type) {
        this.type = type
    }
    
    let obj = new Animal('dog')
    obj.type // dog
    
    // Class 之后
    class Animal {
        constructor(type) {
            this.type = type
        }
    }
    
    let obj = new Animal('dog')
    obj.type // dog
    
    // Class 属性拦截
    class Animal {
        constructor(type) {
            this._type = type
        }
        set type(value) {
            this._type = value + ' is running'
        }
        get type() {
            return this._type
        }
    }
    
    let obj = new Animal('dog')
    obj.type // dog
    obj.type = 'cat'
    obj.type // cat is running
    
  2. 实例方法

    // Class 之前
    function Animal() {
        // 实例属性
    }
    
    Animal.prototype.run = function () {
        return 'running'
    }
    
    let obj = new Animal()
    obj.run() // running
    
    // Class 之后
    class Animal {
        constructor() {
            // 实例属性
        }
        run() {
            return 'running'
        }
    }
    
    let obj = new Animal()
    obj.run() // running
    
  3. 静态属性

    // Class 之前
    function Animal() {
        // 实例属性
    }
    
    Animal.line = 'friend'
    Animal.line // friend
    
    // Class 之后
    class Animal {
        static line = 'friend'
    }
    
    Animal.line // // friend
    
  4. 静态方法

    // Class 之前
    function Animal() {
        // 实例属性
    }
    
    Animal.run = function () {
        return 'running'
    }
    Animal.run() // running
    
    // Class 之后
    class Animal {
        static run() {
            return 'running'
        }
    }
    
    Animal.run() // running
    
  5. 私有属性和私有方法

    实例属性和静态属性均为公有属性,实例方法和静态方式均属于公有方法。私有属性和方法只在类内部可以访问,外部无法访问。

    class Foo {
        #a;
        #b;
        constructor(a, b) {
            this.#a = a;
            this.#b = b;
        }
        #sum() {
            return this.#a + this.#b;
        }
    }
    
    Foo.#a // SyntaxError: Private field '#a' must be declared in an enclosing class
    Foo.#sum() // SyntaxError: Private field '#sum' must be declared in an enclosing class
    
    let obj = new Foo()
    obj.#a // SyntaxError: Private field '#a' must be declared in an enclosing class
    obj.#sum // SyntaxError: Private field '#sum' must be declared in an enclosing class
    
  6. 继承

    子类继承父类,会继承父类的实例属性和方法,静态属性及方法。

    class Father {
        static fs = 'father'
        constructor(fx, fy) {
            this.fx = fx
            this.fy = fy
        }
        static getFatherName() {
            return Father.fs
        }
        fsum() {
            return this.fx + this.fy
        }
    }
    
    class Child extends Father {
        static s = 'child'
        constructor(fx, fy, x, y) {
            super(fx, fy)
            this.x = x
            this.y = y
        }
        static getChildName() {
            return Child.s
        }
        sum() {
            return this.x + this.y
        }
    }
    
    // 静态
    Child.s // child
    Child.getChildName() // child
    // 静态继承
    Child.fs // father
    Child.getFatherName() // father
    
    // 实例
    let obj = new Child(1,2,3,4)
    obj.x // 3
    obj.y // 4
    obj.sum() // 7
    // 实例继承
    obj.fx // 1
    obj.fy // 2
    obj.fsum() // 3
    

    通过 superextends 可以完成静态属性和方法以及实例属性和方法的继承。私有属性和方法的继承需要借助实例方法进行读写操作。

    注意:super 在构造函数中调用,必须在重写实例属性之前,其目的是初始化一个继承父类的 this 对象。super 可以做父类构造函数,也可以做父类原型对象使用。

    class Father {
        #f = 'father'
        getF() {
            return this.#f
        }
        setF(value) {
            this.#f += value
        }
    }
    
    class Child extends Father {
        #c = 'child'
        constructor() {
            super()
        }
        getCF() {
            return this.#c + ' & ' + this.getF()
        }
        setCF(c,f) {
            this.#c += c
            this.setF(f)
        }
    }
    
    let obj = new Child()
    obj.setCF(' is C', ' is F')
    obj.getCF() // child is C & father is F
    

    注意:class 继承实现为 Child.__proto__ === FatherChild.prototype.__proto__ === Father.prototype 双链继承。

后记

ES6 扩展到此结束,其中还有 ES6 拓展的异步编程优化为在本篇体现,主要考虑是异步编程优化方案比较多,其中也涉及到微任务相关,准备单独开篇介绍,主要介绍 Promise,async await,generator 等。

posted @ 2023-03-31 16:54  深巷酒  阅读(35)  评论(0编辑  收藏  举报