7.1.19.2、深拷贝
7.1.19.2.1、自带的
Array:slice()、concat()、Array.from()、… 操作符:只能实现一维数组的深拷贝
slice()方法演示:
var arr1 = [1, 2, 3, 4]; var arr2 = arr1.slice(); arr2[0] = 200; console.log(arr1); console.log(arr2); • 1 • 2 • 3 • 4 • 5
concat()方法演示:
var arr1 = [1, 2, 3, 4]; var arr2 = arr1.concat(); arr2[0] = 200; console.log(arr1); console.log(arr2); • 1 • 2 • 3 • 4 • 5
Array.from()方法演示:
var arr1 = [1, 2, 3, 4]; var arr2 = Array.from(arr1); arr2[0] = 200; console.log(arr1); console.log(arr2); • 1 • 2 • 3 • 4 • 5
… 操作符演示:
var arr1 = [1, 2, 3, 4]; var arr2 = [...arr1]; arr2[0] = 200; console.log(arr1); console.log(arr2); • 1 • 2 • 3 • 4 • 5
Object:Object.assign()、… 操作符:只能实现一维对象的深拷贝
Object.assign()方法演示:
var obj1 = { name: "张三", age: 20, speak: function () { console.log("我是" + this.name); } }; var obj2 = Object.assign({}, obj1); // 当修改obj2的属性和方法的时候,obj1相应的属性和方法不会改变 obj2.name = "李四"; console.log(obj1); console.log(obj2);
… 操作符演示:
var obj1 = { name: "张三", age: 20, speak: function () { console.log("我是" + this.name); } }; var obj2 = { ...obj1 }; // 当修改obj2的属性和方法的时候,obj1相应的属性和方法不会改变 obj2.name = "李四"; console.log(obj1); console.log(obj2);
JSON.parse(JSON.stringify(obj)):可实现多维对象的深拷贝,但会忽略 undefined 、 任意的函数 、Symbol 值
var obj1 = { name: "张三", age: 20, birthday: { year: 1997, month: 12, day: 5 }, speak: function () { console.log("我是" + this.name); } }; var obj2 = JSON.parse(JSON.stringify(obj1)); // 当修改obj2的属性和方法的时候,obj1相应的属性和方法不会改变 obj2.name = "李四"; console.log(obj1); console.log(obj2);
注意:进行JSON.stringify()序列化的过程中,undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时),由上面可知,JS 提供的自有方法并不能彻底解决Array、Object的深拷贝问题,因此我们应该自己实现。
7.1.19.2.2、通用版
var obj1 = { name: "张三", age: 20, birthday: { year: 1997, month: 12, day: 5 }, speak: function () { console.log("我是" + this.name); } }; var obj2 = deepClone(obj1); // 当修改obj2的属性和方法的时候,obj1相应的属性和方法不会改变 obj2.name = "李四"; console.log(obj1); console.log(obj2); /** * 深拷贝通用方法 * @param obj 需要拷贝的对象 * @param has * @returns {any|RegExp|Date} */ function deepClone(obj, has = new WeakMap()) { // 类型检查 if (obj == null) return obj; if (obj instanceof Date) return obj; if (obj instanceof RegExp) return obj; if (!(typeof obj == "object")) return obj; // 构造对象 const newObj = new obj.constructor; // 防止自引用导致的死循环 if (has.get(obj)) return has.get(obj); has.set(obj, newObj); // 循环遍历属性及方法 for (let key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = deepClone(obj[key]); } } // 返回对象 return newObj; }
7.2、ECMAScript7新特性
7.2.1、数组方法扩展
Array.prototype.includes:此方法用来检测数组中是否包含某个元素,返回布尔类型值
const mingzhu = ["西游记", "红楼梦", "三国演义", "水浒传"]; console.log(mingzhu.includes("西游记")); • 1 • 2
7.2.2、幂运算
** 操作符的作用和 Math.pow 的作用是一样,请看代码:
console.log(2 ** 10); console.log(Math.pow(2, 10)); • 1 • 2
7.3、ECMAScript8新特性
7.3.1、async 函数
async 函数的语法:
async function fn(){ }
async 函数的返回值:
返回的结果不是一个 Promise 类型的对象,返回的结果就是成功 Promise 对象
返回的结果如果是一个 Promise 对象,具体需要看执行resolve方法还是reject方法
抛出错误,返回的结果是一个失败的 Promise
async 函数的演示:
//async 函数 async function fn() { return new Promise((resolve, reject) => { resolve('成功的数据'); // reject("失败的错误"); }); } const result = fn(); //调用 then 方法 result.then(value => { console.log(value); }, reason => { console.warn(reason); });
7.3.2、await 表达式
async 和 await 两种语法结合可以让异步代码像同步代码一样
await 表达式的注意事项:
await 必须写在 async 函数中
await 右侧的表达式一般为 promise 对象
await 返回的是 promise 成功的值
await 的 promise 失败了, 就会抛出异常, 需要通过 try…catch 捕获处理
await 表达式的语法演示:
//创建 promise 对象 const p = new Promise((resolve, reject) => { resolve("用户数据"); //reject("失败啦!"); }) //await 要放在 async 函数中. async function fun() { try { let result = await p; console.log(result); } catch (e) { console.log(e); } } //调用函数 fun();
await 表达式的案例演示:async与await封装AJAX请求
// 发送 AJAX 请求, 返回的结果是 Promise 对象 function sendAJAX(url) { return new Promise((resolve, reject) => { //1. 创建对象 const x = new XMLHttpRequest(); //2. 初始化 x.open('GET', url); //3. 发送 x.send(); //4. 事件绑定 x.onreadystatechange = function () { if (x.readyState === 4) { if (x.status >= 200 && x.status < 300) { resolve(x.response);//成功 } else { reject(x.status);//失败 } } } }) } // async 与 await 测试 async function fun() { //发送 AJAX 请求 1 let joke = await sendAJAX("https://api.apiopen.top/getJoke"); //发送 AJAX 请求 2 let tianqi = await sendAJAX('https://www.tianqiapi.com/api/?version=v1&city=%E5%8C%97%E4%BA%AC&appid=23941491&appsecret=TXoD5e8P') console.log(joke); console.error(tianqi);//为了区别数据,我这里用红色的error输出 } // 调用函数 fun();
7.3.3、对象方法拓展
- Object.keys()方法返回一个给定对象的所有可枚举键值的数组
- Object.values()方法返回一个给定对象的所有可枚举属性值的数组
- Object.entries()方法返回一个给定对象自身可遍历属性 [key,value] 的数组
//声明对象 const person = { name: "张三", age: 20 }; //获取对象所有的键 console.log(Object.keys(person)); //获取对象所有的值 console.log(Object.values(person)); //获取对象所有的键值对数组 console.log(Object.entries(person)); //创建 Map const m = new Map(Object.entries(person)); console.log(m.get("name"));
Object.getOwnPropertyDescriptors方法返回指定对象所有自身属性的描述对象
//声明对象 const person = { name: "张三", age: 20 }; //对象属性的描述对象 console.log(Object.getOwnPropertyDescriptors(person)); //声明对象 const obj = Object.create(null, { name: { //设置值 value: "李四", //属性特性 writable: true, configurable: true, enumerable: true }, age: { //设置值 value: 21, //属性特性 writable: true, configurable: true, enumerable: true } }); //对象属性的描述对象 console.log(Object.getOwnPropertyDescriptors(obj));
7.4、ECMAScript9新特性
7.4.1、对象拓展
Rest 参数与 spread 扩展运算符在 ES6 中已经引入,不过 ES6 中只针对于数组,在 ES9 中为对象提供了像数组一样的 rest 参数和扩展运算符
7.4.1.1、对象展开
function connect({host, port, ...user}) { console.log(host); console.log(port); console.log(user); } connect({ host: '127.0.0.1', port: 3306, username: 'root', password: 'root', type: 'master' });
7.4.1.2、对象合并
const skillOne = { q: '天音波' }; const skillTwo = { w: '金钟罩' }; const skillThree = { e: '天雷破' }; const skillFour = { r: '猛龙摆尾' }; const mangseng = {...skillOne, ...skillTwo, ...skillThree, ...skillFour}; console.log(mangseng);
7.4.2、正则表达式拓展
7.4.2.1、命名捕获分组
ES9 允许命名捕获组使用符号 ?<name> ,这样获取捕获结果可读性更强。使用数组下标不好吗?的确不好,因为如果一旦你想要获取的元素一旦增加,数组下标就改变了,所以建议使用命名捕获分组
let str = '<a href="https://www.baidu.com">打开百度,你就知道!</a>'; const reg = /<a href="(?<url>.*)">(?<text>.*)<\/a>/; const result = reg.exec(str); console.log(result.groups.url); console.log(result.groups.text);
7.4.2.2、正向断言
ES9 支持正向断言,通过对匹配结果后面的内容进行判断,对匹配进行筛选。
//声明字符串 let str = "订单编号开始123456789订单编号结束"; //正向断言 const reg = /\d+(?=订单编号结束)/;//也就是说数字的后边一定要跟着 订单编号结束 const result = reg.exec(str); console.log(result);
7.4.2.3、反向断言
ES9 支持反向断言,通过对匹配结果前面的内容进行判断,对匹配进行筛选。
//声明字符串 let str = "订单编号开始123456789订单编号结束"; //正向断言 const reg = /(?<=订单编号开始)\d+/;//也就是说数字的前边一定要跟着 订单编号开始 const result = reg.exec(str); console.log(result);
7.4.2.4、dotAll模式
正则表达式中点 . 匹配除回车外的任何单字符,标记 s 改变这种行为,允许行终止符出现,也就是dotAll模式
let str = ` <ul> <li> <a>肖生克的救赎</a> <p>上映日期: 1994-09-10</p> </li> <li> <a>阿甘正传</a> <p>上映日期: 1994-07-06</p> </li> </ul>`; //声明正则 const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/gs; // 执行匹配 let result; let data = []; while (result = reg.exec(str)) { data.push({title: result[1], time: result[2]}); } //输出结果 console.log(data);
7.5、ECMAScript10新特性
7.5.1、对象方法拓展
Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象。
//ES6:Map //ES10:Object.fromEntries const m = new Map(); m.set("name", "张三"); m.set("age", 20); const result = Object.fromEntries(m); console.log(result); //ES8:Object.entries const arr = Object.entries(result); console.log(arr);
7.5.2、字符串方法拓展
let str = " iloveyou "; console.log(str.trimStart());//只去除前边的空格 console.log(str.trimEnd());//只去除后边的空格 • 1 • 2 • 3
7.5.3、数组方法拓展
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回,说白了就是将多维数组转化为低维数组。
const arr1 = [1, 2, 3, 4, [5, 6]]; console.log(arr1.flat()); const arr2 = [1, 2, 3, 4, [5, 6, [7, 8, 9]]]; console.log(arr2.flat()); console.log(arr2.flat(1));//参数为深度是一个数字 console.log(arr2.flat(2));//参数为深度是一个数字
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 连着深度值为1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。
var arr1 = [1, 2, 3, 4]; console.log(arr1.map(x => x * 2)); var arr2 = [1, 2, 3, 4]; console.log(arr2.flatMap(x => x * 2));
7.5.4、Symbol属性拓展
Symbol.prototype.description用来读取Symbol的描述值
//创建 Symbol let s = Symbol("张三"); console.log(s.description); • 1 • 2 • 3
7.6、ECMAScript11新特性
7.6.1、class 私有属性
私有属性只能在class中访问
class Person { //公有属性 name; //私有属性 #age; #weight; //构造方法 constructor(name, age, weight) { this.name = name; this.#age = age; this.#weight = weight; } //普通方法 intro() { console.log(this.name); console.log(this.#age); console.log(this.#weight); } } //实例化 const girl = new Person("小可爱", 18, "45kg"); girl.intro();
7.6.2、Promise.allSettled
该Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。
相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。
//声明两个promise对象 const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("商品数据 - 1"); }, 1000); }); const p2 = new Promise((resolve, reject) => { setTimeout(() => { // resolve("商品数据 - 2"); reject("出错啦!"); }, 1000); }); //调用 allsettled 方法 const result1 = Promise.allSettled([p1, p2]); console.log(result1); //调用 all 方法 const result2 = Promise.all([p1, p2]); console.log(result2);
7.6.3、字符串方法扩展
String.prototype.matchAll() 方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。
let str = `<ul> <li> <a>肖生克的救赎</a> <p>上映日期: 1994-09-10</p> </li> <li> <a>阿甘正传</a> <p>上映日期: 1994-07-06</p> </li> </ul>`; //声明正则 const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/sg; //调用方法 const result = str.matchAll(reg); for (let v of result) { console.log(v); }
7.6.4、可选链操作符
当我们要使用传进来的一个属性值的时候,我们不知道这个属性值到底有没有传,我们可以使用&&运算符一级一级判断,就像这样 const dbHost = config && config.db && config.db.host;但是这样会显得很麻烦,所以在ES11 中就提供了可选链操作符,它就简化了代码,变成了这样 const dbHost = config?.db?.host; 另一方面,即使用户没有传入这个属性,我们用了也不会报错,而是undefined
function connect(config) { // const dbHost = config && config.db && config.db.host; const dbHost = config?.db?.host; console.log(dbHost); } connect({ db: { host: "192.168.1.100", username: "root" }, cache: { host: "192.168.1.200", username: "admin" } })
7.6.5、动态 import
以前我们import 导入模块是在一开始的时候就全部导入了,这样在模块很多的时候,会显得网页速度加载很慢,在ES11中就提供了一种动态import,案例演示如下:
m1.js
//分别暴露 export let school = "华北理工大学"; export function study() { console.log("我们要学习!"); }
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <button id="btn">点击我,加载m1.js模块</button> <!-- 在这里写JavaScript代码,因为JavaScript是由上到下执行的 --> <script type="module"> const btn = document.getElementById("btn"); btn.onclick = function(){ import("./m1.js").then(module => { module.study(); }); }; </script> </body> </html>
7.6.6、BigInt类型
BigInt数据类型的目的是比Number数据类型支持的范围更大的整数值。在对大整数执行数学运算时,以任意精度表示整数的能力尤为重要。使用BigInt,整数溢出将不再是问题。
此外,可以安全地使用更加准确时间戳,大整数ID等,而无需使用变通方法。 它就是JS 第二个数字数据类型,也将是 JS 第8种基本数据类型:
Boolean
Null
Undefined
Number
BigInt
String
Symbol
Object
对于学过其它语言的程序员来说,JS中缺少显式整数类型常常令人困惑。许多编程语言支持多种数字类型,如浮点型、双精度型、整数型和双精度型,但JS却不是这样。在JS中,按照IEEE 754-2008标准的定义,所有数字都以双精度64位浮点格式表示。
在此标准下,无法精确表示的非常大的整数将自动四舍五入。确切地说,JS 中的Number类型只能安全地表示-9007199254740991 (-(2^53-1)) 和9007199254740991(2^53-1)之间的整数,任何超出此范围的整数值都可能失去精度。
如何定义BigInt?需要在数字的后边加上一个n,例如;
let n = 521n;
我们接下来演示一下,大整数运算的效果:
let max = Number.MAX_SAFE_INTEGER; console.log(max); console.log(max + 1); console.log(max + 2); console.log(BigInt(max)); console.log(BigInt(max) + BigInt(1)); console.log(BigInt(max) + BigInt(2));
7.6.7、globalThis
全局属性 globalThis 包含全局的 this 值,类似于全局对象(global object)。
console.log(globalThis); • 1