29.ES6+学习(二)

本文介绍了ES6中新增的数据类型Symbol,如何避免属性名冲突,以及BigInt用于处理大整数。还详细讲解了Map和Set数据结构,它们的特点和方法,以及新的遍历方式Iterator和类语法的使用,包括类的继承机制。
摘要由CSDN通过智能技术生成

上一篇:28.ES6+学习(一)

新增数据类型(了解)

Symbol

什么是Symbol

ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的原因。

/* 问题:属性名重名冲突 */
function fn(obj) {
  // 想给obj添加一个新属性 =》 问题是传入的obj可能已经有了name属性
  obj.name = 'atguigu'
  obj.foo()
}
const obj = {
  name: 'abc',
  age: 12,
  foo () {
    console.log(this.name, this.age)
  }
}
fn()

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型

Symbol的使用

  • Symbol 值通过Symbol函数生成。

  • 这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突

    const s1 = Symbol()
    const s2 = Symbol('a')
    const s3 = Symbol('b')
    console.log(s1) // Symbol()
    console.log(s2) // Symbol(a)
    console.log(s3) // Symbol(b)
    console.log(typeof s2) // 'symbol'
    console.log(s2===s3) // false
    console.log(s2.toString()) // 'Symbol(a)'
    

Symbol表示独一无二的属性名

/* symbol值作为独一无二的属性名 */
const obj = {
  name: 'jack',
  foo () {
    console.log('foo()')
  }
}

const name = Symbol('name')
const foo = Symbol('foo')
obj[name] = 'jack222'
obj[foo] = () => {
  console.log('foo22')
}

console.log(obj.name, obj[name])
obj.foo()
obj[foo]()

Symbol的注意事项

  • Symbol中传入的字符串没有任何意义,只是用来描述Symbol的

    const obj1 = {
      name: "lily",//原有对象拥有name属性
      age: 18,
    }
    const name = Symbol("name")
    const age = Symbol("age")
    obj1[name] = "xiaowang"
    obj1[age] = 21
    console.log(obj1)
    
  • Symbol不能使用new调用

    new Symbol() // TypeError: Symbol is not a constructor
    
  • 类型转换的时候,能转换为字符串和布尔值,不能转数字

    console.log(String(Symbol("a"))) // Symbol(a)
    console.log(Boolean(Symbol())) // true
    // console.log(Symbol() + 1) // TypeError: Cannot convert a Symbol value to a number
    // console.log(Number(Symbol())) // TypeError: Cannot convert a Symbol value to a number
    
  • 如果把Symbol当作一个对象的属性名时,一定要用一个变量来储存,否则定义的属性没有办法引用

    {
      const obj = {
        name: "lily",
        age: 18,
      }
      obj[Symbol()] = "再见" // 如果直接设置,则再也获取不到这个属性了
      console.log(obj)
    
      console.log(obj[Symbol()]) // undefined
    }
    

BigInt

  • JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回Infinity

  • ES2019 引入了一种新的数据类型 BigInt(大整数),来解决这个问题。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。

  • typeof运算符对于 BigInt 类型的数据返回bigint

  • 为了与 Number 类型区别,BigInt 类型的数据必须添加后缀n

  • 也可以通过BigInt函数将一般整数转换为大整数

    // 大整数
    const n1 = 123n
    console.log(n1, typeof n1) // 123n "bigint"
    
    const n2 = 123
    console.log(n1===n2) // false
    
    // 一般整数转化为大整数
    const n3 = BigInt(n2)
    console.log(n3, n3===n1) // 123n true
    
    const max = Number.MAX_SAFE_INTEGER
    console.log(max)
    console.log(max + 1)
    console.log(max + 2) // 不再变大, 不再正确
    
    console.log(BigInt(max))
    console.log(BigInt(max) + 1n)
    console.log(BigInt(max) + 2n) // 可以正确表示
    

新增数据结构 (次重要)

Map

什么是Map

  • JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

  • 为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

     /* 为什么会有Map,因为对象属性名称必须是字符串,如果是其他类型也会自动转为字符串 */
    const p1 = { name: "lily" };
    const obj = {
        id: 1,
        [p1]: "good"  // 最终属性名是p1.toString()的结果:'[object Object]'
    }
    console.log(obj)
    
    const map = new Map()
    map.set('name', 'tom')
    map.set(p1, 123) // key为p1对象
    console.log(map)
    

