目录
1.4.2、Symbol.for()、Symbol.keyFor()
1.8.9、普通计算:Math.cbrt、Math.imul、Math.hypot、Math.clz32
1.8.10、数字处理:Math.trunc、Math.fround
1.9.3、对象的新方法:Object.assign(target, source_1, ···)、Object.is(value1, value2)
1.10.1、数组创建:Array.of()、Array.from()
1、ECMAScript6新特性
ECMAScript 2015(ECMAScript 6)
ES2015(ES6) 新增加了两个重要的 JavaScript 关键字: let 和 const。
- let 声明的变量只在 let 命令所在的代码块内有效。
- const 声明一个只读的常量,一旦声明,常量的值就不能改变。
在 ES6 之前,JavaScript 只有两种作用域: 全局变量 与 函数内的局部变量。
全局变量
在函数外声明的变量作用域是全局的,全局变量在 JavaScript 程序的任何地方都可以访问。
var carName = "Volvo";
// 这里可以使用 carName 变量
function myFunction() {
// 这里也可以使用 carName 变量
}
局部变量
在函数内声明的变量作用域是局部的(函数内),函数内使用 var 声明的变量只能在函数内容访问,如果不使用 var 则是全局变量。
// 这里不能使用 carName 变量
function myFunction() {
var carName = "Volvo";
// 这里可以使用 carName 变量
}
// 这里不能使用 carName 变量
1.1、let 关键字
let 声明的变量只在 let 命令所在的代码块内有效。
let 关键字用来声明变量,使用 let 声明的变量有几个特点:
- 不允许重复声明
- 块级作用域
- 不存在变量提升
- 不影响作用域链
注意:以后声明变量使用 let 就对了
1.1.1、不允许重复声明
在相同的作用域或块级作用域中,不能使用 let 关键字来重置 var 关键字声明的变量:
var x = 2; // 合法
let x = 3; // 不合法
{
var x = 4; // 合法
let x = 5 // 不合法
}
在相同的作用域或块级作用域中,不能使用 let 关键字来重置 let 关键字声明的变量:
let x = 2; // 合法
let x = 3; // 不合法
{
let x = 4; // 合法
let x = 5; // 不合法
}
在相同的作用域或块级作用域中,不能使用 var 关键字来重置 let 关键字声明的变量:
let x = 2; // 合法
var x = 3; // 不合法
{
let x = 4; // 合法
var x = 5; // 不合法
}
let 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的:
let x = 2; // 合法
{
let x = 3; // 合法
}
{
let x = 4; // 合法
}
1.1.2、块级作用域
使用 var 关键字声明的变量不具备块级作用域的特性,它在 {} 外依然能被访问到。
{
var x = 2;
}
// 这里可以使用 x 变量
var x = 10;
// 这里输出 x 为 10
{
var x = 2;
// 这里输出 x 为 2
}
// 这里输出 x 为 2
在 ES6 之前,是没有块级作用域的概念的。
ES6 可以使用 let 关键字来实现块级作用域。
let 声明的变量只在 let 命令所在的代码块 {} 内有效,在 {} 之外不能访问。
{
let x = 2;
}
// 这里不能使用 x 变量
var x = 10;
// 这里输出 x 为 10
{
let x = 2;
// 这里输出 x 为 2
}
// 这里输出 x 为 10
1.1.3、不存在变量提升
JavaScript 中,var 关键字定义的变量可以在使用后声明,也就是变量可以先使用再声明(JavaScript 变量提升)。
// 在这里可以使用 carName 变量
var carName;
// 在这里可以不使用 carName 变量
let carName;
1.1.4、不影响作用域链
var i = 5;
for (var i = 0; i < 10; i++) {
// 一些代码...
}
// 这里输出 i 为 10
let i = 5;
for (let i = 0; i < 10; i++) {
// 一些代码...
}
// 这里输出 i 为 5
在第一个实例中,使用了 var 关键字,它声明的变量是全局的,包括循环体内与循环体外。
在第二个实例中,使用 let 关键字, 它声明的变量作用域只在循环体内,循环体外的变量不受影响。
1.2、const 关键字
const 用于声明一个或多个常量,声明时必须进行初始化,且初始化后值不可再修改
const 关键字用来声明常量,const 声明有以下特点:
- 不允许重复声明
- 块级作用域
- 声明必须赋初始值
- 值不允许修改
- 标识符一般为大写
注意:声明对象类型使用 const,非对象类型声明选择 let
// 声明常量
const MAX = 100;
console.log(MAX);
// 对于数组和对象的元素修改, 不算做对常量的修改, 不会报错
const TEAM1 = [1, 2, 3, 4];
const TEAM2 = [1, 2, 3, 4];
// 但是不能修改地址指向
// TEAM2 = TEAM1;
const PI = 3.141592653589793;
PI = 3.14; // 报错
PI = PI + 10; // 报错
1.2.1、并非真正的常量
const 的本质: const 定义的变量并非常量,并非不可变,它定义了一个常量引用一个值。使用 const 定义的对象或者数组,其实是可变的。下面的代码并不会报错:
// 创建常量对象
const car = {type:"Fiat", model:"500", color:"white"};
// 修改属性:
car.color = "red";
// 添加属性
car.owner = "Johnson";
// 创建常量数组
const cars = ["Saab", "Volvo", "BMW"];
// 修改元素
cars[0] = "Toyota";
// 添加元素
cars.push("Audi");
但是我们不能对常量对象数组重新赋值:
const car = {type:"Fiat", model:"500", color:"white"};
car = {type:"Volvo", model:"EX60", color:"red"}; // 错误
const cars = ["Saab", "Volvo", "BMW"];
cars = ["Toyota", "Volvo", "Audi"]; // 错误
文中说到 const 定义的变量并非不可改变,比如使用const声明对象,可以改变对象值。
那么什么情况能彻底“锁死”变量呢?
可以使用Object.freeze()方法来 冻结变量 ,如:
const obj = {
name:"1024kb"
}
Object.freeze(obj)
// 此时对象obj被冻结,返回被冻结的对象
需要注意的是,被冻结后的对象不仅仅是不能修改值,同时也
- 不能向这个对象添加新的属性
- 不能修改其已有属性的值
- 不能删除已有属性
- 不能修改该对象已有属性的可枚举性、可配置性、可写性
建议判断清除情况再进行使用
1.2.2、var let const 区别
- 使用var关键字声明的全局作用域变量属于window对象。
- 使用let关键字声明的全局作用域变量不属于window对象。
- 使用var关键字声明的变量在任何地方都可以修改。
- 在相同的作用域或块级作用域中,不能使用let关键字来重置var关键字声明的变量。
- 在相同的作用域或块级作用域中,不能使用let关键字来重置let关键字声明的变量。
- let关键字在不同作用域,或不用块级作用域中是可以重新声明赋值的。
- 在相同的作用域或块级作用域中,不能使用const关键字来重置var和let关键字声明的变量。
- 在相同的作用域或块级作用域中,不能使用const关键字来重置const关键字声明的变量
- const 关键字在不同作用域,或不同块级作用域中是可以重新声明赋值的:
- var关键字定义的变量可以先使用后声明。
- let关键字定义的变量需要先声明再使用。
- const关键字定义的常量,声明时必须进行初始化,且初始化后不可再修改。
1.3、变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。
解构赋值是对赋值运算符的扩展。
他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
注意:频繁使用对象方法、数组元素,就可以使用解构赋值形式
1.3.1、数组的解构赋值(Array)
//数组的解构赋值
const arr = ["张学友", "刘德华", "黎明", "郭富城"];
let [zhang, liu, li, guo] = arr;
console.log(zhang);
console.log(liu);
console.log(li);
console.log(guo);
1.3.2、对象模型的解构(Object)
简单对象的解构赋值:
//对象的解构赋值
const lin = {
name: "林志颖",
tags: ["车手", "歌手", "小旋风", "演员"]
};
let {name, tags} = lin;
console.log(name);
console.log(tags);
复杂对象的解构赋值:
//复杂对象的解构赋值
let wangfei = {
name: "王菲",
age: 18,
songs: ["红豆", "流年", "暧昧"],
history: [
{name: "窦唯"},
{name: "李亚鹏"},
{name: "谢霆锋"}
]
};
let {name, age, songs: [one, two, three], history: [first, second, third]} = wangfei;
console.log(name);
console.log(age);
console.log(one);
console.log(two);
console.log(three);
console.log(first);
console.log(second);
console.log(third);
1.4、Symbol类型
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。它是 JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型,
1.4.1、Symbol的使用
Symbol 特点如下:
- Symbol 的值是唯一的,用来解决命名冲突的问题
- Symbol 值不能与其它数据进行运算
- Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for...in 、 for...of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。
- Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。
- Symbol 作为对象属性名时不能用.运算符,要用方括号。
//创建 Symbol
let s1 = Symbol();
console.log(s1);
console.log(typeof s1);
//添加标识的 Symbol
let s2 = Symbol("张三");
let s2_2 = Symbol("张三");
console.log(s2);
console.log(s2_2);
console.log(s2 === s2_2);
//使用 Symbol for 定义
let s3 = Symbol.for("张三");
let s3_2 = Symbol.for("张三");
console.log(s3);
console.log(s3_2);
console.log(s3 === s3_2);
//在方法中使用 Symbol
let game = {
name: "狼人杀",
[Symbol('say')]: function () {
console.log("我可以发言")
},
[Symbol('zibao')]: function () {
console.log('我可以自爆');
}
};
console.log(game);
注意:遇到唯一性的场景时要想到 Symbol
1.4.2、Symbol.for()、Symbol.keyFor()
- Symbol.for()
Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。let yellow = Symbol("Yellow"); let yellow1 = Symbol.for("Yellow"); yellow === yellow1; // false let yellow2 = Symbol.for("Yellow"); yellow1 === yellow2; // true
-
Symbol.keyFor()
Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。let yellow1 = Symbol.for("Yellow"); Symbol.keyFor(yellow1); // "Yellow"
1.4.3、Symbol内置值
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。
可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。
内置值 | 描述 |
---|---|
Symbol.hasInstance | 当其它对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法 |
Symbol.isConcatSpreadable | 对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值,表示该对象用于 Array.prototype.concat()时, 是否可以展开 |
Symbol.species | 创建衍生对象时,会使用该属性 |
Symbol.match | 当执行 str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值 |
Symbol.replace | 当该对象被 str.replace(myObject)方法调用时,会返回该方法的返回值 |
Symbol.search | 当该对象被 str.search (myObject)方法调用时,会返回该方法的返回值 |
Symbol.split | 当该对象被 str.split(myObject)方法调用时,会返回该方法的返回值 |
Symbol.iterator | 当对象进行 for…of 循环时,会调用 Symbol.iterator 方法, 返回该对象的默认遍历器 |
Symbol.toPrimitive | 当对象被转为原始类型的值时,会调用这个方法,返 回该对象对应的原始类型值 |
Symbol. toStringTag | 当对象上面调用 toString 方法时,返回该方法的返 回值 |
Symbol. unscopables | 当对象指定了使用 with 关键字时,哪些属性会被 with 环境排除 |
Symbol.hasInstance演示:
class Person {
static [Symbol.hasInstance](param) {
console.log("我被用来检测类型了");
}
}
let o = {};
console.log(o instanceof Person);
Symbol.isConcatSpreadable演示:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr2[Symbol.isConcatSpreadable] = true;
console.log(arr1.concat(arr2));
const arr3 = [1, 2, 3];
const arr4 = [4, 5, 6];
arr4[Symbol.isConcatSpreadable] = false;
console.log(arr3.concat(arr4));
1.5 Map与Set
Map和Set是ES6新增的数据结构
1.5.1、Map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键” 的范围不限于字符串,各种类型的值(包括对象、函数、NaN)都可以当作键。Map 也实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。Map 的属性和方法:
- size:返回 Map 的元素个数
- set():增加一个新元素,返回当前 Map
- get():返回键名对象的键值
- has():检测 Map 中是否包含某个元素,返回 boolean 值
- clear():清空集合,返回 undefined
//创建一个空 map
let m = new Map();
//创建一个非空 map
let m2 = new Map([
["name", "张三"],
["gender", "女"]
]);
//属性和方法
//获取映射元素的个数
console.log(m2.size);
//添加映射值
console.log(m2.set("age", 6));
//获取映射值
console.log(m2.get("age"));
//检测是否有该映射
console.log(m2.has("age"));
//清除
console.log(m2.clear());
Map 的迭代:for...of、forEach()
- for...of
var myMap = new Map(); myMap.set(0, "zero"); myMap.set(1, "one"); // 将会显示两个 log。 一个是 "0 = zero" 另一个是 "1 = one" for (var [key, value] of myMap) { console.log(key + " = " + value); } for (var [key, value] of myMap.entries()) { console.log(key + " = " + value); } /* 这个 entries 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 */ // 将会显示两个log。 一个是 "0" 另一个是 "1" for (var key of myMap.keys()) { console.log(key); } /* 这个 keys 方法返回一个新的 Iterator 对象, 它按插入顺序包含了 Map 对象中每个元素的键。 */ // 将会显示两个log。 一个是 "zero" 另一个是 "one" for (var value of myMap.values()) { console.log(value); } /* 这个 values 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的值。 */
- forEach()
var myMap = new Map(); myMap.set(0, "zero"); myMap.set(1, "one"); // 将会显示两个 logs。 一个是 "0 = zero" 另一个是 "1 = one" myMap.forEach(function(value, key) { console.log(key + " = " + value); }, myMap)
Map 对象的操作
- Map 与 Array的转换
var kvArray = [["key1", "value1"], ["key2", "value2"]]; // Map 构造函数可以将一个 二维 键值对数组转换成一个 Map 对象 var myMap = new Map(kvArray); // 使用 Array.from 函数可以将一个 Map 对象转换成一个二维键值对数组 var outArray = Array.from(myMap);
- Map 的克隆
var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]); var myMap2 = new Map(myMap1); console.log(original === clone); // 打印 false。 Map 对象构造函数生成实例,迭代出新的对象。
- Map 的合并
var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]); var second = new Map([[1, 'uno'], [2, 'dos']]); // 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three var merged = new Map([...first, ...second]);
- Maps 和 Objects 的区别
- 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
- Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
1.5.2、Set
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯一的
- +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
- undefined 与 undefined 是恒等的,所以不重复;
- NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。
let mySet = new Set();
mySet.add(1); // Set(1) {1}
mySet.add(5); // Set(2) {1, 5}
mySet.add(5); // Set(2) {1, 5} 这里体现了值的唯一性
mySet.add("some text");
// Set(3) {1, 5, "some text"} 这里体现了类型的多样性
var o = {a: 1, b: 2};
mySet.add(o);
mySet.add({a: 1, b: 2});
// Set(5) {1, 5, "some text", {…}, {…}}
// 这里体现了对象之间引用不同不恒等,即使值相同,Set 也能存储
Set 对象作用
- 数组去重
var mySet = new Set([1, 2, 3, 4, 4]); [...mySet]; // [1, 2, 3, 4]
- 并集
var a = new Set([1, 2, 3]); var b = new Set([4, 3, 2]); var union = new Set([...a, ...b]); // {1, 2, 3, 4}
- 交集
var a = new Set([1, 2, 3]); var b = new Set([4, 3, 2]); var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}
- 差集
var a = new Set([1, 2, 3]); var b = new Set([4, 3, 2]); var difference = new Set([...a].filter(x => !b.has(x))); // {1}
集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历,集合的属性和方法:
- size:返回集合的元素个数
- add():增加一个新元素,返回当前集合
- delete():删除元素,返回 boolean 值
- has():检测集合中是否包含某个元素,返回 boolean 值
- clear():清空集合,返回 undefined
//创建一个空集合
let s = new Set();
//创建一个非空集合
let s1 = new Set([1, 2, 3, 1, 2, 3]);
//集合属性与方法
//返回集合的元素个数
console.log(s1.size);
//添加新元素
console.log(s1.add(4));
//删除元素
console.log(s1.delete(1));
//检测是否存在某个值
console.log(s1.has(2));
//清空集合
console.log(s1.clear());
1.6、Reflect 与 Proxy
Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。
1.6.1、Proxy
一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。
let target = {
name: 'Tom',
age: 24
}
let handler = {
get: function(target, key) {
console.log('getting '+key);
return target[key]; // 不是target.key
},
set: function(target, key, value) {
console.log('setting '+key);
target[key] = value;
}
}
let proxy = new Proxy(target, handler)
proxy.name // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set
// getting name
// setting age
// 25
// target 可以为空对象
let targetEpt = {}
let proxyEpt = new Proxy(targetEpt, handler)
// 调用 get 方法,此时目标对象为空,没有 name 属性
proxyEpt.name // getting name
// 调用 set 方法,向目标对象中添加了 name 属性
proxyEpt.name = 'Tom'
// setting name
// "Tom"
// 再次调用 get ,此时已经存在 name 属性
proxyEpt.name
// getting name
// "Tom"
// 通过构造函数新建实例时其实是对目标对象进行了浅拷贝,因此目标对象与代理对象会互相
// 影响
targetEpt
// {name: "Tom"}
// handler 对象也可以为空,相当于不设置拦截操作,直接访问目标对象
let targetEmpty = {}
let proxyEmpty = new Proxy(targetEmpty,{})
proxyEmpty.name = "Tom"
targetEmpty // {name: "Tom"}
Proxy方法
- get(target, propKey, receiver)
- set(target, propKey, value, receiver)
- apply(target, ctx, args)
- has(target, propKey)
- construct(target, args)
- deleteProperty(target, propKey)
- getOwnPropertyDescriptor(target, propKey)
- getPrototypeOf(target)
- isExtensible(target)
- ownKeys(target)
- preventExtensions(target)
- setPrototypeOf
- Proxy.revocable()
实例:
- get(target, propKey, receiver)
用于 target 对象上 propKey 的读取操作。get() 方法可以继承。let exam ={ name: "Tom", age: 24 } let proxy = new Proxy(exam, { get(target, propKey, receiver) { console.log('Getting ' + propKey); return target[propKey]; } }) proxy.name // Getting name // "Tom"
- set(target, propKey, value, receiver)
用于拦截 target 对象上的 propKey 的赋值操作。如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。第四个参数 receiver 表示原始操作行为所在对象,一般是 Proxy 实例本身。let validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('The age is not an integer'); } if (value > 200) { throw new RangeError('The age seems invalid'); } } // 对于满足条件的 age 属性以及其他属性,直接保存 obj[prop] = value; } }; let proxy= new Proxy({}, validator) proxy.age = 100; proxy.age // 100 proxy.age = 'oppps' // 报错 proxy.age = 300 // 报错
- apply(target, ctx, args)
用于拦截函数的调用、call 和 reply 操作。target 表示目标对象,ctx 表示目标对象上下文,args 表示目标对象的参数数组。function sub(a, b){ return a - b; } let handler = { apply: function(target, ctx, args){ console.log('handle apply'); return Reflect.apply(...arguments); } } let proxy = new Proxy(sub, handler) proxy(2, 1) // handle apply // 1
- has(target, propKey)
用于拦截 HasProperty 操作,即在判断 target 对象是否存在 propKey 属性时,会被这个方法拦截。此方法不判断一个属性是对象自身的属性,还是继承的属性。
注意:此方法不拦截 for ... in 循环。let handler = { has: function(target, propKey){ console.log("handle has"); return propKey in target; } } let exam = {name: "Tom"} let proxy = new Proxy(exam, handler) 'name' in proxy // handle has // true
- construct(target, args)
用于拦截 new 命令。返回值必须为对象。 - deleteProperty(target, [propKey])
用于拦截 delete 操作,如果这个方法抛出错误或者返回 false ,propKey 属性就无法被 delete 命令删除。用于拦截 Object.definePro若目标对象不可扩展,增加目标对象上不存在的属性会报错;若属性不可写或不可配置,则不能改变这些属性。 - getOwnPropertyDescriptor(target, propKey)
用于拦截 Object.getOwnPropertyD() 返回值为属性描述对象或者 undefined 。 - getPrototypeOf(target)
主要用于拦截获取对象原型的操作。包括以下操作:- Object.prototype._proto_ - Object.prototype.isPrototypeOf() - Object.getPrototypeOf() - Reflect.getPrototypeOf() - instanceof
- isExtensible(target)
用于拦截 Object.isExtensible 操作。
该方法只能返回布尔值,否则返回值会被自动转为布尔值。 - ownKeys(target)
用于拦截对象自身属性的读取操作。主要包括以下操作:
- Object.getOwnPropertyNames() - Object.getOwnPropertySymbols() - Object.keys() - or...in
- preventExtensions(target)
拦截 Object.preventExtensions 操作。
该方法必须返回一个布尔值,否则会自动转为布尔值。 - setPrototypeOf
主要用来拦截 Object.setPrototypeOf 方法。
返回值必须为布尔值,否则会被自动转为布尔值。
若目标对象不可扩展,setPrototypeOf 方法不得改变目标对象的原型。 - Proxy.revocable()
用于返回一个可取消的 Proxy 实例。
1.6.2、Reflect
ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上(当前某些方法会同时存在于 Object 和 Reflect 对象上),未来的新方法会只部署在 Reflect 对象上。
Reflect 对象对某些方法的返回结果进行了修改,使其更合理。
Reflect 对象使用函数的方式实现了 Object 的命令式操作。
Reflect方法
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.has(obj, name)
- Reflect.deleteProperty(obj, property)
- Reflect.construct(obj, args)
- Reflect.getPrototypeOf(obj)
- Reflect.setPrototypeOf(obj, newProto)
- Reflect.apply(func, thisArg, args)
- Reflect.defineProperty(target, propertyKey, attributes)
- Reflect.getOwnPropertyDescriptor(target, propertyKey)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.ownKeys(target)
实例:
- Reflect.get(target, name, receiver)
查找并返回 target 对象的 name 属性。let exam = { name: "Tom", age: 24, get info(){ return this.name + this.age; } } Reflect.get(exam, 'name'); // "Tom" // 当 target 对象中存在 name 属性的 getter 方法, getter 方法的 this 会绑定 // receiver let receiver = { name: "Jerry", age: 20 } Reflect.get(exam, 'info', receiver); // Jerry20 // 当 name 为不存在于 target 对象的属性时,返回 undefined Reflect.get(exam, 'birth'); // undefined // 当 target 不是对象时,会报错 Reflect.get(1, 'name'); // TypeError
- Reflect.set(target, name, value, receiver)
将 target 的 name 属性设置为 value。返回值为 boolean ,true 表示修改成功,false 表示失败。当 target 为不存在的对象时,会报错。let exam = { name: "Tom", age: 24, set info(value){ return this.age = value; } } exam.age; // 24 Reflect.set(exam, 'age', 25); // true exam.age; // 25 // value 为空时会将 name 属性清除 Reflect.set(exam, 'age', ); // true exam.age; // undefined // 当 target 对象中存在 name 属性 setter 方法时,setter 方法中的 this 会绑定 // receiver , 所以修改的实际上是 receiver 的属性, let receiver = { age: 18 } Reflect.set(exam, 'info', 1, receiver); // true receiver.age; // 1 let receiver1 = { name: 'oppps' } Reflect.set(exam, 'info', 1, receiver1); receiver1.age; // 1
- Reflect.has(obj, name)
是 name in obj 指令的函数化,用于查找 name 属性在 obj 对象中是否存在。返回值为 boolean。如果 obj 不是对象则会报错 TypeError。let exam = { name: "Tom", age: 24 } Reflect.has(exam, 'name'); // true
- Reflect.deleteProperty(obj, property)
是 delete obj[property] 的函数化,用于删除 obj 对象的 property 属性,返回值为 boolean。如果 obj 不是对象则会报错 TypeError。 - Reflect.construct(obj, args)
等同于 new target(...args)。 - Reflect.getPrototypeOf(obj)
用于读取 obj 的 _proto_ 属性。在 obj 不是对象时不会像 Object 一样把 obj 转为对象,而是会报错。 - Reflect.setPrototypeOf(obj, newProto)
用于设置目标对象的 prototype。 - Reflect.apply(func, thisArg, args)
等同于 Function.prototype.apply.call(func, thisArg, args) 。func 表示目标函数;thisArg 表示目标函数绑定的 this 对象;args 表示目标函数调用时传入的参数列表,可以是数组或类似数组的对象。若目标函数无法调用,会抛出 TypeError 。 - Reflect.defineProperty(target, propertyKey, attributes)
用于为目标对象定义属性。如果 target 不是对象,会抛出错误。 - Reflect.getOwnPropertyDescriptor(target, propertyKey)
用于得到 target 对象的 propertyKey 属性的描述对象。在 target 不是对象时,会抛出错误表示参数非法,不会将非对象转换为对象。 - Reflect.isExtensible(target)
用于判断 target 对象是否可扩展。返回值为 boolean 。如果 target 参数不是对象,会抛出错误。 - Reflect.preventExtensions(target)
用于让 target 对象变为不可扩展。如果 target 参数不是对象,会抛出错误。 - Reflect.ownKeys(target)
用于返回 target 对象的所有属性,等同于 Object.getOwnPropertyNames 与Object.getOwnPropertySymbols 之和。
1.6.3、Proxy、Reflect组合使用
Reflect 对象的方法与 Proxy 对象的方法是一一对应的。所以 Proxy 对象的方法可以通过调用 Reflect 对象的方法获取默认行为,然后进行额外操作。
let exam = {
name: "Tom",
age: 24
}
let handler = {
get: function(target, key){
console.log("getting "+key);
return Reflect.get(target,key);
},
set: function(target, key, value){
console.log("setting "+key+" to "+value)
Reflect.set(target, key, value);
}
}
let proxy = new Proxy(exam, handler)
proxy.name = "Jerry"
proxy.name
// setting name to Jerry
// getting name
// "Jerry"
1.7、字符串扩展
拓展的方法
- 子串的识别
- 字符串重复
- 字符串补全
- 模板字符串
- 标签模板
1.7.1、子串的识别
ES6 之前判断字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的识别方法。
- includes():返回布尔值,判断是否找到参数字符串。
- startsWith():返回布尔值,判断参数字符串是否在原字符串的头部。
- endsWith():返回布尔值,判断参数字符串是否在原字符串的尾部。
以上三个方法都可以接受两个参数,需要搜索的字符串,和可选的搜索起始位置索引。
let string = "apple,banana,orange";
string.includes("banana"); // true
string.startsWith("apple"); // true
string.endsWith("apple"); // false
string.startsWith("banana",6) // true
- 这三个方法只返回布尔值,如果需要知道子串的位置,还是得用 indexOf 和 lastIndexOf
- 这三个方法如果传入了正则表达式而不是字符串,会抛出错误。而 indexOf 和 lastIndexOf 这两个方法,它们会将正则表达式转换为字符串并搜索它。
1.7.2、字符串重复repeat()
repeat():返回新的字符串,表示将字符串重复指定次数返回。
console.log("Hello,".repeat(2)); // "Hello,Hello,"
//如果参数是小数,向下取整
console.log("Hello,".repeat(3.2)); // "Hello,Hello,Hello,"
//如果参数是 0 至 -1 之间的小数,会进行取整运算,0 至 -1 之间的小数取整得到 -0 ,等同于 repeat 零次
console.log("Hello,".repeat(-0.5)); // ""
//如果参数是 NaN,等同于 repeat 零次
console.log("Hello,".repeat(NaN)); // ""
//如果参数是负数或者 Infinity ,会报错:
console.log("Hello,".repeat(-1));
// RangeError: Invalid count value
console.log("Hello,".repeat(Infinity));
// RangeError: Invalid count value
//如果传入的参数是字符串,则会先将字符串转化为数字
console.log("Hello,".repeat("hh")); // ""
console.log("Hello,".repeat("2")); // "Hello,Hello,"
1.7.3、字符串补全
- padStart:返回新的字符串,表示用参数字符串从头部(左侧)补全原字符串。
- padEnd:返回新的字符串,表示用参数字符串从尾部(右侧)补全原字符串。
以上两个方法接受两个参数,第一个参数是指定生成的字符串的最小长度,第二个参数是用来补全的字符串。如果没有指定第二个参数,默认用空格填充。
console.log("h".padStart(5,"o")); // "ooooh"
console.log("h".padEnd(5,"o")); // "hoooo"
console.log("h".padStart(5)); // " h"
//如果指定的长度小于或者等于原字符串的长度,则返回原字符串:
console.log("hello".padStart(5,"A")); // "hello"
//如果原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串:
console.log("hello".padEnd(10,",world!")); // "hello,worl"
//常用于补全位数:
console.log("123".padStart(10,"0")); // "0000000123"
1.7.4、模板字符串
模板字符串(template string)是增强版的字符串,用反引号 ` 标识,特点:
- 字符串中可以出现换行符
- 可以使用 ${x} 形式输出变量,可用于变量拼接
注意:当遇到字符串与变量拼接的情况使用模板字符串
字符串中可以出现换行符:
//普通字符串
let string = `Hello'\n'world`;
console.log(string);
// "Hello'
// 'world"
//多行字符串:
let string1 = `Hey,
can you stop angry now?`;
console.log(string1);
// Hey,
// can you stop angry now?
//模板字符串中的换行和空格都是会被保留的
innerHtml = `<ul>
<li>menu</li>
<li>mine</li>
</ul>
`;
console.log(innerHtml);
// 输出
<ul>
<li>menu</li>
<li>mine</li>
</ul>
变量拼接:
//字符串插入变量和表达式。
变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式。
let name = "Mike";
let age = 27;
let info = `My Name is ${name},I am ${age+1} years old next year.`
console.log(info);
// My Name is Mike,I am 28 years old next year.
//字符串中调用函数:
function f(){
return "have fun!";
}
let string2= `Game start,${f()}`;
console.log(string2); // Game start,have fun!
1.7.5、标签模板
标签模板,是一个函数的调用,其中调用的参数是模板字符串。
alert`Hello world!`;
// 等价于
alert('Hello world!');
当模板字符串中带有变量,会将模板字符串参数处理成多个参数。
function f(stringArr,...values){
let result = "";
for(let i=0;i<stringArr.length;i++){
result += stringArr[i];
if(values[i]){
result += values[i];
}
}
return result;
}
let name = 'Mike';
let age = 27;
f`My Name is ${name},I am ${age+1} years old next year.`;
// "My Name is Mike,I am 28 years old next year."
f`My Name is ${name},I am ${age+1} years old next year.`;
// 等价于
f(['My Name is',',I am ',' years old next year.'],'Mike',28);
应用
过滤 HTML 字符串,防止用户输入恶意内容。
function f(stringArr,...values){
let result = "";
for(let i=0;i<stringArr.length;i++){
result += stringArr[i];
if(values[i]){
result += String(values[i]).replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
}
return result;
}
name = '<Amy&MIke>';
f`<p>Hi, ${name}.I would like send you some message.</p>`;
// <p>Hi, <Amy&MIke>.I would like send you some message.</p>
1.8、数值扩展
1.8.1、二进制和八进制
ES6 新增了二进制(前缀 0b 或 0B )和八进制(前缀 0o 或 0O )的表示方法
let b = 0b1010//二进制
let o = 0o777;//八进制
let d = 100;//十进制
let x = 0xff;//十六进制
console.log(b); //10
console.log(o); //511
console.log(d); //100
console.log(x); //255
1.8.2、Number.EPSILON
Number.EPSILON:它是 JavaScript 表示的最小精度,EPSILON 属性的值接近于 2.2204460492503130808472633361816E-16
function equal(a, b) {
if (Math.abs(a - b) < Number.EPSILON) {
return true;
} else {
return false;
}
}
console.log(0.1 + 0.2 === 0.3); //false
console.log(equal(0.1 + 0.2, 0.3)); //true
1.8.3、最大/最小安全整数
安全整数
安全整数表示在 JavaScript 中能够精确表示的整数,安全整数的范围在 2 的 -53 次方到 2 的 53 次方之间(不包括两个端点),超过这个范围的整数无法精确表示。
最大安全整数
安全整数范围的上限,即 2 的 53 次方减 1 。
Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2; // true
Number.MAX_SAFE_INTEGER === Number.MAX_SAFE_INTEGER + 1; // false
Number.MAX_SAFE_INTEGER - 1 === Number.MAX_SAFE_INTEGER - 2; // false
最小安全整数
安全整数范围的下限,即 2 的 53 次方减 1 的负数。
Number.MIN_SAFE_INTEGER + 1 === Number.MIN_SAFE_INTEGER + 2; // false
Number.MIN_SAFE_INTEGER === Number.MIN_SAFE_INTEGER - 1; // false
Number.MIN_SAFE_INTEGER - 1 === Number.MIN_SAFE_INTEGER - 2; // true
1.8.4、Number.isFinite
检测一个数值是否为有限数
console.log( Number.isFinite(1)); // true
console.log( Number.isFinite(0.1)); // true
// NaN 不是有限的
console.log( Number.isFinite(NaN)); // false
console.log( Number.isFinite(Infinity)); // false
console.log( Number.isFinite(-Infinity)); // false
// Number.isFinate 没有隐式的 Number() 类型转换,所有非数值都返回 false
console.log( Number.isFinite('foo')); // false
console.log( Number.isFinite('15')); // false
console.log( Number.isFinite(true)); // false
Number.isNaN()
用于检查一个值是否为 NaN 。
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN('true'/0)); // true
// 在全局的 isNaN() 中,以下皆返回 true,因为在判断前会将非数值向数值转换
// 而 Number.isNaN() 不存在隐式的 Number() 类型转换,非 NaN 全部返回 false
Number.isNaN("NaN"); // false
Number.isNaN(undefined); // false
Number.isNaN({}); // false
Number.isNaN("true"); // false
1.8.5、Number.isNaN
检测一个数值是否为 NaN
console.log(Number.isNaN(123));
1.8.6、Number.parseInt
将一个字符串转换为整数
// 不指定进制时默认为 10 进制
Number.parseInt('12.34'); // 12
Number.parseInt(12.34); // 12
// 指定进制
Number.parseInt('0011',2); // 3
// 与全局的 parseInt() 函数是同一个函数
Number.parseInt === parseInt; // true
1.8.7、Number.parseFloat
用于把一个字符串解析成浮点数。
Number.parseFloat('123.45') // 123.45
Number.parseFloat('123.45abc') // 123.45
// 无法被解析成浮点数,则返回 NaN
Number.parseFloat('abc') // NaN
// 与全局的 parseFloat() 方法是同一个方法
Number.parseFloat === parseFloat // true
1.8.8、Number.isInteger
用于判断给定的参数是否为整数。
Number.isInteger(value)
Number.isInteger(0); // true
// JavaScript 内部,整数和浮点数采用的是同样的储存方法,因此 1 与 1.0 被视为相同的值
Number.isInteger(1); // true
Number.isInteger(1.0); // true
Number.isInteger(1.1); // false
Number.isInteger(Math.PI); // false
// NaN 和正负 Infinity 不是整数
Number.isInteger(NaN); // false
Number.isInteger(Infinity); // false
Number.isInteger(-Infinity); // false
Number.isInteger("10"); // false
Number.isInteger(true); // false
Number.isInteger(false); // false
Number.isInteger([1]); // false
// 数值的精度超过 53 个二进制位时,由于第 54 位及后面的位被丢弃,会产生误判
Number.isInteger(1.0000000000000001) // true
// 一个数值的绝对值小于 Number.MIN_VALUE(5E-324),即小于 JavaScript 能够分辨
// 的最小值,会被自动转为 0,也会产生误判
Number.isInteger(5E-324); // false
Number.isInteger(5E-325); // true
1.8.9、普通计算:Math.cbrt、Math.imul、Math.hypot、Math.clz32
- Math.cbrt
用于计算一个数的立方根。Math.cbrt(1); // 1 Math.cbrt(0); // 0 Math.cbrt(-1); // -1 // 会对非数值进行转换 Math.cbrt('1'); // 1 // 非数值且无法转换为数值时返回 NaN Math.cbrt('hhh'); // NaN
- Math.imul
两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。// 大多数情况下,结果与 a * b 相同 Math.imul(1, 2); // 2 // 用于正确返回大数乘法结果中的低位数值 Math.imul(0x7fffffff, 0x7fffffff); // 1
- Math.hypot
用于计算所有参数的平方和的平方根。Math.hypot(3, 4); // 5 // 非数值会先被转换为数值后进行计算 Math.hypot(1, 2, '3'); // 3.741657386773941 Math.hypot(true); // 1 Math.hypot(false); // 0 // 空值会被转换为 0 Math.hypot(); // 0 Math.hypot([]); // 0 // 参数为 Infinity 或 -Infinity 返回 Infinity Math.hypot(Infinity); // Infinity Math.hypot(-Infinity); // Infinity // 参数中存在无法转换为数值的参数时返回 NaN Math.hypot(NaN); // NaN Math.hypot(3, 4, 'foo'); // NaN Math.hypot({}); // NaN
- Math.clz32
用于返回数字的32 位无符号整数形式的前导0的个数。Math.clz32(0); // 32 Math.clz32(1); // 31 Math.clz32(0b01000000000100000000000000000000); // 1 // 当参数为小数时,只考虑整数部分 Math.clz32(0.5); // 32 // 对于空值或非数值,会转化为数值再进行计算 Math.clz32('1'); // 31 Math.clz32(); // 32 Math.clz32([]); // 32 Math.clz32({}); // 32 Math.clz32(NaN); // 32 Math.clz32(Infinity); // 32 Math.clz32(-Infinity); // 32 Math.clz32(undefined); // 32 Math.clz32('hhh'); // 32
1.8.10、数字处理:Math.trunc、Math.fround
- Math.trunc:将数字的小数部分抹掉
Math.trunc(12.3); // 12 Math.trunc(12); // 12 // 整数部分为 0 时也会判断符号 Math.trunc(-0.5); // -0 Math.trunc(0.5); // 0 // Math.trunc 会将非数值转为数值再进行处理 Math.trunc("12.3"); // 12 // 空值或无法转化为数值时时返回 NaN Math.trunc(); // NaN Math.trunc(NaN); // NaN Math.trunc("hhh"); // NaN Math.trunc("123.2hhh"); // NaN
- Math.fround:用于获取数字的32位单精度浮点数形式。
// 对于 2 的 24 次方取负至 2 的 24 次方之间的整数(不含两个端点),返回结果与参数本身一致 Math.fround(-(2**24)+1); // -16777215 Math.fround(2 ** 24 - 1); // 16777215 // 用于将 64 位双精度浮点数转为 32 位单精度浮点数 Math.fround(1.234) // 1.125 // 当小数的精度超过 24 个二进制位,会丢失精度 Math.fround(0.3); // 0.30000001192092896 // 参数为 NaN 或 Infinity 时返回本身 Math.fround(NaN) // NaN Math.fround(Infinity) // Infinity // 参数为其他非数值类型时会将参数进行转换 Math.fround('5'); // 5 Math.fround(true); // 1 Math.fround(null); // 0 Math.fround([]); // 0 Math.fround({}); // NaN
1.8.11、判断Math.sign
判断一个数到底为正数、负数、还是零
Math.sign(1); // 1
Math.sign(-1); // -1
// 参数为 0 时,不同符号的返回不同
Math.sign(0); // 0
Math.sign(-0); // -0
// 判断前会对非数值进行转换
Math.sign('1'); // 1
Math.sign('-1'); // -1
// 参数为非数值(无法转换为数值)时返回 NaN
Math.sign(NaN); // NaN
Math.sign('hhh'); // NaN
1.9、对象扩展
ES6 新增了一些 Object 对象的方法,例如:
- Object.is:比较两个值是否严格相等,与『===』行为基本一致(+0 与 NaN)
- Object.assign:对象的合并,将源对象的所有可枚举属性,复制到目标对象
- __proto__、setPrototypeOf、 setPrototypeOf可以直接设置对象的原型
1.9.1、简化对象写法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法,这样的书写更加简洁。
注意:对象简写形式简化了代码,所以以后用简写就对了
let name = "张三";
let age = 18;
let speak = function () {
console.log(this.name);
};
//属性和方法简写
let person = {
name,
age,
speak
};
console.log(person.name);
console.log(person.age);
person.speak();
1.9.2、对象(数组)的拓展运算符
扩展运算符(spread)也是三个点(…)用于取出参数对象(数组)所有可遍历属性然后拷贝到当前对象,它好比 rest 参数的逆运算,将一个对象(数组)转为用逗号分隔的参数序列,对数组进行解包。
展开数组
// 展开数组
let tfboys = ["德玛西亚之力", "德玛西亚之翼", "德玛西亚皇子"];
function fn() {
console.log(arguments);
}
fn(...tfboys);
展开对象
// 展开对象
let skillOne = {
q: "致命打击"
};
let skillTwo = {
w: "勇气"
};
let skillThree = {
e: "审判"
};
let skillFour = {
r: "德玛西亚正义"
};
let gailun = {...skillOne, ...skillTwo, ...skillThree, ...skillFour};
console.log(gailun);
let age = {age: 15};
let name = {name: "Amy"};
let person = {...age, ...name};
person; //{age: 15, name: "Amy"}
//注意点
//自定义的属性和拓展运算符对象里面属性的相同的时候:自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉。
let person1 = {name: "Amy", age: 15};
let someone = { ...person1, name: "Mike", age: 17};
someone; //{name: "Mike", age: 17}
1.9.3、对象的新方法:Object.assign(target, source_1, ···)、Object.is(value1, value2)
- Object.is(value1, value2):用来比较两个值是否严格相等,与(===)基本类似。
Object.is("q","q"); // true Object.is(1,1); // true Object.is([1],[1]); // false Object.is({q:1},{q:1}); // false //与(===)的区别 //一是+0不等于-0 Object.is(+0,-0); //false +0 === -0 //true //二是NaN等于本身 Object.is(NaN,NaN); //true NaN === NaN //false
- Object.assign:对象的合并,后边的对象会把前边对象的相同属性和方法覆盖,没有的属性和方法会合并
- 如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。
- 如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。
- assign 的属性拷贝是浅拷贝
const config1 = {
host: "localhost",
port: 3306,
name: "zhangsan",
pass: "root",
test1: "test1"
};
const config2 = {
host: "127.0.0.1",
port: 3309,
name: "lisi",
pass: "root",
test2: "test2"
}
console.log(Object.assign(config1, config2));
1.9.4、设置原型对象
- Object.setPrototypeOf:设置原型对象
- Object.getPrototypeof:获取原型对象
const school = {
name: "MySchool"
};
const cities = {
xiaoqu: ["北京", "上海", "深圳"]
};
Object.setPrototypeOf(school, cities);
console.log(Object.getPrototypeOf(school));
console.log(school);
1.10、数组扩展
1.10.1、数组创建:Array.of()、Array.from()
- Array.of():将参数中所有值作为元素形成数组。
console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4] // 参数值可为不同类型 console.log(Array.of(1, '2', true)); // [1, '2', true] // 参数为空时返回空数组 console.log(Array.of()); // []
- Array.from():将类数组对象或可迭代对象转化为数组。
// 参数为数组,返回与原数组一样的数组 console.log(Array.from([1, 2])); // [1, 2] // 参数含空位 console.log(Array.from([1, , 3])); // [1, undefined, 3]
Array.from(arrayLike[, mapFn[, thisArg]])
1.10.2、扩展的方法
- find()
- findIndex()
- fill()
- copyWithin()
- entries()
- keys()
- values()
- includes()
- flat()
- flatMap()
实例
- find():查找数组中符合条件的元素,若有多个符合条件的元素,则返回第一个元素。
let arr = Array.of(1, 2, 3, 4); console.log(arr.find(item => item > 2)); // 3 // 数组空位处理为 undefined console.log([, 1].find(n => true)); // undefined
- findIndex():查找数组中符合条件的元素索引,若有多个符合条件的元素,则返回第一个元素索引。
let arr = Array.of(1, 2, 1, 3); // 参数1:回调函数 // 参数2(可选):指定回调函数中的 this 值 console.log(arr.findIndex(item => item == 2)); // 1 // 数组空位处理为 undefined console.log([, 1].findIndex(n => true)); //0
- fill():将一定范围索引的数组元素内容填充为单个指定的值。
let arr = Array.of(1, 2, 3, 4); // 参数1:用来填充的值 // 参数2:被填充的起始索引 // 参数3(可选):被填充的结束索引,默认为数组末尾 console.log(arr.fill(0,1,2)); // [1, 0, 3, 4]
- copyWithin():将一定范围索引的数组元素修改为此数组另一指定范围索引的元素。
// 参数1:被修改的起始索引 // 参数2:被用来覆盖的数据的起始索引 // 参数3(可选):被用来覆盖的数据的结束索引,默认为数组末尾 console.log([1, 2, 3, 4].copyWithin(0,2,4)); // [3, 4, 3, 4] // 参数1为负数表示倒数 console.log([1, 2, 3, 4].copyWithin(-2, 0)); // [1, 2, 1, 2] console.log([1, 2, ,4].copyWithin(0, 2, 4)); // [, 4, , 4]
- 遍历
- entries():遍历键值对。
- keys():遍历键名。
- values():遍历键值。
for(let [key, value] of ['a', 'b'].entries()){
console.log(key, value);
}
// 0 "a"
// 1 "b"
// 不使用 for... of 循环
let entries = ['a', 'b'].entries();
console.log(entries.next().value); // [0, "a"]
console.log(entries.next().value); // [1, "b"]
// 数组含空位
console.log([...[,'a'].entries()]); // [[0, undefined], [1, "a"]]
for(let key of ['a', 'b'].keys()){
console.log(key);
}
// 0
// 1
// 数组含空位
console.log([...[,'a'].keys()]); // [0, 1]
for(let value of ['a', 'b'].values()){
console.log(value);
}
// "a"
// "b"
// 数组含空位
console.log([...[,'a'].values()]); // [undefined, "a"]
- includes():
数组是否包含指定值。
注意:与 Set 和 Map 的 has 方法区分;Set 的 has 方法用于查找值;Map 的 has 方法用于查找键名。
// 参数1:包含的指定值 [1, 2, 3].includes(1); // true // 参数2:可选,搜索的起始索引,默认为0 [1, 2, 3].includes(1, 2); // false // NaN 的包含判断 [1, NaN, 3].includes(NaN); // true
- flat():嵌套数组转一维数组
console.log([1 ,[2, 3]].flat()); // [1, 2, 3] // 指定转换的嵌套层数 console.log([1, [2, [3, [4, 5]]]].flat(2)); // [1, 2, 3, [4, 5]] // 不管嵌套多少层 console.log([1, [2, [3, [4, 5]]]].flat(Infinity)); // [1, 2, 3, 4, 5] // 自动跳过空位 console.log([1, [2, , 3]].flat());<p> // [1, 2, 3]
- flatMap():先对数组中每个元素进行了的处理,再对数组执行 flat() 方法。
1.10.3、数组缓冲区
数组缓冲区是内存中的一段地址。
定型数组的基础。
实际字节数在创建时确定,之后只可修改其中的数据,不可修改大小。
- 创建数组缓冲区:通过构造函数创建
let buffer = new ArrayBuffer(10); console.log(buffer.byteLength); // 10 分割已有数组缓冲区 let buffer = new ArrayBuffer(10); let buffer1 = buffer.slice(1, 3); console.log(buffer1.byteLength); // 2
- 视图:视图是用来操作内存的接口。
视图可以操作数组缓冲区或缓冲区字节的子集,并按照其中一种数值数据类型来读取和写入数据。
DataView 类型是一种通用的数组缓冲区视图,其支持所有8种数值型数据类型。// 默认 DataView 可操作数组缓冲区全部内容 let buffer = new ArrayBuffer(10); dataView = new DataView(buffer); dataView.setInt8(0,1); console.log(dataView.getInt8(0)); // 1 // 通过设定偏移量(参数2)与长度(参数3)指定 DataView 可操作的字节范围 let buffer1 = new ArrayBuffer(10); dataView1 = new DataView(buffer1, 0, 3); dataView1.setInt8(5,1); // RangeError
1.10.4、定型数组
数组缓冲区的特定类型的视图。
可以强制使用特定的数据类型,而不是使用通用的 DataView 对象来操作数组缓冲区。
- 创建
通过数组缓冲区生成let buffer = new ArrayBuffer(10), view = new Int8Array(buffer); console.log(view.byteLength); // 10
通过构造函数
let view = new Int32Array(10); console.log(view.byteLength); // 40 console.log(view.length); // 10 // 不传参则默认长度为0 // 在这种情况下数组缓冲区分配不到空间,创建的定型数组不能用来保存数据 let view1 = new Int32Array(); console.log(view1.byteLength); // 0 console.log(view1.length); // 0 // 可接受参数包括定型数组、可迭代对象、数组、类数组对象 let arr = Array.from({ 0: '1', 1: '2', 2: 3, length: 3 }); let view2 = new Int16Array([1, 2]), view3 = new Int32Array(view2), view4 = new Int16Array(new Set([1, 2, 3])), view5 = new Int16Array([1, 2, 3]), view6 = new Int16Array(arr); console.log(view2 .buffer === view3.buffer); // false console.log(view4.byteLength); // 6 console.log(view5.byteLength); // 6 console.log(view6.byteLength); // 6
1.10.5、扩展运算符
- 复制数组
let arr = [1, 2], arr1 = [...arr]; console.log(arr1); // [1, 2] // 数组含空位 let arr2 = [1, , 3], arr3 = [...arr2]; console.log(arr3); [1, undefined, 3]
- 合并数组
console.log([...[1, 2],...[3, 4]]); // [1, 2, 3, 4]
1.11、函数扩展
1.11.1、默认参数
基本用法
function fn(name,age=17){
console.log(name+","+age);
}
fn("Amy",18); // Amy,18
fn("Amy",""); // Amy,
fn("Amy"); // Amy,17
注意点:使用函数默认参数时,不允许有同名参数。
只有在未传递参数,或者参数为 undefined 时,才会使用默认参数,null 值被认为是有效的值传递。
function fn(name,age=17){
console.log(name+","+age);
}
fn("Amy",null); // Amy,null
1.11.2、不定参数
不定参数用来表示不确定参数个数,形如,...变量名,由...加上一个具名参数标识符组成。具名参数只能放在参数组的最后,并且有且只有一个不定参数。
function f(...values){
console.log(values.length);
}
f(1,2); //2
f(1,2,3,4); //4
1.11.3、箭头函数
ES6 允许使用「箭头」(=>)定义函数,通用写法如下:
参数 => 函数体
let fn = (arg1, arg2, arg3) => {
return arg1 + arg2 + arg3;
}
箭头函数的注意点:
- 如果形参只有一个,则小括号可以省略
- 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果
- 箭头函数 this 指向声明时所在作用域下 this 的值,箭头函数不会更改 this 指向,用来指定回调函数会非常合适
- 箭头函数不能作为构造函数实例化
- 不能使用 arguments 实参
省略小括号的情况:
let fn = num => {
return num * 10;
};
省略花括号的情况:
let fn = score => score * 20;
this 指向声明时所在作用域中 this 的值:
// this 指向声明时所在作用域中 this 的值
let fn = () => {
console.log(this);
}
fn();
let school = {
name: "张三",
getName() {
let subFun = () => {
console.log(this);
}
subFun();
}
};
school.getName();
1.11.4、rest 参数
ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments 参数。
注意:rest 参数非常适合不定个数参数函数的场景
// 作用与 arguments 类似
function add(...args) {
console.log(args);
}
add(1, 2, 3, 4, 5);
// rest 参数必须是最后一个形参
function minus(a, b, ...args) {
console.log(a, b, args);
}
minus(100, 1, 2, 3, 4, 5, 19);
1.12、class 类
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是 一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已,它的一些如下:
- class:声明类
- constructor:定义构造函数初始化
-
getter / setter:封装
- extends:继承父类
- super:调用父级构造方法
- static:定义静态方法和属性
//父类
class Phone {
//构造方法
constructor(brand, color, price) {
this.brand = brand;
this.color = color;
this.price = price;
}
get color(){
console.log('getter');
return this.color;
}
set color(color){
console.log('setter');
this.color = color; // 自身递归调用
}
//对象方法
call() {
console.log("我可以打电话!!!")
}
}
//子类
class SmartPhone extends Phone {
constructor(brand, color, price, screen, pixel) {
super(brand, color, price);
this.screen = screen;
this.pixel = pixel;
}
//子类方法
photo() {
console.log("我可以拍照!!");
}
playGame() {
console.log("我可以玩游戏!!");
}
//方法重写
call() {
console.log("我可以进行视频通话!!");
}
//静态方法
static run() {
console.log("我可以运行程序")
}
static connect() {
console.log("我可以建立连接")
}
}
//实例化对象
const Nokia = new Phone("诺基亚", "灰色", 230);
const iPhone6s = new SmartPhone("苹果", "白色", 6088, "4.7inch", "500w");
//调用子类方法
iPhone6s.playGame();
//调用重写方法
iPhone6s.call();
//调用静态方法
SmartPhone.run();
1.13、模块化
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。
在 ES6 前, 实现模块化使用的是 RequireJS 或者 seaJS(分别是基于 AMD 规范的模块化库, 和基于 CMD 规范的模块化库)。
ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。
ES6 的模块化分为导出(export) @与导入(import)两个模块。
特点
ES6 的模块自动开启严格模式,不管你有没有在模块头部加上 use strict;。
模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。
每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。
每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取。
1.13.1、模块化的好处
- 防止命名冲突
- 代码复用
- 高维护性
1.13.2、模块化的语法
模块导入导出各种类型的变量,如字符串,数值,函数,类。
- 导出的函数声明与类声明必须要有名称(export default 命令另外考虑)。
- 不仅能导出声明还能导出引用(例如函数)。
- export 命令可以出现在模块的任何位置,但必需处于模块顶层。
- import 命令会提升到整个模块的头部,首先执行。
1.13.3、export 和 import
模块功能主要由两个命令构成:export 和 import。
- export 命令用于规定模块的对外接口
- import 命令用于输入其它模块提供的功能
/*-----export [test.js]-----*/
let myName = "Tom";
let myAge = 20;
let myfn = function(){
return "My name is" + myName + "! I'm '" + myAge + "years old."
}
let myClass = class myClass {
static a = "yeah!";
}
export { myName, myAge, myfn, myClass }
/*-----import [xxx.js]-----*/
import { myName, myAge, myfn, myClass } from "./test.js";
console.log(myfn());// My name is Tom! I'm 20 years old.
console.log(myAge);// 20
console.log(myName);// Tom
console.log(myClass.a );// yeah!
建议使用大括号指定所要输出的一组变量写在文档尾部,明确导出的接口。
函数与类都需要有对应的名称,导出文档尾部也避免了无对应名称。
1.13.4、as
export 命令导出的接口名称,须和模块内部的变量有一一对应关系。
导入的变量名,须和导出的接口名称相同,即顺序可以不一致。
/*-----export [test.js]-----*/
let myName = "Tom";
export { myName as exportName }
/*-----import [xxx.js]-----*/
import { exportName } from "./test.js";
console.log(exportName);// Tom
使用 as 重新定义导出的接口名称,隐藏模块内部的变量
/*-----export [test1.js]-----*/
let myName = "Tom";
export { myName }
/*-----export [test2.js]-----*/
let myName = "Jerry";
export { myName }
/*-----import [xxx.js]-----*/
import { myName as name1 } from "./test1.js";
import { myName as name2 } from "./test2.js";
console.log(name1);// Tom
console.log(name2);// Jerry
1.13.5、export default
- 在一个文件或模块中,export、import 可以有多个,export default 仅有一个。
- export default 中的 default 是对应的导出接口变量。
- 通过 export 方式导出,在导入时要加{ },export default 则不需要。
- export default 向外暴露的成员,可以使用任意变量来接收。
var a = "My name is Tom!";
export default a; // 仅有一个
export default var c = "error";
// error,default 已经是对应的导出变量,不能跟着变量声明语句
import b from "./xxx.js"; // 不需要加{}, 使用任意变量接收
1.13.6、复合使用
注:import() 是提案,这边暂时不延伸讲解。
export 与 import 可以在同一模块使用,使用特点:
- 可以将导出接口改名,包括 default。
- 复合使用 export 与 import ,也可以导出全部,当前模块导出的接口会覆盖继承导出的。
export { foo, bar } from "methods";
// 约等于下面两段语句,不过上面导入导出方式该模块没有导入 foo 与 bar
import { foo, bar } from "methods";
export { foo, bar };
/* ------- 特点 1 --------*/
// 普通改名
export { foo as bar } from "methods";
// 将 foo 转导成 default
export { foo as default } from "methods";
// 将 default 转导成 foo
export { default as foo } from "methods";
/* ------- 特点 2 --------*/
export * from "methods";
1.13.7、模块化的暴露
m1.js
//方式一:分别暴露
export let school = "华北理工大学";
export function study() {
console.log("我们要学习!");
}
m2.js
//方式二:统一暴露
let school = "华北理工大学";
function findJob() {
console.log("我们要找工作!");
}
export {school, findJob};
m3.js
//方式三:默认暴露
export default {
school: "华北理工大学",
change: function () {
console.log("我们要改变自己!");
}
}
1.13.8、模块化的导入
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<!-- 在这里写JavaScript代码,因为JavaScript是由上到下执行的 -->
<script type="module">
// 引入 m1.js 模块内容
import * as m1 from "./m1.js";
// 引入 m2.js 模块内容
import * as m2 from "./m2.js";
// 引入 m3.js 模块内容
import * as m3 from "./m3.js";
m1.study();
m2.findJob();
m3.default.change();
</script>
</body>
</html>
1.13.9、解构赋值形式
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<!-- 在这里写JavaScript代码,因为JavaScript是由上到下执行的 -->
<script type="module">
// 引入 m1.js 模块内容
import {school, study} from "./m1.js";
// 引入 m2.js 模块内容
import {school as s, findJob} from "./m2.js";
// 引入 m3.js 模块内容
import {default as m3} from "./m3.js";
console.log(school);
study();
console.log(s);
findJob();
console.log(m3);
m3.change();
</script>
</body>
</html>
注意:针对默认暴露还可以直接
import m3 from "./m3.js"
1.14、Promise
Promise 是 ES6 引入的异步编程的新解决方案,语法上 Promise 是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。
1.14.1、Promise基本使用
//实例化 Promise 对象
const p = new Promise(function (resolve, reject) {
setTimeout(function () {
// 成功调用resolve()处理
let data = "数据读取成功";
resolve(data);
// 失败调用reject()处理
let err = "数据读取失败";
reject(err);
}, 1000);
});
//调用 promise 对象的 then 方法
p.then(function (value) {
console.log(value);
}, function (reason) {
console.error(reason);
});
1.14.2、Promise案例演示
案例演示:
// 接口地址: https://api.apiopen.top/getJoke
const p = new Promise((resolve, reject) => {
//1. 创建对象
const xhr = new XMLHttpRequest();
//2. 初始化
xhr.open("GET", "https://api.apiopen.top/getJoke");
//3. 发送
xhr.send();
//4. 绑定事件, 处理响应结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
//判断响应状态码 200-299
if (xhr.status >= 200 && xhr.status < 300) {
//表示成功
resolve(xhr.response);
} else {
//如果失败
reject(xhr.status);
}
}
}
});
//指定回调
p.then(function (value) {
console.log(value);
}, function (reason) {
console.error(reason);
});
1.14.3、Promise-then方法
调用 then 方法,then 方法的返回结果是 Promise 对象,对象状态由回调函数的执行结果决定,如果回调函数中返回的结果是 非 promise 类型的属性,状态为成功,返回值为对象的成功的值
//创建 promise 对象
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("用户数据");
}, 1000)
});
//链式调用+箭头函数
p.then(value => {
console.log(value);
return value;
}).then(value => {
console.log(value);
});
1.14.4、Promise-catch方法
如果只想处理错误状态,我们可以使用 catch 方法
const p = new Promise((resolve, reject) => {
setTimeout(() => {
//设置 p 对象的状态为失败, 并设置失败的值
reject("出错啦!");
}, 1000);
});
p.catch(function (reason) {
console.error(reason);
});
1.15、生成器Generator
生成器函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
1.15.1、生成器函数使用
代码说明:
- * 的位置没有限制
- 生成器函数返回的结果是迭代器对象,调用迭代器对象的 next 方法可以得到 yield 语句后的值
- yield 相当于函数的暂停标记,也可以认为是函数的分隔符,每调用一次 next 方法,执行一段代码
- next 方法可以传递实参,作为 yield 语句的返回值
function * gen() {
/*代码1开始执行*/
console.log("代码1执行了");
yield "一只没有耳朵";
/*代码2开始执行*/
console.log("代码2执行了");
yield "一只没有尾巴";
/*代码3开始执行*/
console.log("代码3执行了");
return "真奇怪";
}
let iterator = gen();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log("===============");
//遍历
for (let v of gen()) {
console.log(v);
}
1.15.2、生成器函数参数
function * gen(arg) {
console.log(arg);
let one = yield 111;
console.log(one);
let two = yield 222;
console.log(two);
let three = yield 333;
console.log(three);
}
//执行获取迭代器对象
let iterator = gen('AAA');
console.log(iterator.next());
//next方法可以传入实参
console.log(iterator.next('BBB'));
console.log(iterator.next('CCC'));
console.log(iterator.next('DDD'));
1.15.3、生成器函数实例
案例演示:1s后控制台输出 111,2s后输出 222,3s后输出 333
function one() {
setTimeout(() => {
console.log(111);
iterator.next();
}, 1000)
}
function two() {
setTimeout(() => {
console.log(222);
iterator.next();
}, 2000)
}
function three() {
setTimeout(() => {
console.log(333);
iterator.next();
}, 3000)
}
function * gen() {
yield one();
yield two();
yield three();
}
//调用生成器函数
let iterator = gen();
iterator.next();
案例演示:模拟获取 ,用户数据 ,订单数据 ,商品数据
function getUsers() {
setTimeout(() => {
let data = "用户数据";
iterator.next(data);
}, 1000);
}
function getOrders() {
setTimeout(() => {
let data = "订单数据";
iterator.next(data);
}, 1000);
}
function getGoods() {
setTimeout(() => {
let data = "商品数据";
iterator.next(data);
}, 1000);
}
function * gen() {
let users = yield getUsers();
console.log(users);
let orders = yield getOrders();
console.log(orders);
let goods = yield getGoods();
console.log(goods);
}
//调用生成器函数
let iterator = gen();
iterator.next();
1.16、迭代器
遍历器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费,原生具备 iterator 接口的数据:
- Array
- Arguments
- Set
- Map
- String
- TypedArray
- NodeList
注意:需要自定义遍历数据的时候,要想到迭代器
1.16.1、工作原理
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
- 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
- 每调用 next 方法返回一个包含 value 和 done 属性的对象
1.16.2、案例演示:遍历数组
//声明一个数组
const xiyou = ["唐僧", "孙悟空", "猪八戒", "沙僧"];
//使用 for...of 遍历数组
for (let v of xiyou) {
console.log(v);
}
console.log("===============");
//获取迭代器对象
let iterator = xiyou[Symbol.iterator]();
//调用对象的next方法
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
1.16.3、案例演示:自定义遍历数据
//声明一个对象
const banji = {
name: "五班",
stus: [
"张三",
"李四",
"王五",
"小六"
],
[Symbol.iterator]() {
//索引变量
let index = 0;
let _this = this;
return {
next: function () {
if (index < _this.stus.length) {
const result = {value: _this.stus[index], done: false};
//下标自增
index++;
//返回结果
return result;
} else {
return {value: undefined, done: true};
}
}
};
}
}
//遍历这个对象
for (let v of banji) {
console.log(v);
}
1.17、浅拷贝和深拷贝
如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝;如果B没变,那就是深拷贝,深拷贝与浅拷贝的概念只存在于引用数据类型。
1.17.1、浅拷贝
var obj1 = {
name: "张三",
age: 20,
speak: function () {
console.log("我是" + this.name);
}
};
var obj2 = obj1;
// 当修改obj2的属性和方法的时候,obj1相应的属性和方法也会改变
obj2.name = "李四";
console.log(obj1);
console.log(obj2);
1.17.2、深拷贝
1.17.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);
concat()方法演示:
var arr1 = [1, 2, 3, 4];
var arr2 = arr1.concat();
arr2[0] = 200;
console.log(arr1);
console.log(arr2);
Array.from()方法演示:
var arr1 = [1, 2, 3, 4];
var arr2 = Array.from(arr1);
arr2[0] = 200;
console.log(arr1);
console.log(arr2);
… 操作符演示:
var arr1 = [1, 2, 3, 4];
var arr2 = [...arr1];
arr2[0] = 200;
console.log(arr1);
console.log(arr2);
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的深拷贝问题,因此我们应该自己实现。
1.17.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;
}
2、ECMAScript7新特性
2.1、数组方法扩展
Array.prototype.includes:此方法用来检测数组中是否包含某个元素,返回布尔类型值
const mingzhu = ["西游记", "红楼梦", "三国演义", "水浒传"];
console.log(mingzhu.includes("西游记"));
2.2、幂运算
**
操作符的作用和 Math.pow
的作用是一样,请看代码:
console.log(2 ** 10);
console.log(Math.pow(2, 10));
3、ECMAScript8新特性
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);
});
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();
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));
4、ECMAScript9新特性
4.1、对象拓展
Rest 参数与 spread 扩展运算符在 ES6 中已经引入,不过 ES6 中只针对于数组,在 ES9 中为对象提供了像数组一样的 rest 参数和扩展运算符
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'
});
4.1.2、对象合并
const skillOne = {
q: '天音波'
};
const skillTwo = {
w: '金钟罩'
};
const skillThree = {
e: '天雷破'
};
const skillFour = {
r: '猛龙摆尾'
};
const mangseng = {...skillOne, ...skillTwo, ...skillThree, ...skillFour};
console.log(mangseng);
4.2、正则表达式拓展
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);
4.2.2、正向断言
ES9 支持正向断言,通过对匹配结果后面的内容进行判断,对匹配进行筛选。
//声明字符串
let str = "订单编号开始123456789订单编号结束";
//正向断言
const reg = /\d+(?=订单编号结束)/;//也就是说数字的后边一定要跟着 订单编号结束
const result = reg.exec(str);
console.log(result);
4.2.3、反向断言
ES9 支持反向断言,通过对匹配结果前面的内容进行判断,对匹配进行筛选。
//声明字符串
let str = "订单编号开始123456789订单编号结束";
//正向断言
const reg = /(?<=订单编号开始)\d+/;//也就是说数字的前边一定要跟着 订单编号开始
const result = reg.exec(str);
console.log(result);
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);
5、ECMAScript10新特性
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);
5.2、字符串方法拓展
let str = " iloveyou ";
console.log(str.trimStart());//只去除前边的空格
console.log(str.trimEnd());//只去除后边的空格
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));
5.4、Symbol属性拓展
Symbol.prototype.description用来读取Symbol的描述值
//创建 Symbol
let s = Symbol("张三");
console.log(s.description);
6、ECMAScript11新特性
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();
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);
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);
}
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"
}
})
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>
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));
6.7、globalThis
全局属性 globalThis
包含全局的 this
值,类似于全局对象(global object)。
console.log(globalThis);