ES6-ES13
ES6(部分内容)
import() 动态导入方法[23.7.28]
动态导入 是JavaScript ES2019中新增的语法特性,它可以通过将代码 按需导入,从而实现更加 高效的加载方式。动态导入允许用户在运行时动态地加载模块,这是ES6中静态导入所无法实现的
基本语法:
-
/* import(模块路径) 动态导入返回一个 promise + 当模块导入成功后,我们就可以通过 .then 方法继续执行后续的操作 */ import(模块路径).then(模块 => { // 使用导入的模块 }).catch(err => { // 捕获错误 })
对象字面量的增强写法
1.属性的简写(property shorthand)
// 按照以前的写法,时属性名与属性值的名称都一样时也是要两个都写上
var name = "kong";
var age =18;
var obj = {
name:neme,
age:age
}
// ES6后,当属性名与属性值的标识符时一样的时候,可以只书写属性名
var obj = { // 该obj与上面的obj是一样的
name,
age
}
2.方法的简写(method shorthand)
var obj = {
foo:function (){
// 在ES6之前书写对象书写的方式
},
fun() {
// 在ES6后,可以直接书写标识符 + 方法体即可 (更加的简便)
}
}
// 需要注意的是,该简写方式是 function 写法的语法糖 -- 不是箭头函数的语法糖(将头函数不绑定this)
3.计算属性名(computed property name)
// ES6 运算计算属性名的定义 - 语法,使用中括号[]
var obj = {
["name" + 1]:"kong",
["name" + 2]:"wang"
}
obj["name" + 3] = "deng";
// obj对象就有三个属性
let 与 const
1.let-const和window的关系
var a = 0; // 通过var声明的变量,会存在在window里面
consoli.log(a);
consoli.log(window.a);
// 上面两个输出的是完全一样的
// 当window与var变量中任意一个改变了变量的值,另一边都会跟着改变
window.a = 5;
conshilo.log(a); // 5
/* 在以前,每一个执行上下文会关联到一个变量环境(variable object,VO),在代码的变量和函数声明会作为属性添加到VO中
* 对于函数来说,参数也会添加到VO中 */
/* 现在,每一个执行上下会关联到一个变量环境(variableEnvironment`s,VE)中,在执行代码变量和函数声明会作为环境记录(Environment Record)添加到变量环境中
* 对于函数来说,参数也会作为环境记录添加到变量环境变量中 */
// let与const不会添加到window对象之中
2.新增块级作用域
在ES6之前只有两个作用域的存在,一个是全局作用域(window),一个是函数作用域(function)
{
// 块代码 - 在ES6之前没有任何作用 -- ES6块代码(有块级作用域)
/* 对let/const/function/class声明的类型有效
* function比较特殊 - (不同点浏览器有不同的实现效果)- 大部分浏览器为了兼容以前的代码,让function是没有块级作用域
*/
}
// 在块代码中有着块级作用域 - 多以只能在内部使用
{
var a = 0; // var除外 - 因为块级作用域针对于var是无效的
let b = 0;
}
console.log(a);
console.log(b);
其它块级作用域…
// if switch for ...
if(true){
var a = 0;
let b = 1; // 外部不可访问
}
switch (color){
case "red":
var a = 0;
let b = 1; // 外部不可访问
}
// for语句也是块级作用域
for(var i=0;i<10;i++>){
}
consoli.log(i); // 可以访问
for(let i=0;i<10;i++>){
}
consoli.log(i); // 不可访问
应用
<!-- 没有块级作用域的情况 -->
<!-- html代码 -->
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
<!-- js代码 -->
<script>
/*
* 因为var没有块级作用域所以外部可以访问到var - 并且var后存放在window之中(并两者是一一对应的)
* 所以当循环完了之后 var i的值等于4,
* 因为点击的时候才输出 i
* 所以无论点击的是哪一个按钮,打印 i 的值都为4
* 4 + 1 == 5 --> 5
*/
const button = document.getElementsByTagName('button');
for(var i=0; i<button.length; i++){
button[i].addEventListener('click',function(){
console.log("点击的是第 " + (i+1) + " 按钮");
})
}
</script>
<!-- 有块级作用域的情况 -->
<!-- html代码 -->
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
<!-- js代码 -->
<script>
/*
* 只需将var声明改为let声明即可
* 因为let声明既有块级作用域,所以它每一次循环都相当于是一个块级作用域
* 所以每次 i 都有着单独对应的值
* 执行如下
*/
/*
* 第一次执行
* {let i = 0}
* 第二次执行
* {let i = 1}
* 第三次执行
* {let i = 2}
* 第四次执行
* {let i = 3}
* 每一次执行都会有单独的一个块级作用域
*/
const button = document.getElementsByTagName('button');
for(let i=0; i<button.length; i++){
button[i].addEventListener('click',function(){
console.log("点击的是第 " + (i+1) + " 按钮");
})
}
</script>
<!-- 在以前没有块级作用域的使用 - 通常是使用一个立即执行函数来存储i的值(可以理解为是利用闭包) -->
3.let-const其它知识
const arr = ["a","b","c","d"];
for(let i=0;i<arr.length;i++){
consoli.log(arr[i]);
}
// 执行过程
// 它的每一次执行,都会有一个独立的块级作用域来储存 let
{
let = 0;
consoli.log(arr[i]);
}
{
let = 1;
consoli.log(arr[i]);
}
{
let = 2;
consoli.log(arr[i]);
}
{
let = 3;
consoli.log(arr[i]);
}
// 当使用的是 var 时 - 因为var没有块级作用域所以 var 是没有独立存储的,后面的值会覆盖掉前面的值
{
// 它是在自身覆盖的
var = 0; // var = 1 // var = 2 // var = 3
consoli.log(arr[i]);
}
// for of - 遍历数组(对象)
const arr = ["a","b","c","d"];
for(let item of arr){ // 为了有块级作用域,通常使用let - const
consoli.log(item);
}
/* for of可以使用 const -- for不能使用 const
* 因为for中有个 i++ 会改变变量的值 - 所以不能使用const
* 而for of中没有 i++ 不会改变变量的值 - 所以亏可以使用const
*/
4.暂时性死区(temporal dead zone)
表示在一个代码中,使用了let、const声明变量,在声明之前,变量都是不可访问的
// 示例
// 可以访问
var a = 0;
{
console.log(a);
}
// 不可以访问 - 形成暂时性死区
var a = 0;
{
// 因为在let或const变量声明前是不可以访问的
console.log(a); // 所以这里就会形成一个暂时性死区,就会访问不到外部的 var a
let a = 1;
}
5.var、let、const的选择
对于var的使用:
- 我们需要明白一个事实,var所表现出来的特殊性:比如作用域提升、window全局对象、没有块级作用域等都是一些历史遗留问题
- 其实是JavaScript在设计之初的一种语言缺陷
- 当然目前市场上也在利用这种缺陷出一系列的面试题,来考察大家对JavaScript语言本身以及底层的理解
- 但是在实际工作中,我们可以使用最新的规范来编写,也就是不再使用var来定义变量了
对于let、const :
- 对于let和const来说,是目前开发中推荐使用的
- 我们会优先推荐使用const,这样可以保证数据的安全性不会被随意的篡改
- 只有当我们明确知道一个变量后续会需要被重新赋值时,这个时候再使用let
- 这种在很多其他语言里面也都是一种约定俗成的规范,尽量我们也遵守这种规范
模板字符串
1.模板字符串
// 语法 ``
// 插值 ${}
// 使用 - 通常配合着插值来使用
const name = "kong";
const age = 19;
consoli.log(`My name is ${name},age is ${age}`); // My name is kong,age is 19
// 当热插值里面也可以,运算或函数的调用等..
var test = `H${age + 2}el${MyFun()}lo`;
2.标签模板字符串
// 标签模板字符串 - 函数调用时不是使用中括号(),而是使用模板字符串的语法来调用的
function myFun(){
// 语句
}
myFun``; // 这就称 标签模板字符串
// 参数
/*
* 同过标签模板字符串传参
* 第一个参数是一个数组
* 第二个参数是第一个 插值${} 中的内容
* 第三个参数是第二个 插值${} 中的内容
* 所有不属于插值的,都是属于第一个参数的,并且插值相当于是将参数截断在存入数组之中
*/
function myFun (a,b,c){
console.log(a); // [hello world]
console.log(b); // undefined
console.log(c); // undefined
}
myFun`hello world`; // 该参数时第一个参数
// 插值
function myFun (a,b,c){
console.log(a); // [he,llo,wo,rld] -- 长度为4的数组
console.log(b); // 1 - 第一个插值
console.log(c); // 2 - 第二个插值
}
myFun`he${1}llo wo${2}rld`; // 该参数时第一个参数
/* 很少使用 - 可以了解
* 因为在React中使用js书写css样式,就是使用的是这个原理(styled-components库)
*/
展开语法与特性
-
可以在函数调用/数组构造时,将数组表达式或者 string 在语法层面展开
-
还可以在构造字面量对象时,将对象表达式按 key-value 的方式展开
-
构造字面量对象时,进行克隆或者属性拷贝(ECMAScript 2018 规范新增特性)
// 语法 ...
const name = "kxh";
const names = ["kong","deng","wang"];
// 1.调用函数时
function foo (x,y,z){
consoli.log(x,y,z);
}
// foo.apply(null,names); // 以前将数组中的值作为参数传入函数中的方式 - 不易于代码阅读
// 在ES6之后,可以使用展开运算符来将数组中的值,作为参数传入函数中
foo(...names); // kong deng wang
// 不仅仅可以展开数组,也可以展开字符串
foo(...name); // k x h
// 2.构造数组时
const newNames = [...names]; // ["kong","deng","wang"]
const newName = [...name]; // [k,x,h]
// 3.ES2018(ES9) - 构造对象时
const obj = {name:"kong",age:18}
const newObj = {
...obj,
addreese:"HUAIJI"
}
consoli.log(newObj); // {name:"kong",age:18,addreese:"HUAIJI"}
// 也可以将数组展开到对象中,会以键与键值展开
// 展开运算符,相当于是一个浅拷贝(即当拷贝的对象中,有的属性它的属性值又是一个对象或数组时,这时拷贝的是引用值) - 如
const obj = {
name:"kong",
age:19,
arr:[0,1,2],
objSon:{
name:"xiaokong"
}
}
const newObj = {...obj}
// 当你改了某一个对象的属性中值中的对象或数组的值后,另一个也会跟着改变(拷贝的是引用值)
obj.arr[0] = 10;
consoli.log(newObj.arr[0]); // 10 - 因为是引用值,所以一个改变了,另一个也跟着改变
/* 浅拷贝概述
* 当为常量的时候直接拷贝
* 为对象{}或数组[]时拷贝的时引用值
* 注意 null 属于对象(做深克隆的时候可以需要注意,浅克隆可忽略)
*/
【理解】八进制-二进制-连接符
在ES6中规范了二进制与八进制的写法:
// 十进制
const num = 100;
// 二进制 - 0b开头表示 binary
const num = 0b10; // 10 --> 2
// 八进制 - 0o开头表示 octonary
const num = 0o100; // 100 --> 64
// 十六进制 - 0x开头表示 hex
const num= 0x100; // 100 --> 256
连接符
// 比较大的数值连接符 _ (ES2021 ES12)
/* 通常数值比较大的,会很不好查看(会将数值每3个用一个逗号隔开,但在js中会报错)
* 所以就有了连接符 _
* 使用连接符不会有任何的效果,只是可以在阅读代码时可以更好的阅读体验
*/
const num = 100_000_000_000;
consoli.log(num); // 100000000000 - s实际打印没有变化
新增Symbol类型
Symbol是ES6中新增的一个基本数据类型,翻译为符号
为什么需要Symbol
- 在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突口
- 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下很容易造成冲突,从而覆盖掉它内部的某个属性
- 比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉
Symbol就是为了解决上面的问题,用来生成一个独一无二的值
- Symbol值是通过Symbol函数来生成的,生成后可以作为属性名
- 也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值
Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的
我们也可以在创建Symbol值的时候传入一个描述description:这个是ES2019(ES10)新增的特性
// 在ES6之前
var obj ={
name:"kong",
"age":19 // 因为在js中对象的键(key),不管有无引号,在底层都会是字符串
}
// 可以打印obj中的key来看效果
consoli.log(Object.keys(obj)); // ['name', 'age']
// 所以在我们赋值的时候就很容易将某些值覆盖掉
obj["name"] = "kxh"; // 此时就将obj中的name覆盖掉了
// 所以Symbol就可以为我们创建一个独一无二的key值
// Symbol的基本使用 Symbol()
const s1 = Symbol(); // 每一个创建的Symbol值都是独一无二的
const s2 = Symbol();
consoli.log(s1 === s2); // false
// 在ES2019(ES10)中我们可以在Symbol()中传入一个描述 description
const s3 = Symbol("MiaoShu");
consolo.log(s3.description); // 'MiaoShu'
/* 若想创建相同的Symbol函数(非独一无二)
* 可以使用Symbol中的 for(key) 语法
* 当有两个for(key)中的key值是相等的话,那么它两就是相同的Symbol函数
* 如
*/
const sa = Symbol.for('public');
const sb = Symbol.for('public');
consoli.log(sa === sb); // true
// 使用Symbol创建对象的key值 - 使用之前讲的【属性名的计算】的方法
// 先创建4个Symbol值 - 用于演示
const s1 = Symbol();
const s2 = Symbol();
const s3 = Symbol();
const s4 = Symbol();
// 在定义对象字面量时使用
const obj = {
[s1]:"a",
[s2]:"b"
}
consoli.log(obj); // {Symbol(): 'a', Symbol(): 'b'} -- 虽然里面的key值都是Symbol(),但它们每一个都是独一无二的
// 新增属性
obj[s3] = "c";
// 通过Object.defineProperty()方法 -- 使用方法下方
Object.defineProperty(obj,s4,{
enumerable:true,
configurable:true,
writable:true,
value:"d"
});
/* 需要注意的是,使用Symbol函数创建的键值,是无法遍历与使用 Object.keys()方法的
* 不过可以通过
* Object.getOwnPropertySymbols(obj) -- 获取所有的 Symbol 类型的键值
* Object.getOwnPropertyNames(obj) -- 获取所有不是 Symbol 类型的键值
* Object.getOwnPropertyDescriptors(obj) -- 获取整个对象的所有值
*
* 所以我们就可以通过 Object.getOwnPropertyDescriptors(obj) 的方式获取所有值
* 或者遍历所有值
*/
// Object.defineProperty()方法的使用 【后续学习-目前未学】
/* Object.defineProperty(obj,prop,descritor)
* obj -- 要定义属性的对象
* prop -- 要定义或修改的属性的名称或 Symbol
* descriptor -- 要定义或修改的属性描述符
*/
/* 🔺在对象中,无论写不写 Object.defineProperty() 方法,其内部都会有默认的 description 值...
* 不过默认情况下是不展示出来的,只有通过 Object.getOwnPropertyDescriptors(obj) 的方法调用才会显示
*
*/
/* 属性描述符
* 拥有布尔值的键 configurable、enumerable 和 writable 的默认值都是 false
* 属性值和函数的键 value、get 和 set 字段的默认值为 undefined
*/
// 详细文档学习 : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
新增数据结构Set
在ES6之前,我们存储数据的结构主要有两种:数组、对象
而在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap
1.新增数据结构Set
Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复
创建Set需要通过Set构造函数(暂时没有字面量创建方法)
// 创建Set
const set = new Set();
// 方法
// Set中添加元素的方法 - add
set.add(10);
set.add(20);
set.add(20); // 与上一个是相同的,只会保留一个(不能有重复的数据)
set.add(30);
console.log(set);
// Set中删除元素的方法 - delete
set.delete(30); // 书写的是所要删除的元素(非索引)
console.log(set);
// Set中清空元素方法 - clear
set.clear();
console.log(set);
// 判断Set中 是否存在某一个元素 - has
set.has(10); // false
// forEach遍历集合
...
// 属性
// Set中获取长度属性 - size
console.log(set.size)
// 在创建时,在Set()参数中,可以直接添加数据 -- 使用中括号
const s1 = new Set([1,2]); // 添加数据
// 也可以添加已有的数组(数据)
const arr = [1,2,3];
const s2 = new Set(arr);
数组去重
// 定义去重的数据 - public
const arr = [10,20,30,40,50,20,20,30,40,];
// 在之前是使用for 或 forEach...
var newArr = [];
arr.forEach( item => { // forEach里面是一个函数,参数未arr的value(当两个参数时,第二个为key/index)
if(newArr.indexOf(item) === -1){
newArr.push(item);
}
});
// 现在可以利用Set中的 (不能存在重复的元素这一特性) 来实现数组去重
const setArr = new Set(arr); // 将已有 arr 数据添加进Set中
const newArr = [...setArr]; // 使用扩展运算符,将setArr中的数据添加到对应的数组 - 即完成了去重
const newArrFun = Array.from(set); // 或者使用 Array中的 from 方法存入数据(两者都可以)
// 弃用-过于复杂
// 现在可以利用Set中的 (不能存在重复的元素这一特性) 来实现数组去重
const set = new Set(); // 创建Set集合
const newArrSet = []; // 去重后的数据
for(item of arr){ // 遍历arr中每一个数据,再将每一个数据都存放入Set集合中
set.add(item); // 存放数据,重复的Set会自动忽略
}
newArrSet.push(...set); // 使用扩展运算符,将set集合中的数据,添加进newArrSet中,完成去重
2.新增数据结构WeakSet
与Set类似,也数数据不能重发的数据结构
- 区别一:WeakSet中只能存放对象类型,不能存放基本数据类型
- 区别二:WeakSet元素的引用是弱引用,如果没有其它引用对某个对象引用,那么GC可以对该对象进行回收
// 创建方法与Set一致(只能使用构造方法)
const weakSet = new WeakSet();
/* WeakSet也可以在参数中直接书写值,与set不同的是,因为WeakSet不能书写基本数据类型
* 所以,[[],[],[]] -- 每一个值都必须是对象类型的(不是方对象数组,放的是对象对象会忽视)
*/ const weakSetTest = new WeakSet([[1,2]["a","b"]]);
// WeakSet 方法
const arr1 = [10,20,30];
const arr2 = [40,50,60];
const weakSet = new WeakSet([arr1,arr2]); // 建立的是弱引用
// has 判断是否包含某一对象(数组)
weakSet.has(arr1); // true
// 添加数据 - add
weakSet.add(arr1);
// 删除数据 - delete
weakSet.delete(arr1);
// WeakSet 数据结构不能使用 forEach等进行遍历
3.WeakSet使用场景
不常用
// 像下例中,调用方法的时候可以将里面的this指向,通过call来改变掉
class Person {
constructor(){
}
running (){
console.log("【going running】",this);
}
}
const p = new Person(); // 创建 Person 类的实例化对象
p.running(); // 【going running】 Person{}
// 让this指向另一个对象
p.running.call({name:"kong"}); // 【going running】 {name: 'kong'}
/* 如果有一个需求,就是不能向上面的一样可以改变this指向
* 相当于是,调用方法时,方法中的this指向,只能是本主体构造函数才能正常调用
* 否则,抛出错误
* 这里就可以用到 WeakSet 数据结构
* 具体如下
*/
const weakSet = new WeakSet();
class Person {
constructor(){
/* 因为每次调用方法时,构造函数都会创建一个 this对象
* 所以可以将该构造函数创建的 this对象 存储到 WeakSet中
* 用于后续在调用方法时,来判断 thhis 是否是该构造函数创建的 this
*/
weakSet.add(this);
}
running (){
/* 当调用该方法时,会生成一个 this
* 下面的条件语句,就是用来判断该方法在调用的时候,this有没有发生改变
*/
/* 当该方法生成的 this 不在weakSet,则相当于该 this 不是构造函数创建的 this
* 抛出错误
*/
if(!weakSet.has(this)){
throw new Error("不能通过非构造方法创建出来的对象来调用running方法");
}
console.log("【going running】",this);
}
}
const p = new Person();
/* 报错 - 因为调用的时候,使用了call改变了this对象
* 所以调用方法的this不属于构造函数创建出来的对象
*/
p.running.call({name:"kong"});
/* 上面为什么不用Set,而用WeakSet
1.因为使用Set时,Set时属于强引用,当我们将 p=null 给清空时
因为Set时强引用,所以 GC 是没有办法清理掉的
除非在你清理掉 p 后,在调用 weakSet.clear() - 将Set清理掉
这样不仅多了一个步骤,在比较大的项目或是其它比较复杂的情况的时候 - 很难保证会记得清除
从而就会造成 GC 无法清理掉 p -- 存在内存泄漏
2.但使用 WeakSet 就不一样了,因为 WeakSet 是弱引用
所以只要当 p=null 也就是说那一个对象就只剩下WeakSet引用着
又因为 WeakSet 是弱引用,所以 GC 会当做没有对象引用该存储空间,会直接给清除
*/
新增数据结构Map
1.新增数据结构Map
用于存储映射关系(可以理解为键值对),与对象差不多
-
事实上我们对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key)
-
某些情况下我们可能希望通过其他类型作为key,比如对象,这个时候会自动将对象转成字符串来作为key
Map:
- 使用Map数据结构,就可以传入其它类型作为key值
// 创建map对象
const map = new Map();
// 基本方法
// 创建时添加数据
const map = new Map([[key1,value1]],[key2,value2]]);
// 添加键值对 - set
map.set(key,value);
// 获取 value - get
map.get(key);
// 删除值方法 - delete
map.delete(key);
// 清空值 - clear
map.clear();
// 获取长度属性 - size
map.size;
// 使用其它类型来作为key值 -- 使用对象如下
const map = new Map();
const obj = {
name:"kong",
age:19
}
map.set(obj,"kxh"); // 可以使用对象来当键值(映射关系)
console.log(map); // Map(1) {{…} => 'kxh'} -- 打印出来有个箭头,是为了与对象做一个区分
// 遍历map
map.forEach((value,key) => {
console.log(key,value);
});
// 使用for of遍历出来的item是一个数组,里面又分别放着key与value
for(const item of map){
// console.log(item);
console.log(item[0],item[1]);
}
// 也可以在遍历的同时做一个解构
for(const [key,value] of map){
console.log(key,value);
}
2.新增数据结构WeakMap
和Map的区别:
- 区别一:WeakMap的key只能使用对象,不接受其它类型作为key
- 区别二:WeakMap的key对对象的引用时弱引用,如果没有其它引用引用这个对象,那么GC可以回收该对象
- 区别三:WeakMap不能进行遍历
// 创建WeakMap
const weakMap = new WeakMap();
// 常见方法
// set - 添加值set(key,value)
// get - 获取值get(key)
// had - 判断对象是否存在某个属性
// delete - 删除某个数据delete(key)
// 基本使用
const obj = {
name:"kong"
}
const weakMap = new WeakMap();
weakMap.set(obj,"kong");
console.log(weakMap); // WeakMap {{…} => 'kong'}
3.WeakMap的应用🔺
// vue3响应式原理(得学完先【proxy reflect】,在写vue响应式源代码)
........
......
....
..
ES7
includes方法和指数运算符【array】
includes
const names = ["kong","xinag","huang"];
// ES7之前,判断数组是否包含某数据的方式
/* 因为该方式,返回的是索引值(相当于是用于查看某一个数据的位置的)
* 所以借助它来判断数组是否包含某一数据,只是看它又这一特性
* ES7之后就出现了一个比较好语义等的 includes【返回的是true或false】
*/
if(names.indexOf("kong") !== -1){ // 判断数组是否包含某数据
console.log("包含");
}
// ES7 - includes[判断是否包含某一数据]
const names = ["kong","xinag","huang"];
/* includs() - 可以有两给参数(第一个参数为所向想要判断是否包含的值 - 第二个参数是书写整数类型的参数,变数从第一个楷书查找)
* name.includes("kong",2) -- 表示从数组name的第二项开始查找
*/
console.log(names.includes("kong")); // true
consoli.log(names.includs("kong",2)); // 当又两个参数的时候,第二个参数表示从第几个开始判断
// 使用includes判断数组是否包含某一个值
if(names.includes("kong")){
console.log("包含");
}
指数运算符 - **
// ES7之前
const num1 = Math.pow(3,3); // 表示3的3次方
// ES7新增
const mun2 = 3 ** 3; // 表示3的3次方
ES8
values-entries-pad等
1.Object.values - 获取说有value值(返回的是一个数组)
// 基本使用
const obj = {
neme:"kxh",
age:19
}
console.log(Object.keys(obj)); // Object.keys之前有过 - 表示获取所有key值(键值)(2) ['neme', 'age']
console.log(Object.values(obj)); // 表示后去所有的内容(value)(2) ['kxh', 19]
// 也可以传入其它类型的参数(很少会使用)
// 数组
console.log(Object.values(["kong","wang","deng"])); // ['kong', 'wang', 'deng']
// 字符串
console.log(Object.values("kong")); // ['k', 'o', 'n', 'g']
2.Object…entries -获取一个数组,数据中会存放可枚举的键值对数组
// 基本使用 - 放回的是一个数组(里面存放发一样是数组,分别存放着键值对)
const obj = {
neme:"kxh",
age:19
}
console.log(Object.entries(obj));
// 也可以传入其它类型的参数(很少会使用)
// 数组
console.log(Object.entries(["kong","wang","deng"])); // ['kong', 'wang', 'deng']
// 字符串
console.log(Object.entries("kong")); // ['k', 'o', 'n', 'g']
3.String.padding - 字符串填充
// 填充字符出,其中有两个属性,分别是向前填充(padStrat)与项后填充(padEnd)
const str = "Hello world";
// padStrat - 在前面进行填充
const newStrStratPad = str.padStart(15); // 表示将字符串扩大至15个字符的大小(宽度/空格),在最前面进行填充(使用空格)
// padEnd - 在后面进行填充
const newStrEndPad = str.padEnd(15); // 表示将字符串扩大至15个字符的大小(宽度/空格),在最后面进行填充(使用空格)
// 打印
console.log(newStrStratPad);
console.log(newStrEndPad);
// 默认以空格填充,若想使用其它符号来进行填充 - 可以书写第二个参数(表示以什么填充)
// 也可以多次调用该方法,多次斤西瓜填充
const str = "Hello world";
const newStrStratPad = str.padStart(15,"-"); // 向前填充,以(-)来填充
const newStrEndPad = str.padEnd(15,"."); // 向前填充,以(.)来填充
const newStrContinuous = str.padEnd(15,".").padEnd(20,"--"); // 多次填充,分别以(-)与(.)来进行填充
// 打印
console.log(newStrStratPad);
console.log(newStrEndPad);
console.log(newStrContinuous);
小案例
// 将身份证前面的隐藏(hide)掉,保留后四位
const myIdentity = "441224202201025418"; // 某某身份证
const myIdentityLastFourBit = myIdentity.slice(-4); // 表示从第4位开始截取(雄厚向前数)
const myIdentityHide = myIdentityLastFourBit.padStart(myIdentity.length,"*"); // 将身份证前面使用星号填充,保留后四位
console.log(myIdentityHide); // 打印
4.trailing-commas
// 只是修改了一点与法问题
function fun (n,m,){}
fun(1,2,);
// 在ES8之前,是不允许向上面一样在函数与调用函数中的最后一个参数后加逗号(,)会报错
// ES8中就增加了该与法,相当于在ES8之后允许项上面一样的写法(不会报错)
5.Object.getOwnPropertyDescriptors - 获取对象的所有值 - 前面已经写过(详细往前看)
ES9
Async iterators(后续 - 迭代器讲解先)
Object spread operators(后续在学)
Promise finally(后续学 - Promise)
ES10
flat-flatMap(降维)
1.flat() 方法会按照一个可以指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并位一个新数组返回
// 基本使用
const nums = [10,20,30,[100,200],[[1000,2000],[3000,4000]]]; // 三维数组
const newNums1 = nums.flat(); // 降维 - 默认为1(即降一个维度)
const newNums2 = nums.flat(2); // 降维 - 深度为2
console.log(newNums1); // [10,20,30,[100,200],[1000,2000],[3000,4000]] - 二维
console.log(newNums2); // [10,20,30,100,200,1000,2000,3000,4000] - 一维
2.flatMap() 方法首先使用映射函数映射每一个元素,然后将结果压缩成一个新数组
- 注意一:flatMap是先进行map操作,在做flat操作
- 注意二:flatMap中的flat相当于深度为1
// 基本使用 - 可传入一个函数(里面的参数可以接收flatMap每次遍历数组的值)
const nums = [10,20,30];
const newNums = nums.flatMap(item => {
return item * 2;
});
console.log(newNums); // [20, 40, 60]
// 基本应用场景
const messages = ["Hello world","hello dcl","my name is kong"];
const words = messages.flatMap( item => {
return item.split(" "); // 在每个空格处进行拆分
/* 拆分后会性成一个二维数组
* 因为flatMap也会将数组进行降维,所以直接使用flatMap可以不用再重新遍历成一位数组
*/
});
console.log(words); // [Hello,world,hello,dcl,my,name,is,kong]
/* 为什么不直接用flat
* 因为flat会直接进行降维操作 - 遍历遍历出来后是个二维数组(还需要再次的给其手动实现降维效果)
* 又因为flatMap是先进行map操作再进行flat的降维操作
* 福哦一这里不是用flat而是使用flatMap
*/
fromEntries
// 基本使用 - 参数传入的是可迭代的数据
const obj = {
name:"kong",
age:19
}
// 前面讲过,该方法是将对象分别以键值对生成一个子数组,包含在一个大数组之中
const entries = Object.entries(obj);
// 当项将该数组再转换成数组
// 1.以前的方法
const newObj = {} // 创建一个新的对象,用于存储数组中的值
for(const item of entries){ // 遍历改采那个数组
newObj[item[0]] = item[1]; // 最后再将里面两个值,分别对应的添加进对象之中作为key与value
}
// 2.ES10新增方法Object.fromEntries
const fromObj = Object.fromEntries(entries); // 与墙面所讲到的entries是恰好相反的
trimStrat与trimEnd
// 基本使用
const str = " hello world ";
// 1.trim -- 去除字符串前后多余的空格
const newStr = str.trim(); // "hello world"
// 2.trimStrat -- 去除字符串前面多余的空格
const newStrStrat = str.trim(); // "hello world "
// 3.trimEned -- 去除字符串后面多余的空格
const newStrEnd = str.trim(); // " hello world"
catch binding(学习try cach时再学)【抛出异常】
ES11
新增大整数数据类型BigInt
再早期JavaScript中,是不能正确的表示过大的数字 - ES11之前最大的int数值为
// ES11之前 MAX_SAFE_INTEGER
// Number中的一个方法,获取整数能够正常表示的最大值 - 当然也可以更大,但不安全
const maxInt = Number.MAX_SAFE_INTEGER;
console.log(maxInt); // 9007199254740991
// 基本使用
// Number中的一个方法,获取整数能够正常表示的最大值 - 当然也可以更大,但不安全
const maxInt = Number.MAX_SAFE_INTEGER;
console.log(maxInt); // 9007199254740991
// ES11之后:BigInt -- 语法:在数字的最后加一个n(就表示是大数字数据类型)
const bigInt = 900719925474099100000034789n;
// 注意:大数没有隐式类型转换 - 所以大数只能跟大数进行运算 - 否则报错
console.log(bigInt + 10); // 报错
console.log(bigInt + 10n); // 正确做法
// 大数字类型转换 - BigInt() - 方法
const num = 10;
console.log(bigInt + num); // 报错
console.log(bigInt + BigInt(num)); // 不会报错,因为使用了BigInt函数进行了类型转换(大整数类型)
// 大整数转成小整数 - 可直接使用Number数字类型转换即可
const smallNum = Number(bigInt);
空值合并运算符使用
?? : 判断某一变量是否为空(使用方式与或运算符差不多)
// 属于一个运算符 - 语法 ??
// 以前 - 当项将某一个变量作为另一个变量的值,担忧不确定该变量是否存有数据,就会使用或运算符做一下判断
const foo = undefined;
// 不过或运算符有一个弊端,就是当那一个变量是一个空的字符串、0、null - 它也会判断成假(即也会使用已定义的默认值)
const bar = foo || "default value";
/* ES11空值判断运算符 ?? (特惠明确的判断该值是不是 undefined 或 null)
* 若不是 undefined 或 nul 就直接使用那个变量中的值
* 反之,就使用定义好的默认值
*/
const newBar = foo ?? "default value";
可选链的使用和场景
主要作用是为了让代码在进行null盒undefined判断是更加的清晰和简洁
// 判断属性是否是undefined
// 基本使用
const info = {
name:"kong",
// 当他不再是怕朋友了,即没有了friend整个对象,那么下面的输出语句就会报错
// friend:{
// name:"xiang",
// girlFriend:{
// name:"huang"
// }
// }
}
// info.undefined.girlFriend.name -- (在undefined处就已经报错了,所以后面的就不会在执行 - 就只有第一个是undefined会显示)
console.log(info.friend.girlFriend.name);
// 在之前通常就会先判断一下它里面是否有值 - 不过这种写法会比较混乱,不好维护
if(info && info.friend && info.friend.girlFriend && info.friend.girlFriend.name){
console.log(info.friend.girlFriend.name);
}
// ES11提供了可选链(optional chainling) 语法:?.
/* 更加的安全更加的严谨 - ?. 判断属性是否是undefined
* 是 - 返回undefined
* 不是,就继续执行
* 这样就各异很好的容错了
*/
console.log(info.friend?.girlFriend?.name); // undefined
GlobalThis和for-in标准化
1.GlobalThis
// 获取某一个环境下的全局对象(Global Object)
// 在浏览器下
console.log(window);
// console.log(this);
// 在node中,或报错 - 因为node中没有window的变量定义
// console.log(this);
// 在node下 - 有一个global,表示全局变量(注意实在node下,没有node的环境配置,下面会报错)
console.log(global);
// 在以前想要无论是在浏览器中还是在node中都能执行,通常会进行一个判断 - 判断某一个值是否为undefined
/* ES11中的 GlobalThis - 它在不同的环境下指向的东西是不一样的
* 当在浏览器的环境下,就指向的是window
* 当在node的环境下,就指向的是global
* 所以使用 GlobalThis 可以实现无论在哪一个环境下全局变量
* 所以在日常的开发中,如果需要用到全局变量的,最好就是使用 GlobalThis
*/
console.log(globalThis);
// 如下 - 就是将window替换成globalThis
globalThis.setInterval(() => {
console.log("哈哈")
},1000);
2.for in标准化
/* 在以前使用for in,可能会在不同的浏览器中该item值获取的内容是不一致的
有的浏览器获取的key,有的获取的是value */
// ES11后就做了个标准化 - for in,白能力出来的item都为 key
const obj = {
name:"kong",
age:19
}
for( const item in obj){
console.log(item);
}
3.Dynamic Import (后续ES Module模块化中讲解)
4.Promise.allSettled (Promise中的内容)
5.import mete (后续ES Module模块化中讲解)
ES12
FinalizationRegistry和WeakRef
1.FinalizationRegistry 对象(监听GC销毁某一个对象)
// 一般情况的基本使用
let obj = {
nema:"kong"
}
/* 创建FinalizationRegistry对象,参数可以传入一个回调函数
FinalizationRegistry对象,是用于监听某一对象被GC销毁的进程
不过需要注意的是,在js中的GC销毁时每隔一段时间进行依次销毁 - 不是实时的(不定时)
*/
const finalRegistry = new FinalizationRegistry(() => {
console.log("注册在finalRegistry的对象,某一个被销毁了");
});
finalRegistry.register(obj); // 注册对象方法 - register
/* 让obj对象为null,因为obj没有指向 {name:"kong"}对象,所以会被销毁
系统就会给该对象打上一个标签,GC回收的时候就会根据这些标签来进项销毁
因为 GC 销毁不是实时的,所以不时null就会立即销毁
所以就需要等一小会才会销毁,这样就能知道GC的的销毁进程了
*/
obj = null;
// 注册两个或多个对象的情况
let obj1 = {
nema:"kong"
}
let obj2 = {
name:"deng"
}
/* 创建FinalizationRegistry对象,参数可以传入一个回调函数
FinalizationRegistry对象,是用于监听某一对象被GC销毁的进程
不过需要注意的是,在js中的GC销毁时每隔一段时间进行依次销毁 - 不是实时的(不定时)
*/
const finalRegistry = new FinalizationRegistry((value) => { // value 拿到用户注册时绑定的值
console.log(`注册在finalRegistry的对象的${value}被销毁了`);
});
/* 若项知道时哪一个对象被销毁了,其实可以在注册的时候在绑定一个值(value) - 它会通过参数的方式传入到回到函数中
与注册对象以逗号隔开,填写绑定内容valu/自定义名称...
如下就分别给其绑定的时对应的对象名
*/
finalRegistry.register(obj1,"obj1"); // 注册obj1对象的监听
finalRegistry.register(obj2,"obj2"); // 注册obj2对象的监听
obj1 = null;
obj2 = null;
/* 从下图中就可以看到,上面虽然时先对obj1进行了null,在对obj2进行null的
但销毁却是obj2先被销毁掉,从而也可以看出
所以 GC 的的销毁机制是不定时的,也不缺定会先销毁哪一个对象
*/
2.WeakRef - 表示弱引用
// 创建 WeakRef 与使用
let info = new WeakRef(obj); // 表示将obj赋值给info,以弱引用的方式
// 查看 WeakRef 中的属性(value)的方法 - deref();
console.log(info.deref().name); // 在WeakRef中必须通过 deref() 方法才能查看里面的内容(value)
// 基本使用
let obj1 = {
nema:"kong"
}
/* obj1赋值给obj2时是一个弱引用 - 属于一个类,所以也是需要使用构造方法创建(参数为传入发对象...)
注意:使用lWeakRef赋值的,是不能直接查看里面的内容的 - 需要使用 deref() 方法 - 【表示查看参数的主体内容】
如下查看nema就需要 --> obj2.deref().name
*/
let obj2 = new WeakRef(obj1);
const finalRegistry = new FinalizationRegistry((value) => { // value 拿到用户注册时绑定的值
console.log(`注册在finalRegistry的对象的${value}被销毁了`);
// 可以使用可选链,来避免错误 - 因为GC回收后 obj2.deref() = undefined - undefined.name会发生错误
console.log(obj2.deref()?.name); // undefineed - 因为使用了WeakRef就相当于是以弱引用的方式进行赋值
});
finalRegistry.register(obj1,"obj1"); // 注册obj1对象的监听
obj1 = null;
逻辑赋值运算符的使用
1. ||= - 逻辑或赋值运算
// 基本使用
let message = undefined;
message = message || "default value"; // ES12之前
message ||= "default value"; // ES12新增(逻辑或赋值运算符)
2. &&= - 逻辑与赋值运算
// 【理解逻辑与的使用】易班逻辑与(&&)一般会用于判读某一对象、属性、方法.. - 看是否存在(存在就使用)
const obj = {
name:"kong",
foo(){
console.log("foo函数被调用");
}
}
obj && obj.foo && obj.foo(); // 通常会这样使用
// 所以一般来说是用不傲等号的 ,所以逻辑与赋值运算符,目前来说用的还是比较少的
// &&= 基本使用 (很少会这样子去写)
const info = {
name:"kong",
}
// 判断info是否有值,有只的情况下,就去取处info中的name(赋值给info)
info = info && info.name; // ES12之前
info &&= info.name; // ES12新增(逻辑与赋值运算符)
3. ??= - 逻辑空赋值运算
// 基本使用 ??=
// 与逻辑或 ||= 相似 - 区别在于逻辑或在遇到空的字符串时也会直接判断成 false,而 ?? 逻辑空不会
let message = "";
// 使用逻辑或,它就会直接把 message 当成时一个空的值(随后直接赋值 default value)
message ||= "default value";
// 使用逻辑空,它会判断实实在在的值,即只有undefined与null才为空(这样就能保留本身的空的字符串了)
message ??= "default value";
4.Number 数字的分割符(-)【前面已经讲过】
5.String,replaceAll 字符串替换方法(与replace相识)【区别:它可以替换掉所有匹配的字符串】
ES13
method .at()【数组的方法】
表示查找数组或字符串某一索引的值,区别可以书写负值
// 数组
const arr = [1,2,3,4];
console.log(arr.at(1)); // 2
console.log(arr.at(-1)); // 4
// 字符串
const str = "Hello world";
console.log(str.at(1)); // e
console.log(str.at(-1)); // d
// 注意:负值的话是从-1开始就相当于是最后一位(因为没有-0)
对象属性判断 hasOwn 方法
// ES13之前判断对象是否有某一个属性 obj.hasOwnProperty() 方法
const obj = {
name:"kong",
age:19,
/*/ __proto__ 属性是表示原型(相当于是在原型上设置属性)
相当于是:obj.__proto__.address = "HUAIJI"; 【在obj原型上设置address属性】
*/
__proto__:{
address:"HUAIJI"
}
}
console.log(obj.name,obj.age); // kong 19
console.log(obj.address); // HUAIJI - 根据原型链,是从原型上找到
// 在以前判断对象是否有某一个属性
console.log(obj.hasOwnProperty("name")); // true
console.log(obj.hasOwnProperty("address")); // false - 因为address是在原型上设置的所以,在obj对象中是没有的
// ES13中的 Object.hasOwn方法,用来替代 hasOwnProperty()方法
const obj = {
name:"kong",
age:19,
/*/ __proto__ 属性是表示原型(相当于是在原型上设置属性)
相当于是:obj.__proto__.address = "HUAIJI"; 【在obj原型上设置address属性】
*/
__proto__:{
address:"HUAIJI"
}
}
// ES13 Object.hasOwn(objName,property)
// 需要传入两个值,第一个是需要判断的对象,第二个为判断的属性
console.log(Object.hasOwn(obj,'name')); // true
console.log(Object.hasOwn(obj,'address')); // false
/* 与原来的区别
区别一:【可避免方法的重写】 - 因为obj.hasOwnProperty()方法有可能会被重写,导致使用的混乱(防止对象中也有自己对应的方法)
区别二:【防止对象上的原型的指向变化后出现问题】
*/
// 区别二演示
const info = Object.create(null); // 创建一个对象,并将原型(__proto__)指向null
info.name = "kong";
/* 这个时候调用 hasOwnProperty() 方法来判断就会报错
因为 hasOwnProperty() 方法是放在 __proto__(原型)上的,上面在创建对象的时候将对象的原型指向了 null
所以原型上就已经没有了 hasOwnProperty() 方法
故:掉哟个会报错 (这时就可以使用ES13中的方法了)
*/
console.log(info.hasOwnProperty("name")); // 报错
console.log(Object.hasOwn(info,"name")); // ES13
类中增加的新成员和私有属性
// 1.实例属性
class Person {
// 【ES13】对象的属性也可以在 constructor 外面书写 - 如
height = 1.65; // 对象的属性 - public[公共的属性]
/* 私有对象属性 private 私有:程序员之间的约定
外部也能访问,不过时为了方便开发,程序员们之间的一个约定【或者说时指定的规则】
*/
_intro = "neme id kong";
// 【ES13】- 就增加了真正的私有属性 语法:以井号(#)开头的 - #
#wife = "deng";
constructor(name,age,address){
// 【以前】对象的属性:咋construct通过this来设置
this.name = name;
this.age = age;
this.address = "HUAIJI";
}
}
const p = new Person("kong",19);
console.log(p,p.name,p.age,p.address,p.height); // kong 19 HUAIJI 1.65
console.log(p.#wife); // 报错 - 因为井号开头的代表着时私有属性
// 2.类属性(static)
class Person {
// 属性
/* 类属性:默认 public
也可以设置成私有(private)的,一样加一个井号开头
*/
static totalCount = "70亿"; // public
static #manTotalCount = "20亿"; // public
constructor(name,age,address){
// 【以前】对象的属性:咋construct通过this来设置
this.name = name;
this.age = age;
this.address = "HUAIJI";
}
// 静态代码块 语法:static {}
/* 第一次加载这类的时候,就会执行这里面的东西(并且只执行一次)
在new之前就会先执行静态代码块中的内容(多数用在后端,如Java)
一般用于做一些初始化的东西
*/
static {
console.log("Hello world");
console.log("Hello dcl");
}
}
const p = new Person("kong",19);