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语句,当他们的代码块被执行的时候,词法环境被创建出来
- 一般一个执行上下文会关联两个词法环境:词法环境组件(let const声明的) 和 变量环境组件(var 声明的)
- 词法环境组件(let const声明的) :执行代码时候,变量会被创建,但是在赋值之前,是没有办法通过任何方式访问的
- 变量环境组件(var 声明的):在执行代码的时候,变量会被创建,同时会给一个默认值:undefined,可以在赋值之前访问
-
环境记录(Environment Record)
- 声明式环境记录(函数声明、变量声明等) 和对象式环境记录(with语句)
- 环境记录不仅会记录声明,还会记录let等声明的变量是否赋值了,若没有赋值则不让访问,若赋值了才可以访问
- 大致的流程如下
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
- 通过上面的解释,我们就可以了解,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声明的变量
- 监听点击的按钮
//现在有多个按钮
//先前使用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认为,是可达的,不会被清理
-
因此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");
}
}