重学JS-基础篇

80 阅读19分钟

重学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

标记语句可以和 breakcontinue 语句一起使用。标记就是在一条语句前面加个可以引用的标识符(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—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");
}