Skip to content

Javascript

JS 相关知识。

一. js 常见模块类型

  1. CommonJS
    • 主要用于服务器端(如 Node.js)。
    • require加载模块,module.exportsexports导出。
    • 加载模块是同步的。
  2. AMD(Asynchronous Module Definition)
    • 用于浏览器端,解决异步加载。
    • 依赖前置,通过define函数定义模块,回调函数处理模块逻辑。
    • require.js库实现该规范,支持异步加载模块。
  3. UMD(Universal Module Definition)
    • 通用模块定义,兼容 CommonJS 和 AMD。
    • 可在不同环境(浏览器、Node.js)使用。
    • 通过判断环境,采用不同方式加载和定义模块。
  4. ES6 模块
    • 语言层面的模块系统。
    • import导入,export导出。
    • 静态分析导入导出关系,支持模块的循环引用。

讲讲前端模块化规范

前端模块化主要有几种规范。

  • CommonJS主要用于Node.js,同步加载。
  • AMDCMD是为老浏览器设计的异步方案,现在用的少了。
  • 目前的主流是官方的ES Module,支持浏览器和Node.js,支持静态优化。
  • UMD是一种兼容写法,能让一个模块在多种环境下运行。

CJS和ESM最核心的区别

  • 加载时机:CJS是运行时加载(动态),ESM是编译时加载(静态)。
  • 值拷贝 vs 引用:CJS导出的是值的拷贝,而ESM导出的是引用。

正因为ESM是静态的,所以打包工具才能做Tree-shaking优化,去掉未使用的代码。

运行时加载和编译时加载

  • 编译时加载:在代码开始执行之前,模块系统就会扫描所有import语句,构建出完整的模块依赖关系图。
  • 运行时加载:在代码执行到require函数时,才会加载并执行相应的模块。

Tree-shaking

Tree-shaking是一种在构建阶段通过静态分析,移除JS代码中未被使用的导出,以减小最终打包体积的性能优化技术。

如何在项目中有效启用Tree-shaking?

1.使用ESM语法:确保代码和第三方库使用import/export。

2.配置生产模式:在Webpack等工具中,设置mode: 'production'通常会默认开启优化。

3.注意副作用:在package.json中通过 "sideEffects"字段标记哪些文件有副作用(如 CSS、Polyfill),防止被误删。

4.按需引入库:避免引入整个库。例如import { debounce } from 'lodash-es'而不是 import _ from 'lodash'。

二. slice 和 substring 和 substr 的区别

substring

在 JavaScript 中,substring()方法用于提取字符串的一部分。它接受两个参数,start 和 end。start 是必需的,表示提取的起始位置(索引从 0 开始),end 是可选的,表示提取的结束位置(不包括该位置的字符)。例如:

如果 start > end,会自动交换这两个参数的位置;如果其中一个小于 0,则替换成 0。

js
let str = "Hello, World!";
let result = str.substring(7, 12);
// result的值为"World",提取从索引7(包含)到索引12(不包含)的字符

slice

slice()方法同样用于提取字符串的一部分,它也接受两个参数,start 和 end,含义和 substring 类似,不过 slice()方法在处理 负数 索引时有所不同。负数索引表示从字符串末尾开始计数,例如-1 表示最后一个字符。

js
let str = "Hello, World!";
let result = str.slice(7, -1);
// result的值为"World",提取从索引7(包含)到倒数第一个字符(不包含)的字符

三. includes 和 indexOf

indexOf 和 includes 都可以接受第二个可选参数,用于指定开始查找的索引位置。

效率

对于简单数据类型(如数字、字符串等),在大多数现代浏览器中,includes 方法和 indexOf 方法的性能差异不大。它们在底层实现上都需要遍历数组元素来进行查找。不过,includes 方法在语义上更简洁,当只需要判断元素是否存在时,使用 includes 可能更易读。

复杂数据类型查找

当数组元素是复杂数据类型(如对象)时,indexOf 方法使用严格相等(===)来比较元素。这意味着它会比较对象的引用,而不是对象的内容。

js
let obj1 = { name: "John" };
let arr = [obj1];
console.log(arr.indexOf(obj1)); // 0
console.log(arr.includes(obj1)); // true

四. 数组 splice()方法

基本语法

