【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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

karshey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值