• 【JavaScript高级】ES6常见新特性:词法环境、let、const、模板字符串、函数增强、Symbol、Set、Map


    词法环境

    可以看这里:1. 彻底搞懂javascript-词法环境(Lexical Environments)词法环境是什么?js 图解变量环境、词法环境、执行上下文,作用域链查找过程

    • 一个词法环境由环境记录(Environment Record)和一个外部词法环境(outer Lexical Environment)组成
    • 用于关联一个函数声明、代码块语句、try-catch语句,当它们的代码被执行时,词法环境被创建出来

    在这里插入图片描述

    执行上下文一般会关联两个词法环境:

    1. 词法环境组件:用于处理letconst声明的标识符
    2. 变量环境组件:用于处理varfunction声明的标识符

    两种环境记录值:声明式环境记录和对象环境记录。

    let、const

    基本使用

    let:用于声明一个变量。
    const:

    • 保存的数据一旦被赋值,不能修改
      -若赋值的是引用类型,可以通过引用找到对应对象,修改对象内容

    let、const在同一个作用域里面, 不允许重复声明变量。

    作用域提升

    var声明的变量会作用域提升:在声明之前可以访问, 只不过值是undefined

    console.log(foo); // undefined
    var foo = "foo";
    '
    运行

    而let、const声明的变量,在声明之前访问会报错:let、const定义的变量不是当代码执行到那一行才创建出来的, 在这之前就已经创建出来, 只是不能访问。

    省流:let、const没有作用域提升,但是会在解析阶段被创建出来。只是创建出来后、代码执行到那一行之前不能访问。

    暂时性死区

    已知在let、const定义的标识符在执行到声明代码前是不能被访问的。
    从块作用域的顶部一直到变量声明完成之前,这个变量处在的区域就是暂时性死区(TDZ,temporal dead zone)。

    暂时性死区与定义的位置无关, 与代码的执行顺序有关:若与定义的位置有关,则这里应该是undefined。

    function foo(){
        console.log(address);
    }
    
    let address="address"
    foo()//address
    '
    运行

    暂时性死区形成后, 在该区域内这个标识符不能访问

    如下面代码中, foo作用域下会优先访问自己作用域下的address, 而foo作用域中, 执行到第7行代码之前, 已经形成暂时性死区, 所以不能访问, 就会报错。

    let address="window address"
    function foo(){
        console.log(address);
        let address="foo address"
    }
    
    foo()
    

    不添加到window

    已知:在全局通过var来声明一个变量,事实上会在window上添加一个属性。
    但是:let、const是不会给window上添加任何属性的。

    let message="message"
    const address="address"
    
    console.log(window.message);//undefined
    console.log(window.address);//undefined
    

    块级作用域

    ES6之前,JavaScript只会形成两个作用域: 全局作用域和函数作用域。在ES6中新增了块级作用域,并且通过letconstfunctionclass声明的标识符是具备块级作用域的限制的:

    • let
    {
        let message="let"
    }
    console.log(message);//Uncaught ReferenceError: message is not defined
    
    • const
    {
        const message="const"
    }
    console.log(message);//Uncaught ReferenceError: message is not defined
    
    • class
    {
        class Person{}
    }
    
    var p=new Person()//Uncaught ReferenceError: Person is not defined
    

    但是:函数拥有块级作用域,外面依然可以访问的——JS引擎会对函数的声明进行特殊的处理,允许像var那样进行提升

    {          
        function foo(){
            console.log("foo");
        }
    }
    
    foo()//foo
    '
    运行

    var/let/const用哪个

    var:

    • 省流:别用
    • var很多特殊性如:作用域提升、window全局对象、没有块级作用域等,是历史遗留问题,是语言缺陷
    • 工作中用最新的规范来写,别用var

    let vs const:

    • 优先推荐const,可以保证数据的安全性不会被随意的篡改
    • 用let:当我们明确知道一个变量后续会需要被重新赋值时

    模板字符串

    ES6允许我们使用字符串模板来嵌入JS的变量或者表达式来进行拼接:

    • `` 符号来编写字符串,称之为模板字符串
    • 通过 ${expression} 来嵌入动态的内容

    动态嵌入内容:

    const name="kaisa"
    const age=18
    
    console.log(`name is ${name},age is ${age}`);//name is kaisa,age is 18
    '
    运行

    拼接表达式:

    const age=18
    
    console.log(`我是成年人吗?${age>=18?'是':'不是'}`);//我是成年人吗?是
    '
    运行

    接收函数的返回值:

    function foo() {
        return "foo";
    }
    
    console.log(`${foo()}`); //foo
    '
    运行

    标签模板字符串

    如果我们使用标签模板字符串调用函数 ,并且在调用的时候插入其他的变量:

    • 模板字符串被拆分了
    • 第一个元素是数组,是被模块字符串拆分的字符串组合
    • 后面的元素是一个个模块字符串传入的内容变量
    const name="kaisa"
    const age=18
    
    function foo(...args){
        console.log(args);
    }
    
    foo`name is ${name},age is ${age}`
    //[Array(3), 'kaisa', 18]
    //Array(3):['name is ', ',age is ', '']
    '
    运行

    ES6函数增强

    函数的默认参数

    // x的默认值为20 y的默认值为30
    function foo(x = 20, y = 30) {
      console.log(x, y);
    }
    
    foo(); // 20 30
    foo(10, 50); // 10 50
    '
    运行

    注意:

    • 有默认值的参数, 我们通常将其放到参数最后(JS中可以不放最后,但很多其他语言不放最后会报错)
    • 若有剩余参数,默认参数要放到剩余参数前
    • 默认值会改变函数的length的个数:有默认值的参数, 以及后面的参数都不计算在length之内
    • 可以和解构一起使用:
    function foo({name,age}={name:"kaisa",age:18}){
        console.log(name,age);
    }
    
    foo()//kaisa 18
    '
    运行
    function foo({name="kaisa",age=18}={}){
        console.log(name,age);
    }
    
    foo()//kaisa 18
    '
    运行

    箭头函数补充

    箭头函数没有显式原型prototype,所以不能作为构造函数,使用new来创建对象。

    如下:

    • 箭头函数隐式原型指向Function的显式原型
    • 箭头函数没有显式原型
    const foo=()=>{}
    
    console.log(foo.__proto__===Function.prototype);//true
    console.log(foo.prototype)//undefined
    '
    运行

    展开语法

    展开运算符其实是一种浅拷贝

    函数调用时的使用:

    const names=['a','b','c','d']
    function foo(arg,...args){
       console.log(arg,args);
    }
    
    foo(...names);//a (3) ['b', 'c', 'd']
    '
    运行
    const str="hello"
    function foo(arg,...args){
        console.log(args);
    }
    
    foo(...str);//(4) ['e', 'l', 'l', 'o']
    '
    运行

    数组构造时使用:

    const names = ["aaa", "bbb", "ccc"];
    const newNames = [...names, "ddd", "eee"]
    console.log(newNames) // ['aaa', 'bbb', 'ccc', 'ddd', 'eee']
    '
    运行

    构建对象字面量时使用:

    const obj = {
      name: "kaisa",
      age: 18
    }
    
    const info = {
      ...obj,
      height: 1.88,
      address: "成都市"
    }
    
    console.log(info) // {name: 'kaisa', age: 18, height: 1.88, address: '成都市'}
    '
    运行

    二进制、八进制、十六进制

    • 二:b
    • 八:o
    • 十六:x
    // 十进制
    const num1 = 100
    // 二进制
    const num2 = 0b100
    // 八进制
    const num3 = 0o100
    // 十六进制
    const num4 = 0x100
    '
    运行

    数字过长时,可以使用_作为连接符:

    const num5 = 100_000_000
    '
    运行

    Symbol数据类型

    是基本数据类型,翻译为符号

    为什么要有Symbol:防止造成属性名的冲突。如:有一个对象,我们希望在其中添加一个新的属性和值,但我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性。Symbol的出现,就是为了防止这种冲突的。

    • Symbol值通过Symbol函数生成,生成后可以作为属性名
    • ES6中对象的属性名可以使用字符串,也可以使用Symbol
    const s1=Symbol()
    
    const obj={
        [s1]:'a'
    };
    
    console.log(obj);//{Symbol(): 'a'}
    '
    运行

    Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的。

    const s1=Symbol()
    const s2=Symbol()
    
    const obj={
        [s1]:'a',
        [s2]:'b'
    };
    
    console.log(obj);//{Symbol(): 'a', Symbol(): 'b'}
    console.log(s1===s2);//false
    '
    运行

    作为属性名

    我们通常会使用Symbol在对象中表示唯一的属性名。我们有以下几种写法。

    1.属性名赋值

    const s1 = Symbol();
    const s2 = Symbol();
    const obj = {};
    
    obj[s1] = "aaa";
    obj[s2] = "bbb";
    '
    运行

    2.定义字面量直接使用

    const s1 = Symbol();
    const s2 = Symbol();
    const obj = {
      [s1]: "aaa",
      [s2]: "bbb"
    };
    '
    运行

    3.Object.defineProperty

    const s1 = Symbol();
    const s2 = Symbol();
    const obj = {};
    
    Object.defineProperty(obj, s1, {
      value: "aaa",
    });
    
    Object.defineProperty(obj, s2, {
      value: "bbb",
    });
    '
    运行

    获取Symbol对应的key

    我们获取对象的key的方法, 无法获取Symbol的key:

    const s1 = Symbol();
    const s2 = Symbol();
    const obj = {
      name: "kaisa",
      age: 18,
      [s1]: "aaa",
      [s2]: "bbb",
    };
    console.log(Object.keys(obj)); // ['name', 'age']
    '
    运行

    不过,可以通过Object.getOwnPropertySymbols()方法:

    const s1 = Symbol();
    const s2 = Symbol();
    const obj = {
      name: "kaisa",
      age: 18,
      [s1]: "aaa",
      [s2]: "bbb",
    };
    console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(), Symbol()]
    '
    运行

    相同值的Symbol

    ES10新增了特性:description

    const s1=Symbol('des')
    console.log(s1.description);//des
    '
    运行

    创建相同的Symbol的方法:Symbol.for(key)

    const s1=Symbol.for('a')
    const s2=Symbol.for('a')
    console.log(s1===s2);//true
    '
    运行

    获取对应的key:Symbol.keyFor

    console.log(Symbol.keyFor(s1));//a
    

    Set/Map数据结构

    ES6中新增了两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap。

    Set

    可以用来保存数据,类似于数组,但是元素不能重复。

    创建Set我们需要通过Set 构造函数

    const set = new Set();
    console.log(set); // Set(0) {size: 0}
    '
    运行

    Set中存放的元素是不会重复的,所以可以数组去重:

    const set=new Set();
    console.log(set);//Set(0) {size: 0}
    
    set.add(1)
    set.add(1)
    set.add(1)
    set.add(2)
    console.log(set);//Set(2) {1, 2}
    '
    运行

    常见属性和方法

    属性:

    • size:返回Set中元素的个数

    方法:

    • add(value):添加某个value,返回Set对象本身
    • delete(value):从set中删除和value值相等的元素,返回boolean类型
    • has(value):判断set中是否存在某个元素,返回boolean类型
    • clear():清空set中所有的元素,没有返回值
    • forEach(callback, [, thisArg]):通过forEach遍历set
    const set=new Set();
    
    set.add(1)
    set.add(10)
    set.add(100)
    
    set.forEach((item)=>{
       console.log(item+1);//2 11 101
    })
    '
    运行
    • set也支持for…of:
    set.add(1)
    set.add(10)
    set.add(100)
    
    for(item of set){
       console.log(item+1);//2 11 101
    }
    

    WeakSet使用(了解)

    与Set的区别:

    • WeakSet中只能存放对象类型,不能存放基本数据类型
    • WeakSet对元素的引用是弱引用,如果没有其他的对某个对象进行引用,那么GC可以对该对象进行回收

    WeakSet常见的方法:

    • add(value):添加某个元素,返回WeakSet对象本身;
    • delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型;
    • has(value):判断WeakSet中是否存在某个元素,返回boolean类型;

    当我们不想通过非构造方法创建出来的对象来调用类方法时,可以使用weakSet:

    使用前:

    class Person {
    eating() {
     console.log(this, "在eating~")
    }
    }
    
    const p = new Person()
    
    p.eating()//Person {} 在eating~
    p.eating.call({ name: "kaisa" }) //{ name: 'kaisa' } 在eating~
    
    
    const newEating = p.eating.bind("aaa")
    newEating() //aaa 在eating~
    '
    运行

    使用后:

    const personSet = new WeakSet()
    class Person {
     constructor(){
         personSet.add(this)
     }
      eating() {
          if(!personSet.has(this)){
              throw new Error("不能通过非构造方法创建出来的对象调用running方法")
          }
        console.log(this, "在eating~")
      }
    }
    '
    运行

    Map

    Map,用于存储映射关系。对象也可以存储映射关系,它们的区别是:

    • 对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key)
    • 某些情况下我们可能希望通过其他类型作为key,比如对象,这个时候会自动将对象转成字符串来作为key
    • Map可以使用对象类型作为Key

    常见属性与方法

    属性:

    • size:返回Map中元素的个数

    方法:

    • set(key, value):在Map中添加或者设置key、value,并且返回整个Map对象
    • get(key):根据key获取Map中的value
    • has(key):判断是否包括某一个key,返回Boolean类型
    • delete(key):根据key删除一个键值对,返回Boolean类型
    • clear():清空所有的元素
    • forEach(callback, [, thisArg]):通过forEach遍历Map, 获取的是对应的value
    const key1={name:"name1"};
    const key2={age:"18"}
    
    const map=new Map()
    map.set(key1,"a")
    map.set(key2,"asd")
    
    map.forEach(item=>{
    	console.log(item);//a asd
    })
    '
    运行
    • Map也可以通过for…of进行遍历, for…of遍历, 直接拿到的是将key和value组成的数组
    const key1={name:"name1"};
    const key2={age:"18"}
    
    const map=new Map()
    map.set(key1,"a")
    map.set(key2,"asd")
    
    for(item of map){
    	console.log(item);//[{…}, 'a']   [{…}, 'asd']
    }
    '
    运行
    • 如果想通过for…of拿到分开的key和value, 要解构
    const key1={name:"name1"};
    const key2={age:"18"}
    
    const map=new Map()
    map.set(key1,"a")
    map.set(key2,"asd")
    
    for(item of map){
    	const[key,value]=item
    	console.log(key);//{name: 'name1'}   {age: '18'}
    }
    '
    运行

    WeakMap使用

    与Map的区别:

    • WeakMap的key只能使用对象
    • WeakMap的key对 对象 的引用是弱引用

    常见方法:

    • set(key, value):在Map中添加key、value,并且返回整个Map对象
    • get(key):根据key获取Map中的value
    • has(key):判断是否包括某一个key,返回Boolean类型
    • delete(key):根据key删除一个键值对,返回Boolean类型

    应用:

    • 不能遍历
    • 没有forEach方法,也不支持通过for…of的方式进行遍历

    参考

    coderwhy的课
    ES6-ES12部分简单知识点总结,希望对大家有用~
    ES6常见的新特性(超详细)、let/const、模板字符串、ES6函数增强、Symbol数据类型、set/map数据结构详细介绍
    1. 彻底搞懂javascript-词法环境(Lexical Environments)
    词法环境是什么?
    js 图解变量环境、词法环境、执行上下文,作用域链查找过程
    ES6-12

  • 相关阅读:
    3D打印服务展示预约小程序的效果如何
    一个.NET开发的开源跨平台二维码生成库
    Linux:169.254.0.0/24路由的来龙去脉
    USACO21FEB Modern Art 3 G
    【leetcode】按奇偶排序数组 c++
    逐点流行正则化方法及其在半监督学习上的应用
    ExtraTrees之ExtraTreesRegressor参数详解以及调参
    idea中VM options的设置 (分配内存)
    女孩起名字:诗经中惊艳的女孩名字
    ElasticSearch-head前端安装以及连接ES基本步骤(linux)
  • 原文地址:https://blog.csdn.net/karshey/article/details/127094785