js
array.splice(start, deleteCount, item1, item2, ...)

主要功能

删除元素

js
let arr = [1, 2, 3, 4, 5];
arr.splice(2, 2); // 从索引2开始删除2个元素
console.log(arr); // [1, 2, 5]

添加元素

js
let arr = [1, 2, 3];
arr.splice(1, 0, "a", "b"); // 在索引1位置插入元素
console.log(arr); // [1, 'a', 'b', 2, 3]

替换元素

js
let arr = [1, 2, 3, 4];
arr.splice(1, 2, "x", "y"); // 从索引1开始删除2个元素,并插入新元素
console.log(arr); // [1, 'x', 'y', 4]

返回值

splice()返回被删除的元素组成的数组:

js
let arr = [1, 2, 3, 4];
let removed = arr.splice(1, 2);
console.log(removed); // [2, 3]
console.log(arr); // [1, 4]

实用技巧

  • ​ 删除最后一个元素 ​:arr.splice(-1, 1)
  • ​ 清空数组 ​:arr.splice(0)
  • 留下前 n 个元素:arr.splice(n)
  • ​ 批量插入 ​:arr.splice(1, 0, ...['a', 'b', 'c'])

splice()会直接修改原数组,使用时请注意这一点。

五.创建二维数组

如果用 fill 方法创建二维数组,会导致所有子数组引用相同的内存地址,修改一个子数组会影响所有子数组。

因此可以用 map 方法创建二维数组:

js
const arr = new Array(3).fill(0).map(() => new Array(2).fill(0));

或者:

js
const dp = Array.from({ length: 3 }, () => new Array(2).fill(0));

六. ES6

ES6 (ECMAScript 2015) 为JavaScript 引入了许多重要的新特性。

1.块级作用域(let & const)

let和 const声明的变量具有块级作用域,即只在其所在的 {}内有效,且不存在变量提升。

js
if (true) {
  let a = 10;
  const B = 20;
  // B = 30; // 报错,const声明的常量不可重新赋值
}
// console.log(a); // 报错,a is not defined

const用于声明常量,声明后必须立即赋值,且不能重新赋值。

2.箭头函数

箭头函数没有自己的 this,其内部的 this指向的是定义时所在上下文的 this。

箭头函数不能用作构造函数,不能使用 new命令,也没有 arguments对象。

3.模板字符串与解构赋值

模板字符串使用反引号(`)定义,可以嵌入变量(${expression})和换行。

解构赋值允许从数组或对象中快速提取值。

4.Promise与Async/Await

Promise是处理异步操作的对象,有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。

js
const promise = new Promise((resolve, reject) => {
  // 异步操作,例如请求数据
  if (/* 成功 */) {
    resolve(value);
  } else {
    reject(error);
  }
});

promise
  .then(value => { /* 处理成功结果 */ })
  .catch(error => { /* 处理错误 */ });

async/await是基于 Promise的语法糖,让异步代码看起来更像同步代码。

js
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Error:', error);
  }
}

5.Class类

ES6 的 class让对象的原型写法更清晰。

js
class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log(`Hello, I'm ${this.name}`);
  }
}

class Student extends Person {
  constructor(name, grade) {
    super(name); // 调用父类的constructor
    this.grade = grade;
  }
  study() {
    console.log(`${this.name} is studying.`);
  }
}

6.新的数据结构Set和Map

Set​ 成员唯一,常用于数组去重。

Map​ 键值对集合,键可以是任何类型。

7. 迭代器(Iterator)与生成器(Generator)

迭代器协议

迭代器是按需产生序列值的对象,必须实现next()方法:

js
const arrayIterator = [1, 2, 3][Symbol.iterator]();
console.log(arrayIterator.next()); // { value: 1, done: false }
console.log(arrayIterator.next()); // { value: 2, done: false }[10](@ref)

生成器函数

生成器是特殊的函数,可以暂停和恢复执行:

js
function* numberGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = numberGenerator();
console.log(gen.next().value); // 1[9](@ref)

当生成器函数执行完毕(即 next()方法返回 {done: true})后,​生成器对象的状态就永久终止了,无法从中断处恢复执行或重置内部状态。

如果你需要再次从头开始迭代序列,最直接的方法是创建一个新的生成器对象​。

