重学JavaScript高级(八):ES6-ES12新增特性学习

发布时间:2024年01月10日

ES6-ES12新增特性学习

ES6–对象字面量增强

  • 属性的简写
  • 方法的简写
  • 计算属性名
let name = "zhangcheng"
//我想让sum作为obj的key值
let objKey = "sum"
let obj = {
    //属性名的简写
    name//等同于name:name
    
    //方法的简写
    running(){}//等同于running:function(){}

	//计算属性名
	[objKey]:456//打印出来就是sum:456
}

ES6–数组以及对象的解构

ES6中引入了解构的用法

  • 数组解构的基本用法、顺序问题、解构出数组,解构默认值
//不要在意变量名字是否重复

let arr = ["abc","asd","ewf"]
//基本用法
let [name1,name2,name3] = arr//就会按照顺序打印出来
//顺序问题:数组解构有严格的顺序问题
let [name1,,name3] = arr //"abc","ewf",只想获取间隔的位置时候,中间需要空出来
//解构出数组
let [name,...newName] = arr //"abc",[asd,"ewf"],用...接收剩余的值,就会装入数组中
//解构默认值
let [name1 = "defaults",name2] = arr//若arr[0]的值为undefined时候,name1就是defaults,否则就是arr[0]的值
  • 对象解构的基本用法、顺序问题、对变量重命名、默认值、…接收剩余参数
let obj = {
  name: "zhangcheng",
  age: 18,
  height: 188,
};

//基本用法
let { name, age, height } = obj;
console.log(name, age, height);

//顺序问题:对象的解构是没有顺序问题的,是根据对象的key进行解构的
let { age, name, height } = obj;
console.log(name, age, height);

//对变量进行重命名 原本的key:重命名的变量名
let { age: newAge, name: newName } = obj;
console.log(newAge, newName);

//默认值:若该属性为undefined,则使用默认值
let { age, name, address = "默认值" } = obj;
console.log(age, name, address);

//利用...接收剩余参数:会将剩余参数放到对象中去
let { name, ...arg } = obj;
console.log(name, arg);

  • 我们了解了常规语法,用法主要是在一下方面
//在sum这里,我们就可以用解构接收参数
function sum({a,b}){}
sum({a:100,b:200})

//或者接收返回值的时候,用解构
let {data} = await request()

新的ECMA代码执行描述

在先前的文章 重学JavaScript高级(三):深入JavaScript运行原理中提到过,只不过是ES5的执行描述

ES5中ECMA文档中的术语

  • 执行上下文栈(ECS):用于执行上下文的栈结构
  • 执行上下文(EC):代码执行之前会先创建对应的执行上下文
  • 变量对象(VO):执行上下文关联的VO对象,用于记录函数和变量声明
  • 全局对象(GO):全局执行上下文关联的VO
  • **激活对象(AO):**函数执行上下文关联的VO
  • 作用域链(scope chain):作用域链,用于关联指向上下文的变量查找

ES6之后的术语

  • 在ES6中,有些执行描述进行了改变,但是思路是一样的执行上下文和执行上下文栈没有变化

  • 词法环境(Lexical Environments)—由执行上下文关联,相当于替代了VO对象

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

    image.png

    • 一般一个执行上下文会关联两个词法环境词法环境组件(let const声明的)变量环境组件(var 声明的)
    • 词法环境组件(let const声明的) :执行代码时候,变量会被创建,但是在赋值之前,是没有办法通过任何方式访问的

    image.png

    • 变量环境组件(var 声明的):在执行代码的时候,变量会被创建,同时会给一个默认值:undefined,可以在赋值之前访问

    image.png

  • 环境记录(Environment Record)

    • 声明式环境记录(函数声明、变量声明等)对象式环境记录(with语句)
    • 环境记录不仅会记录声明,还会记录let等声明的变量是否赋值了,若没有赋值则不让访问,若赋值了才可以访问

image.png

  • 大致的流程如下

image.png

ES6–let/const的基本使用

  • ES6新增了 let/const声明变量的方式
  • let关键字:用法与var没有区别
  • const关键字
    • 表示常量、衡量的意思
    • 被声明的变量,不能被更改
    • 但是被赋值的是 引用类型(对象、数组、函数),可以改变引用类型内部的东西
