重学JS-基础篇
现代深拷⻉
structuredClone
const obj = {
a: 1,
b: {
c: 2,
},
};
obj.d = obj;
const clone = structuredClone(obj);
obj.b === clone.b; // false
clone.d === clone; // true
// 不能拷贝方法
function name(params) {
console.log("name");
}
const cloneName = structuredClone(name);
cloneName();
// 不能拷贝DOM
const input = document.getElementById("input-name");
const cloneInput = structuredClone(input);
setter 和 getter
const getter = {
get foo() {
return "foo";
},
};
const cloneGetter = structuredClone(getter);
getter; // {}
cloneGetter; // {foo: 'foo'}
对象原型
class MyClass {
foo = "foo";
method() {
"method";
}
}
const myClass = new MyClass();
const cloneMyClass = structuredClone(myClass);
myClass; // MyClass {foo: 'foo'}
cloneMyClass instanceof MyClass; // false
parse(stringify())无法拷贝的属性
const originalObject = {
set: new Set([1, 3, 3]),
map: new Map([[1, 2]]),
regex: /foo/,
deep: { array: [new File(["foo"], 'file.txt')] },
error: new Error('Hello!'),
date: new Date(123)
}
// 现代深拷⻉.html:73 Uncaught TypeError: Converting circular structure to JSON
// --> starting at object with constructor 'Object'
// --- property 'circular' closes the circle
// at JSON.stringify (<anonymous>)
originalObject.circular = originalObject
const copied = JSON.parse(JSON.stringify(originalObject))
copied);
// {
// "set": {},
// "map": {},
// "regex": {},
// "deep": {
// "array": [
// {}
// ]
// },
// "error": {},
// "date": "1970-01-01T00:00:00.123Z"
// }
数组
Array.from
// 从语法上看, Array.from 有 3 个参数:
// 类似数组的对象, 必选;
// 加⼯函数, 新⽣成的数组会经过该函数的加⼯再返回;
// this 作⽤域, 表示加⼯函数执⾏时 this 的值。
var obj = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
let arr = Array.from(obj, function (value, index) {
value, index, this, arguments.length); // a 0 {0: 'a', 1: 'b', 2: 'c', length: 3} 2
return value.repeat(3); //必须指定返回值,否则返回 undefined
}, obj);
arr; // (3) ['aaa', 'bbb', 'ccc']
let arr1 = Array.from(obj, (value) => value.repeat(3))
arr1; // (3) ['aaa', 'bbb', 'ccc']
Array.from([1, , , , 5]); // [1, undefined, undefined, undefined, 5]
Array.of
Array(8); // [空 ×8]
Array.of(8); // [8]
Array(8.0, 5); // [8, 5]
Array.of(8.0, 5); // [8, 5]
Array("8"); // ['8']
Array.of("8"); // ['8']
数组索引
let color = new Array("1", "2", "3");
color[2]; // 3
color.length = 2;
color[2]; // undefined
color[4]; // undefined
数组判断
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call([]).slice(8, -1); // Array
[].constructor === Array; // true
[] instanceof Array; // true
Array.prototype.isPrototypeOf([]); // true
Object.getPrototypeOf([]) === Array.prototype; //true
Array.isArray([]); // true
if (!Array.isArray) {
Array.isArray = function (arg) {
return Object.prototype.toString.call(arg) === "[object Array]";
};
}
数组方法
改变原数组的方法:fill()、pop()、push()、shift()、splice()、unshift()、reverse()、sort();
不改变原数组的方法:concat()、every()、filter()、find()、findIndex()、forEach()、indexOf()、join()、lastIndexOf()、map()、reduce()、reduceRight()、slice()、some。
fill
let count = [1, 2, 3, 4, 5];
count.fill(0); // [0, 0, 0]
count.fill(5, 1); // [0, 5, 5, 5, 5]
count.fill(1, 1, 3); // [0, 1, 1, 5, 5]
copyWithin
let count1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
count1.copyWithin(6); // [1, 2, 3, 4, 5, 6, 1, 2, 3, 4]
count1.copyWithin(0, 2); // [3, 4, 5, 6, 7, 8, 9, 10, 9, 10]
count1.copyWithin(0, 2, 4); // [3, 4, 3, 4, 5, 6, 7, 8, 9, 10]
toLocaleString()
// toLocaleString()方法可能会返回和toString()方法相同的结果,但也不一定。在调用toLocaleString()方法时会得到一个逗号分隔的数组值的字符串,它与toString()方法的区别是,为了得到最终的字符串,会调用每个值的toLocaleString()方法,而不是toString()方法,
let array = [{ name: "zz" }, 123, "abc", new Date()];
let str = array.toLocaleString();
str; // [object Object],123,abc,2016/1/5 下午1:06:23
push() 从后添加
pop() 从后删除
unshift() 从前添加
shift() 从前删除
sort() 排序
[1, 3, 2, 4, 5].sort(); // [1, 2, 3, 4, 5]
[1, 3, 10, 5].sort(); // [1, 10, 3, 5]
// sort()方法会在每一个元素上调用String转型函数,然后比较字符串来决定顺序,即使数组的元素都是数值,也会将数组元素先转化为字符串在进行比较、排序。这就造成了排序不准确的情况,
[1, 3, 10, 5].sort((a, b) => a - b); // [1, 3, 5, 10]
[1, 3, 10, 5].sort((a, b) => b - a); // [10, 5, 3, 1]
reverse() 反转
concat() 连接
let arr2 = [1, 2, 3];
arr2.concat([[4, 5, 6]]); // [1, 2, 3, Array(3)]
arr2.concat([4, 5, 6]); // [1, 2, 3, 4, 5, 6]
arr2; // [1, 2, 3]
slice() 返回选中元素
// splice 向数组中添加或者删除元素
let arr3 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
arr3.splice(1, 3); // [2, 3, 4]
arr3); // [1, 5, 6, 7, 8, 9, 10]
arr3.splice(2, 1, 9); // [9]
arr3); // [1, 5, 9, 7, 8, 9, 10]
reduce()
对数组中的每个元素执行一个 reducer 函数
let count3 = [1, 2, 3, 4].reduce((prev, cur, index, arr) => {
prev, cur, index, arr);
// 1 2 1 (4) [1, 2, 3, 4]
// 3 3 2 (4) [1, 2, 3, 4]
// 6 4 3 (4) [1, 2, 3, 4]
return prev + cur
});
count3); // 10
let count4 = [1, 2, 3, 4].reduce((prev, cur, index, arr) => {
prev, cur, index, arr);
// 6 2 1(4)[1, 2, 3, 4]
// 8 3 2(4)[1, 2, 3, 4]
// 11 4 3(4)[1, 2, 3, 4]
return prev + cur
}, 5)
count4); // 15
reduceRight()
对数组中的每个元素执行一个 reducer 函数(倒序)
let count5 = [1, 2, 3, 4].reduceRight((prev, cur, index, arr) => {
prev, cur, index, arr);
// 4 3 2(4)[1, 2, 3, 4]
// 7 2 1(4)[1, 2, 3, 4]
// 9 1 0(4)[1, 2, 3, 4]
return prev + cur
});
count3); // 10
indexOf lastIndexOf includes
let arr4 = [1, 2, 3, 4, 5];
arr4.indexOf(2)) // 1
arr4.lastIndexOf(3)) // 2
arr4.includes(4)) // true
keys values entries
let arr5 = [10, 20, 30, 40, 50];
Array.from(arr5.keys(); // [0, 1, 2, 3, 4]
Array.from(arr5.values(); // [10, 20, 30, 40, 50]
Array.from(arr5.entries(); // [[0, 10], [1, 20], [2, 30], [3, 40], [4, 50]]
forEach map 第二个参数
用来绑定回调函数内部 this 变量
[1, 2, 3, 4, 5].forEach(function (item, index, arr) {
this[index]) // 10 20 30 40 50
}, [10, 20, 30, 40, 50])
[10, 20, 30, 40, 50].map(item => item + 1); // [11, 21, 31, 41, 51]
[10, 20, 30, 40, 50].filter(item => item > 20); // [30, 40, 50]
[10, 20, 30, 40, 50].every(item => item > 20); // false
[10, 20, 30, 40, 50].some(item => item > 20); // true
for in; for of
for (var item in [1, 2, 3, 4, 5]) {
item); // 0 1 2 3 4
}
for (var item of [1, 2, 3, 4, 5]) {
item); // 1 2 3 4 5
}
flat()
默认只会打平一级嵌套
[[[[12]]]].flat(); // [[[12]]]
[[[[12]]]].flat(3); // [12]
类数组
类数组-arguments
function foo(name, age) {
arguments); // Arguments(2) [13, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
arguments.callee); // 打印出自己本身
// function foo(name, age) {
// arguments); // Arguments(2) [13, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// arguments.callee);
// typeof arguments); // object
// Object.prototype.toString.call(arguments); // [object Arguments]
// }
typeof arguments); // object
Object.prototype.toString.call(arguments); // [object Arguments]
}
foo(13, 2)
类数组 HTMLCollection
let elem1 = document.forms[0]
let elem2 = document.forms.item(0)
elem1); // <form action="1"></form>
elem2); // <form action="1"></form>
typeof elem1); // object
Object.prototype.toString.call(elem1); // [object HTMLFormElement]
类数组 NodeList
let list = document.querySelectorAll('input')
list); // NodeList(4) [input, input, input, input]
for (const iterator of list) {
iterator.checked = true
}
typeof list); // Object
Object.prototype.toString.call(list); // [object NodeList]
类数组转成数组
Array.prototype.concat.apply([], { 0: 'java', 1: 'script', length: 2 });
Array.from(list); // [input, input, input, input]
let arr6 = [...list]
arr6); // [input, input, input, input]
数组扁平化
递归
function flatten(params) {
let result = [];
for (let index = 0; index < params.length; index++) {
const element = params[index];
if (Array.isArray(element)) {
result = result.concat(flatten(element));
} else {
result.push(element);
}
}
return result;
}
flatten([1, [[[[3]]]]]); // (2) [1, 3]
reduce
function flatten(params) {
return params.reduce((prev, next) => {
return prev.concat(Array.isArray(next) ? flatten(next) : next);
}, []);
}
flatten([1, [[[[3]]]]]); // (2) [1, 3]
扩展运算符
function flatten(arr) {
while (arr.some((item) => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
flatten([1, [[[[3]]]]]); // (2) [1, 3]
split 和 toString
function flatten(params) {
return params.toString().split(",");
}
flatten([1, [[[[3]]]]]); // (2) [1, 3]
flat
[1, [[[[3]]]]].flat(Infinity); // [1, 3]
正则和 JSON 方法
function flatten(arr) {
let str = JSON.stringify(arr);
str = str.replace(/(\[|\])/g, "");
str = "[" + str + "]";
return JSON.parse(str);
}
[1, [[[[3]]]]].flat(Infinity); // [1, 3]
JSON
JSON.parse
const json = '{"name": "zhangsan", "age": 18, "city": "beijing"}';
const myJSON = JSON.parse(json, (key, value) => {
if (typeof value === "number") {
return String(value).padStart(3, "0");
}
return value;
});
myJSON.name, myJSON.age); // zhangsan 018
JSON.stringify
const json1 = {
name: "zhangsan",
age: 18,
city: "beijing",
};
JSON.stringify(json1); // {"name":"zhangsan","age":18,"city":"beijing"}
JSON.stringify(json1, null, 4);
// {
// "name": "zhangsan",
// "age": 18,
// "city": "beijing"
// }
格式化 JSON
JSON.stringify(json1, null, "--");
// {
// --"name": "zhangsan",
// --"age": 18,
// --"city": "beijing"
// }
改变属性
JSON.stringify(json1, (key, value) => {
if (key === "name") {
return value + "我变化了";
}
return value;
});
// {"name":"zhangsan我变化了","age":18,"city":"beijing"}
隐藏属性
JSON.stringify(json1, (key, value) => {
if (key === "name") {
return;
}
return value;
});
// {"age":18,"city":"beijing"}
过滤结果
JSON.stringify(json1, ["name"]); // {"name":"zhangsan"}
字符串
charAt charCodeAt
获取字符串指定位置的值
'hello'.charAt(0); // h
'hello'.charAt(5); // ''
'hello'[5]); // undefined
'hello'.charCodeAt(0); // h -> 104
slice()
提取字符串的某个部分
"hello".slice(0, 3); // hel
"hello".slice(1); // ello
"hello".slice(); // hello
substr()
抽取从开始下标开始的指定数目的字符
"hello".substr(0, 3); // hel
"hello".substr(1); // ello
"hello".substr(); // hello
"hello".substr(-1); // o
"hello".substr(-2); // lo
substring()
提取字符串中介于两个指定下标之间的字符
"hello".substring(0, 3); // hel
"hello".substring(1); // ello
"hello".substring(); // hello
"hello".substring(-1); // hello
"hello".substring(-2); // hello
replace()
字符串模式匹配
"hello".replace("l", "L"); // heLlo
"hello".replace(/l/g, "L"); // heLLo
match()
在字符串内检索指定的值
"hello".match("l"); // ['l', index: 2, input: 'hello', groups: undefined]
"hello".match(/l/g); // ['l', 'l']
search()
检索字符串中的指定字符
"hello".search("l"); // 2
"hello".search(/lo/); // 3
trim() trimStart() trimEnd()
移除字符串收尾空白符
" hello ".trim(); // [hello]
" hello ".trimStart(); // [hello ]
" hello ".trimEnd(); // [ hello]
repeat()
将原字符串重复 n 次
"hello".repeat(2); // hellohello
"hello".repeat(2.9); // hellohello 如果参数是小数,会向下取整:
"hello".repeat(-0.9); // ''
"hello".repeat(NaN); // ''
"hello".repeat("NaN"); // ''
"hello".repeat("3"); // hellohellohello
padStart() padEnd()
补齐字符串长度
"hello".padStart(5, "xy"); // hello
"hello".padStart(10, "xy"); // xyxyxhello
"hello".padStart(11, "xy"); // xyxyxyhello
"hello".padStart(11); // [ hello]
"hello".padEnd(5, "xy"); // hello
"hello".padEnd(10, "xy"); // helloxyxyx
"hello".padEnd(11, "xy"); // helloxyxyxy
"hello".padEnd(11); // [hello ]
parseInt()
解析一个字符串并返回指定基数的十进制整数
parseInt("20"); // 20
parseInt("17", 8); // 15
parseInt("010"); // 10
parseInt("0x10"); // 16
parseInt("50", 1); // NaN
parseInt("50", 40); // NaN
parseInt("410 da"); // 410
parseInt("new100"); // NaN
parseInt(" 110 "); // 110
parseFloat()
解析一个字符串返回一个浮点数
parseFloat("10.00"); // 10
parseFloat("10.01"); // 10.01
parseFloat("-10.01"); // -10.01
parseFloat("123.45#"); // 123.45
parseFloat("new 10.25"); // NaN
操作符
一元加运算符
console.log(+-1); // -1
console.log(+"hello"); // NaN
console.log(+BigInt); // NaN
一元减运算符
console.log(-(-1)); // 1
console.log(-1); // -1
console.log(-BigInt); // NaN
按位与操作符(&)
// 1. 判断奇数偶数
console.log((2 & 1) === 0); // true
console.log((3 & 1) === 0); // false
console.log(2 % 2 === 0); // true
console.log(3 % 2 === 0); // false
指数操作符(**)
console.log(2 ** 10); // 1024
console.log(Math.pow(2, 10)); // 1024
in 操作符(in)
console.log("a" in { a: 1 }); // true
console.log("b" in { a: 1 }); // false
console.log("length" in ["b"]); // true
空值合并操作符(??)
console.log(0 ? 0 : "default"); // default
console.log(false ? false : "default"); // default
console.log(false ?? "default"); // false
Infinity
Infinity 值
// Infinity 表示所有大于 1.7976931348623157e+308 的值:
console.log(1e308); // 1e+308
console.log(1e309); // Infinity
得到 Infinity
console.log(Infinity); // Infinity
console.log(Number.POSITIVE_INFINITY); // Infinity
console.log(Math.pow(2, 1024)); // Infinity
console.log(1.8e308); // Infinity
console.log(1 / 0); // Infinity
得到 -Infinity
console.log(-Infinity); // -Infinity
console.log(Number.NEGATIVE_INFINITY); // -Infinity
console.log(-1 * Math.pow(2, 1024)); // -Infinity
console.log(-1.8e308); // -Infinity
console.log(1 / -0); // -Infinity
Infinity 计算
console.log(Infinity - Infinity); // NaN
console.log(Infinity / Infinity); // NaN
Infinity 检查
console.log(Infinity == 1 / 0); // true
console.log(Infinity === 1 / 0); // true
console.log(Infinity == "Infinity"); // true
console.log(Infinity === "Infinity"); // false
console.log(Object.is(Infinity, 1 / 0)); // true
console.log(Object.is(Infinity, "Infinity")); // false
console.log(isFinite(Infinity)); // false
console.log(isFinite(1.8e308)); // false
console.log(isFinite(1.8e307)); // true
转换为 JSON 时
console.log(JSON.stringify({ a: Infinity })); // {"a":null}
console.log(JSON.stringify({ a: -Infinity })); // {"a":null}
console.log(JSON.stringify({ a: 1.8e308 })); // {"a":null}
parseFloat() 和 parseInt()
console.log(parseFloat("Infinity")); // Infinity
console.log(parseInt("Infinity", 10)); // NaN
数据类型
NUll
console.log(typeof null); // object
console.log(Object.prototype.toString.call(null)); //[object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
Symbol
var a = Symbol("1");
var b = Symbol(1);
console.log(a.description === b.description); // true
var c = Symbol({ id: 1 });
console.log(c.description); // [object Object]
var d = Symbol("1");
console.log(d == a); // false
const obj = {
[Symbol("1")]: 123,
};
console.log(obj[Object.getOwnPropertySymbols(obj)[0]]); // 123
console.log(obj[Reflect.ownKeys(obj)[0]]); // 123
BigInt
var max = Number.MAX_SAFE_INTEGER; // 最大安全整数 9007199254740991
console.log(max); // 9007199254740991
console.log(max + 1 === max + 2); // true
var max = BigInt(Number.MAX_SAFE_INTEGER);
console.log(max + 1n === max + 2n); // false
console.log(typeof 1n); // bigint
console.log(typeof BigInt("1")); // bigint
console.log(Object.prototype.toString.call(1n)); // [object BigInt]
console.log(10n == 10); // true
console.log(10n === 10); // false
类型转换
隐式类型转换
// 运算相关的操作符:+、-、+=、++、* 、/、%、<<、& 等。
// 数据比较相关的操作符: >、<、== 、<=、>=、===。
// 逻辑判断相关的操作符: &&、!、||、三目运算符。
+ 运算符
// 一个操作数
console.log(+1); // 1
console.log(+"1"); // 1
console.log(+"1234No"); // NaN
console.log(+null); // 0
console.log(+undefined); // NaN
console.log(+new Date()); // 1700532110896
in
console.log("1" in { 1: "" }); // true
console.log(1 in { 1: "" }); // true
console.log(1 in ["a", "b"]); // true
特殊规则
console.log(2 + 3 + "4" + "number"); // 54number
Symbol 类型转换
console.log(Symbol.for("aaa")); // Symbol(aaa)
console.log(String(Symbol.for("aaa"))); // 'Symbol(aaa)'
// console.log(Symbol.for('aaa') + ''); // Uncaught TypeError: Cannot convert a Symbol value to a string
// console.log(Symbol.for('aaa') /2 ); // Uncaught TypeError: Cannot convert a Symbol value to a number
对象类型转换
// 对象通过内部的 ToPrimitive 方法将其转换为原始类型,该算法允许我们根据使用对象的上下文来选择应如何转换对象。从概念上讲,ToPrimitive 算法可以分为两部分:Hints 和 Object-to-primitive 转换方法。
Hints
// String({ a: 1 }) // [object Object]
console.log(String({ a: 1 })); // [object Object]
console.log(Number({ a: 1 })); // NaN
Methods
// toString/valueOf : toString() 和 valueOf() 被 JavaScript 中的所有对象继承。 它们仅用于对象到原始值的转换。 ToPrimitive 算法首先会尝试 toString() 方法。 如果定义了方法,它返回一个原始值,那么 JavaScript 使用原始值(即使它不是字符串)。 如果 toString()返回一个对象或不存在,那么 JavaScript 会尝试使用 valueOf() 方法,如果该方法存在并返回一个原始值,JavaScript 将使用该值。 否则,转换失败并提示 TypeError 。
// toString -> valueOf :用于 Hints 为 string 的情况。
// valueOf -> toString :其他情况。(Hints 为“number”或“default”)。
var Person = {
name: "Mary",
age: 22,
// hint 是 "string"
toString() {
return `{name: "${this.name}"}`;
},
// hint 是 "number" 或 "default"
valueOf() {
return this.age;
},
};
console.log(String(Person)); // toString -> {name: "Mary"}
console.log(String(+Person)); // valueOf -> 22
console.log(String(Person + 10)); // valueOf -> 32
// Symbol.toPrimitive :与 toString() 和 valueOf() 方法不同, Symbol.toPrimitive 允许覆盖 JavaScript 中的默认对象到原始值的转换
var Person = {
name: "LGQ",
age: 12,
[Symbol.toPrimitive](hint) {
console.log(`hint: ${hint}`);
return hint === "string" ? `{name: "${this.name}"}` : this.age;
},
};
console.log(String(Person)); // hint: string -> {name: "Mary"}
console.log(String(+Person)); // hint: number -> 22
console.log(String(Person + 10)); // hint: default -> 32
循环遍历
for of
// for...of 语句创建一个循环来迭代可迭代的对象。在 ES6 中引入的 for...of 循环,以替代 for...in 和 forEach() ,并支持新的迭代协议。
for (const item of [1, 2, 3]) {
console.log(item); // 1 2 3
}
for (const item of "123") {
console.log(item); // 1 2 3
}
var map = new Map();
map.set("1", 1);
map.set("2", 2);
for (const item of map) {
console.log(item); // ['1', 1] ['2', 2]
}
var set = new Set();
set.add("1");
set.add("2");
for (const item of set) {
console.log(item); // 1 2
}
Object.getOwnPropertyNames()
console.log(Object.keys(["a", "b"])); // ['0', '1']
console.log(Object.getOwnPropertyNames(["a", "b"])); // ['0', '1', 'length']
console.log(Object.keys({ 0: "a", 1: "b", 2: "c" })); // ['0', '1', '2']
console.log(Object.getOwnPropertyNames({ 0: "a", 1: "b", 2: "c" })); // ['0', '1', '2']
Object.getOwnPropertySymbols()
var obj = { a: 1 };
Object.defineProperties(obj, {
[Symbol("a")]: {
value: "Symbol a",
enumerable: false,
},
});
obj[Symbol("b")] = "Symbol b";
Object.getOwnPropertySymbols(obj).forEach((el) => {
console.log(obj[el]); // Symbol a, Symbol b
});
Reflect.ownKeys()
// Reflect.ownKeys() 返回一个数组,包含对象自身的所有属性。它和Object.keys()类似,Object.keys()返回属性key,但不包括不可枚举的属性,而Reflect.ownKeys()会返回所有属性key:
console.log(Object.keys(obj)); // ['a']
console.log(Reflect.ownKeys(obj)); // ['a', Symbol(a), Symbol(b)]
do / while
var num = 3;
do {
console.log(num); // 3 2 1 0
num--;
} while (num > 0);
console.log(num);
for await of
// for await...of 方法被称为异步迭代器,该方法是主要用来遍历异步对象。它
function Gen(time) {
return new Promise((res, rej) => {
setTimeout(() => {
res(time);
}, time);
});
}
async function test() {
let arr = [Gen(100), Gen(200), Gen(300)];
for await (const item of arr) {
console.log((Date.now(), item));
}
}
test(); // 100 200 300
错误类型
SyntaxError 语法错误
// let a b = 10 // SyntaxError: Unexpected identifier 'b'
TypeError 类型错误
try {
let count = 1;
count.map((el) => console.log(el)); // TypeError: count.map is not a function
} catch (error) {
console.log(error); // TypeError: count.map is not a function
}
ReferenceError 引用错误
try {
console.log(a); // ReferenceError: a is not defined
console.log(age); // ReferenceError: Cannot access 'age' before initialization
let age = 18;
function name(params) {
let sex = "name";
}
console.log(sex); // ReferenceError: sex is not defined
} catch (error) {
console.log(error);
}
RangeError 范围错误
try {
const arr = new Array(9999999999999999999999999999999); // RangeError: Invalid array length
(3.1415).toFixed(123); // RangeError: toFixed() digits argument must be between 0 and 100
} catch (error) {
console.log(error);
}
URIError URI 错误
try {
decodeURI("%%"); // URIError: URI malformed
decodeURI("%a%b%c"); // URIError: URI malformed
} catch (error) {
console.log(error);
}
EvalError Eval 错误(todo)
try {
throw new EvalError("Hello", "someFile.js", 10); // EvalError: Hello
} catch (error) {
console.log(error);
}
InternalError 内部错误(todo)
// 1. "too many switch cases"(过多 case 子句);
// 2. "too many parentheses in regular expression"(正则表达式中括号过多);
// 3. "array initializer too large"(数组初始化器过大);
// 4. "too much recursion"(递归过深)。
try {
function loop(x) {
if (x >= 100000) return;
// 做一些事情
loop(x + 1);
}
loop(0);
// InternalError: too much recursion
} catch (error) {
console.log(error);
}
深浅拷贝
浅拷贝
var obj = { a: { b: 1 } };
var obj1 = obj;
console.log(obj === obj1); // true
var obj1 = Object.assign({}, obj); // 可以实现一维对象的深拷贝
console.log(obj1.a === obj.a); // true
var obj1 = { ...obj }; // 可以实现一维对象的深拷贝
console.log(obj1.a === obj.a); // true
var arr = [1, [2, 3], 4, 5];
console.log(arr.slice()[1] === arr[1]); // true
console.log(arr.concat()[1] === arr[1]); // true
手写实现浅拷贝
function shallowCopy(object) {
if (!object || typeof object !== "object") return;
let newObject = Array.isArray(object) ? [] : {};
for (const key in object) {
const element = object[key];
newObject[key] = element;
}
return newObject;
}
console.log(arr[1] === shallowCopy(arr)[1]); // true
深拷贝
var obj = { a: { b: 1 } };
var obj1 = JSON.parse(JSON.stringify(obj));
console.log(obj.a === obj1.a); // false
var obj1 = structuredClone(obj);
console.log(obj.a === obj1.a); // false
手写实现深拷贝函数
function deepCopy(object) {
if (!object || typeof object !== "object") return;
let newObject = Array.isArray(object) ? [] : {};
for (const key in object) {
if (object.hasOwnProperty(key)) {
if (typeof object[key] === "object") {
newObject[key] = deepCopy(object[key]);
} else {
newObject[key] = object[key];
}
}
}
return newObject;
}
console.log(obj.a === deepCopy(obj).a); // false
解决 爆栈
function cloneLoop(x) {
const root = {};
const loopList = [
{
parent: root,
key: undefined,
data: x,
},
];
while (loopList.length) {
// 深度优先
const node = loopList.pop();
const { parent, key, data } = node;
let res = parent;
if (typeof key !== "undefined") {
res = parent[key] = {};
}
for (const key in data) {
if (Object.hasOwnProperty.call(data, key)) {
if (typeof data[key] === "object") {
loopList.push({
parent: res,
key,
data: data[key],
});
} else {
res[key] = data[key];
}
}
}
}
return root;
}
console.log(obj.a === cloneLoop(obj).a); // false
解决 爆栈+循环引用
function cloneForce(x) {
const uniqueList = []; // 用来去重
let root = {};
// 循环数组
const loopList = [
{
parent: root,
key: undefined,
data: x,
},
];
while (loopList.length) {
// 深度优先
const node = loopList.pop();
const { parent, key, data } = node;
const find = function (arr, item) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].source === item) {
return arr[i];
}
}
return null;
};
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== "undefined") {
res = parent[key] = {};
}
// 数据已经存在
let uniqueData = find(uniqueList, data);
if (uniqueData) {
parent[key] = uniqueData.target;
continue; // 中断本次循环
}
// 数据不存在
// 保存源数据,在拷贝数据中对应的引用
uniqueList.push({ source: data, target: res });
// =============
for (let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === "object") {
// 下一次循环
loopList.push({ parent: res, key: k, data: data[k] });
} else {
res[k] = data[k];
}
}
}
}
return root;
}
console.log(obj.a === deepCopy(obj).a); // false
label
标记语句可以和
break
或continue
语句一起使用。标记就是在一条语句前面加个可以引用的标识符(identifier)。
loop1: for (let i = 0; i < 2; i++) {
for (let e = 0; e < 2; e++) {
for (let l = 0; l < 2; l++) {
if (i + e + l > 2) {
break loop1;
}
console.log(`i: ${i}, e: ${e}, l: ${l}`);
}
}
}
// i: 0, e: 0, l: 0
// i: 0, e: 0, l: 1
// i: 0, e: 1, l: 0
// i: 0, e: 1, l: 1
// i: 1, e: 0, l: 0
// i: 1, e: 0, l: 1
// i: 1, e: 1, l: 0
变量提升
变量提升有两个好处
(1)提高性能
// 在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍该变量(函数),这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。
// 在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数),并且因为代码压缩的原因,代码执行也更快了。
(2)容错性更好
a = 1;
var a;
console.log(a); // 1
// 如果没有变量提升,这两行代码就会报错,但是因为有了变量提升,这段代码就可以正常执行。
变量提升导致的问题
(1)变量被覆盖
var name = "JavaScript";
function showName() {
console.log(name); // 先使用函数执行上下文中的变量
if (0) {
var name = "CSS";
}
console.log(name); // 先使用函数执行上下文中的变量
}
showName(); // undefined
(2)变量没有被销毁
function foo() {
for (var i = 0; i < 5; i++) {}
console.log(i);
}
foo(); // 5
暂时性死区
var name = "JavaScript";
{
name = "CSS";
let name;
}
// 外层的name就么用了, 因为let形成了暂时性死区
// Uncaught ReferenceError: Cannot access 'name' before initialization
对象不变性
简而言之,对象的不变性就是将它的状态变为只读的,
Object.freeze()
var user = {
name: "CUGGZ",
age: 24,
};
var freezeUser = Object.freeze(user);
freezeUser.age = 18;
console.log(freezeUser.age); // 24
// Object.isFrozen(),它可以用来判断一个对象是否被冻结:
console.log(Object.isFrozen(user)); // true
// Object.freeze() 方法不会影响嵌套对象
var user1 = {
name: "CUGGZ",
age: 24,
hobby: {
run: true,
},
};
var user2 = Object.freeze(user1);
user2.hobby.run = false;
console.log(user2.hobby.run); // false
递归冻结实现
var user = {
name: "CUGGZ",
age: 24,
hobby: {
run: true,
},
};
var deepFreeze = (obj) => {
Object.keys(obj).forEach((prop) => {
if (typeof obj[prop] === "object") {
deepFreeze(obj[prop]);
}
});
return Object.freeze(obj);
};
var user1 = deepFreeze(user);
user1.hobby.run = false;
console.log(user1.hobby.run); // true
Object.seal()
// Object.seal() 顾名思义就是密封对象,它是另一种使对象不可变的方法。相对于freeze(),Object.seal() 方法仅保护对象不能添加和删除属性,它允许更新现有的属性。
var user = {
name: "CUGGZ",
age: 24,
};
var sealUser = Object.seal(user);
sealUser.age = 18;
delete sealUser.name;
console.log(sealUser); // {name: 'CUGGZ', age: 18}
递归密封实现
var user = {
name: "CUGGZ",
age: 24,
hobby: {
run: true,
},
};
var deepSeal = (obj) => {
Object.keys(obj).forEach((prop) => {
if (typeof obj[prop] === "object") {
deepSeal(obj[prop]);
}
});
return Object.seal(obj);
};
var sealUser = Object.seal(user);
delete sealUser.hobby.run;
console.log(sealUser); // {name: 'CUGGZ', age: 18}
Object.preventExtensions
var user = {
name: "CUGGZ",
age: 24,
};
var newUser = Object.preventExtensions(user);
newUser.age = 18;
newUser.sex = "nan";
console.log(newUser); // {name: 'CUGGZ', age: 18}
总结
// 方法/操作 读取 创建 更新 删除
// Object.freeze() ✔️ ❌ ❌ ❌
// Object.seal() ✔️ ❌ ✔️ ❌
// Object.preventExtensions() ✔️ ❌ ✔️ ✔️
ES6—ES13
// ES6 新特性 (2015)
// 1.let和var
// 2.解构赋值z
// 3.模板字符串
// 4.函数默认参数
// 5.箭头函数
// 6.扩运算符
// 7.Symbol
// 8.集合 Set
// 9.Map
// 10.模块化
// 11.字符方法
// 12.数组方法
// ES7 新特性 (2016)
// 1.Array.includes()
// 2指操作符
// ES8 新特性 (2017)
// 1.padStart()和padEnd()
// 2.Object.values()和Object.entries(
// 3.函数扩展
// 4.Object.values
// ES9 新特性 (2018)
// l.for await...of
// 2.Promise.finally
// 3.对象的扩展运算符
// 4.对象的 Rest
// ES10 新特性 (2019)
// trimStart() 和 trimEnd()
// 2.flat()和flatMap()
// 3.Object.fromEntries()
// 4.Symbol描述
// 5.toString()
// 6.catch
// ES11 新特性 (2020)
// 1.Biglnt
// 2.空值合并运算符(??)3选链操作符(?.)
// 4.PromiseallSettled
// 5.String.matchAll()
// ES12 新特性 (2021)
// 1.String.replaceAll()
// 2.数字分隔符
// 3.Promise.any
// 4.逻辑赋值操作符
// ES13 新特性(2022)
// 1.Object.hasOwn()
// 2.at()
// 3.error.cause
// 4.Top-level Await
Set
// 属性和方法 概述
// size 返回集合的元素个数
// add 增加一个新的元素,返回当前的集合
// delete 删除元素,返回布尔值
// has 检查集合中是否包含某元素,返回布尔值
// clear 清空集合,返回undefined
var set = new Set();
var set1 = new Set(["a", "b", "c", "a"]);
console.log(set1); // Set(3) {'a', 'b', 'c'}
console.log(set1.add(5)); // Set(4) {'a', 'b', 'c', 5}
console.log(set1.delete(5));
console.log(set1); // Set(3) {'a', 'b', 'c'}
console.log(set1.clear());
console.log(set1); // Set(0) {size: 0}
console.log(set1.has("a")); // false
console.log(set1.size); // 3
// 可以通过set来求两个数组的交集和并集:
// 模拟求交集
var intersection = new Set([...set1].filter((x) => set2.has(x)));
// 模拟求差集
var difference = new Set([...set1].filter((x) => !set2.has(x)));
// Array与Map的相互转化:
// Set集合转化为数组
var arr = [...set1];
var arr = Array.from(set1);
// 数组转化为Set集合
var mySet = new Set(arr);
Map
// 属性和方法 概述
// size 返回Map的元素个数
// set 增加一个新的元素,返回当前的Map
// get 返回键名对象的键值
// has 检查Map中是否包含某元素,返回布尔值
// clear 清空Map,返回undefined
var map = new Map([
["name", "张三"],
["age", 18],
]);
console.log(map); // Map(2) {'name' => '张三', 'age' => 18}
//获取映射元素的个数
console.log(map.size); // 2
//添加映射值
console.log(map.set("sex", "man"));
console.log(map); // Map(3) {'name' => '张三', 'age' => 18, 'sex' => 'man'}
//获取映射值
console.log(map.get("age")); // 6
//检测是否有该映射
console.log(map.has("age")); // true
//清除
console.log(map.clear()); // undefined
console.log(map); // Map(0) {size: 0}
var map = new Map();
map.set(NaN, 123);
map.get(NaN); // 123
map.set(-0, 123);
map.get(+0); // 123
模块化
// (1)export 导出模块
// 方式一
export var first = "test";
export function func() {
return true;
}
// 方式二
var first = "test";
var second = "test";
function func() {
return true;
}
export { first, second, func };
// as关键字:
export { first as one };
// export default (export default只能使用一次。)
// 导出
export default function () {
console.log("foo");
}
// 导入
import customName from "./export-default";
// (2)import 导入模块
import { firstName, lastName, year } from "./profile";
import { lastName as surname } from "./profile";
import "lodash"; //仅仅是加载而已,无法使用
// 加载整个模块(有输出)
import * as circle from "./circle";
console.log("圆面积:" + circle.area(4));
console.log("圆周长:" + circle.circumference(14));
// (3)导入导出复合用法
export { foo, bar } from "my_module";
// 等同于
import { foo, bar } from "my_module";
export { foo, bar };
// 整体先导入再输出以及default
// 整体输出
export * from 'my_module';
// 导出default,正如前面所说,export default 其实导出的是default变量
export { default } from 'foo';
// 具名接口改default
export { es6 as default } from './someModule';
// (4)模块的继承
export * from 'circle'; // export * 会忽略default。
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
Array.includes(searchElement, fromIndex)
// 从fromIndex 索引处开始查找目标值。如果为负值,则按升序从 array.length +fromIndex 的索引开始搜 (即使从末尾开始往前跳 fromIndex 的绝对值个索引,后往后搜寻)。默认为 0
console.clear();
console.log([1, 2, 3, 4, 5].includes(1)); // true
console.log([1, 2, 3, 4, 5].includes(6)); // false
console.log([1, 2, 3, 4, 5].includes(3, 2)); // true
console.log([1, 2, 3, 4, 5].includes(3, 3)); // false
console.log([1, 2, 3, 4, 5].includes(5, -1)); // true
指数操作符
console.log(Math.pow(2, 10)); // 1024
console.log(2 ** 10); // 1024
padStart() 和 padEnd()
// padStart()
console.log("x".padStart(1, "123456")); // x
console.log("x".padStart(5, "12")); // 1212x
console.log("x".padStart(6, "12")); // 12121x
console.log("x".padStart(6)); // [ x]
// padEnd()
console.log("x".padEnd(1, "123456")); // x
console.log("x".padEnd(5, "12")); // x1212
console.log("x".padEnd(6, "12")); // x12121
console.log("x".padEnd(6)); // [x ]
Object.values()和 Object.entries()
// Object.keys():返回包含对象键名的数组;
// Object.values():返回包含对象键值的数组;
// Object.entries():返回包含对象键名和键值的数组。
console.log(Object.keys({ a: 1, b: 2 })); // ['a', 'b']
console.log(Object.values({ a: 1, b: 2 })); // [1, 2]
console.log(Object.entries({ a: 1, b: 2 })); // [["a", 1],["b", 2],];
for await…of
// for await...of 方法被称为异步迭代器,该方法是主要用来遍历异步对象。它
function Gen(time) {
return new Promise((res, rej) => {
setTimeout(() => {
res(time);
}, time);
});
}
async function test() {
var arr = [Gen(100), Gen(200), Gen(300)];
for await (var item of arr) {
console.log((Date.now(), item));
}
}
test(); // 100 200 300
Promise.finally
// 添加了 finally() 方法,表示无论 Promise 实例最终成功或失败都会执行的方法
// finally() 函数不接受参数,finally() 内部通常不知道 promise 实例的执行结果,所以通常在 finally() 方法内执行的是与 promise 状态无关的操作。
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
var one = "1";
reject(one);
}, 1000);
});
promise
.then(() => console.log("success"))
.catch(() => console.log("fail"))
.finally(() => console.log("finally"));
trimStart() 和 trimEnd()
console.log(" xxx ".trimStart()); // [xxx ]
console.log(" xxx ".trimEnd()); // [ xxx]
flat()和 flatMap()
// flat()方法用于创建并返回一个新数组,
console.log([[1]].flat()); // [1]
console.log([[[1]]].flat()); // [[1]]
console.log([[[1]]].flat(2)); // [1]
console.log([[[1, , ,]]].flat(2)); // [1]
console.log([[1]].flat(0)); // [[1]]
console.log([[1]].flat(-20)); // [[1]]
// flatMap()
// flatMap()方法使用映射函数映射每个元素,然后将结果压缩成一个新数组。
console.log([1, 2, 3, 4].flatMap((x) => x * 2)); // [2, 4, 6, 8]
console.log([1, 2, 3, 4].flatMap((x) => [x * 2])); // [2, 4, 6, 8]
console.log([1, 2, 3, 4].flatMap((x) => [[x * 2]])); // [[2], [4], [6], [8]]
console.log([1, 2, 3, 4].map((x) => [x * 2])); // [[2], [4], [6], [8]]
Object.fromEntries()
// Object.fromEntries()方法可以把键值对列表转换为一个对象。
var object = { key1: "value1", key2: "value2" };
var array = Object.entries(object); // [ ["key1", "value1"], ["key2", "value2"] ]
Object.fromEntries(array); // { key1: 'value1', key2: 'value2' }
Symbol 描述
var dog = Symbol("dog"); // dog 为描述
console.log(String(dog)); // Symbol(dog)
console.log(dog.toString()); // Symbol(dog)
console.log(dog.description); // dog
BigInt
var max = Number.MAX_SAFE_INTEGER; // 最大安全整数
var max1 = max + 1;
var max2 = max + 2;
console.log(max1 === max2); // true
var max = BigInt(Number.MAX_SAFE_INTEGER);
var max1 = max + 1n;
var max2 = max + 2n;
console.log(max1 === max2); // false
typeof 1n === "bigint"; // true
typeof BigInt("1") === "bigint"; // true
console.log(Object.prototype.toString.call(1n)); // [object BigInt]
console.log(10n === 10); // false
console.log(10n == 10); // true
1n < 2; // true
2n > 1; // true
2 > 2; // false
2n > 2; // false
2n >= 2; // true
可选链操作符(?.)
var name =
(system && system.user && system.user.addr && system.user.addr.province) ||
"Unknown";
var name = system?.user?.addr?.province || "default";
a?.[x];
// 等同于
a == null ? undefined : a[x];
a?.b();
// 等同于
a == null ? undefined : a.b();
a?.();
// 等同于
a == null ? undefined : a();
Promise.allSettled
// Promise.allSettled 全部处理完成后,我们可以拿到每个 Promise 的状态,而不管其是否处理成功。
var resolved = Promise.resolve(2);
var rejected = Promise.reject(-1);
var allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// 返回结果:
// [{ status: 'fulfilled', value: 2 },{ status: 'rejected', reason: -1 }]
// Promise.allSettled 最后返回的是一个数组,记录传进来的参数中每个 Promise 的返回值,这就是和 all 方法不太一样的地方。你也可以根据 all 方法提供的业务场景的代码进行改造,其实也能知道多个请求发出去之后,Promise 最后返回的是每个参数的最终状态。
String.matchAll()
for (var match of "abcabc".matchAll(/a/g)) {
console.log(match);
}
// ['a', index: 0, input: 'abcabc', groups: undefined]
// ['a', index: 3, input: 'abcabc', groups: undefined]
// -----------------------------------
// String.replaceAll();
let string = "hello world, hello ES12";
string.replace(/hello/g, "hi");
console.log(string); // hi world, hi ES12
string.replaceAll("hello", "hi");
// string.replaceAll(/hello/,'hi') // ncaught TypeError: String.prototype.replaceAll called with a non-global RegExp argument
// replaceAll 在使用正则表达式的时候,如果非全局匹配(/g)
console.log(string); // hi world, hi ES12
数字分隔符
console.log(1000000 === 1000_000); // true
// 支持在八进制数中使用
console.log(0o123_456 === 0o123456); // true
Promise.any
// 它接收一个 Promise 可迭代对象(例如数组),只要其中的一个promise 成功,就返回那个已经成功的 promise
var promises = [
Promise.reject("ERROR A"),
Promise.reject("ERROR B"),
Promise.resolve("result1"),
Promise.resolve("result"),
];
Promise.any(promises)
.then((value) => {
console.log("value: ", value);
})
.catch((err) => {
console.log("err: ", err);
});
// 输出结果:value: result1
// 全部失败
var promises = [Promise.reject("ERROR A"), Promise.reject("ERROR B")];
Promise.any(promises)
.then((value) => {
console.log("value: ", value);
})
.catch((err) => {
console.log("err: ", err);
});
// 输出结果:err: AggregateError: All promises were rejected
逻辑赋值操作符
// 等同于 a = a || b
a ||= b;
// 等同于 c = c && d
c &&= d;
// 等同于 e = e ?? f
e ??= f;
Object.hasOwn()
// 检查一个属性是否属于对象
var example = {
property: "123",
};
console.log(Object.prototype.hasOwnProperty.call(example, "property")); // true
console.log(Object.hasOwn(example, "property")); // true
at()
// 个数组方法,用于通过给定索引来获取数组元素。
var arr = [0, 1, 2, 3, 4, 5];
console.log(arr[arr.length - 1]); // 5
console.log(arr.at(-1)); // 5
console.log(arr.at(-2)); // 4
console.log(arr.at(0)); // 0
var str = "hello world";
console.log(str[str.length - 1]); // d
console.log(str.at(-1)); // d
new Error
function readFiles(filePaths) {
return filePaths.map((filePath) => {
try {
// ···
} catch (error) {
throw new Error(`While processing ${filePath}`, { cause: error });
}
});
}
Top-level Await
顶层 await 允许我们在 async 函数外面使用 await 关键字。它允许模块充当大型异步函数,通过顶层 await ,这些 ECMAScript 模块可以等待资源加载。这样其他导入这些模块的模块在执行代码之前要等待资源加载完再去执行
// a.js
const resp = await fetch("https://jsonplaceholder.typicode.com/users");
const users = resp.json();
export { users };
// usingAwait.js
import { users } from "./a.mjs";
console.log(users);
console.log("usingAwait module");
// 顶级 await 在以下场景中将非常有用:
// 动态加载模块: const strings = await import(`/i18n/${navigator.language}`);
// 资源初始化:const connection = await dbConnector();
// 依赖回退:
let translations;
try {
translations = await import("https://app.fr.json");
} catch {
translations = await import("https://fallback.en.json");
}