Map的属性和方法

  • size属性返回 Map 结构的成员总数。

  • set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则值会被更新。set方法返回的是当前的Map对象,因此可以采用链式写法。

  • get方法读取key对应的键值,如果找不到key,返回undefined

  • has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。

  • delete方法删除某个键,返回true。如果删除失败,返回false

  • clear方法清除所有成员,没有返回值。

  • forEach():遍历 Map 的所有成员。

  • keys():返回键名的遍历器。

  • values():返回键值的遍历器。

  • entries():返回所有成员的遍历器。

    const p1 = { name: "lily" }
    const map = new Map([
      ['name','tom'],
      [p1, 123]
    ])
    
    /* 
    - `size`属性返回 Map 结构的成员总数。
    - `set`方法设置键名`key`对应的键值为`value`,然后返回整个 Map 结构。如果`key`已经有值,则值会被更新。`set`方法返回的是当前的`Map`对象,因此可以采用链式写法。
    - `get`方法读取`key`对应的键值,如果找不到`key`,返回`undefined`。
    - `has`方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
    - `delete`方法删除某个键,返回`true`。如果删除失败,返回`false`。
    - `clear`方法清除所有成员,没有返回值。
    - `forEach()`:遍历 Map 的所有成员。
    */
    console.log(map, map.size)
    map.set(() => {}, 12)
    console.log(map.get(p1))
    console.log(map.has(p1), map.has(() => {}))
    map.delete('name')
    map.forEach((value, key) => console.log(value, key))
    map.clear()
    console.log(map.size)
    
    

Set

什么是Set

  • ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

  • Set本身是一个构造函数,用来生成 Set 数据结构。

  • Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化

    let st = new Set([1, 2, 3, 4, 4, 3, 3, 1, 5]);
    console.log(st);
    

Set的属性及方法

  • size 返回set的长度

  • add 添加某个值,返回 Set 结构本身。

  • delete 删除某个值,返回一个布尔值,表示删除是否成功。

  • has 返回一个布尔值,表示该值是否为Set的成员

  • clear 清除所有成员,没有返回值。

  • forEach():遍历每个成员

  • keys():返回包含键名的遍历器,由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。

  • values():返回键值的遍历器

  • entries():返回键值对的遍历器

    const set = new Set([2, 3, 4, 4, 3, 3, 1, 5])
    console.log(set)
    
    /* 
    - size 返回set的长度
    - add 添加某个值,返回 Set 结构本身。
    - delete 删除某个值,返回一个布尔值,表示删除是否成功。
    - has 返回一个布尔值,表示该值是否为`Set`的成员
    - clear 清除所有成员,没有返回值。
    - `forEach()`:遍历每个成员
    */
    
    console.log(set.size)
    set.add('abc')
    set.add(3)
    console.log(set, set.size)
    set.delete(6)
    console.log(set, set.size)
    console.log(set.has(5))
    console.log(set.has(6))
    set.forEach(value => console.log(value))
    set.clear()
    console.log(set, set.size)
    

应用:利用Set去重

// 数组去重
[...new Set(array)]

// 字符串去重
[...new Set('ababbc')].join('')
// 'abc'

新增的遍历方式 (了解)

什么是Iterator

  • JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了MapSet。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是MapMap的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。

  • 遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

  • Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

    /* 自定义遍历器演示 */
    const arr = [1, 2, 3];
    function iterator() {
        //用一个计数器,保存现在处理到哪里了
        let index = 0;
        return {
            next: () => {
                if (index < arr.length) {
                    return {
                        value: arr[index++],
                        done: false
                    }
                } else {
                    return {
                        done: true
                    }
                }
            }
        }
    }
    
    //只要调用这个iterator函数,就可以依次得到成员了
    const it = iterator(arr)
    console.log(it)
    let {value, done} = it.next()
    while(!done) {
      console.log(value)
      const item = it.next()
      value = item.value
      done = item.done
    }
    

使用ES6内置的iterator

  • 一种数据结构只要部署了Iterator接口,我们就称这种数据结构是可以迭代的

  • 在ES6中,只要数据结构具有了Symbol.iterator属性,那么就认为是可以迭代的

  • 在ES6中,很多数据结构都部署了iterator接口(string,Array,set,Map)

  • 应用场景:

    • 解构赋值的时候默认调用iterator接口
    • 扩展运算符使用的时候也默认调用iterator接口
    • for of 使用的是iterator接口
  • 注意:object对象是没有部署Iterator接口

    /* 遍历数组 */
    const arr = [1, 2, 3, 4, 5]
    console.log(arr[Symbol.iterator]) //arr[Symbol.iterator] 就是values函数
    
    // 得到数组对应的遍历器对象
    let it = arr[Symbol.iterator]()
    console.log(it)
    let {value, done} = it.next()
    while(!done) {
      console.log(value)
      const item = it.next()
      value = item.value
      done = item.done
    }
    
    /* 遍历字符串 */
    const str = "abc";
    const it = str[Symbol.iterator]();
    let {value, done} = it.next()
    while(!done) {
      console.log(value)
      const item = it.next()
      value = item.value
      done = item.done
    }
    