const obj = {
    a:100
}
obj = {}//这样是不被允许的
obj.a = 200//这样是可以的
  • 注意事项
    • 变量不能被重复声明(var可以

let/const作用域提升—没有作用域提升

  • var一定会作用域提升:语言设计缺陷
//可以在定义之前访问,就是作用域提升
console.log(message)
var message = "zhangcheng"
  • let const没有作用域提升,但是会提前被创建,只是不能再赋值之前访问
    • 通过上面对 词法环境组件定义的理解:可以看出被let 和 const创建的变量,会随着词法环境的创建而创建,只是没有办法再赋值之前提前访问
    • 由于对 作用域提升没有一个官方的解释,所以这个再好好研究一下:作用域提升指的是提前创建还是提前访问?、
  • 暂时性死区(TDZ)
    • 指的就是被 let/const声明的变量,在真正赋值之前,上面的区域称为暂时性死区
    • 且暂时性死区和 变量定义位置无关,与代码执行的时机有关
function foo(){
    console.log(123)
    console.log(4456)    
    //以上就是暂时性死区
    let a = 100
    //虽然在这里提前访问b,但是代码执行却是在给b赋值之后
    console.log(b)
}
let b = 200
foo()

不会添加window

  • var声明的变量,会添加到window上面
  • 但是let/const不会添加到window

image.png

  • 通过上面的解释,我们就可以了解,let/const定义的变量被添加到了什么地方
    • 对于全局的环境记录实际上是由两部分组成的复合环境记录:**对象环境记录(Object Environment Record)**和 声明性环境记录(declarative Environment Record)
    • 对象环境记录(Object Environment Record)就是我们常用的window,里面存放着 var定义的变量等
    • 声明性环境记录(declarative Environment Record)存放着let/const声明的变量

let/const块级作用域

  • 块指的就是代码块

  • 在ES5之前只有 全局作用域以及函数作用域

  • 但是在ES6之后,let /const/class/function都会形成块级作用域

    • function函数需要特别注意一下,这是因为浏览器做的特殊处理,早期的工具文件有的函数写在了代码块中,需要能够正常访问
foo()//这样是没办法访问的
{
    var message = "hello";
    let a = "123"//const/class同理
    //但是函数不同
    function foo(){
        consloe.log(456)
    }
}
foo()//可以在块级作用域外访问(需要在定义之后)

console.log(a)//不能访问,因为形成了块级作用域
console.log(message)//可以访问var定义的,因为var没有形成块级作用域

let/const块级作用域的应用

  • 形成词法环境,但是不会生成新的执行上下文
var message = "hello"
let address = "河北"

{
    var height = 1.88
    let title = "学生"
}
//在执行代码块的时候,会形成新的词法环境,相对应的环境记录中会存放let声明的变量

image.png

  • 监听点击的按钮
//现在有多个按钮
//先前使用var定义的i,没有形成块级作用域,所以在打印i的时候,都是全局作用域中的
//我们可以换成let,会形成块级作用域,在每次执行的时候,都会生成新的词法环境,同时会有对应的环境记录,保存每次i的值
//for循环多少次,就相当于创建了多少个词法环境,每个环境记录都记录着不同的i和不同的function
const btnEls = document.querySelectorAll("button")
for(var i = 0;i < btnEls.length; i++){
    let btnEl = btnEls[i]
    btnEl.onclick = function(){
        console.log(`点击了第${i}个按钮`)
    }
}

var-let-const的选择

  • 我们需要明白,var所有表现出来的特殊性:比如作用域提升,window全局对象等都是历史遗留问题
  • 是语言设计的缺陷,同时面试的时候会被提问,来查看是否掌握了底层原理
  • 在实际工作中不会再使用var,优先使用const,当确定该变量会改变的时候,使用let

ES6–模板字符串

基本用法

let a = `aaa`//模板字符串
let b = `my functon ${foo()}`//${}中包含的是js表达式、变量等

标签模板字符串(在React框架中会用到 CSS in js)

  • 使用模板字符串调用函数
  • 里面的标签会当作参数传入函数中
  • 字符串部分会进行分割
let name = "zhangcheng";
let age = 18;

function foo(...arg) {
  console.log(arg);
}

foo`my name is ${name},i am ${age}`;//[ [ 'my name is ', ',i am ', '' ], 'zhangcheng', 18 ]

ES6–函数增强用法

默认参数用法

function foo(name = "zhangcheng ") {}

解构和默认参数搭配

  • 解构赋值 = 形参(等号右边是形参左边是对形参的解构
function foo({ name = "zhangcheng" } = {}) {
  console.log(name);
}

let obj = {
  name: "zhangsan",
};
foo(obj);

注意事项

  • 没有传入参数或者传入的参数为undefined的时候,会使用默认参数,null值不会使用默认参数
  • 默认参数,以及 默认参数之后的参数,不会统计在函数的length中(foo.length)
  • 因此书写顺序为:正常参数,默认参数,剩余参数
function foo(name,age = 18,...args){
    
}

箭头函数的额外强调

  • 箭头函数没有 显式原型prototype,代表不能 new创建对象
  • 不绑定 this、arguments、super
let bar = ()=>{
    
}
new bar()//是错误的

展开语法的再学习(浅拷贝)

  • 展开语法是ES6提出来的,最开始只能对 数组以及字符串进行展开,后来可以对 字面量创建对象的时候进行展开
let arr = [1,2,3,4]
let str = "hello"
let obj = {
    a:100
}

function foo(...arg){
    console.log(arg)
}

//对数组进行展开
let arr1 = [...arr,5,6]
//对字符串进行展开
foo(...str)

//创建字面量时候,将对象展开
let obj2 = {
    ...obj,
    c:200
}

ES6数值的表示

console.log(100)
console.log(0b100)//2进制
console.log(0o100)//8进制
console.log(0x100)//16进制
  • 长数字的表示(ES2021)
let money = 1_0000//一万

Symbol的基本使用

  • 是一个基本数据类型,翻译为类型
  • 在ES6之前,对象的属性名都是字符串形式的,很容易造成 命名冲突
    • 比如之前我有一个对象,将这个对象传入了一个函数中,函数内部对参数的某个属性做了修改刚好是这个对象中的属性,那么就会产生bug
  • 因此就产生了Symbol,专门生成一个独一无二的值:通过 Symbol函数创造的
let s1 = Symbol();
let obj = {
  [s1]: 100,
};
console.log(obj[s1]);

额外补充

  • 获取Symbol的key
let s1 = Symbol();
let s2 = Symbol();
let obj = {
  [s1]: 100,
  [s2]: 200,
  a: 100,
};
console.log(Object.keys(obj)); //只能获取普通的key
console.log(Object.getOwnPropertySymbols(obj)); //只能获取Symbol的key
  • 给Symbol添加description
let s1 = Symbol("aaa")//添加一个description
s1.description//aaa获取
  • 创建两个或者多个相同的Symbol
let s1 = Symbol.for("sss")
let s2 = Symbol.for("sss")
s1 == s2

Set-Map数据结构

Set的基本使用

  • 是一种数据结构,类似于数组,但是和数组的区别就是里面不能有重复的元素
let arr = [1, 2, 1, 2, 3];
let newArrSet = new Set(arr);
console.log(newArrSet);//[1,2,3]
  • 经常用于数组的去重
let arr = [1, 2, 1, 2, 3];
let newArrSet = new Set(arr);
let newArr = Array.from(newArrSet)
  • 其他用法:增删改查,批量清空
//属性size:返回Set中元素的个数
console.log(newArrSet.size);

//常用方法
//add(value)添加某个元素,返回Set本身
newArrSet = newArrSet.add(4);

//delete(value):删除和这个值相等的元素,返回boolean
let isDelete = newArrSet.delete(4);

//has(value):判断是否存在与value值相等的元素,返回boolean
let isHas = newArrSet.has(3);

//clear:清空set,没有返回值
newArrSet.clear();

//forEach():遍历set
newArrSet.forEach((item) => console.log(item));

//支持用for..of进行遍历
for (const item of newArrSet) {
  console.log(item)
}

WeakSet使用(使用的不多)

  • 首先让我们理解 弱引用强引用的概念
    • 根据GC,我们知道其垃圾回收机制,主要是在看该对象从根节点是否可达,中间的这些引用,就分为 弱引用和强引用
    • 弱引用在GC认为,这是不可达的,所以有可能会被清理
    • 强引用在GC认为,是可达的,不会被清理

image.png

  • 因此WeakSet就是弱引用类型,其指向的对象,有可能会被GC回收

  • 拥有add(value)/delete(value)/has(value)等方法

  • Set的区别

    • 1.WeakSet中只能传递对象类型,使用基本数据类型会报错
    • 2.对元素的引用都是弱引用
    • 3.不能使用forEach进行遍历
const wset = new WeakSet()

wset.add({a:100})

Map的基本使用(映射)

  • 用于存储映射关系

  • 对象也可以存储映射关系,那么他们之间有什么区别

    • 对象存储映射关系只能用字符串或者Symbol进行存储
    • 而Map可以用其余的数据类型作为key(比如对象)
  • 常用属性和方法

let obj1 = { a: 100 };
let obj2 = { b: 200 };
let newMap = new Map();
newMap.set(obj1, "abc");
newMap.set(obj2, "abc");

//size:返回map中元素个数
console.log(newMap.size);

//set(key,value)添加key value,返回整个map
newMap.set({ c: 100 }, "cba");

//get(key)获取key对应的value
console.log(newMap.get(obj1));

//has(key)查找是否包含key值对应的value
newMap.has(obj1);

//delete(key)删除key对应的元素
newMap.delete(obj1);

//clear()清空元素
newMap.clear();

//forEach遍历:item打印的是对应的value
newMap.forEach((item) => console.log(item));

//可以使用for of 遍历
for (const item of newMap) {
  console.log(item); //[key,value],会将key和value装入一个数组
}

WeakMap

  • 只能用对象作为key,不能用其他类型作为key
  • 与Map相比,该数据结构是 弱引用类型,有可能会被垃圾回收器清除掉
  • 常用方法
    • set、get、has、delete
  • 应用
    • 不能遍历
    • 在Vue3响应式中会用到

ES6其他知识点说明

Proxy/Reflect(后续文章)

Promise(后续文章)

ES module(后续文章)

ES8–新增特性

padStart/pad主要用字符串的填充

  • 基本用法(保持)
//至少两位,不足两位在头部补充0
let str = "5".padStart(2, "0");
console.log(str);//05
  • 主要在敏感字符串替换上(身份证,银行卡)
let str = "1306261558515255";
let newStr = str.slice(-4).padStart(str.length, "*");
console.log(newStr);

Trailing Commas尾部逗号添加

function foo(num1,num2,){
    //
}

Object Description

  • Object.getOwnPropertyDescriptors获取属性描述符
  • Async Function 后续文章

ES9–新增特性

  • Async iterators 后续文章
  • Object spread operators展开运算符
  • Promise finallya 后续文章

ES10 新特性

flat flatMap

  • flat对多维数组进行遍历,将所有元素与遍历到的子数组中的元素合并称为一个新的数组
    • 可以理解为,将多维数组转成低维数组
let arr = [100, [100, 200], [[300, 400]]];
let newArr1 = arr.flat(1);
let newArr2 = arr.flat(2);
console.log(newArr1);//[ 100, 100, 200, [ 300, 400 ] ]
console.log(newArr2);//[ 100, 100, 200, 300, 400 ]
  • flatMap会先对元素进行映射,随后压入一个数组中
//现在有多个字符串,需要先将字符串按照空格分隔,而后装入一个数组中
let arr = ["hello world", "nihao hahah"];
let newArr = arr.flatMap((item) => item.split(" "));
console.log(newArr);//[ 'hello', 'world', 'nihao', 'hahah' ]

Object fromEntries

  • 基本用法

    Object.entries静态方法返回一个数组

    Object.fromEntries(静态方法将键值对列表转换为一个对象)

let obj = {
  a: 100,
  b: 200,
};

console.log(Object.entries(obj)); //[ [ 'a', 100 ], [ 'b', 200 ] ]

let arr = [
  ["a", 100],
  ["b", 200],
];
console.log(Object.fromEntries(arr));

trimStrat trimEnd

  • 去除首位空格的
let str = "  aaa  "
str.trim()//去除首尾空格
str.trimStart()//去除头部空格
str.trimEnd()//去除尾部空格

ES11–新增特性

BigInt

  • 在很大的数字后面+n即可
let num = 189156198165165616n

Nullish Coalescing Operator空值合并运算符

  • ??会对值进行判断,当为 undefined/“”/0/false的时候会采用默认值(遇到null的时候会采用null
info = info ?? "默认值"
//当info为null的时候,使用null
//当info为“”  0   undefined  false的时候用默认值

Optional Chaining可选链

  • 主要是让我们的代码在 进行null和undefined判断的时候更加清晰
  • 存在b么?存在就调用,不存在就返回undefined
//
let obj = {
  a: 100,
  b: {
    c: function () {
      console.log("zhangcheng");
    },
  },
};
obj?.b?.c?.();

ES12新增特性

FinalizationRegistry

  • 该对象可以让 某对象在被回收时候,请求一个回调
let obj = {
  a: 100,
};

//FinalizationRegistry是一个类,回调函数可以接收参数
const registry = new FinalizationRegistry((value) => {
  console.log(`对象${value}被销毁了`);
});

//register这是实例方法,第一个参数是监听对象,第二个参数是描述
registry.register(obj, "zhangcheng");
obj = null;

WeakRef(尽量避免使用)

  • WeakRef对象允许保留对另一个对象的弱引用
let obj  = {
    a:100
}
let weakRefObj = new WeakRef(obj)
//实例方法
//返回当前实例的 WeakRef 对象所绑定的 target 对象,如果该 target 对象已被 GC 回收则返回undefined
let a = weakRefObj.deref()

逻辑赋值运算符

  • 首先看之前学过的赋值运算符、
let b = b + 100
//我们可以写成以下形式
b+= 100
  • 再看逻辑赋值运算符
function foo(value) {
  //通常我们会对value是否有值进行判断
  value = value || "默认值";
  //我们现在可以简写成这样
  value ||= "默认值";

  //我们知道||运算符,有缺陷,若传进来的是“” 0 false会当作有值的
  //此运算符,只有value为null的时候
  value ??= "默认值";
}

foo(0);

字符串replaceAll方法

  • 我们之前学过字符串的replace的方法,此方法只能替换一个,不能替换全部
let str = "aaaa";
let newStr = str.replace("a", "b");
let newStr1 = str.replaceAll("a", "b");

console.log(newStr);//baaa
console.log(newStr1);//bbbb

ES13新特性

对象属性hasOwn --用来替代hasOwnProperty

  • 之前我们学习过一个实例方法hasOwnProperty,用于查找一个属性是否属于对象自身的
let obj = { a: 100 };
obj.__proto__ = { b: 200 };

console.log(obj.hasOwnProperty("a")); //true
//因为b是在隐式原型上面的,所以返回false
console.log(obj.hasOwnProperty("b")); //false

//第一个参数传递对象,第二个参数传递要查找的属性
console.log(Object.hasOwn(obj, "a")); //true
console.log(Object.hasOwn(obj, "b")); //false
  • 通过以上代码我们可以看出以下区别

    • 1.hasOwn是一个静态方法:通过 Object.hasOwn进行调用

      hasOwnProperty是一个实例方法,需要创建出来的对象进行调用

    • 2.防止创建出来的对象中,有 hasOwnProperty属性,对内置的进行修改

    • 3.接下来看一个例子,就能明白 hasOwnProperty的局限性

const obj = Object.create(null);
obj.a = 100;

//因为现在obj指向了null,obj本身没有hasOwnProperty方法,而null中也没有,所以会报错
//而之前的代码可以用,是因为obj指向的是Object_prototype,Object原型对象中有hasOwnProperty该方法可以调用
console.log(obj.hasOwnProperty("a"));

//而这个是直接调用静态方法,因此无论对象的隐式原型指向什么地方,都能使用该方法
console.log(Object.hasOwn(obj, "a"));

New members of classes 新成员字段

  • 对象属性
    • public实例对象属性
    • 私有实例对象属性
  • 类属性
    • 公共类属性
    • 私有类属性
  • 静态代码块
class Person {
  //对象属性:public
  height = 1.88;

  //对象属性:private,程序员之间的约定
  _intro = "";
  //真正的私有属性.只能在clas类中访问,不能被实例访问
  #intro1 = "123";

  //类属性:public
  static intro2 = "70";

  //私有类属性:外界不能访问
  static #intro3 = "70";
  constructor(name) {
    this.name = name;
  }
  //静态代码块:只会执行一次,用于对class进行初始化
  static {
    console.log("hello");
  }
}
文章来源:https://blog.csdn.net/weixin_55041125/article/details/135399379
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。