美文网首页
ECMAScript新特性(一)

ECMAScript新特性(一)

作者: 洲行 | 来源:发表于2020-10-05 02:33 被阅读0次
    ECMAScript是什么

    ECMAScript(ES),也是一门脚本语言,通常把它看作为JavaScript(JS)的标准化规范,实际上JS是ES的扩展语言,因为ES只提供了最基本的语法(如定义变量,for循环等),JS实现了ES语言的标准,并且又做了扩展,使得我们可以在浏览器环境中操作BOM和DOM,在Node环境中,可以去读写文件这种操作


    浏览器环境中

    这是在浏览器环境中,js包含es和浏览器提供的API,这些API又包含DOM和BOM


    Node环境中
    这是在node环境中,js包含es和node提供的API,这些API又包含fs(用于操作系统中的文件),net(用于底层的网络通信),etc.
    所以ES就是JS语言本身

    ES版本

    2015年开始,ES保持每年一个版本的迭代,所以现在变得越来越高级,便捷


    目前的所有版本

    从2009年发布的5版本到其中的ES2015,经历了6年时间才被完全标准化,这6年时间,也是web发展的黄金时间,所以这个版本包含了很多颠覆式的新功能,由于ES2015迭代的时间太长,发布的内容过多,所以从之后的版本开始,es的发布就变得更加频繁,也符合当下前端发展小步快跑的现状。
    自从ES2015开始,ES2015开始按照发行年份命名,不再按照版本号命名,由于这个决定是在2015年才开始,所以很多人也把ES2015叫做ES6。
    目前市面上主流的运行环境,也都纷纷跟进,也都开始逐步支持这些最新的特性,所以对于我们这些开发者而言,学习这些新特性很有必要。

    ES2015

    也就是ES6,新时代ES的版本标准,相比之前变化较大。目前很多人又将ES6泛指ES5.1后所有的新标准,比如我们在很多资料中看到“使用ES6的async和await”,实际上呢async函数是ES2017中制定的标准,所以以后逛论坛得注意分辨,它说的ES6是泛指还是特指。
    ES2015规范链接
    这里就整理ES5.1之后的,比较重要的且值得了解的新特性,这些变化可以分为四大类

    • 解决原有语法上的问题与不足(let,const等)
    • 对原有语法进行增强,使其变得更为便捷易用(解构,模版字符串等)
    • 全新的对象,全新的的方法,全新的功能(Promise等)
    • 全新的数据类型和数据结构(symbol,set,map等)

    let与块级作用域

    作用域:代码中某个成员能够起作用的范围。在ES2015之前,只有全局作用域和函数作用域,ES2015中又新增块级作用域。
    块:指的就是代码中,用一对{ }所包裹起来的范围,例如if,for语句中的{},都会产生我们所说的块。以前块是没有独立作用域的。

    if(true) {
        var aa = 'aaa';
        let bb = 'bbb';
    }
    console.log(aa) // => 'aaa'
    console.log(bb) // => 'ReferenceError: bb is not defined'
    

    let与var还有一个区别是,let没有变量提升

    // 变量提升
    console.log(a) // => undefined,说明在我们打印的时候,a就已经被声明了,只是还没赋值,这种现象就叫变量提升
    var a = 123
    
    console.log(b) // => Uncaught ReferenceError: Cannot access 'b' before initialization
    let b = 123
    

    其实let,const是有变量提升的,var在提升后的初始化阶段会赋值undefined,let与const不会赋值,所以此时访问没有任何赋值的变量时,会抛出错误,称为暂时性死区
    JS变量提升与时间死区

    const

    const:用来声明一个只读的常量。
    它的特点就是在let的特点上多了一个只读,只读的意思是声明后不允许再被修改

    const a = 123
    a = 456 // => Uncaught TypeError: Assignment to constant variable.
    
    const b = {name: 123}
    b.name = 456
    console.log(b) // => {name: 456}
    

    注意,不能被修改,只是说我们用const声明过后,不允许重新指向一个新的内存地址,并不是说不能修改常量中的属性成员。所以b.name修改成功

    最佳实践:不用var,主用const,配合let

    数组的解构

    // 普通方式获得数组成员
    const arr = ['aa', 'bb', 'cc']
    let a = arr[0];
    let b = arr[1];
    let c = arr[2];
    console.log(a, b, c)
    
    // 解构
    let [a, b, c] = arr
    console.log(a, b, c) // => aa bb cc
    // 只获取第一个
    let [a] = arr
    console.log(a) // =>  aa
    // 只获取第三个,把变量名称删除,要保留逗号
    let [, , c] = arr
    console.log(c) // =>  cc
    // 只获取后两个,用...表示,这种用法只能放在最后
    let [a, ...rest] = arr
    console.log(rest) // => ["bb", "cc"]
    // 使用默认值
    let [a, b, c, more = 'dd'] = arr // 没有第四项,本来是undefined
    console.log(a, b, c, more) // => aa bb cc dd
    

    对象的解构

    // 与数组的解构类似
    const obj = {name: 123, age: 18}
    let {name, sex = "female"} = obj
    console.log(name, sex) // => 123 "female"
    // 变量重命名
    const obj = {name: 123, age: 18}
    let {name : objName} = obj
    console.log(objName) // => 123
    

    模版字符串

    const name = 'Tom';
    let str1 = 'hey,' + name // 传统方式
    let str2 = `hey,${name}` // 反引号方式
    
    // ${}中不仅可以加入变量,任何规范的js语句都可以
    function getAge(n) {
        return n + 1;
    }
    let str3 = `hey ${name}, i am ${getAge(10)} years old`
    
    

    新方式还有一个好处是支持换行,以前要是想在字符串中换行需要\n

    带标签的模版字符串

    const gender = 1;
    function tag(string, gender) {
        console.log(string)
        gender = gender === 1 ? '男' : '女';
        return string[0] + string[1] + gender;
    }
    let result = tag`我的性别是${gender}`
    console.log(result) // => 我的性别是男
    

    字符串的扩展方法

    const message = 'hello, i love you';
    
    message.startsWith('hello') // => true  是否以hello开头
    message.endsWith('he') // => false  是否以he结束
    message.includes('love') // => true  是否包含love
    

    函数参数默认值

    // 以前赋默认值
    function f1 (value) {
        // value = value || 100;  // 其实这样是错的,如果传进去false ,也变成 内容是100了
        value = value === undefined ? 100 : value; // 这样最标准
        return '内容是' + value;
    }
    console.log(f1()) // => 内容是100
    
    // 新方式
    function f1 (value = 100) {
        return '内容是' + value;
    }
    console.log(f1()) // => 内容是100
    

    剩余参数

    // 传统方式
    function f1 () {
        console.log(arguments) // 伪数组 => 1,2,3,4
    }
    f1(1,2,3,4)
    
    // ES2015
    function f1 (a, ...args) {
        console.log(a) // => 1
        console.log(args) // => [2,3,4] 正常数组
    }
    f1(1,2,3,4)
    

    展开数组参数

    const arr = [1,2,3]
    // 传统方式
    console.log(arr[0], arr[1], arr[2]) // => 1 2 3
    // 如果参数不固定
    console.log.apply(this, arr) // => 1 2 3
    // ES2015
    console.log(...arr) // => 1 2 3
    

    箭头函数

    function add(a, b) {
        return a + b
    }
    // 箭头函数    更简短易读
    const add = (a, b) => a + b
    

    箭头函数与this

    const obj = {
        name: 'Tom',
        sayHi1: function () {
            console.log(this)
        },
        sayHi2: () => {
            console.log(this)
        }
    }
    obj.sayHi1() // this指向person
    obj.sayHi2() // this指向window
    
    • 普通函数,谁调用了这个普通函数,this 指向谁。是在执行时绑定。
    • 箭头函数,this指向定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。
      比如这里箭头函数本身所在的作用域为全局作用域(对象的这个花括号没有作用域),而全局作用域的this指向window。

    如果你是懒人,只需要记住这一句话就好了:不管在什么情况下,箭头函数的this跟外层function的this一致,外层function的this指向谁,箭头函数的this就指向谁,如果外层不是function则指向window。

    对象字面量增强

    const name = 'Tom';
    const obj = {
        // name: name 以前必须要接冒号,现在变量名相同可省略
        name,
    
        // sayHi: function f () {  函数也是,可精简为以下方式
        //     console.log(this)
        // },
        sayHi () {  // 此方式和上面的function一样,不是箭头函数
            console.log(this)
        }
        ['ha' + 'ha']: 123 // 计算属性名,不用再像以前一样 obj['ha' + 'ha'] = 123
    }
    

    Object.assign

    将多个源对象中的属性,复制到一个目标对象中
    如果有相同的属性,源对象的属性会覆盖掉目标对象的属性

    const source1 = {
      a: 123,
      b: 123
    }
    const target = {
      a: 456,
      c: 456
    }
    const result = Object.assign(target, source1) // 第一个入参是目标对象,后续都是源对象
    
    console.log(target) // => {a: 123, b: 456, c: 456}
    console.log(result === target) // => true 返回值就是目标对象
    

    经常用Object.assign({}, obj) 来复制一个一模一样的对象

    Object.is

    比较两值是否相等

    console.log(
      // 0 == false              // => true
      // 0 === false             // => false
      // +0 === -0               // => true
      // NaN === NaN             // => false
      // Object.is(+0, -0)       // => false
      // Object.is(NaN, NaN)     // => true
    )
    

    Proxy

    为对象设置访问代理器的(代理器就想像成门卫,进去干任何事都要经过他)

    const person = {
      name: 'zce',
      age: 20
    }
    
    const personProxy = new Proxy(person, {
      // 监视属性读取
      get (target, property) {
        return property in target ? target[property] : 'default'
        // console.log(target, property) // => 目标对象,属性名
        // return 100
      },
      // 监视属性设置
      set (target, property, value) {
        if (property === 'age') {
          if (!Number.isInteger(value)) {
            throw new TypeError(`${value} is not an int`)
          }
        }
        target[property] = value
        // console.log(target, property, value) // => 目标对象,属性名,值
      }
    })
    
    person.age = 100 // 可以更改person,但是不会经过set方法
    personProxy.age = 100 // 可以更改person,也会经过set方法
    

    Proxy VS Object.defineproperty

    defineproperty只能监视对象的读取和写入
    proxy能监视到更多的对象操作,比如delete,调用一个函数,都能监视到
    proxy支持数组的监视

    const list = []
    const listProxy = new Proxy(list, {
      set (target, property, value) {
        console.log('set', property, value) // => [], 0, 100
        target[property] = value
        return true // 表示设置成功
      }
    })
    
    listProxy.push(100)
    

    proxy是以非侵入的方式监管了对象的读写

    const person = {}
    
    Object.defineProperty(person, 'name', {
      get () {
        console.log('name 被访问')
        return person._name
      },
      set (value) {
        console.log('name 被设置')
        person._name = value
      }
    })
    Object.defineProperty(person, 'age', {
      get () {
        console.log('age 被访问')
        return person._age
      },
      set (value) {
        console.log('age 被设置')
        person._age = value
      }
    })
    
    person.name = 'jack'
    
    console.log(person.name)
    
    // Proxy 方式更为合理 不需要对每个属性都绑定
    const person2 = {
      name: 'zce',
      age: 20
    }
    
    const personProxy = new Proxy(person2, {
      get (target, property) {
        console.log('get', property)
        return target[property]
      },
      set (target, property, value) {
        console.log('set', property, value)
        target[property] = value
      }
    })
    
    personProxy.name = 'jack'
    
    console.log(personProxy.name)
    

    Reflect

    Reflect最大的意义在于统一对象的操作

    const obj = {
      name: 'zce',
      age: 18
    }
    
    console.log('name' in obj)  // 判断是否有某个属性
    console.log(delete obj['age']) // 删除某个属性
    console.log(Object.keys(obj)) // 枚举属性的key
    // 由上可以看出,对于对象的操作,既有操作符,又有对象的某个方法,并不统一
    
    // Reflect最大的意义在于统一对象的操作
    console.log(Reflect.has(obj, 'name')) // 判断是否有某个属性
    console.log(Reflect.deleteProperty(obj, 'age')) // 删除某个属性
    console.log(Reflect.ownKeys(obj)) // 枚举属性的key
    

    class类

    // 传统的创建类的方式
    function Person(name) { // 构造函数
        this.name = name;
    }
    Person.prototype.sayHi = function () { // 原型
        console.log(`my name is ${this.name}`);
    }
    // class
    class Person {
        constructor(name) { // 构造函数
            this.name = name;
        }
        sayHi() { // 实例方法
            console.log(`my name is ${this.name}`);
        }
        static create (name) { // 静态方法
            return new Person(name)
        }
    }
    const p = new Person('Eric');
    const p2 = Person.create('Eric);
    

    类的继承

    class Person {
      constructor (name) {
        this.name = name
      }
      say () {
        console.log(`hi, my name is ${this.name}`)
      }
    }
    class Student extends Person {
      constructor (name, number) {
        super(name) // 执行父类构造函数,必须有
    //子类构造器中,this是由 super()调用所产生的(即所谓「父类的this」)。在super()调用之前,你是不能在子类构造器中使用this的,如果访问之,会产生ReferenceError
        this.number = number
      }
      hello () {
        super.say() // 调用父类方法
        console.log(`my school number is ${this.number}`)
      }
    }
    const s = new Student('jack', '100')
    s.hello()
    s.say()
    

    相关文章

      网友评论

          本文标题:ECMAScript新特性(一)

          本文链接:https://www.haomeiwen.com/subject/qdswuktx.html