8.Symbol

Symbol​ 是一种新的原始数据类型,用于创建唯一的标识符,解决命名冲突问题。

js
// 每个Symbol都是唯一的
const sym1 = Symbol('id');
const sym2 = Symbol('id');
console.log(sym1 === sym2); // false[1](@ref)

// 用作对象属性名
const obj = {
  [Symbol('private')]: 'secret value',
  name: 'public value'
};

实际应用场景:

  • 避免属性冲突:在第三方库开发中防止属性名覆盖
  • 模拟私有属性:Symbol属性不会被常规方法遍历到
  • 定义内置行为:通过如Symbol.iterator等改变对象默认行为

通过Symbol.iterator可以自定义对象的迭代行为,例如:

js
class students {
  members = ["小明", "小王"];

  [Symbol.iterator](){
    let index = 0;
    return {
      next: () => {
        return {done: index >= this.members.length, value: this.members[index]}
      }
    }
  }
}

也可以使用 ​Generator 函数​ 实现自定义迭代器:

js
class students {
  members = ["小明", "小王"];

  *[Symbol.iterator](){
    for (let member of this.members) {
      yield member;
    }
  }
}

测试一下:

js
let stu = new students();
for (let member of stu) {
  console.log(member);
}

输出:

小明
小王

常见问题

暂时性死区(TDZ)

定义:在代码块内,使用let或const声明变量前,该变量不可访问(引用会报错),这段时间称为暂时性死区(TDZ)。

为什么会有TDZ?

  • 避免变量提升导致的逻辑错误(如未声明就使用)
  • 使JavaScript更符合静态语言的变量声明规范

如何绕过TDZ?

  • 始终在作用域顶部声明变量(最佳实践)
  • 使用var(不推荐,破坏代码可维护性)

七、检测数据类型

  1. typeof:

typeof操作简单易用,可以快速检测基本数据类型。但它无法区分Object、Array和Null,因为都会返回"object"。

示例:

js
console.log(typeof 'Hello, World!'); // 输出'string'
console.log(typeof 3.14); // 输出'number'
console.log(typeof true); // 输出'boolean'
console.log(typeof undefined); // 输出'undefined'
console.log(typeof null); // 输出'object'
console.log(typeof Symbol()); // 输出'symbol'
console.log(typeof 123n); // 输出'bigint'
console.log(typeof {}); // 输出'object'
console.log(typeof []); // 输出'object'
console.log(typeof function() {}); // 输出'function'
  1. instanceof:

主要用于检测引用数据类型、判断一个实例是否属于某个类。

js
console.log([] instanceof Array); // 输出true
console.log({} instanceof Object); // 输出true
console.log(function() {} instanceof Function); // 输出true
  1. Object.prototype.toString.call():

这是一种更通用的方法,可用于检测所有数据类型,包括内置对象和自定义对象。

js
console.log(Object.prototype.toString.call('Hello, World!')); // 输出'[object String]'
console.log(Object.prototype.toString.call(3.14)); // 输出'[object Number]'
console.log(Object.prototype.toString.call(true)); // 输出'[object Boolean]'
console.log(Object.prototype.toString.call(undefined)); // 输出'[object Undefined]'
console.log(Object.prototype.toString.call(null)); // 输出'[object Null]'
console.log(Object.prototype.toString.call(Symbol())); // 输出'[object Symbol]'
console.log(Object.prototype.toString.call(123n)); // 输出'[object BigInt]'
console.log(Object.prototype.toString.call({})); // 输出'[object Object]'
console.log(Object.prototype.toString.call([])); // 输出'[object Array]'
console.log(Object.prototype.toString.call(function() {})); // 输出'[object Function]'

八、闭包

闭包是指能够访问并记住其词法作用域(定义时所处于的作用域)变量的函数,即使这个函数在作用域之外被执行。

形成条件:

  • 函数嵌套
  • 内部函数引用外部函数的变量

常见应用:

1.创建私有变量

闭包可以模拟私有变量,这些变量只能通过特定的公共方法进行访问和修改,从而实现数据的封装。

