Skip to content

一、数据类型

1.JavaScript 有哪些数据类型?它们的区别是什么?

JavaScript 有八种基本数据类型,分为原始类型(Primitive Types)和引用类型(Reference Types):

原始类型(基本类型)

特点:

  • 不可变性:一旦一个原始值被创建,就不能再更改。如果对原始值进行“修改”,其实是创建了一个新的值。
  • 按值传递:在赋值或传参时,是将值的副本传递给变量或函数,不会相互影响。
  1. Undefined:表示变量已声明但未赋值时的默认值。

  2. Null:表示一个空的值或一个不存在的对象。null 是一个特殊的关键字,它代表“无值”。

  3. Boolean:只有两个值:true 和 false,用于逻辑判断。

  4. Number:表示双精度 64 位二进制格式的浮点数,可以表示整数和浮点数。特殊值包括 NaN(Not a Number)和 Infinity

  5. String:表示字符序列,可以用单引号双引号反引号括起来的文本,字符串是不可变的。

  6. Symbol:用来创建唯一且不可变的值,主要用于对象属性的唯一标识,避免属性名冲突。

  7. BigInt(ES2020 引入):用于表示任意精度的大整数,允许操作超过 Number 能表示的范围的整数。

引用类型

特点:

  • 可变性:对象内部的属性是可以被修改的,即使使用 const 声明对象,虽然变量绑定不可变,但对象内容是可以变更的。
  • 按引用传递:在赋值或传参时,实际上传递的是对象在内存中的引用,因此多个变量可以指向同一个对象,对其中一个变量的修改会影响到其他变量。 `
  1. Object(包括普通对象、数组、函数等)

(Array、Function、Date、RegExp、Map、Set、WeakMap、WeakSet)都是内置特殊对象

两者区别

存储区别

  1. 原始类型存储在栈(stack)中,值直接保存在变量访问的位置,由于其大小固定且频繁使用,存储在栈中具有更高的性能。

  2. 引用类型存储在堆(heap)中,占用空间较大且大小不固定,变量保存的是对实际对象的引用(即指针),这些引用存储在栈中。

赋值方式区别

  1. 原始类型:复制的是值本身。例如,将一个 number 类型的变量赋值给另一个变量,两个变量互不影响。

  2. 引用类型:复制的是引用(指针)。多个变量引用同一个对象时,一个变量的修改会影响其他变量。

扩展知识点

类型检测

  1. 使用 typeof 检查原始类型(例如:typeof 123 === "number")。

  2. 使用 instanceof 检查引用类型(例如:[] instanceof Array === true)。

  3. null 是一个特殊情况,typeof null 返回 "object",这是 JavaScript 早期实现中的一个 bug,但被保留了下来。

类型转换

  1. 自动类型转换:如字符串与数字相加时,数字会被转换为字符串。

  2. 显式类型转换:使用 Number()、String()、Boolean() 等函数将值转换为指定类型。

堆和栈的区别

  1. 栈:内存分配效率高,自动管理(由编译器分配和释放)。

  2. 堆:内存分配灵活,堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。

2.数据类型检测的方式有哪些

1)typeof 操作符

typeof 操作符可以用来判断一个变量的基本数据类型(除了 null,它会返回 "object"),示例如下:

js
typeof 42; // "number"
typeof "hello"; // "string"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof null; // "object" (注意这是一个历史遗留问题,null被错误地归类为对象)
typeof {}; // "object"
typeof []; // "object" (数组也是对象的一种特殊形式)
typeof function() {}; // "function"

2)instanceof 操作符

instanceof 操作符用来判断一个对象是否是某个构造函数的实例,也可以用来判断是否是某个对象的子类实例,示例如下:

js
let arr = [];
arr instanceof Array; // true
arr instanceof Object; // true,因为数组也是对象的一种

3)Object.prototype.toString.call() 方法

Object.prototype.toString.call() 方法返回一个表示对象的内部属性 [[Class]]的字符串,通过它可以准确判断对象的类型,示例如下:

js
Object.prototype.toString.call(42); // "[object Number]"
Object.prototype.toString.call("hello"); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(function() {}); // "[object Function]"

4)Array.isArray() 方法

Array.isArray() 方法用来判断一个对象是否为数组,示例如下:

js
Array.isArray([]); // true
Array.isArray({}); // false

5)typeof 与 instanceof 结合使用

结合 typeof 和 instanceof 可以更精确地判断某些复杂类型,如函数和正则表达式,示例如下:

js
typeof /regex/; // "object"
/regex/ instanceof RegExp; // true

6)constructor 属性

对象通常会有一个 constructor 属性指向创建它的构造函数。

  • 适用场景: 快速获取对象的类名。
  • 缺点: 不安全。constructor 属性很容易被手动修改,或者在原型继承中丢失。
js
([]).constructor === Array; // true
({}).constructor === Object; // true

constructor 属性存在于对象的原型(prototype)中。当你尝试对一个数字使用它时,情况分为两种:

  • 对于数字变量/字面量: 当你调用 (123).constructor 时,JS 会自动进行**装箱(Boxing)**操作,临时将数字包装成 Number 对象,从而访问到原型上的 constructor
js
(123).constructor === Number; // true
let n = 45.6;
n.constructor === Number;     // true
  • 致命缺点:
    1. 无法处理 nullundefined 这两者没有包装对象,直接访问 .constructor 会抛出 TypeError 错误。
    2. 容易被篡改: constructor 只是一个普通的属性,开发者可以轻易更改它,导致判断失效。
    3. 原型链断裂: 如果一个自定义类的原型被重写(如 Sub.prototype = new Super()),constructor 指向会乱掉,除非手动修复。

3.如何判断 JavaScript 变量是数组?

1.使用 Object.prototype.toString.call() 做判断

这是一个通用的类型判断方法,适用于判断各种数据类型,Object.prototype.toString.call(obj) 会返回一个类似 [object Array] 的字符串,通过切片操作获取其中的类型部分并与 'Array' 比较,示例如下:

js
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';

2.通过原型链做判断

通过检查对象的原型链是否指向 Array.prototype,但是直接访问 proto 不推荐,因为它是非标准属性,虽然现在大多数浏览器都支持,示例如下:

js
obj.__proto__ === Array.prototype;

3.使用 ES6 的 Array.isArray() 判断

这是 ES6 中新增的方法,专门用于判断一个变量是否为数组,非常简洁且可靠,示例如下:

js
Array.isArray(obj);

4.使用 instanceof 做判断

比较常用的原型链判断方法,示例如下:

js
obj instanceof Array

5.使用 Array.prototype.isPrototypeOf 检查 Array.prototype 是否存在于对象的原型链中,示例如下:

js
Array.prototype.isPrototypeOf(obj)

4.JavaScript 中 null 和 undefined 的区别是什么?

undefined 未定义 是JavaScript 的一种内置数据类型,表示变量声明了但未赋值。 null 空对象 同样是一种内置数据类型,表示一个空对象引用。

两者区别

类型检测

  1. 使用 typeof 检测 undefined 会返回 "undefined"。
  2. 使用 typeof 检测 null 会返回 "object",这是一个历史遗留问题。
js
console.log(typeof undefined); // 输出: "undefined"
console.log(typeof null); // 输出: "object"

比较操作

  1. undefined 和 null 使用双等号 == 比较时会被认为相等,因为它们都代表“没有值”的概念。
  2. 使用严格等号 === 比较时,它们是不相等的,因为它们是不同类型的值。
js
console.log(undefined == null); // 输出: true
console.log(undefined === null); // 输出: false

变量赋值

  1. undefined 是 JavaScript 引擎自动赋予未赋值变量的值,而 null开发者显式赋值以表示变量没有值。
js
let x; // 未赋值,默认是 undefined
let y = null; // 明确赋值为 null

undefined 在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0

5.typeof null 的结果是什么?为什么

typeof null 的结果是 "object"

为什么 typeof null 是 "object"?

  1. JavaScript 在最初设计时,使用了32位系统。为了优化性能,JavaScript 的值被存储为二进制数据,低位用来表示数据的类型。
  2. 对象的类型标识符是 000,而 null 被认为是一个空指针(即零地址),它的二进制表示全是 0,也即 00000000。
  3. 由于 null 的二进制表示和对象的类型标识符相同,typeof null 结果就被错误地设置为 "object"。
  4. 尽管这个错误很早就被发现,但为了保持向后兼容性,修复这个错误会导致大量现有代码出错。因此,这个行为被保留下来了。 参考历史

运行示例

js
console.log(typeof null); // 输出: "object"

扩展知识

判断 null 的推荐方法

使用严格相等 === 进行比较,示例如下:

js
let value = null;
console.log(value === null); // 输出: true

6.typeof 和 instanceof 有什么区别?

typeofinstanceof 是 JavaScript 中用于检查变量类型的两个关键字,但它们的使用场景和功能有所不同。

typeof

typeof 操作符用于检测变量的类型返回一个字符串,表示操作数的数据类型,常见的返回值如下:

  1. "undefined":表示值未定义。
  2. "boolean":表示布尔值。
  3. "number":表示数字。
  4. "string":表示字符串。
  5. "object":表示对象(包括 null,数组,对象字面量等)。
  6. "function":表示函数。
  7. "symbol":表示符号(ES6 引入)。
  8. "bigint":表示大整数(ES11 引入)。

示例如下:

js
console.log(typeof undefined); // "undefined"
console.log(typeof true);      // "boolean"
console.log(typeof 42);        // "number"
console.log(typeof "hello");   // "string"
console.log(typeof {});        // "object"
console.log(typeof anyOtherObj);        // "object"
console.log(typeof null);      // "object" (特殊情况)
console.log(typeof function(){}); // "function"
console.log(typeof Symbol());  // "symbol"
console.log(typeof 10n);       // "bigint"

instanceof

instanceof 操作符用于检测某个对象是否是另一个对象(构造函数)的实例,返回一个布尔值,一些使用场景如下:

  1. 用于检测复杂类型,比如对象、数组、函数等。
  2. 检测某个对象是否继承自某个构造函数的原型链。 示例如下:
js
console.log({} instanceof Object);           // true
console.log([] instanceof Array);            // true
console.log(function(){} instanceof Function); // true
console.log(new Date() instanceof Date);     // true

function MyClass() {}
let myInstance = new MyClass();
console.log(myInstance instanceof MyClass);  // true

两者区别

  1. 检测类型的范围:typeof 主要用于检测基本数据类型(如 number,string,boolean 等)以及函数、未定义类型和 symbol,而 instanceof 主要用于检测对象的具体类型,检查某个对象是否是某个构造函数的实例。
  2. 检测基本类型和引用类型:typeof 对于基本类型非常有用,但对于复杂引用类型(如数组、对象字面量)只会返回 "object",而 instanceof 只能用于引用类型,不能用于检测基本数据类型。
  3. 特殊情况:typeof null 返回 "object",这是一个 JavaScript 语言的历史遗留问题,而 instanceof 可以用来检测自定义对象的类型,通过检查原型链来确认实例关系。

7.为什么 JavaScript 中 0.1 + 0.2 !== 0.3,如何让其相等?

详细讲解参考 在 JavaScript 中,数字是以二进制浮点数表示的。这种表示方式会导致某些十进制小数在二进制下无法精确表示,例如 0.1 和 0.2。它们在二进制中是无限循环的小数,示例如下:

js
0.1 的二进制表示约为:0.0001100110011001100110011001100110011001100110011001101...

0.2 的二进制表示约为:0.001100110011001100110011001100110011001100110011001101...

将这些二进制数相加时,由于精度限制,结果不能完全精确地表示为 0.3,而是一个非常接近的值:0.30000000000000004。

如何让其相等?

1)使用误差范围 一个常见的解决方案是设置一个误差范围,通常称为“机器精度”。在 JavaScript 中,这个值为 Number.EPSILON,它表示可接受的最小误差范围,示例如下:

js
function numbersAreEqual(num1, num2) {
  return Math.abs(num1 - num2) < Number.EPSILON;
}
console.log(numbersAreEqual(0.1 + 0.2, 0.3)); // 输出: true

2)使用 toFixed() 方法 将结果四舍五入到指定的小数位数。toFixed() 方法会返回一个字符串类型的结果,因此需要注意类型转换,示例如下:

js
let sum = 0.1 + 0.2;
let roundedSum = Number(sum.toFixed(1)); // 注意: toFixed 返回字符串,所以需要转换为数字
console.log(roundedSum === 0.3); // 输出: true

3)使用Number.toPrecision()方法 toPrecision() 方法也可以用于格式化数字并减少精度问题,示例如下:

js
let sum = 0.1 + 0.2;
let preciseSum = Number(sum.toPrecision(12)); // 12 是常用的精度位数
console.log(preciseSum === 0.3); // 输出: true

8.如何获取安全的 undefined 值?

使用 void 运算符对其后的表达式进行求值,然后返回 undefined。 因为 void 运算符总是返回 undefined,而且 0 是一个非常短的常量表达式,所以 void 0 是一种简洁且安全的方式来获得 undefined,示例如下:

js
let safeUndefined = void 0;

console.log(safeUndefined); // 输出: undefined

9.typeof NaN 的结果是什么?

NaN 代表“不是一个数字”(Not a Number),它通常用来表示无法执行数学运算或运算结果未定义的情况。尽管 NaN 表示“不是一个数字”,但它的类型却是 number,示例如下:

js
console.log(typeof NaN); // 输出: "number"

拓展知识

NaN 的特点

1)NaN 是唯一一个不等于自身的值(自反)。这意味着 NaN !== NaN 总是返回 true,示例如下:

js
console.log(NaN === NaN); // 输出: false
console.log(NaN !== NaN); // 输出: true

2)判断 NaN 可以使用 isNaN() 函数来判断,示例如下:

js
console.log(isNaN(NaN)); // 输出: true
console.log(isNaN(123)); // 输出: false

NaN 产生的原因

1)数学运算结果未定义或无法表示,示例如下:

js
console.log(0 / 0); // 输出: NaN

2)将无法解析为数字的字符串转换为数字,示例如下:

js
console.log(Number("abc")); // 输出: NaN

3)计算结果超出 JavaScript 能表示的数字范围,示例如下:

js
console.log(Math.sqrt(-1)); // 输出: NaN

10.isNaN 和 Number.isNaN 函数有什么区别?

isNaN 函数会先尝试将传入的参数转换为数字,然后检查转换后的值是否为 NaN。这意味着它不仅检测 NaN 本身,还会将那些不能转换为有效数字的值视为 NaN,示例如下:

js
console.log(isNaN(NaN));          // 输出: true
console.log(isNaN('hello'));      // 输出: true
console.log(isNaN(undefined));    // 输出: true
console.log(isNaN({}));           // 输出: true
console.log(isNaN(123));          // 输出: false
console.log(isNaN('123'));        // 输出: false

Number.isNaN 函数不会进行类型转换,只会在参数本身是 NaN 的情况下返回 true。它更为严格,只有传入的值是 NaN 时才会返回 true,示例如下:

js
console.log(Number.isNaN(NaN));          // 输出: true
console.log(Number.isNaN('hello'));      // 输出: false
console.log(Number.isNaN(undefined));    // 输出: false
console.log(Number.isNaN({}));           // 输出: false
console.log(Number.isNaN(123));          // 输出: false
console.log(Number.isNaN('123'));        // 输出: false

拓展知识

  1. 当你想检查一个值是否为无法被解析为数字时,可以使用 isNaN。例如,在用户输入需要被转换为数字的情况下,这个函数可以帮助检测非法输入。
  2. 当你明确地想检查某个值是否为 NaN,而不希望任何类型转换干扰判断结果时,应使用 Number.isNaN

11.== 操作符的强制类型转换规则是什么?

== 会在比较两个值时进行强制类型转换。这种类型转换遵循一套规则,使得不同类型的值可以相互比较。

强制转换规则

  1. null 和 undefined:null 和 undefined 仅相等于自身和对方,示例如下:
js
console.log(null == undefined); // true
console.log(null == null); // true
console.log(undefined == undefined); // true
console.log(null == 0); // false
console.log(undefined == 0); // false
  1. Boolean 类型:如果有一个操作数是布尔值,JavaScript 会将布尔值转换为数字,然后再进行比较,示例如下:
js
console.log(true == 1); // true
console.log(false == 0); // true
console.log(true == 2); // false
  1. 字符串和数字:如果是字符串和数字比较,JavaScript 会将字符串转换为数字,然后再进行比较,示例如下:
js
console.log('42' == 42); // true
console.log('42' == '42'); // true
console.log('42' == 43); // false
console.log('0' == false); // true
  1. 对象和原始类型:如果有一个操作数是对象,另一个是原始类型(字符串、数字、布尔值),JavaScript 会尝试调用对象的 toPrimitive 方法(valueOf 或 toString)将对象转换为原始类型,然后再进行比较,示例如下:
js
console.log([1, 2] == '1,2'); // true
console.log([1] == 1); // true
console.log({} == '[object Object]'); // true
  1. 符号和其他类型:Symbol 类型只能与 Symbol 类型进行比较,与其他类型的比较总是返回 false,示例如下:
js
console.log(Symbol() == Symbol()); // false
console.log(Symbol() == 'symbol'); // false
console.log(Symbol() == false); // false

特殊情况

  1. 空字符串:空字符串会被转换为数字 0 进行比较,示例如下:
js
console.log('' == 0); // true
console.log('' == false); // true
  1. 对象转换为原始类型:对象的比较会触发类型转换,通过调用 toPrimitive 方法(valueOf 或 toString),转换为原始类型后再比较,示例如下:
js
let obj = { toString: () => '42' };
console.log(obj == '42'); // true
console.log(obj == 42); // true

拓展知识

==操作符在比较两个不同类型的值时,会根据上述规则进行类型转换。这些规则可以导致一些意想不到的结果,因此在进行值比较时,需要格外小心。如果希望避免这些复杂的类型转换规则,推荐使用严格相等操作符 ===,它不进行类型转换,仅在值和类型都相等时才返回 true 。

12.JavaScript 其他值到字符串的转换规则是什么?

值到字符串的转换规则非常明确。不同类型的值在转换为字符串时,遵循各自的规则,转换规则如下:

基本数据类型到字符串的转换

1.Null 和 Undefined 类型

null 转换为字符串 "null",undefined 转换为字符串 "undefined",示例如下:

js
console.log(String(null));      // "null"
console.log(String(undefined)); // "undefined"

2.Boolean 类型

true 转换为字符串 "true",false 转换为字符串 "false",示例如下:

js
console.log(String(true));  // "true"
console.log(String(false)); // "false"

3.Number 类型

数字直接转换为字符串,极小或极大的数字使用指数形式,示例如下:

js
console.log(String(42));       // "42"
console.log(String(3.14));     // "3.14"
console.log(String(1e21));     // "1e+21"
console.log(String(1 / 7));    // "0.14285714285714285"

4.Symbol 类型

Symbol 值只能显式转换为字符串,隐式转换会抛出错误,示例如下:

js
let sym = Symbol('desc');
console.log(String(sym));   // "Symbol(desc)"
console.log(sym.toString()); // "Symbol(desc)"
// console.log(sym + "");    // TypeError: Cannot convert a Symbol value to a string

对象到字符串的转换

1.普通对象

如果对象没有自定义 toString() 方法,会调用 Object.prototype.toString() 返回 "[object Object]"。如果对象有自己的 toString() 方法,字符串化时会调用该方法并使用其返回值,示例如下:

js
let obj = {};
console.log(String(obj)); // "[object Object]"

let objWithToString = {
  toString() {
    return "custom object";
  }
};
console.log(String(objWithToString)); // "custom object"

2.数组对象

数组调用默认的 toString() 方法,会将数组元素转换为字符串并以逗号分隔,示例如下:

js
let arr = [1, 2, 3];
console.log(String(arr)); // "1,2,3"

3.日期对象

日期对象调用 toString() 方法,会返回日期的字符串表示,示例如下:

js
let date = new Date();
console.log(String(date)); // "Wed Jun 28 2023 10:30:45 GMT+0200 (Central European Summer Time)"

4.函数对象 调用 Function.prototype.toString(),返回函数的代码字符串,示例如下

js
function foo() {
  return "bar";
}
console.log(String(foo)); // "function foo() { return "bar"; }"

13.JavaScript 其他值到数字值的转换规则是什么?

将其他类型的值转换为数字值时,具体规则如下:

基本数据类型到数字的转换

1)Undefined 类型

undefined 转换为 NaN,示例如下:

js
console.log(Number(undefined)); // NaN

2)Null 类型

null 转换为 0,示例如下:

js
console.log(Number(null)); // 0

3)Boolean 类型

true 转换为 1,false 转换为 0,示例如下:

js
console.log(Number(true));  // 1
console.log(Number(false)); // 0

4)String 类型

字符串按照 Number() 函数进行转换。如果字符串包含非数字字符,则转换为 NaN,空字符串转换为 0,示例如下:

js
console.log(Number("42"));        // 42
console.log(Number("3.14"));      // 3.14
console.log(Number(""));          // 0
console.log(Number("hello"));     // NaN
console.log(Number("42abc"));     // NaN

5)Symbol 类型

Symbol 值不能转换为数字,会抛出 TypeError,示例如下:

js
let sym = Symbol("desc");
// console.log(Number(sym)); // TypeError: Cannot convert a Symbol value to a number

对象到数字的转换

对象(包括数组)会首先被转换为相应的基本类型值,然后再根据基本类型值的转换规则进行强制转换,步骤如下:

  1. JavaScript 尝试将对象转换为基本类型值。内部会首先检查该对象是否有 valueOf() 方法。
  2. 如果 valueOf() 存在并返回基本类型值,则使用该值进行强制类型转换。
  3. 如果没有 valueOf() 方法或其返回值不是基本类型,则使用 toString() 方法的返回值进行转换。
  4. 如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。

示例代码如下:

js
let obj1 = {
  valueOf() {
    return 42;
  }
};
console.log(Number(obj1)); // 42

let obj2 = {
  toString() {
    return "3.14";
  }
};
console.log(Number(obj2)); // 3.14

let obj3 = {
  valueOf() {
    return {};
  },
  toString() {
    return {};
  }
};
// console.log(Number(obj3)); // TypeError: Cannot convert object to primitive value

数组的转换

数组在转换为数字时,会被首先转换为字符串,然后再根据字符串的转换规则进行转换。如果数组包含多个元素,结果通常为 NaN,因为转换后的字符串包含逗号分隔的元素,示例如下

js
console.log(Number([1, 2, 3]));  // NaN
console.log(Number([42]));       // 42
console.log(Number([]));         // 0
console.log(Number(["3.14"]));   // 3.14

14.JavaScript 其他值到布尔值的转换规则是什么?

以下值在转换为布尔值时会被转换为 false:

  1. undefined
  2. null
  3. false
  4. +0
  5. -0
  6. NaN
  7. ""(空字符串)

示例代码如下:

js
console.log(Boolean(undefined)); // false
console.log(Boolean(null));      // false
console.log(Boolean(false));     // false
console.log(Boolean(0));         // false
console.log(Boolean(-0));        // false
console.log(Boolean(NaN));       // false
console.log(Boolean(""));        // false

除了上述之外的所有值,在转换为布尔值时都会被转换为 true。这包括:

  1. 非空字符串
  2. 非零数字(包括正数和负数)
  3. 对象(包括空对象)
  4. 数组(包括空数组)
  5. 函数

示例代码如下:

js
console.log(Boolean("hello"));     // true
console.log(Boolean(42));          // true
console.log(Boolean(-42));         // true
console.log(Boolean({}));          // true
console.log(Boolean([]));          // true
console.log(Boolean(function(){}));// true

15.JavaScript 中 || 和 && 操作符的返回值

逻辑操作符 || 和 && 有特定的返回值规则,它们不仅仅返回布尔值,还可能返回操作数本身规则如下:

逻辑或操作符 (||)

逻辑或操作符 || 会在找到第一个真值时立即返回该值。 如果所有操作数都为假值,则返回最后一个操作数。具体规则如下:

  1. 对第一个操作数进行条件判断。
  2. 如果第一个操作数的条件判断结果为 true,则返回第一个操作数的值。
  3. 如果第一个操作数的条件判断结果为 false,则返回第二个操作数的值。

示例代码如下:

js
console.log(false || true);       // true
console.log(0 || 42);             // 42
console.log('' || 'default');     // "default"
console.log(null || 'fallback');  // "fallback"
console.log(undefined || 'ok');   // "ok"
console.log(false || 0 || 'foo'); // "foo"
console.log('' || 0 || NaN);      // NaN

逻辑与操作符 (&&)

逻辑与操作符 && 会在找到第一个假值时立即返回该值。 如果所有操作数都为真值,则返回最后一个操作数。具体规则如下:

  1. 对第一个操作数进行条件判断。
  2. 如果第一个操作数的条件判断结果为 false,则返回第一个操作数的值。
  3. 如果第一个操作数的条件判断结果为 true,则返回第二个操作数的值。
js
console.log(true && false);       // false
console.log(42 && 0);             // 0
console.log('foo' && 'bar');      // "bar"
console.log('hello' && 123);      // 123
console.log(true && 'ok');        // "ok"
console.log(1 && 2 && 3);         // 3
console.log('' && 'fallback');    // ""
console.log(null && 'should not reach'); // null

两者区别

1)|| 操作符:返回第一个真值,或者在所有操作数均为假值时返回最后一个操作数。 2)&& 操作符:返回第一个假值,或者在所有操作数均为真值时返回最后一个操作数。

16.Object.is() 与比较操作符 == 和 === 的区别是什么?

双等号(==)

双等号进行相等判断时,如果两边的类型不一致,则会进行类型转换后再进行比较,规则如下:

  1. 如果类型不同,会进行类型转换。
  2. 将 null 和 undefined 视为相等。
  3. 将布尔值转换为数字再进行比较。
  4. 将字符串和数字进行比较时,会将字符串转换为数字。
  5. 对象与原始类型进行比较时,会将对象转换为原始类型。

示例如下:

js
console.log(2 == '2');       // true
console.log(null == undefined); // true
console.log(true == 1);      // true
console.log(false == 0);     // true
console.log('' == 0);        // true
console.log([1, 2] == '1,2'); // true

三等号(===)

三等号进行相等判断时,不会进行类型转换。如果两边的类型不一致,则直接返回 false,规则如下:

  1. 如果类型不同,返回 false。
  2. 如果类型相同,再进行值的比较。

示例如下:

js
console.log(2 === '2');       // false
console.log(null === undefined); // false
console.log(true === 1);      // false
console.log(false === 0);     // false
console.log('' === 0);        // false
console.log([1, 2] === '1,2'); // false

Object.is()

Object.is() 在大多数情况下与三等号的行为相同,但它处理了一些特殊情况,如 -0 和 +0,以及 NaN,规则如下:

  1. 如果类型不同,返回 false。
  2. 如果类型相同,再进行值的比较。
  3. 特殊情况:-0 和 +0 不相等,两个 NaN 是相等的。

示例如下:

js
console.log(Object.is(2, '2'));       // false
console.log(Object.is(null, undefined)); // false
console.log(Object.is(true, 1));      // false
console.log(Object.is(false, 0));     // false
console.log(Object.is('', 0));        // false
console.log(Object.is([1, 2], '1,2')); // false

console.log(Object.is(NaN, NaN));     // true
console.log(Object.is(+0, -0));       // false
console.log(Object.is(-0, -0));       // true
console.log(Object.is(+0, +0));       // true

区别总结

  1. ==:进行类型转换后再比较,适用于宽松相等性判断。
  2. ===:不进行类型转换,直接比较,适用于严格相等性判断。
  3. Object.is():与 === 类似,但处理了一些特殊情况,如 NaN 和 -0。

17.什么是 JavaScript 中的包装类型?

JavaScript 中,原始值没有方法或属性,但为了能够使用方法和属性,JavaScript 提供了包装类型,使得原始值可以像对象一样被操作。

包装类型的概念

包装类型是 JavaScript 中的一种机制,它允许原始值临时拥有对象的属性和方法。JavaScript 提供了三个包装类型:

  1. String
  2. Number
  3. Boolean

这些包装类型分别对应于原始值 string、number 和 boolean。

包装类型的行为

当你试图访问一个原始值的属性或方法时,JavaScript 会在后台自动创建一个对应的包装对象,然后在该对象上调用方法或访问属性。一旦操作完成,这个临时创建的对象就会被销毁,示例如下:

js
let str = "hello";
console.log(str.toUpperCase()); // "HELLO"

let num = 42;
console.log(num.toFixed(2)); // "42.00"

let bool = true;
console.log(bool.toString()); // "true"

原始值和包装对象的区别

虽然包装类型使得原始值可以像对象一样操作,但它们本质上是不同的,示例如下:

js
let strPrimitive = "hello";
let strObject = new String("hello");

console.log(typeof strPrimitive); // "string"
console.log(typeof strObject);    // "object"

console.log(strPrimitive === strObject); // false
console.log(strPrimitive == strObject);  // tr

这个例子中,strPrimitive 是一个原始值,而 strObject 是一个 String 对象。它们在类型上是不同的,严格相等(===)比较时会返回 false,但宽松相等(==)比较时会返回 true,因为 strObject 会被转换为原始值进行比较。

18.JavaScript 中如何进行隐式类型转换?

隐式类型转换也称为类型强制转换,是指 JavaScript 在表达式求值时自动将一种数据类型转换为另一种数据类型的过程。隐式类型转换主要发生在以下三种情况下:算术运算、比较运算和逻辑运算。常见的隐式类型转换规则如下:

1.算术运算

在算术运算中,JavaScript 会将操作数转换为数字类型,示例如下:

js
console.log(5 + "5"); // "55"(字符串拼接)
console.log("5" + 5); // "55"(字符串拼接)
console.log(5 + 5);   // 10(数值相加)
console.log(5 - "2"); // 3
console.log("6" * "2"); // 12
console.log("8" / 2); // 4
console.log("10" % 3); // 1

2.比较运算

在比较运算中,JavaScript 会将操作数转换为相同的类型再进行比较,示例如下:

js
// 双等号 ==
console.log(5 == "5"); // true(字符串 "5" 被转换为数字 5)
console.log(false == 0); // true(false 被转换为数字 0)
console.log(true == 1); // true(true 被转换为数字 1)
console.log(null == undefined); // true
// 三等号 ===
console.log(5 === "5"); // false
console.log(false === 0); // false
console.log(true === 1); // false
console.log(null === undefined); // false

3.其他比较运算符

对于其他比较运算符(>、<、>=、<=),操作数会被转换为数字或字符串,示例如下:

js
console.log(5 &gt; "2"); // true
console.log("6" &lt; "12"); // false(字符串比较)
console.log("8" &gt;= 8); // true
console.log("10" &lt;= 20); // true

4.逻辑运算

js
// 逻辑!
console.log(!0); // true(0 被转换为 false,然后取反为 true)
console.log(!1); // false(1 被转换为 true,然后取反为 false)
console.log(!""); // true(空字符串被转换为 false,然后取反为 true)
console.log(!"hello"); // false(非空字符串被转换为 true,然后取反为 false)
// 逻辑||
console.log(0 || 1); // 1(0 被转换为 false,因此返回第二个操作数 1)
console.log(1 || 0); // 1(1 被转换为 true,因此返回第一个操作数 1)
console.log(0 && 1); // 0(0 被转换为 false,因此返回第一个操作数 0)
console.log(1 && 2); // 2(1 被转换为 true,因此返回第二个操作数 2)
// 逻辑&&
console.log(0 || 1); // 1(0 被转换为 false,因此返回第二个操作数 1)
console.log(1 || 0); // 1(1 被转换为 true,因此返回第一个操作数 1)
console.log(0 && 1); // 0(0 被转换为 false,因此返回第一个操作数 0)
console.log(1 && 2); // 2(1 被转换为 true,因此返回第二个操作数 2)

5.字符串与数字之间的转换

字符串和数字之间的隐式转换在很多情况下都会发生,示例如下:

js
console.log("5" - 2); // 3("5" 被转换为数字 5,然后 5 - 2 = 3)
console.log("5" * "2"); // 10(两个字符串都被转换为数字)
console.log("5" / 2); // 2.5("5" 被转换为数字 5,然后 5 / 2 = 2.5)

19.JavaScript 中 + 操作符什么时候用于字符串的拼接?

根据 ECMAScript 规范,+ 操作符的具体行为取决于操作数的类型,规则如下:

1.字符串拼接

如果两个操作数中有一个是字符串,则 + 操作符将执行字符串拼接,示例如下:

js
console.log("hello" + " world"); // "hello world"
console.log(5 + "5"); // "55"
console.log("The answer is " + 42); // "The answer is 42"

2.数值加法

如果两个操作数都是数字,或者一方是布尔值或null ,则 + 操作符将执行数值加法(隐式转换为数字再执行运算),示例如下:

js
console.log(5 + 5); // 10
console.log(true + 1); // 2 (true 被转换为 1)
console.log(null + 1); // 1 (null 被转换为 0)

3.对象转换为原始值

如果操作数是对象或,它们会首先转换为原始值,然后再根据原始值的类型进行操作,示例如下:

js
console.log([1, 2, 3] + [4, 5, 6]); // "1,2,34,5,6"
console.log({} + {}); // "[object Object][object Object]"

4.数字和字符串 优先转为字符串处理,示例如下:

js
console.log(10 + "20"); // "1020"
console.log("10" + 20); // "1020"

5.布尔值和字符串

js
console.log(true + "test"); // "truetest"
console.log(false + "test"); // "falsetest"

20.JavaScript 中为什么会有 BigInt 的提案?

JavaScript 中的数字类型是基于 IEEE 754 双精度浮点数标准实现的。这种实现方式虽然在绝大多数情况下足够,但在处理非常大的整数时会出现精度问题。为了处理和表示任意精度的整数,JavaScript 引入了 BigInt 类型。BigInt 可以表示和操作任意大的整数而不会丢失精度,从而解决了大整数运算中的问题,问题如下:

js
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992 (错误)

BigInt 的优势

  1. 支持任意大整数:BigInt 可以表示任意大的整数,而不会丢失精度。
  2. 专门设计用于整数运算:与浮点数不同,BigInt 专门用于整数运算,确保了精度和一致性。
  3. 与 Number 类型区别明确:BigInt 是一种新的原始数据类型,与现有的 Number 类型区别明确,避免了混淆。

使用示例

使用 BigInt 可以非常简单,只需在整数后加上 n 后缀,或者使用 BigInt 构造函数,示例如下:

js
const bigInt1 = 1234567890123456789012345678901234567890n;
const bigInt2 = BigInt("1234567890123456789012345678901234567890");

console.log(bigInt1); // 1234567890123456789012345678901234567890n
console.log(bigInt1 + 1n); // 1234567890123456789012345678901234567891n

需要注意的是,BigInt 和 Number 类型不能直接混合使用,示例如下:

js
const num = 42;
const bigInt = 12345678901234567890n;

console.log(num + bigInt); // TypeError: Cannot mix BigInt and other types

需要进行显式转换,示例如下:

js
console.log(BigInt(num) + bigInt); // 12345678901234567932n

21.Object.assign 和对象扩展运算符有什么区别?是深拷贝还是浅拷贝?

省流:基本上没区别

Object.assign 和对象扩展运算符 ... 都是执行浅拷贝,即只复制对象的第一层属性,不会递归复制整个对象结构。对于需要深拷贝的情况,需要额外的处理逻辑来实现。

  • Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。
  • 扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。

Object.assign

用法Object.assign(target, ...sources):将一个或多个源对象的所有可枚举属性复制到目标对象,并返回目标对象。也可以用于合并对象或复制对象属性到一个新的对象。

深浅拷贝Object.assign 执行的是浅拷贝,即它只会复制对象的第一层属性。如果源对象的属性值是对象或数组等引用类型,只复制引用,不会递归复制整个引用对象。

覆盖属性 如果多个源对象具有相同的属性,后续对象的属性会覆盖之前对象的属性。

返回值 返回的是目标对象本身,而不是新创建的对象,示例如下:

js
const obj1 = { a: 1, b: &#123; c: 2 &#125; };
const obj2 = { b: &#123; d: 3 &#125;, e: 4 };

const mergedObj = Object.assign({}, obj1, obj2);
console.log(mergedObj); // { a: 1, b: &#123; d: 3 &#125;, e: 4 }
console.log(obj2.b === mergedObj.b); // true,浅拷贝只复制了对象的引用

对象扩展运算符...

用法 对象扩展运算符通常用于创建新的对象字面量或复制现有对象的属性到新对象中。也可以用于对象字面量、对象解构赋值、函数参数等场景。

深浅拷贝 Object.assign 一样,对象扩展运算符也执行浅拷贝。它只复制对象的第一层属性。

覆盖属性Object.assign 类似,如果多个源对象具有相同的属性,后续对象的属性会覆盖之前对象的属性。

返回值 对象扩展运算符在对象字面量和对象解构赋值中使用时,会创建一个新的对象,示例如下:

js
const obj1 = { a: 1, b: &#123; c: 2 &#125; };
const obj2 = { b: &#123; d: 3 &#125;, e: 4 };

const mergedObj = &#123; ...obj1, ...obj2 &#125;;
console.log(mergedObj); // { a: 1, b: &#123; d: 3 &#125;, e: 4 }
console.log(obj2.b === mergedObj.b); // true,浅拷贝只复制了对象的引用

22.Map,Set,Object,Array对比

在 JavaScript 中,MapSetObjectArray 是最常见的数据结构,它们在底层实现、适用场景、性能等方面各有不同。本文将详细解析它们的区别,包括 底层实现、特性、使用场景、性能分析 等。


1️⃣Object(对象)

特点

  • Object 是 JavaScript 最基本的键值存储结构,用于存储 键值对
  • 只能使用 字符串 (String) 和 Symbol 作为键,不能使用对象或其他数据类型作为键。
  • 键的顺序一般是按照属性创建的顺序(但 Object 早期实现中,对数字键可能会按照数值大小排序)。
  • 具有 原型链,继承自 Object.prototype,因此可能会受到原型上的属性影响。

⚙️ 底层实现

  • 隐藏类(Hidden Class)+ Shape(属性形状表)
    • V8 引擎使用 隐藏类 (Hidden Class) 技术来优化 Object 访问性能。
    • 结构稳定的对象采用 快属性存储 (Fast Properties),类似于 C++ 结构体,提高访问速度。
    • 如果 Object 结构变化较大(动态添加/删除属性),可能会退化为 字典模式 (Dictionary Mode),降低访问速度。

适用场景

  • 适用于存储少量 键值对,尤其是键是 字符串 的情况。
  • 适用于 静态结构(即不频繁增删属性)的对象。

性能

  • 访问性能较好 (O(1)),但当 Object 结构复杂时,可能会退化为 O(log n)
  • 动态添加/删除属性可能会导致 隐藏类变更,影响性能。

🔍 示例

js
const obj = &#123; name: "Alice", age: 25 &#125;;
obj.gender = "female"; // 动态添加属性,可能导致隐藏类变更
console.log(obj.name); // O(1) 访问

2️⃣ Map(映射)

特点

  • Map 是一种 键值存储 结构,支持任何数据类型 作为键(包括对象、数组、函数等)。
  • Map 保持键的插入顺序,迭代时按插入顺序遍历。
  • 没有 Object原型链 问题(即 map.has("__proto__") 不会出错)。
  • 提供了一些专门的方法,如 .get().set().delete() 等。

⚙️ 底层实现

  • 哈希表 (Hash Table) 或 红黑树 (Red-Black Tree)
    • V8 使用 哈希表 实现 Map,查找/插入/删除的时间复杂度通常是 O(1)
    • 如果哈希冲突较多,可能会退化到 红黑树,时间复杂度变为 O(log n)

🚀 适用场景

  • 适用于需要频繁增删键值对的场景,性能比 Object 更稳定。
  • 适用于键是 非字符串(如对象、数组等) 的情况。

性能

  • .get().set().has() 时间复杂度通常为 O(1)(哈希表)。
  • 大数据量时,比 Object 更高效,因为 Object 可能退化为字典模式。

🔍 示例

js
const map = new Map();
map.set("name", "Alice");
map.set(123, "Number Key");
map.set(&#123; key: "objectKey" &#125;, "Object Key");
console.log(map.get("name")); // Alice
console.log(map.size); // 3

3️⃣ Array(数组)

特点

  • Array有序 数据结构,元素通过 索引 (index) 访问。
  • JavaScript 的 Array 并不是严格的连续内存存储,而是 动态数组(Dynamic Array)。
  • 允许 不同类型的元素,但如果类型统一,V8 可能会优化存储结构。

⚙️ 底层实现 V8 采用了 多种存储优化,根据数组的数据结构不同,存储方式也不同:

  1. Packed Elements(紧密存储)
    • 适用于索引连续、类型相同的数组。
    • V8 使用类似 C++ 的连续内存存储,访问速度 O(1)
  2. Holey Elements(稀疏数组)
    • 如果数组有 空洞 (hole)(如 arr[100] = 1),V8 会转换为 哈希表存储,查找速度降低到 O(log n) 或更慢。
  3. Dictionary Elements(字典模式)
    • 如果数组非常稀疏,V8 会采用 字典模式,查找速度较慢。

🚀 适用场景

  • 适用于 有序数据
  • 适用于 需要快速索引 的场景(如果是紧密存储)。
  • 不适用于 大范围稀疏索引(可能退化为哈希表)。

性能

  • push() / pop() O(1)(如果是Packed Elements)。
  • shift() / unshift() O(n)(需要移动所有元素)。
  • 如果是 Holey Elements,查询可能退化到 O(log n)

🔍 示例

js
const arr = [1, 2, 3, 4, 5];
arr.push(6);  // O(1)
console.log(arr[2]); // O(1)
arr[100] = 10; // 变成 Holey Elements,存储方式变化

4️⃣ Set(集合)

特点

  • Set 只存储 唯一值(自动去重)。
  • 保持插入顺序,遍历时按插入顺序进行。
  • 允许存储 任意类型 的值(对象、数组、基本类型等)。

⚙️ 底层实现

  • 哈希表 (Hash Table)
    • Set 的底层数据结构与 Map 类似,通常使用 哈希表 存储元素。
    • .has().add().delete() 的时间复杂度通常是 O(1)
    • 如果哈希冲突严重,可能退化到 O(log n)

🚀 适用场景

  • 适用于存储 唯一值集合,避免重复元素。
  • 适用于 去重操作(比 Array 进行 indexOf() 更高效)。

性能

  • .add() / .delete() O(1)(哈希表)。
  • 遍历性能 可能比 Array 略慢(取决于哈希表实现)。

🔍 示例

js
const set = new Set([1, 2, 3, 3, 4]);
set.add(5);
console.log(set.has(3)); // true
console.log(set.size); // 5

总结

特性ObjectMapArraySet
键类型仅字符串/Symbol任意类型仅整数索引任意类型
存储结构哈希表+隐藏类哈希表Packed/ Holey 数组哈希表
查找性能O(1)(快模式) / O(log n)(字典模式)O(1)O(1)(连续存储)/ O(log n)(稀疏)O(1)
去重
适用场景少量键值对大量键值对有序数据唯一值集合

23. WeakMapWeakSet vs MapSet:优势及底层原理


WeakMapWeakSet 的核心优势

WeakMapWeakSetMapSet 的“弱引用”版本,它们的主要优势包括:

  1. 弱引用(Weak References)
    • WeakMap 的键只能是对象,不会影响垃圾回收机制。
    • WeakSet 只能存储对象,同样不会阻止垃圾回收。
    • 当对象没有其他引用时,JS 引擎会自动回收该对象的内存,即 WeakMapWeakSet 里的对象可能会被自动清除
  2. 自动垃圾回收(Garbage Collection, GC)
    • Map / Set 强引用 存储数据,即使没有其他引用,仍然存在于 MapSet 中,不会被 GC 回收。
    • WeakMap / WeakSet 弱引用 存储数据,如果对象不再被引用,它就会被回收,不会造成内存泄漏
  3. 无法遍历(No Enumeration)
    • 由于 WeakMapWeakSet 内部存储的对象随时可能被垃圾回收,它们 不能被遍历,没有 .keys().values().entries().forEach() 这样的 API。
    • 只能使用 .get().set().has().delete() 进行操作。

WeakMap vs Map

特性MapWeakMap
键类型任何类型(字符串、对象、数字等)只能是对象
值类型任何类型任何类型
垃圾回收不会自动回收自动垃圾回收
是否可遍历✅ 可遍历 .keys() / .values()❌ 不能遍历
常见用途存储长期数据存储临时对象的元数据

WeakMap 使用示例

js
let weakMap = new WeakMap();
let obj = &#123; name: "Alice" &#125;;

weakMap.set(obj, "some value");
console.log(weakMap.get(obj)); // 输出: "some value"

obj = null; // 现在对象没有引用,会被GC回收
console.log(weakMap.get(obj)); // undefined (因为对象被垃圾回收了)

🔹 优势:

  • obj 设为 nullWeakMap 里的键值对会被自动清除,不会导致内存泄漏。

底层原理

  • WeakMap 不会增加对象的引用计数,不会影响垃圾回收 (GC)。
  • 现代 JS 引擎(如 V8)采用 标记-清除算法 进行垃圾回收,当对象没有其他引用时,会自动释放 WeakMap 里的对象。
  • WeakMap 可能使用 弱哈希表(Weak Hash Table) 作为底层实现,使得 GC 能安全地回收键。

WeakSet vs Set

特性SetWeakSet
存储数据类型任何类型(对象、数字、字符串等)只能存对象
值是否唯一✅ 不能有重复值✅ 不能有重复对象
垃圾回收不会自动回收自动垃圾回收
是否可遍历✅ 可遍历 .values()❌ 不能遍历
常见用途存储唯一数据存储临时对象的引用

WeakSet 使用示例

js
let weakSet = new WeakSet();
let obj1 = &#123; id: 1 &#125;;
let obj2 = &#123; id: 2 &#125;;

weakSet.add(obj1);
weakSet.add(obj2);

console.log(weakSet.has(obj1)); // true

obj1 = null; // 现在对象没有引用,会被GC回收
console.log(weakSet.has(obj1)); // false(因为 obj1 已被垃圾回收)

🔹 优势:

  • WeakSet 自动回收不再使用的对象,适用于 存储不需要长期引用的对象,如 缓存、临时数据存储

底层原理

  • WeakSet 也是使用 弱哈希表 存储对象,且不影响 GC 。
  • WeakSet 不允许遍历,因为存储的对象可能随时被垃圾回收,导致数据不完整。

适用场景对比

适用场景MapWeakMapSetWeakSet
存储键值对✅(仅对象键)
存储唯一值集合✅(仅对象)
垃圾回收
可遍历性
适用于缓存
适用于 DOM 监听

WeakMapWeakSet 的实际应用

1. WeakMap 用于存储对象的元数

js
const metadata = new WeakMap();

class User {
  constructor(name) {
    this.name = name;
    metadata.set(this, &#123; id: Date.now() &#125;); // 存储对象的元数据
  }

  getId() {
    return metadata.get(this).id;
  }
}

let user = new User("Alice");
console.log(user.getId()); // 显示唯一 ID
user = null; // 用户对象被GC回收,WeakMap 自动清除

优势

  • 这样存储的 元数据不会影响 GC,如果对象被销毁,对应的数据也会被销毁。

2️. WeakSet 用于存储访问过的 DOM 元素

js
const visitedElements = new WeakSet();

function trackElement(element) {
  if (!visitedElements.has(element)) {
    visitedElements.add(element);
    console.log("Tracking new element:", element);
  }
}

let div = document.createElement("div");
trackElement(div);

div = null; // `div` 不再被引用,WeakSet 自动清除

优势

  • WeakSet 自动回收 DOM 失效引用,防止 内存泄漏

总结

对比项MapWeakMapSetWeakSet
存储数据键值对键值对(仅对象键)唯一值集合唯一对象集合
键类型任何类型只能是对象任何类型只能是对象
是否可遍历可遍历不可遍历可遍历不可遍历
垃圾回收不会自动回收自动回收不会自动回收自动回收
适合场景存储数据(长期存在)存储临时对象数据存储唯一数据(长期存在)存储短期对象集合

选择建议

  • 需要 持久存储键值对?👉 使用 Map
  • 需要 存储短期对象信息,避免内存泄漏?👉 使用 WeakMap
  • 需要 存储唯一值集合?👉 使用 Set
  • 需要 存储临时对象集合,避免引用影响 GC?👉 使用 WeakSet

评论区

欢迎留言、补充或勘误。

xiaoba.blog