for…of

  • ES6 借鉴 C++、Java、C# 和 Python 语言,引入了for...of循环,作为遍历所有数据结构的统一的方法。

  • 一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。

  • for...of循环可以使用的范围包括字符串、数组、Set 和 Map 结构、某些伪数组对象(比如arguments对象、DOM的NodeList 对象)、后文的 Generator 对象。

    /* 使用for...of 遍历string/array/set/map */
    
    const str = 'abcd'
    for (const value of str) {
      console.log(value)
    }
    for (const index in str) {
      console.log(index, str[index])
    }
    
    console.log('-------------------------------')
    const arr = ['a', 'b', 'c']
    for (const item of arr) {
      console.log(item)
    }
    
    for (const index in arr) {
      console.log(index, arr[index])
    }
    
    console.log('----------------------------------')
    const set = new Set(['a', 'b', 'c'])
    for (const value of set) {
      console.log(value)
    }
    // for (const item in set) {  // for...in 不能遍历set
    //   console.log(item)
    // }
      
    console.log('----------------------------------')
    const map = new Map([['a',1], [2, 'b']])
    for (const entry of map) {
      console.log(entry)
    }
    // for (const item in map) { // for...in 不能遍历map
    //   console.log(item)
    // }
    
    console.log('----------------------------------')
    const obj = {a: 1, b: 3}
    // for (const item of obj) { // TypeError: obj is not iterable
    //   console.log(item)
    // }
    for (const key in obj) {
      console.log(key, obj[key])
    }
    

区别for…of 与 for…in

  • for…in, ES6之前就有语法, 可以用来遍历字符串、object对象和数组,注意:不能遍历set和map
  • for…of, 是ES6才有的语法, 可以遍历所有实现了Iterator接口的数据,包括:字符串,数组,set,map、arguments、NodeList。注意不能遍历object对象

类语法 (重要)

类/class的由来

  • JavaScript 语言中,生成实例对象的传统方法是通过构造函数

    /* 通过构造函数创建实例 */
    
    // 定义构造函数
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    
    // 给原型对象添加方法
    Person.prototype.sayInfo = function () {
      console.log(`我叫${this.name}, 今年${this.age}`)
    }
    
    // 添加静态属性
    Person.staticProp = 'abc'
    
    // 创建实例对象
    const p1 = new Person('tom', '12')
    p1.sayInfo()
    console.log(Person.staticProp)
    
  • 上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。

  • ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

  • 基本上,ES6 的class的本质就是构造函数, 可以看作是一个语法糖,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

  • constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

  • 构造函数的静态的方法或属性 直接用构造函数调用 使用static定义 ,如果不加static 则声明的属性和方法是构造器的

    /* 通过类创建实例 */
    class Person2 {
      // 添加静态属性
      static staticProp = 'abcdef'
    
      // 构造器 ==》相当于构造函数
      constructor (name, age) {
        this.name = name
        this.age = age
      }
    
      // 定义原型上的方法
      sayInfo () {
        console.log(`偶叫${this.name}, 今年${this.age}`)
      }
    }
    const p2 = new Person2('jack', 13)
    p2.sayInfo()
    console.log(Person2.staticProp)
    
    /* 类的本质也就构造函数 */
    console.log(typeof Person2, p2)
    console.log(p1)
    
    

类的继承

  • 在ES5中,我们通常这么写

    /* 基于构造函数的继承 */
    function Student (name, age, price) {
      Person.call(this, name, age) // 借用父类型构造函数
      this.price = price
    }
    Student.prototype = new Person() // 子类型的原型对象为父类型实例
    Student.prototype.constructor = Student // 子类型原型对象的构造器为子类型
    Student.prototype.sayInfo = function () {
      console.log(`我叫${this.name}, 今年${this.age}, 就业薪资${this.price}`)
    }
    
    const s1 = new Student('bz', 21, 15000)
    console.log(s1.name, s1.age, s1.price)
    s1.sayInfo()
    
  • class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多

  • ES6中继承的子类中,constructor中必须显示执行super()来调用父类型的构造器,用来初始化子类型对象的属性

    /* 基于类的继承 */
    class Student2 extends Person2 {
      constructor (name, age, price) {
        super(name, age)
        this.price = price
      }
      // 重写从父类型继承的方法
      sayInfo () {
        console.log(`偶叫${this.name}, 今年${this.age}, 就业薪资${this.price}`)
      }
    }
    const s2 = new Student2('lz', 22, 14000)
    console.log(s2.name, s2.age, s2.price)
    s2.sayInfo()
    

下一篇:Promise学习

  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值