js
function createCounter() {
  let count = 0; // 私有变量
  return {
    increment: function() { count++; return count; },
    decrement: function() { count--; return count; },
    getCount: function() { return count; }
  };
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
// 无法直接从外部访问 count,例如 counter.count 是 undefined

2.函数工厂

闭包可以用于创建函数工厂,即根据不同参数生成不同函数的模式。

js
function createMultiplier(factor) {
  return function(number) {
    return number * factor;
  };
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15

注意事项与潜在问题

1.内存泄漏

由于闭包会长期持有对外部变量的引用,如果不慎引用了不再需要的大对象(如大型数组、DOM元素),可能会导致这些对象无法被垃圾回收,从而引起内存泄漏。在不需要闭包时,可以手动将引用置为 null来辅助垃圾回收。

2.循环中的闭包陷阱

在循环中使用 var声明变量并创建闭包时,可能会遇到一个常见问题:所有闭包都引用同一个循环变量,最终得到的是循环结束后的最终值。

js
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 输出三个 3,而不是预期的 0, 1, 2
  }, 100);
}

​解决方案​:使用 let声明循环变量,因为 let具有块级作用域,每次迭代都会创建一个新的变量绑定,每个闭包捕获的都是当次迭代的 i值。

js
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 正确输出 0, 1, 2
  }, 100);
}

另一种传统方法是使用**立即执行函数表达式(IIFE)**​​ 为每次迭代创建一个新的作用域。

js
for (var i = 0; i < 3; i++) {
  (function(i) {
    setTimeout(function() {
      console.log(i); // 正确输出 0, 1, 2
    }, 100);
  })(i); // 将当前 i 的值作为参数传入
}

九、原型和原型链

原型是一个对象,它是用来创建其他对象的模板。每个函数都有一个prototype属性,它指向该函数的原型对象。

原型链是由一系列原型对象组成的链状结构。每个对象都有一个__proto__属性,它指向它的原型对象。当访问一个对象的属性或方法时,JavaScript引擎会先在当前对象上查找,如果找不到,就会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的末尾(null)。

原型链的作用是实现继承

原型链图示:

person1 --> Person.prototype --> Object.prototype --> null

如何检查一个属性是对象自身的还是继承自原型链的?

使用hasOwnProperty()方法:

js
person1.hasOwnProperty('name'); // true
person1.hasOwnProperty('greet'); // false

如何实现继承?

通过原型链实现继承:

js
function Person(name) {
    this.name = name;
  }

  function Employee(name, title) {
    Person.call(this, name);
    this.title = title;
  }

  Employee.prototype = Object.create(Person.prototype);
  Employee.prototype.constructor = Employee;

  Employee.prototype.sayTitle = function () {
    console.log("My title is " + this.title);
  }

如何避免原型链污染?

  • 不要直接修改Object.prototype,因为这会影响所有对象。
  • 使用Object.freeze()冻结原型。
  • 使用Object.create(null)创建无原型的对象。

十、this指向

this的值完全取决于函数的调用方式,而不是定义的位置。

js
// 1.在全局作用域中,this指向全局对象:
console.log(this === window); // 输出true(在浏览器中)

// 2.在函数调用中,如果函数不是作为对象的方法被调用,那么this指向全局对象:
function foo() {
    console.log(this === window); // 输出true(在浏览器中)
}
foo();

// 3.在作为对象方法调用时,this指向调用该方法的对象:
let obj = {
    myMethod: function() {
        console.log(this === obj); // 输出true
    }
};
obj.myMethod();

// 4.在构造函数中,this指向新创建的对象:
function MyConstructor() {
    this.myProperty = 'Hello World!';
    console.log(this instanceof MyConstructor); // 输出true
}
let myInstance = new MyConstructor();

// 5.在事件处理程序中,this指向触发事件的元素:
<button id="myButton">点击!</button>
<script>
    let button = document.getElementById('myButton');
    button.onclick = function() {
        console.log(this === button); // 输出true
    };
</script>

// 6.使用call()、apply()和bind()方法显式地设置函数调用时的this值:
function foo() {
    console.log(this);
}
let obj = { a: 1 };
foo.call(obj); // 输出{ a: 1 }
foo.apply(obj); // 输出{ a: 1 }
let bar = foo.bind(obj);
bar(); // 输出{ a: 1 }

十一、call、apply、bind

用于显式控制函数执行时 this指向。

  1. call

