美文网首页
[JavaScript] 记录下我学习ES6的一些总结

[JavaScript] 记录下我学习ES6的一些总结

作者: 沉江小鱼 | 来源:发表于2024-03-11 11:54 被阅读0次

    前言:这篇文章主要是对于ES6特性的学习和总结,作为一个前端初级开发者,刚开始我是跟着菜鸟教程中自学的,只是学了那个基础的 JavaScript。这次花了两天时间了解了下 ES6的特性,收获还是挺多的。最起码之前看前端同事的代码所遇到的一些问题,在这里都能找到答案。

    ES6特性 描述 备注
    let 用于声明变量,作用域相比于 var 更严格
    const 用于声明常量
    Symbol 新的原始数据类型,创建的变量都是独一无二的
    Map 容器类,存储键值对,FIFO
    Set 容器类,存储的值或引用都是唯一的
    Proxy 用于对目标对象的读取、函数调用等操作进行拦截,实现观察者模式 除了常见的set、get方法,还包括原型操作、遍历、方法调用等操作
    Reflect 和Object类似,用于获取目标对象的默认行为,将Object操作都变成函数行为 Proxy提供的方法一一对应,可以和Proxy结合使用,在Proxy修改对象行为的时候,可以先用Reflect先获取对象的默认行为
    字符串 增加了判断子串包含的能力,并提供了模板字符串,内部可嵌入变量或者调用方法
    数值 主要是针对 Math 对象新增了很多方法,可以在用的时候再去搜索
    对象 提供了构建对象时属性和方法的简写,以及支持拓展运算符...
    数组 数组提供了一些新的方法,感觉比较有用的是判断是否包含了某个元素
    函数 支持默认参数和不定参数,更重要的是提供了箭头函数,可以在回调函数中很好的保持 this 指向
    类 Class Class 本质上是 function,更加面向对象了
    模块 JavaScript 模块化就很方便了,一个文件就是一个模块
    Promise 异步编程的一种解决方案

    1.简介

    1.1 ECMAScript和JavaScript的关系

    ECMAScript:是一种标准化的脚本语言规范,定义了 JavaScript 的核心语法和行为。这个规范规定了 JavaScript 应该如何工作,应该包含哪些特性,但是它不是一种编程语言。

    JavaScript:则是一种根据 ECMAScript 规范开发的编程语言,是 ECMAScript 的具体实现。

    ECMAScript 是 JavaScript 的基石,它提供了 JavaScript 的语法基础和行为准则。而 JavaScript 则是在这个基础上进行扩展和实现。由于 ECMAScript 的版本不断更新,每次更新都会引入新的特性和语法,因此 JavaScript 也会随之发展和完善,以适应新的编程需求和技术趋势。

    简单来说,ECMAScript 是 JavaScript 的语法规范,而 JavaScript 则是 ECMAScript 的具体实现。

    1.2 ECMAScript的历史

    • 1997 年 ESMAScript 1.0 诞生;
    • 1998 年 6 月 ECMAAScript 2.0 诞生,包含一些小的更改,用于同步独立的 ISO 国际标准;
    • 1999 年 12 月 ECMAScript3.0 诞生,它是一个巨大的成功,奠定了 JS 的基本语法;
    • 2000 年 ECMAScript4.0 是 ESMAScript6 的前身,但这个版本改动过于激进,被放弃了;
    • 2009 年 12 月,ECMAScript5.0 正式发布,在3.0 的基础上进行了许多改进和扩展,进一步提高了 JavaScript 的可用性和安全性;
    • 2015 年,ECMAScript 6.0 发布,引入了很多新的语言特性和语法糖;
    • ECMAScript 7(2016年)、ECMAScript 8(2017年)等版本..

    因为作者懒,所以下面都会用 ES JS 分别代替ECMAScript和JavaScript。

    既然 ES 有这么多版本,为什么在前端同事的口中听到最多的是 ES6 呢?

    这主要是因为 ES6 (ES2015)引入了大量新的特性和语法,这些改动对 JS 开发者产生了深远的影响,至今仍然是日常开发中广泛使用的,因此,前端开发者谈论的最多的是 ES6,后续的版本虽然也带来了一些改动,但是改动通常较小,所以没有像 ES6 那样引起广泛的关注。

    2.ES6特性

    2.1 let 和 const

    ES6 新增了两个重要的关键字 let 和 const 用来声明变量和常量。

    2.1.1 let

    1.声明的变量只在代码块有效

    相比于之前的 var 关键字声明的变量全局有效,let 声明的变量只在所在的代码块有效,这会更符合当前开发语言的习惯。

    2. 不存在变量提升
    let 不存在变量提升,var 会变量提升:

    console.log(b); var b = 10;
    VM203:1 undefined
    
    console.log(a); let a = 10;
    VM227:1 Uncaught ReferenceError: a is not defined
        at <anonymous>:1:13
    

    变量 b 用 var 声明存在变量提升,所以当脚本运行的时候,b 已经存在了,只是没有赋值,所以会输出 undefined;
    变量 a 用 let 声明不存在变量提升,在声明变量 a 之前,a 不存在,所以会报错;

    3.不能重复声明
    let 只能声明一次,var 可以声明多次:

    let a = 1;
    let a = 2;
    var b = 3;
    var b = 4;
    VM231:2 Uncaught SyntaxError: Identifier 'a' has already been declared
    

    2.1.2 const

    const 就很好理解了,就是声明一个常量。

    不过需要注意的是,const 只是保证变量指向的内存地址所保存的数据不允许变动。

    • 对于简单类型,值就保存在变量指向的那个内存地址,因此 const 声明的简单类型等同于常量;
    • 对于引用类型,变量指向的内存地址其实是保存了一个指向实际数据的指针,所以 const 只能保证指针是固定的。至于指向的数据结构变不变就无法控制了;

    2.2 解构赋值

    解构复制是对赋值运算符的扩展,针对于数组或者对象进行模式匹配,然后对其中的变量进行赋值。方便了复杂对象中数据字段获取。在解构中,有两部分参与:

    解构的目标 和 解构的源
    

    2.2.1 数组模型结构

    // 基本
    let [a,b,c] = [1,2,3]  // a=1 b=2 c=3
    
    // 嵌套
    let [a,[[b], c]] = [1,[[2], 3]]; // a=1 b=2 c=3
    
    // 可忽略
    let [a, , b] = [1, 2, 3] // a=1 c=3
    
    // 不完全解构
    let [a = 1, b] = [] // a = 1, b = undefined
    
    // 剩余运算符
    let [a, ...b] = [1,2,3] // a = 1, b = [2,3]
    
    

    2.2.2 对象模型的解构

    // 基本使用
    let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; // foo = 'aaa' // bar = 'bbb' 
    let { baz : foo } = { baz : 'ddd' };  // foo = 'ddd'
    
    // 解构默认值
    let {a = 10, b = 5} = {a: 3}; // a = 3; b = 5; 
    let {a: aa = 10, b: bb = 5} = {a: 3}; // aa = 3; bb = 5;
    

    2.3 Symbol

    Symbol 是一种原始数据类型,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。

    2.3.1 基本用法

    Symbol 是原始数据类型,所以不能使用 new 命令。Symbol()函数接收一个字符串参数,主要是用于描述 Symbol 变量,打印时容易区分:

    let sy = Symbol("test")
    console.log(sy); // Symbol(test)
    typeof(sy); // "symbol"
    
    // 即使字符串参数相等,这两个变量也不相等
    let sy1 = Symbol("test")
    sy === sy1 // false
    

    2.3.2 作为属性名

    由于每一个 Symbol 的值都不相等,所以 Symbol 作为对象的属性名,可以保证属性不重名。

    let symbol = Symbol("key1")
    let object = {};
    object[symbol] = 'value1'
    console.log(object) // {Symbol(key1): 'value1'}
    

    需要注意,Symbol 作为对象属性名时不能用.运算符,要用方括号。

    Symbol 值作为属性名时,该属性是公有属性,可以在类的外部访问。但是不会出现在 for...in/of 的循环中,也不会被 Object.keys() Object.getOwnPropertyNames()返回。可以通过Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到:

    let syObject = {};
    syObject[sy] = "kk";
    console.log(syObject);
     
    for (let i in syObject) {
      console.log(i);
    }    // 无输出
     
    Object.keys(syObject);                     // []
    Object.getOwnPropertySymbols(syObject);    // [Symbol(key1)]
    Reflect.ownKeys(syObject);                 // [Symbol(key1)]
    

    2.3.3 Symbol.for()

    Symbol.for()用于根据给定的 key 在全局的 Symbol 注册表中搜索对应的 Symbol:

    • 如果找到了匹配的 Symbol,则返回它;
    • 如果没有找到,则创建一个新的与该键关联的 Symbol,并将其添加到全局注册表中;

    通过 Symbol.for() 和 Symbol()创建的变量是不同的,即使使用相同的 key 值,因为 Symbol()始终会创建一个新的、唯一的 Symbol:

    let yellow = Symbol("Yellow");
    let yellow1 = Symbol.for("Yellow");
    yellow === yellow1;      // false
     
    let yellow2 = Symbol.for("Yellow");
    yellow1 === yellow2;     // true
    

    Symbol.keyFor()会返回一个已经在全局注册表中登记的 Symbol 类型值的 key,用来检测该字符串参数作为名称的 Symbol 值是否已经在全局注册表中登记:

    let yellow1 = Symbol.for("Yellow");
    Symbol.keyFor(yellow1);    // "Yellow"
    

    2.4 Map 和 Set

    我称之为容器类。

    2.4.1 Map

    Map对象用来保存键值对,和Object功能相同,但有所区别:

    • Map的键可以是任意值,Object的键只能是字符串或者Symbol
    • Map中的键值是有序的(FIFO),但Object则不是
    • Map的键值对个数可以从size属性获取,Object的键值对个数只能手动计算
    • Object有自己的原型,原型链上的键名有可能和自己在对象上添加的键名重复

    2.4.1.1 基础使用

    Map对象基础的语法就是set和get,一个存值,一个取值:

    let map = new Map()
    map.set("name", "zhangsan") // Map(1) {'name' => 'zhangsan'}
    map.get("name") // 'zhangsan'
    

    2.4.1.2 遍历Map

    for...of遍历Map:

    // 获取key value
    let map = new Map()
    map.set("name", "zhangsan") 
    map.set("age", 18)
    for (var [key, value] of map) { 
        console.log(key + "=" + value) 
    }
    // name=zhangsan
    // age=18
    
    
    // 获取key
    for (var key of map.keys()) {
      console.log(key);
    }
    // name
    // age
    
    // 获取value
    for (var value of map.values()) {
      console.log(value);
    }
    // zhangsan
    // 18
    

    forEach遍历:

    map.forEach(function(value, key) {
      console.log(key + " = " + value);
    }, map)
    // name = zhangsan
    // age = 18
    

    2.4.1.3 Map对象操作

    Map对象的合并:

    var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]);
    var second = new Map([[1, 'uno'], [2, 'dos']]);
     
    // 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three
    var merged = new Map([...first, ...second]);
    

    2.4.2 Set

    Set对象允许存储任何类型的唯一值,无论是原始值或者是对象引用。

    2.4.2.1 基础使用

    let set = new Set()
    set.add(1)
    set.add(2)
    set.add(3) // Set(3) {1, 2, 3}
    set.add(1) // Set(3) {1, 2, 3}
    

    可以看到,当第二次添加1后,set中仍然只有一个1。不过需要注意几个特殊情况:

    • +0和-0在存储判断唯一性的时候是恒等的,不重复;
    • undefined和undefined恒等,不重复;
    • NaN和NaN不重复,但是在Set中只能存一个,不重复;

    2.4.2.2 其它使用

    类型转换:

    // Array 转 Set
    let set = new Set(["1","2","3"])
    // Set 转 Array
    let array = [...set]
    // String 转 Set
    let set = new Set("hello") // Set(4) {h,e,l,o}
    
    需要注意,Set中的toString方法不能将Set转成String
    

    对象的作用:

    // 数组去重
    var set = new Set([1, 2, 3, 4, 4]); 
    [...set]; // [1, 2, 3, 4]
    
    // 并集
    var a = new Set([1, 2, 3]);
    var b = new Set([4, 3, 2]);
    var union = new Set([...a, ...b]); // {1, 2, 3, 4}
    
    // 交集
    var a = new Set([1, 2, 3]);
    var b = new Set([4, 3, 2]);
    var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}
    
    // 差集
    var a = new Set([1, 2, 3]);
    var b = new Set([4, 3, 2]);
    var difference = new Set([...a].filter(x => !b.has(x))); // {1}
    

    2.5 Proxy

    Proxy可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。顾名思义,它是个代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些额外操作。

    2.5.1 基本用法

    一个Proxy对象由两部分组成:

    • target:目标对象
    • handler:用于声明代理target的指定行为
    let target = { name: 'Tom', age: 24 } 
    let handler = { 
        get: function(target, key) { 
            console.log('getting '+key); 
            return target[key]; // 不是target.key 
        }, 
        set: function(target, key, value) {
            console.log('setting '+key); 
            target[key] = value; 
        } 
    } 
    let proxy = new Proxy(target, handler) 
    proxy.name // 实际执行 handler.get 
    proxy.age = 25 // 实际执行 handler.set 
    // getting name 
    // setting age 
    // 25
    

    target可以为空对象:

    let target = { } 
    let proxy = new Proxy(target, handler) 
    proxy.name // 调用get方法,此时目标对象为空,没有name属性
    proxy.name = "Tom" // 调用set方法,向目标对象中添加了name属性
    target.name = "Tom" // 通过构造函数新建Proxy实例时其实是对目标对象进行了浅拷贝,因此目标对象与代理对象会互相影响
    

    handler对象也可以为空,相当于不设置拦截操作,直接访问目标对象:

    let target = {}
    let proxy = new Proxy(target, {})
    proxy.name = "Tom"
    target.name // "Tom"
    

    2.5.2 实例方法

    Proxy对象提供了以下方法,用于拦截对target对象的操作:

    • get(target, propKey, receiver)
    • set(target, propKey, value, receiver)
    • apply(target, ctx, args)
    • has(target, propKey)
    • construct(target, args)
    • has(target, propKey)
    • construct(target, args)
    • deleteProperty(target, propKey)
    • defineProperty(target, propKey, propDesc)
    • getOwnPropertyDescriptor(target, propKey)
    • getPrototypeOf(target)
    • isExtensible(target)
    • ownKeys(target)
    • preventExtensions(target)
    • setPrototypeOf

    2.5.2.1 get

    get(target, propKey, receiver)
    

    用于target对象上propKey的读取操作:

    let target = { name: "Tom", age: 18}
    let proxy = new Proxy(target, {
        get(target, propKey, receiver) {
            console.log("getting " + propKey)
            return target[propKey]
        }
    })
    proxy.name
    // getting name
    // Tom
    

    get()方法可以继承:

    let proxy = new Proxy(target, {
        get(target, propKey, receiver) {
            // 通常私有属性前会有下划线_,这里可以实现对私有属性的读取保护
            if(propKey[0] === "_") {
                throw new Erro(`Invalid attempt to get private "${propKey}"`);
            }
            console.log("getting " + propKey)
            return target[propKey]
        }
    })
    // Object.create()用于创建一个新对象obj,新对象obj的原型为传入的proxy对象,这样obj可以集成proxy的get方法。
    let obj = Object.create(proxy)
    obj.name
    // getting name
    

    2.5.2.2 set

    set(target, propKey, value, receiver)
    

    用于拦截target对象上的propKey的赋值操作,如果目标对象自身的某个属性,不可写也不可配置,那么set方法将不起作用:

    let validator = {
        set: function(target, prop, value) {
            if (prop === 'age') {
                if (!Number.isInteger(value)) {
                    throw new TypeError('The age is not an integer');
                }
                if (value > 200) {
                    throw new RangeError('The age seems invalid');
                }
            }
            // 对于满足条件的 age 属性以及其他属性,直接保存
            target[prop] = value;
        }
    };
    let proxy= new Proxy({}, validator)
    proxy.age = 100;
    proxy.age           // 100
    proxy.age = 'oppps' // 报错
    proxy.age = 300     // 报错
    

    第四个参数receiver表示原始操作行为所在的对象,一般是Proxy实例本身:

    const handler = {
        set: function(target, prop, value, receiver) {
            target[prop] = receiver;
        }
    };
    const proxy = new Proxy({}, handler);
    proxy.name = 'Tom';
    proxy.name === proxy // true
     
    const exam = {}
    // Object.setPrototypeOf方法会设置exam的原型为proxy
    Object.setPrototypeOf(exam, proxy)
    exam.name = "Tom"
    exam.name === exam // true
    

    2.5.2.3 apply(target, ctx, args)

    用于拦截函数的调用、call和reply操作,target表示目标对象,ctx表示目标对象上下文,args表示目标对象的参数数组:

    function sub(a, b){
        return a - b;
    }
    let handler = {
        apply: function(target, ctx, args){
            console.log('handle apply');
            return Reflect.apply(...arguments);
        }
    }
    let proxy = new Proxy(sub, handler)
    proxy(2, 1) 
    // handle apply
    // 1
    

    2.5.2.4 has(target, propKey)

    用于拦截hasProperty操作,在判断 target 对象是否存在 propKey 属性时,会被这个方法拦截。此方法不判断一个属性是对象自身的属性,还是继承的属性:

    let  handler = {
        has: function(target, propKey){
            console.log("handle has");
            return propKey in target;
        }
    }
    let exam = {name: "Tom"}
    let proxy = new Proxy(exam, handler)
    'name' in proxy
    // handle has
    // true
    

    这个方法不会拦截for...in循环。

    2.5.3.4 construct(target, args)

    用于拦截new命令,返回值必须为对象:

    let handler = {
        construct: function (target, args, newTarget) {
            console.log('handle construct')
            return Reflect.construct(target, args, newTarget)  
        }
    }
    class Exam { 
        constructor (name) {  
            this.name = name 
        }
    }
    let ExamProxy = new Proxy(Exam, handler)
    let proxyObj = new ExamProxy('Tom')
    console.log(proxyObj)
    // handle construct
    // exam {name: "Tom"}
    

    2.5.3.5 其它拦截操作

    拦截delete操作:

    deleteProperty(target, propKey)
    

    拦截Object.defineProperty方法:

    let handler = {
        defineProperty: function(target, propKey, propDesc){
            console.log("handle defineProperty");
            return true;
        }
    }
    let target = {}
    let proxy = new Proxy(target, handler)
    proxy.name = "Tom"
    // handle defineProperty
    target
    // {name: "Tom"}
     
    // defineProperty 返回值为false,添加属性操作无效
    let handler1 = {
        defineProperty: function(target, propKey, propDesc){
            console.log("handle defineProperty");
            return false;
        }
    }
    let target1 = {}
    let proxy1 = new Proxy(target1, handler1)
    proxy1.name = "Jerry"
    target1
    // {}
    

    erty操作,用于拦截 Object.getOwnPropertyDescriptor() 返回值为属性描述对象或者 undefined 。

    let handler = {
        getOwnPropertyDescriptor: function(target, propKey){
            return Object.getOwnPropertyDescriptor(target, propKey);
        }
    }
    let target = {name: "Tom"}
    let proxy = new Proxy(target, handler)
    Object.getOwnPropertyDescriptor(proxy, 'name')
    // {value: "Tom", writable: true, enumerable: true, configurable: 
    // true}
    

    ptor属性:

    getPrototypeOf(target)
    
    主要用于拦截获取对象原型的操作,包括以下操作:
    - Object.prototype._proto_
    - Object.prototype.isPrototypeOf()
    - Object.getPrototypeOf()
    - Reflect.getPrototypeOf()
    - instanceof
    
    // 示例:
    let exam = {}
    let proxy = new Proxy({},{
        getPrototypeOf: function(target){
            return exam;
        }
    })
    Object.getPrototypeOf(proxy) // {}
    

    拦截Object.isExtensible操作:

    let proxy = new Proxy({},{
        isExtensible:function(target){
            return true;
        }
    })
    // Object.isExtensible() 用于检测指定对象是否可扩展(是否可以添加新的属性)
    Object.isExtensible(proxy) // true
    

    拦截自身属性的读取操作:

    // 比如:
    - Object.getOwnPropertyNames()
    - Object.getOwnPropertySymbols()
    - Object.keys()
    - or...in
    
    let proxy = new Proxy( {
      name: "Tom",
      age: 24
    }, {
        ownKeys(target) {
            return ['name'];
        }
    });
    
    Object.keys(proxy)
    // [ 'name' ] 返回结果中,三类属性会被过滤:
    //          - 目标对象上没有的属性
    //          - 属性名为 Symbol 值的属性
    //          - 不可遍历的属性
    
    
    // 针对于特殊属性
    let target = {
      name: "Tom",
      [Symbol.for('age')]: 24,
    };
    
    // 添加不可遍历属性 'gender'
    Object.defineProperty(target, 'gender', {
      enumerable: false,
      configurable: true,
      writable: true,
      value: 'male'
    });
    
    let handler = {
        ownKeys(target) {
            return ['name', 'parent', Symbol.for('age'), 'gender'];
        }
    };
    
    let proxy = new Proxy(target, handler);
    Object.keys(proxy)
    // ['name']
    

    拦截 Object.preventExtensions 操作:

    // 只有目标对象不可扩展时(即 Object.isExtensible(proxy) 为 false ),
    // proxy.preventExtensions 才能返回 true ,否则会报错
    // Object.preventExtensions用于禁止对象扩展(比如添加新的属性)
    var proxy = new Proxy({}, {
      preventExtensions: function(target) {
        return true;
      }
    });
    // 由于 proxy.preventExtensions 返回 true,此处也会返回 true,因此会报错
    Object.preventExtensions(proxy) 被// TypeError: 'preventExtensions' on proxy: trap returned truish but // the proxy target is extensible
     
    // 解决方案
     var proxy = new Proxy({}, {
      preventExtensions: function(target) {
        // 返回前先调用 Object.preventExtensions
        Object.preventExtensions(target);
        return true;
      }
    });
    Object.preventExtensions(proxy)
    // Proxy {}
    

    拦截 Object.setPrototypeOf 方法:

    // 如果目标对象不可扩展,setPrototypeOf方法不得改变目标对象的原型:
    let proto = {}
    let proxy = new Proxy(function () {}, {
        setPrototypeOf: function(target, proto) {
            console.log("setPrototypeOf");
            return true;
        }
    }
    );
    Object.setPrototypeOf(proxy, proto);
    // setPrototypeOf
    

    返回一个可取消的Proxy实例:

    Proxy.revocable()
    
    let {proxy, revoke} = Proxy.revocable({}, {});
    proxy.name = "Tom";
    revoke();
    proxy.name 
    // TypeError: Cannot perform 'get' on a proxy that has been revoked
    

    2.6 Reflect

    Reflect对象提供了一系列方法,允许开发者通过调用这些方法访问一些JS底层功能。使用Reflect可以实现诸如属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在于对象中等功能。

    主要功能包括:

    • 将Object的一些明显属于语言内部的方法放到Reflect对象上,比如Object.defineProperty
    • 修改某些Object方法的返回结果
    • 让Object操作都变成函数行为,比如将对象的操作符操作封装为函数

    简单来说,就是将Object的方法移植到了Reflect对象上(当前某些方法会同时存在于Object和Reflect对象上)。

    另外,Reflect对象的设计也考虑到了和proxy对象的对应关系,Proxy对象主要用于修改某些操作的默认行为。而Reflect对象上可以找到与proxy对象方法一一对应的方法,不管Proxy怎么修改默认行为,都可以在Reflect上获取默认行为。

    2.6.1 静态方法

    查找并返回 target 对象的属性:

    Reflect.get(target, name, receiver)
    
    let exam = {
        name: "Tom",
        age: 24,
        get info(){
            return this.name + this.age;
        }
    }
    Reflect.get(exam, 'name'); // "Tom"
     
    // 当 target 对象中存在 name 属性的 getter 方法, getter 方法的 this 会绑定 // receiver
    let receiver = {
        name: "Jerry",
        age: 20
    }
    Reflect.get(exam, 'info', receiver); // Jerry20
     
    // 当 name 为不存在于 target 对象的属性时,返回 undefined
    Reflect.get(exam, 'birth'); // undefined
     
    // 当 target 不是对象时,会报错
    Reflect.get(1, 'name'); // TypeError
    

    给对象的属性赋值:

    // 将target的name属性设置为value,返回值为boolean,true表示修改成功,false表示修改失败。
    Reflect.set(target, name, value, receiver)
    
    let exam = {
        name: "Tom",
        age: 24,
        set info(value){
            return this.age = value;
        }
    }
    exam.age; // 24
    Reflect.set(exam, 'age', 25); // true
    exam.age; // 25
     
    // value 为空时会将 name 属性清除
    Reflect.set(exam, 'age', ); // true
    exam.age; // undefined
     
    // 当 target 对象中存在 name 属性 setter 方法时,setter 方法中的 this 会绑定 // receiver , 所以修改的实际上是 receiver 的属性,
    let receiver = {
        age: 18
    }
    Reflect.set(exam, 'info', 1, receiver); // true
    receiver.age; // 1
     
    let receiver1 = {
        name: 'oppps'
    }
    Reflect.set(exam, 'info', 1, receiver1);
    receiver1.age; // 1
    

    判断对象中是否存在某个属性:

    // 它是name in obj指令的函数化,用于查找name属性在obj对象中是否存在
    Reflect.has(obj, name)
    
    let exam = {
        name: "Tom",
        age: 24
    }
    Reflect.has(exam, 'name'); // true
    

    删除对象的某个属性:

    // 是delete obj[property]的函数化,用于删除obj对象的property属性
    Reflect.deleteProperty(obj, property)
    
    let exam = {
        name: "Tom",
        age: 24
    }
    Reflect.deleteProperty(exam , 'name'); // true
    exam // {age: 24} 
    // property 不存在时,也会返回 true
    Reflect.deleteProperty(exam , 'name'); // true
    

    创建对象:

    // 等同于 new target(...args)
    Reflect.construct(obj, args)
    
    function exam(name){
        this.name = name;
    }
    Reflect.construct(exam, ['Tom']); // exam {name: "Tom"}
    

    读取对象的proto属性:

    // 在obj不是对象时,会报错
    Reflect.getPrototypeOf(obj)
    
    class Exam{}
    let obj = new Exam()
    Reflect.getPrototypeOf(obj) === Exam.prototype // true
    

    设置对象的prototype:

    Reflect.setPrototypeOf(obj, newProto)
    
    let obj = {}
    Reflect.setPrototypeOf(obj, Array.prototype); // true
    

    apply.call:

    等同于 Function.prototype.apply.call(func, thisArg, args) 。
    - func 表示目标函数;
    - thisArg 表示目标函数绑定的 this 对象;
    - args 表示目标函数调用时传入的参数列表,可以是数组或类似数组的对象。若目标函数无法调用,会抛出 TypeError 。
    
    Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1]); // 5
    

    为目标对象定义属性:

    let myDate= {}
    Reflect.defineProperty(MyDate, 'now', {
      value: () => Date.now()
    }); // true
     
    const student = {};
    Reflect.defineProperty(student, "name", {value: "Mike"}); // true
    student.name; // "Mike"
    

    得到target对象的propertyKey属性的描述对象:

    Reflect.getOwnPropertyDescriptor(target, propertyKey)
    
    var exam = {}
    Reflect.defineProperty(exam, 'name', {
      value: true,
      enumerable: false,
    })
    Reflect.getOwnPropertyDescriptor(exam, 'name')
    // { configurable: false, enumerable: false, value: true, writable:
    // false}
     
     
    // propertyKey 属性在 target 对象中不存在时,返回 undefined
    Reflect.getOwnPropertyDescriptor(exam, 'age') // undefined
    

    判断对象是否可以扩展:

    Reflect.isExtensible(target)
    

    让target对象不可扩展:

    let exam = {}
    Reflect.preventExtensions(exam) // true
    

    返回target对象的所有属性:

    等同于 Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和
    
    var exam = {
      name: 1,
      [Symbol.for('age')]: 4
    }
    Reflect.ownKeys(exam) // ["name", Symbol(age)]
    

    2.6.2 组合使用

    Reflect对象的方法和Proxy对象的方法是一一对应的。所以Proxy对象的方法可以通过调用Reflect对象的方法获取默认行为,然后进行额外操作:

    let exam = {
        name: "Tom",
        age: 24
    }
    let handler = {
        get: function(target, key){
            console.log("getting "+key);
            return Reflect.get(target,key);
        },
        set: function(target, key, value){
            console.log("setting "+key+" to "+value)
            Reflect.set(target, key, value);
        }
    }
    let proxy = new Proxy(exam, handler)
    proxy.name = "Jerry"
    proxy.name
    // setting name to Jerry
    // getting name
    // "Jerry"
    

    2.6.3 实现观察者模式

    // 定义 Set 集合,防止添加重复的观察者
    const queuedObservers = new Set();
    // 把观察者函数都放入 Set 集合中
    const observe = fn => queuedObservers.add(fn);
    // observable 返回原始对象的代理,拦截赋值操作
    const observable = obj => new Proxy(obj, {set});
    
    // 拦截原始对象obj的set方法,回调给各个观察者
    function set(target, key, value, receiver) {
      // 获取对象的赋值操作
      const result = Reflect.set(target, key, value, receiver);
      // 执行所有观察者
      queuedObservers.forEach(observer => observer());
      // 执行赋值操作
      return result;
    }
    

    2.7 ES6字符串

    判断是否包含字符串:

    - includes():判断是否找到参数字符串。
    - startsWith():判断参数字符串是否在原字符串的头部
    - endsWith():判断参数字符串是否在原字符串的尾部
    
    let string = "apple,banana,orange";
    string.includes("banana");     // true
    string.startsWith("apple");    // true
    string.endsWith("apple");      // false
    string.startsWith("banana",6)  // true
    

    字符串重复:

    repeat():返回新的字符串,表示将字符串重复指定次数返回。
    
    console.log("Hello,".repeat(2));  // "Hello,Hello,"
    

    字符串补全:

    • padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串
    • padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。

    以上两个方法接受两个参数,第一个参数是指定生成的字符串的最小长度,第二个参数是用来补全的字符串。如果没有指定第二个参数,默认用空格填充:

    console.log("h".padStart(5,"o"));  // "ooooh"
    console.log("h".padEnd(5,"o"));    // "hoooo"
    console.log("h".padStart(5));      // "    h"
    

    模板字符串:
    模板字符串相当于加强版的字符串,用反引号 ... 包裹字符串,并且可以在字符串中嵌入变量和表达式:

    // 字符串中插入变量和表达式:
    let name = "Mike";
    let age = 27;
    let info = `My Name is ${name},I am ${age+1} years old next year.`
    console.log(info);
    // My Name is Mike,I am 28 years old next year.
    
    // 字符串中调用函数:
    function f(){
      return "have fun!";
    }
    let string2= `Game start,${f()}`;
    console.log(string2);  // Game start,have fun!
    

    标签模板:
    如果参数是一个模板字符串,可以将函数调用写成标签模板:

    alert`Hello world!`;
    // 等价于
    alert('Hello world!');
    
    如果模板字符串中带有变量,会将模板字符串参数处理成多个参数:
    function f(stringArr,...values){
     let result = "";
     for(let i=0;i<stringArr.length;i++){
      result += stringArr[i];
      if(values[i]){
       result += values[i];
            }
        }
     return result;
    }
    let name = 'Mike';
    let age = 27;
    f`My Name is ${name},I am ${age+1} years old next year.`;
    // "My Name is Mike,I am 28 years old next year."
     
    f`My Name is ${name},I am ${age+1} years old next year.`;
    // 等价于
    f(['My Name is',',I am ',' years old next year.'],'Mike',28);
    

    2.8 数值

    二进制表示法:前缀0b 或者 0B

    console.log(0b11 === 3); // true
    console.log(0B11 === 3); // true
    

    八进制表示法:前缀0o 或者 0O

    console.log(0o11 === 9); // true
    console.log(0O11 === 9); // true
    

    需要了解下安全整数表示在 JavaScript 中能够精确表示的整数,安全整数的范围在 2 的 -53 次方到 2 的 53 次方之间(不包括两个端点),超过这个范围的整数无法精确表示。

    Number.parseInt:

    // 将给定字符串转化为指定进制的整数,默认是 10 进制;
    Number.parseInt('12.34'); // 12 
    Number.parseInt(12.34); // 12
    
    // 指定进制 
    Number.parseInt('0011',2); // 3
    

    除了这些,还对于 Math 对象做了很多扩展,增加了很多方法,可以在使用的时候再去搜索。

    2.9 对象

    2.9.1 关于对象的一些简写

    属性的简洁表示法:ES6允许对象的属性直接写变量,这时候属性名是变量名,属性值是变量值。

    const age = 12;
    const name = "zhangsan";
    const person = {age, name};
    // 等同于
    const person = {age: age, name: name}
    

    方法名简写:

    const person = {
        // 方法简写
        say() {
            console.log("hello");
        }
        // 等同于
        say: function() {
            console.log("hello");
        }
    }
    person.say();
    

    属性名表达式,使用表达式作为属性名,需要用方括号包裹:

    const hello = "Hello"; 
    const obj = { [hello+"2"]:"world" }; 
    obj //{Hello2: "world"}
    

    2.9.2 对象的拓展运算符

    拓展运算符 ... 用于取出参数对下昂所有可遍历属性然后拷贝到当前对象。
    基本用法:

    let person = {name: "Amy", age: 15};
    let someone = { ...person };
    someone;  //{name: "Amy", age: 15}
    

    合并两个对象:

    let age = {age: 15};
    let name = {name: "Amy"};
    let person = {...age, ...name};
    person;  //{age: 15, name: "Amy"}
    

    有可能会出现有相同属性的对象,后面的会覆盖前面的。

    2.9.3 对象的新方法

    Object.assign(target, source_1, ···):

    将源对象的所有可枚举属性复制到目标对象中(浅拷贝):
    let target = {a: 1};
    let object2 = {b: 2};
    let object3 = {c: 3};
    Object.assign(target,object2,object3);  
    // 第一个参数是目标对象,后面的参数是源对象
    target;  // {a: 1, b: 2, c: 3
    

    Object.is(value 1,value2):

    用来比较两个值是否严格相等,和===类似:
    Object.is("q","q");      // true
    Object.is(1,1);          // true
    Object.is([1],[1]);      // false
    Object.is({q:1},{q:1});  // false
    

    和===的区别:

    //一是+0不等于-0
    Object.is(+0,-0);  //false
    +0 === -0  //true
    //二是NaN等于本身
    Object.is(NaN,NaN); //true
    NaN === NaN  //false
    

    2.10 数组

    2.10.1 数组创建

    Array.of(),将参数中所有值作为元素形成数组:

    console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
     
    // 参数值可为不同类型
    console.log(Array.of(1, '2', true)); // [1, '2', true]
     
    // 参数为空时返回空数组
    console.log(Array.of()); // []
    

    Array.from(),将类数组对象或可迭代对象转化为数组:

    Array.from(arrayLike[, mapFn[, thisArg]])
    
    // arrayLike: 类数组对象或可迭代对象
    console.log(Array.from([1, 2, 3])); // [1, 2, 3]
    
    // mapFn: 可选,map 函数,用于对每个元素进行处理,放入数组的是处理后的元素
    console.log(Array.from([1, 2, 3], (n) => n * 2)); // [2, 4, 6]
    
    // thisArg: 可选,用于指定 map 函数执行时的 this 对象
    let map = {
        do: function(n) {
            return n * 2;
        }
    }
    let arrayLike = [1, 2, 3];
    console.log(Array.from(arrayLike, function (n){
        return this.do(n);
    }, map)); // [2, 4, 6]
    

    类数组对象:

    一个类数组对象必须含有 length 属性,且元素属性名必须是数值或者可转换为数值的字符:
    let arr = Array.from({
      0: '1',
      1: '2',
      2: 3,
      length: 3
    });
    console.log(arr); // ['1', '2', 3]
     
    // 没有 length 属性,则返回空数组
    let array = Array.from({
      0: '1',
      1: '2',
      2: 3,
    });
    console.log(array); // []
     
    // 元素属性名不为数值且无法转换为数值,返回长度为 length 元素值为 undefined 的数组  
    let array1 = Array.from({
      a: 1,
      b: 2,
      length: 2
    });
    console.log(array1); // [undefined, undefined]
    

    转换可迭代对象:

    map:
    let map = new Map();
    map.set('key0', 'value0');
    map.set('key1', 'value1');
    console.log(Array.from(map)); // [['key0', 'value0'],['key1',
    // 'value1']]
    
    set:
    let arr = [1, 2, 3];
    let set = new Set(arr);
    console.log(Array.from(set)); // [1, 2, 3]
    
    string:
    let str = 'abc';
    console.log(Array.from(str)); // ["a", "b", "c"]
    

    2.10.2 扩展的方法

    查找:

    // find:查找数组中符合条件的元素,如果有多个符合条件的元素则返回第一个:
    let arr = Array.of(1, 2, 3, 4);
    console.log(arr.find(item => item > 2)); // 3
     
    // 数组空位处理为 undefined
    console.log([, 1].find(n => true)); // undefined
    
    // findIndex:查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引。
    let arr = Array.of(1, 2, 1, 3);
    // 参数1:回调函数
    // 参数2(可选):指定回调函数中的 this 值
    console.log(arr.findIndex(item => item == 2)); // 1
     
    // 数组空位处理为 undefined
    console.log([, 1].findIndex(n => true)); //0
    

    填充:

    // fill:将一定范围内索引的数组元素内容填充为单个指定的值
    let arr = Array.of(1, 2, 3, 4);
    // 参数1:用来填充的值
    // 参数2:被填充的起始索引
    // 参数3(可选):被填充的结束索引,默认为数组末尾
    console.log(arr.fill(0,1,2)); // [1, 0, 3, 4]
    

    判断包含:

    判断数组是否包含指定值:
    // 参数1:包含的指定值
    [1, 2, 3].includes(1);    // true
     
    // 参数2:可选,搜索的起始索引,默认为0
    [1, 2, 3].includes(1, 2); // false
     
    // NaN 的包含判断
    [1, NaN, 3].includes(NaN); // true
    

    嵌套数组转一维数组:

    flat:
    console.log([1 ,[2, 3]].flat()); // [1, 2, 3]
     
    // 指定转换的嵌套层数
    console.log([1, [2, [3, [4, 5]]]].flat(2)); // [1, 2, 3, [4, 5]]
     
    // 不管嵌套多少层
    console.log([1, [2, [3, [4, 5]]]].flat(Infinity)); // [1, 2, 3, 4, 5]
     
    // 自动跳过空位
    console.log([1, [2, , 3]].flat());<p> // [1, 2, 3]
    
    
    flatMap:
    // 参数1:遍历函数,该遍历函数可接受3个参数:当前元素、当前元素索引、原数组
    // 参数2:指定遍历函数中 this 的指向
    console.log([1, 2, 3].flatMap(n => [n * 2])); // [2, 4, 6]
    

    2.11 函数

    支持默认参数:

    // 需要注意:只有在未传递参数,或者参数为 undefined时,才会使用默认参数,null 值被认为是有效的值传递:
    function fn(name,age=17){
     console.log(name+","+age);
    }
    fn("Amy",18);  // Amy,18
    fn("Amy","");  // Amy,
    fn("Amy");     // Amy,17
    

    不定参数:

    // 支持不确定数量的参数,比如...变量名,只能放在参数组的最后一个
    
    function f(...values){
        console.log(values.length);
    }
    f(1,2);      //2
    f(1,2,3,4);  //4
    

    箭头函数:

    基本用法:
    var f = v => v;
    //等价于
    var f = function(a){
     return a;
    }
    f(1);  //1
    
    当箭头函数没有参数或者有多个参数,要用 () 括起来:
    var f = (a,b) => a+b;
    f(6,2);  //8
    
    当箭头函数要返回对象的时候,为了区分于代码块,要用()将对象包裹起来:
    // 报错
    var f = (id,name) => {id: id, name: name};
    f(6,2);  // SyntaxError: Unexpected token :
     
    // 不报错
    var f = (id,name) => ({id: id, name: name});
    f(6,2);  // {id: 6, name: 2}
    

    需要注意箭头函数不会更改 this 指向,箭头函数中的 this 对象,是定义函数时的对象,而不是使用函数时的对象:

    function fn(){
      setTimeout(()=>{
        // 定义时,this 绑定的是 fn 中的 this 对象
        console.log(this.a);
      },0)
    }
    var a = 20;
    // fn 的 this 对象为 {a: 18}
    fn.call({a: 18});  // 18
    

    适合的场景:

    在之前使用 uni 开发的时候,由于不知道箭头函数,所以经常会用 var self = this 这样的代码,为了将外部的 this 传入到回调函数中,现在可以直接用 this 了
    
    // 回调函数
    var Person = {
        'age': 18,
        'sayHello': function () {
          setTimeout(function () {
            // 这里的 this 指向 window
            console.log(this.age);
          });
        }
    };
    var age = 20;
    Person.sayHello();  // 20
     
    var Person1 = {
        'age': 18,
        'sayHello': function () {
          setTimeout(()=>{
            // 这里的 this 指向 person1
            console.log(this.age);
          });
        }
    };
    var age = 20;
    Person1.sayHello();  // 18
    

    所以,当我们需要维护一个 this 上下文的时候,就可以使用箭头函数。

    2.12 Class 类

    class 的本质是 function,可以看作是一个语法糖,让对象原型的写法更加清晰、更加面向对象。

    class Example {
    
        // 静态属性,class 本身的属性,可以通过 Example.a调用;
        static b = 2;
        
        // 实例属性,定义在示例 this 上的属性
        a = 1; 
    
        // 构造方法,创建类的实例化对象时调用
        constructor(a) {
            this.a = a;
        }
        
        // getter不能单独出现,需要和 getter 同级出现
        get a(){
            console.log('getter');
            return this._a;
        }
        
        set a(a){
            console.log('setter');
            this._a = a; // 自身递归调用
        }
    }
    
    // 公共属性
    Example.prototype.c = 2;
    
    // 类的实例化
    let example = new Example()
    

    类的继承:

    通过 extends 实现类的继承:
    class Father {
        constructor() {
        
        }
        
        test() {
            return 0;
        }
        
        static test1() {
            return 1;
        }
    }
    
    class Child extends Father {
        constructor(a) {
            // 子类构造方法中必须调用 super(),并且要在 this 之前;
            super();
            this.a = a;
            console.log(super.test()); // 0
        }
        
        static test3() {
            return super.test1 + 2; // 3
        }
    }
    
    

    不能继承常规对象:

    var Father = {
        // ...
    }
    class Child extends Father {
         // ...
    }
    // Uncaught TypeError: Class extends value #<Object> is not a constructor or null
     
    // 解决方案
    Object.setPrototypeOf(Child.prototype, Father);
    

    2.13 模块

    ES6中引入了模块化,设计思想是在编译的时候就能确定模块的依赖关系,以及输入和输出的变量。分为导出 export 和导入 import 两个模块。

    特点:

    • 可以导出和导入各种类型的变量:函数,对象,字符串等
    • 每个模块内声明的变量都是局部变量,不会污染全局作用域
    • 每个模块只加载一次,会进行缓存

    2.13.1 基础使用

    // export test.js
    
    let name = "tom"
    let age = 20
    let fn = function() {
        return `name:${name},age:${age}`
    }
    let customClass = class CustomClass {
        static a = "aaa";
    }
    // 建议使用大括号指定输出的一组变量,写在最底部
    export {name, age, fn, customClass}
    
    // import xxx.js
    import {name, age, fn, customClass} from "./test.js";
    ...可以直接使用 name age fn customClass 了。
    
    // 使用 as
    import {myName as name} from "./test.js"
    
    

    import命令的特点:

    • 只读属性,不能修改导出的引用指向,但是可以修改变量类型为对象的属性值;
    • 单例模式:多次重复执行同一 import 语句,只会执行一次;
    • 导入时不能使用表达式和变量;
    // 只读属性
    import {a} from "./xxx.js"
    a = {}; // error
     
    import {a} from "./xxx.js"
    a.foo = "hello"; // a = { foo : 'hello' }
    
    // 单例模式:
    import { a } "./xxx.js";
    import { a } "./xxx.js";
    // 相当于 import { a } "./xxx.js";
     
    import { a } from "./xxx.js";
    import { b } from "./xxx.js";
    // 相当于 import { a, b } from "./xxx.js";
    
    //静态执行
    import { "f" + "oo" } from "methods";
    // error
    
    let module = "methods";
    import { foo } from module;
    // error
    
    if (true) {
      import { foo } from "method1";
    } else {
      import { foo } from "method2";
    }
    // error
    

    export default:

    • 在一个模块或者文件中,export default 只能有一个
    • export default 中的 default 是对应的导出接口变量
    • 通过export 方式导出,在导入时需要加 {},export default则不需要
    • export default 向外暴露的成员,可以使用任意变量来接收
    var a = "My name is Tom!";
    export default a; // 仅有一个
    export default var c = "error"; 
    // error,default 已经是对应的导出变量,不能跟着变量声明语句
     
    import b from "./xxx.js"; // 不需要加{}, 使用任意变量接收
    

    3. Babel

    Babel是一个用于web开发的自由开源的JavaScript编译器和转换器。它的主要作用是在当前和较旧的浏览器或环境中,将使用ECMAScript 2015+(也称为ES6+)或更高版本编写的代码转换为向后兼容的JavaScript版本。

    Babel使得软件开发者能够使用他们偏好的编程语言或风格来编写源代码,然后利用Babel将其转换为所有现代浏览器都能理解的JavaScript。这极大地提高了开发者的编程效率和代码的可读性,同时也解决了浏览器兼容性问题。

    相关文章

      网友评论

          本文标题:[JavaScript] 记录下我学习ES6的一些总结

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