美文网首页
es6+疑难解析

es6+疑难解析

作者: zxhnext | 来源:发表于2020-05-18 12:26 被阅读0次

    实际上 JavaScript 是 ECMAScript 的扩展语言
    ECMAScript 只提供了最基本的语法


    浏览器端 服务器端

    es6发布的特性可归纳为以下4类:

    1. 解决原有语法上的一些问题或者不足(如:let、const)
    2. 对原有语法进行增强(如:解构赋值)
    3. 全新的对象、全新的方法、全新的功能(如:promise)
    4. 全新的数据类型和数据结构(如:symbol、set、get)

    到目前为止,es6+中共有8种数据类型,分别是:
    StringNumberBooleannullundefinedSymbolBigIntObject
    下面我们分别来对es6中的新特性来做下介绍。

    1. let、const

    首先来看var与let的区别

    1. var会变量提升,let不会
    2. let会形成块级作用域,仅在块级作用域内有效
    3. let会形成暂时性死区,在作用域内,声明前不可使用
    4. 同一个作用域内,let不允许重复声明

    const和let作用类似,但是const在let基础上多了只读属性(变量声明过后不允许再被修改),另外const不允许修改是指不允许修改内存地址,而不是指不允许修改常量中的属性。
    来看下面代码,我们可以发现,i的输出每次都为foo,而不是for循环中i的值,

    for (let i = 0; i < 3; i++) {
      let i = "foo";
      console.log(i); // foo
    }
    

    来对for循环做一下拆解,结合上面let的的功能,我们就明白了。

    let i = 0;
    if (i < 3) {
      let i = "foo";
      console.log(i);
    }
    i++;
    if (i < 3) {
      let i = "foo";
      console.log(i);
    }
    i++;
    if (i < 3) {
      let i = "foo";
      console.log(i);
    }
    i++;
    

    我们可以看到,let形成了块级作用域,所以每次的i都为'foo',只有当我们没有在块作用域中声明i时,i的值才会取外部for循环中的i

    总结:建议主用const,辅助let,不用var

    2. 赋值解构

    2.1 数组赋值解构

    来看下面这个例子,当我们需要获取路径时,es5中我们需要这么做

    var path = '/foo/bar/baz'
    var tmp = path.split('/')
    var rootDir = tmp[1]
    

    而在es6中使用赋值解构,我们只需要按如下写法:

    const [, rootDir] = path.split('/')
    console.log(rootDir)
    

    2.2 对象赋值解构

    我们来对对象做赋值解构,并为之设置别名和默认值:

    const obj = { name: "zxh", age: 18 };
    const { name: objName = 'jack' } = obj;
    console.log(objName);
    

    应用:简化console.log()方法

    const { log } = console
    log(1)
    

    3. 模版字符串

    3.1 换行

    在es5中,如果我们需要对字符串换行,那么我们需要在每行末尾使用\n,那么在es6中,就简单多了,来看下面例子:

    const str = `
      this is first 
      this is second
    `
    

    3.2 转义

    如果字符串中含有** ` **,那么我们需要使用\来转义

    const str = `this is a \`string\``
    

    3.3 带标签的模版字符串

    const name = "tom";
    const gender = true;
    function myTagFunc(strings, name, gender) {
      console.log(strings); // ['hey,', 'is a', '.']
      console.log(name); // tom
      console.log(gender); // true
      return 1;
    }
    const result = myTagFunc`hey, ${name} is a ${gender}.`;
    console.log(result); // 1
    

    4. 字符串扩展

    startsWithendsWithincludes

    5. 函数扩展

    5.1 函数参数默认值

    如果函数某个参数有默认值,那么有默认值的参数必须写在尾部。如果非尾部的参数设置默认值,那么该参数的实参是没法省略的。

    function f(x = 1, y) {
      return [x, y];
    }
    
    f() // [1, undefined]
    f(2) // [2, undefined]
    f(, 1) // 报错
    f(undefined, 1) // [1, 1]
    

    如果我们给函数传入undefined,将触发该参数等于默认值,null则没有这个效果。

    function f(x, y = 5, z) {
      return [x, y, z];
    }
    
    f() // [undefined, 5, undefined]
    f(1) // [1, 5, undefined]
    f(1, ,2) // 报错
    f(1, undefined, 2) // [1, 5, 2]
    
    // -----------------------------------
    function foo(x = 5, y = 6) {
      console.log(x, y);
    }
    
    foo(undefined, null)
    // 5 null
    

    5.2 剩余参数

    剩余参数只能出现在尾部,并且只能使用一次

    function getname(name, ...args) {}
    

    5.3 函数length属性

    指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
    length属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数就不包括这个参数了。同理,rest 参数也不会计入length属性

    (function (a) {}).length // 1
    (function (a = 5) {}).length // 0
    (function (a, b, c = 5) {}).length // 2
    (function(...args) {}).length // 0
    (function (a = 0, b, c) {}).length // 0
    (function (a, b = 1, c) {}).length // 1
    

    5.4 箭头函数

    我们通过筛选数组中的基数这个例子来看普通函数和箭头函数区别

    // 普通函数
    const arr = [1, 2, 3, 4, 5, 6, 7];
    arr.filter(function (item) {
      return item % 2;
    });
    
    // 箭头函数
    arr.filter(i => i % 2);
    

    必包:

    // 这也算一个必包,setTimeout是在sayHiAsync函数外执行的,但是拿到了sayHiAsync函数的this
    const person = { 
        name: 'tom',
        sayHiAsync() {
            const _this = this
            setTimeout(function () {
                console.log(_this.name)
            }, 1000)
        }
    }
    

    6. 对象

    6.1 计算()动态属性名

    const obj = {
      [Math.random]: 123
    }
    

    6.2 Object.is()

    由于==会做隐式转换,而===又无法判断NaN,因此我们可以使用Object.is()

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

    7. proxy

    7.1 Object.defineProperty与Proxy区别

    1. defineProperty只能监视属性的读写,而Proxy监听的更多对象操作。
      具体可参考下图表格:


      image.png
    2. 支持数组对象监视
      ES5通过重写数组方法来实现劫持,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)
    
    1. 非侵入式监听,不需要对原对象做任何操作

    8. Reflect

    8.1 Reflect成员方法就是Proxy处理对象的默认实现

    const obj = {
      foo: "123",
      bar: "456",
    };
    
    const proxy = new Proxy(obj, {
      // 如果我们没有定义get方法,那么Proxy会默认一个get方法如下,返回Reflect
      get(target, property) {
        console.log(" watch logic~");
        return Reflect.get(target, property);
      },
    });
    console.log(proxy.foo);
    

    8.2 Reflect最大作用是提供了一套完整的对象调用方法

    它的内部封装了一系列对对象的底层操作,便于我们更方便的操作对象

    const obj = {
      name: "zce",
      age: 18,
    };
    // es5
    console.log("name" in obj);
    console.log(delete obj["age"]);
    console.log(Object.keys(obj));
    // Reflect
    console.log(Reflect.has(obj, "name"));
    console.log(Reflect.deleteProperty(obj, "age"));
    console.log(Reflect.ownKeys(obj));
    

    Reflect共有13个api,具体查阅文档

    8. Promise

    9. 类

    9.1 静态方法

    es5中通过给函数挂载方法来实现静态方法(函数也是一个对象,可以像对象一样直接挂载),在中es6,我们通过static关键词来定义静态方法
    注意:静态方法的this不指向实例对象

    class Person {
      constructor(name) {
        this.name = name;
      }
      say() {
        console.log(`hi, my name is ${this.name}`);
      }
      static create(name) {
        return new Person(name);
      }
    }
    const tom = Person.create("tom");
    tom.say();
    

    2. 继承

    class Student extends Person {
      constructor(name, number) {
        super(name, number);
        this.number = number;
      }
      hello() {
        super.say();
        console.log(`my school number is ${this.number}`);
      }
    }
    
    const s = new Student("jack", " 100");
    s.hello();
    

    10. Set Map

    10.1 Set

    我们通过Array.from()或者展开运算符可以将Set转化为数组

    const arr = [1, 2, 3, 4, 5]
    const result = Array.from(new Set(arr))
    // or
    const result = [...new Set(arr)]
    

    10.2 Map

    与对象类似,是一个键值对集合,目的是为了解决对象只能用字符串作键值
    在es5中,普通对象如果键不是字符串,那么键就等于输入键的toString()结果作为键,es6开始对象支持用字符串或Symbol来作为键值而在Map中,任意类型数据都可作为键

    // 普通对象
    const obj = {}
    obj[true] = 'value'
    obj[123] = 'value'
    obj[{ a: 1 }] = 'value'
    // 如果键不是字符串,那么键就等于输入键的toString()结果作为键
    console.log(Object.keys(obj)) // ['true', '123', '[object Object]']
    
    // Map 任意类型数据都可作为键
    const m = new Map();
    const tom = { name: "tom" };
    m.set(tom, 90);
    console.log(m);
    console.log(m.get(tom));
    

    11. Symbol

    es6开始对象支持用字符串或Symbol来作为键值,symbol最主要的作用就是为对象添加独一无二的属性名

    Symbol()===Symbol // false
    // Symbol 描述符
    Symbol('foo')
    Symbol('bar')
    

    11.1 利用Symbol来实现私有成员

    // a.js ======================================
    const name = Symbol();
    const person = {
      [name]: "zce",
      say() {},
    };
    
    // b.js =======================================
    // Symbol是唯一的,外面的Symbol与定义时的不一样
    // person[Symbol()]
    person.say();
    

    11.2 如何获取Symbol

    1. 通过Symbol.for()传入标识符获取
    const s1 = Symbol.for('foo') // 传入标识符
    const s2 = Symbol.for('foo')
    console.log(s1 === s2) // true
    Symbol('foo') === Symbol('foo') // false
    

    注意,传入的标识符应该是字符串,如果不是字符串,会默认转为字符串,所以以下代码是相等的

    Symbol.for(true) === Symbol.for('true')
    

    11.3 扩展方法

    const obj0 = {};
    console.log(obj0.toString()); // [object Object]
    
    const obj1 = {
      [Symbol.toStringTag]: "Xobject",
    };
    console.log(obj1.toString()); // [object Xobject]
    

    11.4 获取Symbol键值

    普通的for inObject.keys()JSON.stringify()是获取不到Symbol的键值的,我们需要通过Object.getOwnPropertySymbols获取,所以Symbol特别适合设置对象私有成员

    const obj = {
      [Symbol()]: "symbol value",
      foo: "normal value",
    };
    
    for (var key in obj) {
      console.log(key); // foo
    }
    console.log(Object.keys(obj)); //  'foo' ]
    console.log(JSON.stringify(obj)); // {"foo":"normal value"}
    console.log(Object.getOwnPropertySymbols(obj)); // [ Symbol() ]
    

    12. for of

    for of可以通过break跳出循环,在代码中建议使用for of。但是我们可以发现,for of不支持对象,因为对象没有实现Iterable接口,我们在Iterable会详细讲解。
    实现Iterable接口就是for..of的前提,也就是说,只要该种数据结构实现了(可迭代接口)Iterable,那么就可以使用for of

    const arr = [1, 2, 3, 4, 5];
    for (const item of arr) {
      console.log(item); // 1,2,3,4,5
      if (item > 100) {
        break;
      }
    }
    
    const m = new Map();
    m.set("foo", "123");
    m.set("bar", " 345");
    for (const [key, value] of m) {
      console.log(key, value);
    }
    

    13. Iterable(迭代器)

    13.1 每一个可迭代数据原型上都有一个Symbol.iterator方法

    const set = new Set(["foo", "bar", "baz"]);
    const iterator = set[Symbol.iterator]();
    console.log(iterator.next()); // { value: 'foo', done: false }
    console.log(iterator.next()); // { value: 'bar', done: false }
    console.log(iterator.next()); // { value: 'baz ', done: false }
    console.log(iterator.next()); // { value: undefined, done: true }
    console.log(iterator.next()); // { value: undefined, done: true }
    

    13.2 给对象添加迭代器

    给对象添加迭代器以后,就可以使用for of循环了

    const obj = {
        store: ['foo', 'bar', 'baz'],
        [Symbol.iterator]: function () {
            let index = 0
            const _self = this
            return {
                next: function () {
                    const result = {
                        value: _self.store[index],
                        done: index >= _self.store.length
                    }
                    index++
                    return result
                }
            }
        }
    }
    

    13.3 迭代器应用

    遍历结构

    const todos = {
        life: ['吃皈', '睡觉', '打豆豆'],
        learn: ['语文', '数学', '外語'],
        work: ['喝茶'],
        // 普通模式,通过each模式
        each: function (callback) {
            const all = [].concat(this.life, this.learn, this.work)
            for (const item of all) {
                callback(item)
            }
        },
        // 迭代器模式
        [Symbol.iterator]: function () {
            const all = [...this.life, ...this.learn, ...this.work]
            let index = 0
            return {
                next: function () {
                    return {
                        value: all[index],
                        done: index++ >= all.length
                    }
                }
            }
        }
    }
    
    // 使用each
    todos.each(function (item) {
        console.log(item)
    })
    
    // 使用迭代器
    for(const item of todos) {
      console.log(item)
    }
    

    14. Generator(生成器)

    解决异步嵌套

    14.1 应用

    1. 使用Generator实现迭代器
    // 以上一节例子为例
    [Symbol.iterator]: function* () {
            const all = [...this.life, ...this.learn, ...this.work]
            // let index = 0
            // return {
            //     next: function () {
            //         return {
            //             value: all[index],
            //             done: index++ >= all.length
            //         }
            //     }
            // }
            for (const item of all) {
                yield item
            }
        }
    
    1. 发号器
    function* createIdMaker() {
        let id = 1
        while (true) {
            yield id++
        }
    
    }
    
    const idMaker = createIdMaker()
    console.log(idMaker.next().value)
    console.log(idMaker.next().value)
    console.log(idMaker.next().value)
    console.log(idMaker.next().value)
    

    15. es2016

    15.1 includes

    indexof不能查找数组中的NaN(掘金遍历数组的文章)

    15.2 指数运算

    计算2的10次方

    // es5: 
    Math.pow(2, 10)
    // es7: 
    2 ** 10
    

    16. es2017

    16.1 对象扩展

    Object.values() // 对象值的数组
    Objece.entries // 对象键值对数组

    1. 通过Objece.entries 可以使对象用for of 循环
    const obj = {
        foo: 'foo',
        bar: 'bar'
    }
    for (const [item, index] of Object.entries(obj)) { 
        console.log(index, item)
    }
    
    1. 将对象转为Map形式对象
    new Map(Object.entries(obj))
    
    1. Object.getOwnPropertyDescriptors
      以下问题,当拷贝一个对象时,对象中的getter属性被当作了普通属性,所以修改p2中firstName的值,fullName没有变化,这时我们可以通过Object.getOwnPropertyDescriptors解决
    const p1 = {
        firstName: 'Lei',
        lastName: 'Wang',
        get fullName() {
            return this.firstName + ' ' + this.lastName
        }
    }
    console.log(p1.fullName) // Lei Wang
    
    const p2 = Object.assign({}, p1)
    p2.firstName = 'Zhao'
    console.log(p2.fullName) // Lei wang
    
    const descriptors = Object.getOwnPropertyDescriptors(p1)
    const p2 = Object.defineProperties({}, descriptors)
    p2.firstName = 'Zhao'
    console.log(p2.fullName) // zhao wang
    

    16.2 padStart、padEnd

    相关文章

      网友评论

          本文标题:es6+疑难解析

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