call方法会立即调用函数,第一个参数指定 this的指向,后续参数以列表形式逐个传递给函数。

js
const person = { name: 'Alice' };

function introduce(greeting, age) {
  console.log(`${greeting}, 我是${this.name}, 今年${age}岁。`);
}

// 使用 call,'Hello' 和 25 作为单独参数传递
introduce.call(person, 'Hello', 25); // 输出:Hello, 我是Alice, 今年25岁。[2,7](@ref)
  1. apply

apply方法也会立即调用函数,与 call的关键区别在于它接受一个数组或类数组作为第二个参数,数组元素会展开成为函数的参数。

js
const person = { name: 'Bob' };

function introduce(greeting, age, hobby) {
  console.log(`${greeting}, 我是${this.name}, 今年${age}岁,喜欢${hobby}。`);
}

// 参数放在一个数组中传递
const args = ['Hi', 30, '爬山'];
introduce.apply(person, args); // 输出:Hi, 我是Bob, 今年30岁,喜欢爬山。[2,9](@ref)
  1. bind

bind方法不会立即执行函数,而是返回一个全新的函数。这个新函数的 this值被永久绑定到 bind的第一个参数,并且可以预先传入部分参数(这被称为柯里化)。

js
const person = { name: 'Charlie' };

function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

// 创建一个新函数boundGreet,其this永久指向person
const boundGreet = greet.bind(person);
// 在未来的某个时刻调用这个新函数
boundGreet('Hey', '!'); // 输出:Hey, Charlie![3,5,14](@ref)

十二、事件循环机制

事件循环(Event Loop)是JavaScript 处理异步任务的核心机制,它确保单线程的 JavaScript 能够高效、非阻塞地运行。

Javascript是单线程的,意味着它只有一个主线程来执行代码。

异步任务的回调函数会被放入不同的队列中等待执行。这些队列主要分为两类:

  • 宏任务队列:包括 setTimeoutsetInterval、I/O 操作(如网络请求、文件读写)、UI 渲染(浏览器)、DOM 事件回调等。可以将其视为相对“庞大”的工作单元。

  • 微任务队列: 包括 Promise.then/catch/finally、MutationObserver、queueMicrotask,以及在 Node.js 中优先级极高的 process.nextTick。

详细工作流程

1.​执行同步代码​

→ 主线程按顺序执行所有同步代码(如 console.log),遇到异步任务(如 setTimeout/Promise)时,将其回调分别丢进 ​宏任务队列​ 或 ​微任务队列,继续执行后续同步代码。

2.​清空微任务队列​

→ 同步代码执行完后,​立即检查微任务队列(如 Promise.then、await后的代码),​全部执行完毕​(包括执行过程中新产生的微任务),不留一个。

3.​渲染(浏览器特有)​​

→ 微任务清空后,浏览器可能更新页面(重绘/回流),但这一步不是每次循环都会触发。

4.​执行一个宏任务​

→ 从宏任务队列(如 setTimeout、click事件)中取出 ​最早的一个任务​ 执行。

5.​循环​

→ 重复步骤2-4,直到所有队列清空。

代码示例:

示例一:

js
console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

async function async1() {
  console.log('async1 start');
  await async2();
  // async函数会隐式返回一个 Promise。
  // await关键字会暂停其后函数的执行,​其后的代码会被转换为微任务
  console.log('async1 end');
}

async function async2() {
  console.log('async2');
}

async1();

console.log('script end');

// 输出结果:
script start
async1 start
async2
script end
promise1
promise2
async1 end
setTimeout

示例二:

js
console.log('script start');  // 同步代码

  setTimeout(() => {  // 宏任务1
    console.log('setTimeout1');

    Promise.resolve().then(() => {  // 宏任务1内部的微任务
      console.log('promise3');
      // 由于 promise3是在宏任务1执行过程中添加的,所以 ​在宏任务1结束后立即执行。
    });
  }, 0);

  setTimeout(() => {  // 宏任务2
    console.log('setTimeout2');
  }, 0);

  Promise.resolve().then(() => {  // 微任务1
    console.log('promise1');
  }).then(() => {  // 微任务2
    console.log('promise2');
  });

  console.log('script end');  // 同步代码
  
  // 输出结果:
  
   script start
   script end
   promise1
   promise2
   setTimeout1
   promise3
   setTimeout2

