JavaScript高级ES6常见新特性:词法环境letconst模板字符串函数增强SymbolSetMap

Posted karshey

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript高级ES6常见新特性:词法环境letconst模板字符串函数增强SymbolSetMap相关的知识,希望对你有一定的参考价值。

文章目录

词法环境

可以看这里: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、

以上是关于JavaScript高级ES6常见新特性:词法环境letconst模板字符串函数增强SymbolSetMap的主要内容,如果未能解决你的问题,请参考以下文章

总结常见的ES6新语法特性。

总结常见的ES6新语法特性

常见ES6新属性

ES6新特性:Function函数扩展, 扩展到看不懂

ES6~ES13

ES6新特性:Javascript中Generator(生成器)