十三、垃圾回收机制

JavaScript的垃圾回收(Garbage Collection, GC)机制是其实现自动内存管理的核心,它负责在代码执行过程中自动分配内存,并在内存不再使用时自动回收,从而避免内存泄漏,减轻开发者的负担。

垃圾回收的核心概念是可达性。一个值如果能够通过某种引用链从根对象访问到,它就是可达的,会被视为存活对象;反之则是不可达的,即垃圾。

根对象通常包括:

  • 全局对象(如浏览器中的 window 对象、Node.js 中的 global 对象)
  • 当前执行上下文中的局部变量和函数参数
  • 活动的函数调用栈
  • 未被移除的DOM元素的引用等

标记-清除法 回收的过程可以概括为:

  1. 标记阶段:从根对象开始,遍历所有可达对象,将它们标记为“可达”。
  2. 清除阶段:遍历所有对象,将未被标记为“可达”的对象(即垃圾)清除。

引用-计数法 回收的过程可以概括为:

跟踪每个对象被引用的次数,当引用数为0时立即回收。

常见的内存泄漏

1. 意外的全局变量

在函数中忘记使用 varletconst 声明变量,导致它成为全局对象的属性。

js
function createGlobal() {
  accidentalGlobal = "这个变量会泄漏到window对象上"; // 忘记使用 let/const/var
}
createGlobal();
// 即使函数执行完,accidentalGlobal 仍可通过 window.accidentalGlobal 访问,不会被回收

2. 被遗忘的定时器或事件监听器

如果在代码中创建了定时器(如 setTimeout、setInterval)或事件监听器(如 addEventListener),但在适当的时候没有清除它们,就会导致内存泄漏。

js
function startTimer(element) {
  setInterval(() => {
    // 这个回调函数持有对 element 的引用(即使element已从DOM移除)
    if (element.parentNode) {
      element.textContent = new Date().toLocaleTimeString();
    }
  }, 1000);
}
const timerElement = document.getElementById('timer');
startTimer(timerElement);
// 即使从DOM中移除了timerElement,由于定时器回调仍在执行并引用它,该DOM元素占用的内存不会被释放

3. 脱离DOM的引用

在JavaScript中保存了对DOM元素的引用,即使该元素已从页面中移除,它也不会被回收。

js
const detachedNodes = [];
function storeElement() {
  const listItem = document.createElement('li');
  listItem.textContent = 'List Item';
  document.body.appendChild(listItem);

  detachedNodes.push(listItem); // 在数组中也保留一份引用
  document.body.removeChild(listItem); // 从DOM中移除
}
storeElement();
// 此时,listItem 元素虽然不在DOM树中,但仍被 detachedNodes 数组引用,因此不会被GC回收

4. 闭包的不当使用​

闭包可以维持函数内部变量的引用,如果闭包本身长期存在,其引用的变量也会一直存活。

js
function outer() {
  const largeData = new Array(1000000).fill('data'); // 占用大量内存的数据

  return function inner() { // 返回一个内部函数(闭包)
    // 这个闭包隐式地持有了对 largeData 的引用
    console.log('Inner function called');
  };
}
const closureFn = outer(); // closureFn 持有对 inner 函数的引用,而 inner 又引用了 largeData
// 即使 outer 执行完毕,只要 closureFn 存在,largeData 就无法被回收

十四、isNaN()和Number.isNaN()的区别

isNaN() 函数用于检查一个值是否为 NaN(Not-a-Number)。它会尝试将参数转换为数字,然后检查是否转换失败。如果转换失败,返回 true;否则返回 false。

Number.isNaN() 方法用于检查一个值是否为 NaN,与 isNaN() 不同的是,它不会尝试将参数转换为数字,只有当参数严格等于 NaN 时才返回 true。

示例:

js
isNaN('123'); // false,'123' 可以转换为数字 123
isNaN('abc'); // true,'abc' 不能转换为数字
isNaN(NaN); // true,NaN 是 NaN
isNaN(undefined); // true,undefined 不能转换为数字

Number.isNaN(NaN); // true,NaN 是 NaN
Number.isNaN('123'); // false,'123' 不是 NaN
Number.isNaN(undefined); // false,undefined 不是 NaN