JavaScript 面试题---基础

69 阅读18分钟

js 数据类型

Primitive values vs. Objects

isNaN()Number.isNaN()的区别

都是用来判断一个值是否是 NaN,但它们的行为有重要区别:

isNaN(value)

  • 功能:先将参数转为数字,再判断是否是 NaN

  • 问题:可能出现“误判”,因为它会尝试将非数字类型转换为数字。

  • 例子

    isNaN('hello')     // true,因为 'hello' 转换为数字是 NaN
    isNaN(undefined)   // true,因为 undefined 转换为数字是 NaN
    isNaN('123')       // false,因为 '123' 转成数字是 123,不是 NaN
    isNaN(NaN)         // true
    

Number.isNaN(value)

  • 功能不会做类型转换,只有在值本身就是 NaN 的时候才返回 true

  • 更精确,推荐使用。

  • 例子

    Number.isNaN('hello')    // false,因为 'hello' 不是 NaN
    Number.isNaN(undefined)  // false
    Number.isNaN('123')      // false
    Number.isNaN(NaN)        // true
    

✅ 总结对比:

输入isNaN()Number.isNaN()
'hello'truefalse
undefinedtruefalse
'123'falsefalse
NaNtruetrue

✅ 结论:

  • 使用 Number.isNaN() 更安全、更精确;
  • 避免使用 isNaN(),除非你明确知道它的隐式转换行为对你是有用的。

let const var

juejin.cn/post/747664…

说说你对iterrator Generator和Async、await的理解

xie.infoq.cn/article/abd…

JavaScript 原型,原型链? 有什么特点?

在 JavaScript 中,每个对象都有一个特殊的隐藏属性 [[Prototype]],可以通过 Object.getPrototypeOf(obj) 访问。他的值是指向另一个对象的引用,这个对象就是当前对象的原型。原型提供了一个机制,使得对象可以共享其他对象的方法和属性。。

原型链 对象之间通过__proto__连接起来,就是原型链

在 JavaScript 中,原型链的终点null。 原型链是一系列对象的链接,最终会指向 Object.prototype,而 Object.prototype 的原型(proto 属性)就是 null。这是链条的最后一个节点,表示没有更高层的原型对象。

Javascript 中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是?

hasOwnProperty 所有继承了 Object 的对象都会继承到 hasOwnProperty 方法。这个方法可以用来检测一个对象是否含有特定的自身属性,和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。

js箭头函数 和普通函数的区别

call apply和bind方法

这个问题超常考,Amanda!我们来把 callapplybind区别解释清楚,并附上记忆方法,让你一看就会、一用就灵 ✨


📌 核心作用:都可以改变函数中的 this 指向


🆚 区别详解

方法名作用参数形式是否立即执行返回值
call调用函数,改变 this单个参数依次传:fn.call(thisArg, arg1, arg2...)✅ 立即执行函数执行结果
apply调用函数,改变 this参数打包成数组:fn.apply(thisArg, [arg1, arg2...])✅ 立即执行函数执行结果
bind绑定 this,不调用函数参数与 call 一样:fn.bind(thisArg, arg1, arg2...)❌ 不立即执行,返回新函数新函数

🧠 超好记口诀:

call用逗号,apply用数组,bind不执行(返回新函数)

这样记最省力!


🔧 示例说明

function sayHello(greeting, punctuation) {
  console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}

const person = { name: "Amanda" };

// call
sayHello.call(person, "Hi", "!"); // Hi, I'm Amanda!

// apply
sayHello.apply(person, ["Hello", "."]); // Hello, I'm Amanda.

// bind
const sayHiAmanda = sayHello.bind(person, "Hey", "~");
sayHiAmanda(); // Hey, I'm Amanda~

🔍 小补充:适用场景

方法场景示例
call参数数量不确定时,快速传参
apply需要“传入数组”时,例如 Math.max.apply(null, arr)
bind延迟执行(如事件监听、定时器中)

深入理解JavaScript-函数

函数的作用域,跟代码在哪里定义有关

深入理解JavaScript-this

函数的this刚好反过来,跟代码在哪里定义没有关系,而跟代码在哪里调用有关系

介绍一下 js 的节流与防抖?

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。 例如:搜索框输入。当用户停止输入时再执行搜索操作

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。 如scroll 或者调节屏幕宽度

6, Object.is() 与原来的比较操作符 “===”、“==” 的区别?

在 JavaScript 中,Object.is()、严格相等 (===) 和宽松相等 (==) 都是用来比较值是否相等的方法,但它们的行为和用途有所不同。下面是它们之间的具体区别:

. Object.is()

Object.is() 是 ES6 引入的一种更精确的比较方法,主要用于解决 === 比较中的一些特殊情况。

特点:

  • 区分 +0 和 -0Object.is(+0, -0) 返回 false,而 === 会认为它们是相等的。
  • 区分 NaNObject.is(NaN, NaN) 返回 true,而 === 则认为 NaN 不等于 NaN

使用示例:

Object.is(+0, -0);   // false
Object.is(NaN, NaN); // true
Object.is(5, 5);     // true
Object.is("test", "test"); // true
Object.is({}, {});   // false (对象引用不同)

7. 严格相等 ===

严格相等(===)是 JavaScript 中常用的比较运算符,它会在不进行类型转换的前提下比较两个值是否相等。

特点:

  • 不进行类型转换:两个值类型不同则直接返回 false
  • 不区分 +0 和 -00 === -0 返回 true
  • NaN 不等于 NaNNaN === NaN 返回 false

使用示例:

5 === 5;        // true
5 === "5";      // false (类型不同)
NaN === NaN;    // false (NaN 是唯一一个不等于自身的值)
+0 === -0;      // true

3. 宽松相等 ==

宽松相等(==)在比较时会进行类型转换,然后再判断两个值是否相等。

特点:

  • 进行类型转换:如果两个值类型不同,== 会尝试将它们转换为相同的类型再进行比较。
  • 容易引发意外:由于类型转换机制,有些情况下可能会得到意想不到的结果。

使用示例:

5 == "5";        // true (字符串 "5" 转换为数字 5)
0 == false;      // true (false 转换为 0)
null == undefined; // true (null 和 undefined 互相等)
"" == false;     // true (空字符串转换为 false)

比较总结

  • Object.is():最精确的比较方式,能够区分 +0-0,以及让 NaN === NaN 成立。适用于需要精确比较的情况。
  • ===(严格相等):不进行类型转换,比 == 更安全,适合大多数场景。但对于 NaN+0/-0 的特殊情况不太适用。
  • ==(宽松相等):进行类型转换,容易引发错误。仅在明确需要类型转换的情况下使用,例如判断 nullundefined 是否相等。

使用建议

一般来说,优先使用 === 来进行相等比较,除非需要处理 NaN+0/-0 的特殊情况,这时可以使用 Object.is()。避免使用 ==,以减少因类型转换导致的意外情况。

8. 内部属性 [[Class]] 是什么?

[[Class]] 是对象的一个内部属性,用于表示该对象的类型。这个属性仅在 ECMAScript 规范中定义,无法直接访问。Object.prototype.toString 方法可以间接访问该属性,帮助开发者识别 JavaScript 中的一些数据类型。 console.log(Object.prototype.toString.call({})); // [object Object]

9,call、apply 及 bind 函数

在 JavaScript 中,callapplybind 都是 Function 对象的三个方法,它们的主要功能是显式地指定函数的 this 上下文,并允许传递参数 区别:

特性call applybind
用法立即调用函数并指定 this 和参数立即调用函数并指定 this,参数为数组返回一个新的函数,绑定 this 和初始参数
参数格式参数是一个一个列举出来的参数是一个数组(或类数组对象)返回的新函数会绑定 this 和初始参数
返回值没有返回新函数,直接执行没有返回新函数,直接执行返回一个新的函数
使用场景适用于知道具体的参数时适用于知道具体的参数时适用于需要预先设置 this 或参数时

10,函数柯里化的实现

,柯里化是把一个接受多个参数的函数转换为一系列接受单一参数的函数。

柯里化的好处包括:

  • 参数的复用:你可以部分地应用一些参数,得到一个新的函数,方便以后复用。
  • 函数组合:柯里化使得函数可以组合成更复杂的功能。
  • 延迟执行:函数可以在接收到所有必要的参数后再执行。

, 如何判断当前脚本运行在浏览器还是 node 环境中?(阿里)

this === window ? 'browser' : 'node';

通过判断 Global 对象是否为 window,如果不为 window,当前脚本没有运行在浏览器中

11,什么是 XSS 攻击?如何防范 XSS 攻击?

XSS(跨站脚本,Cross-Site Scripting)攻击是一种注入攻击,通过将恶意脚本(通常是 JavaScript)注入到网页中,使得其他用户在访问该网页时执行这些脚本。

XSS(跨站脚本,Cross-Site Scripting)攻击是一种注入攻击,通过将恶意脚本(通常是 JavaScript)注入到网页中,使得其他用户在访问该网页时执行这些脚本。XSS 攻击的目标是窃取用户信息、劫持会话、重定向到恶意网站或对用户进行社会工程攻击。

14,### XSS 攻击的类型

  1. 存储型 XSS(Stored XSS):恶意脚本被持久存储在目标服务器上,用户每次访问受影响的页面时都会执行这些脚本。例如在论坛、评论区、用户输入的内容处注入脚本。
  2. 反射型 XSS(Reflected XSS):恶意脚本通过 URL 或参数直接反射到用户浏览器上执行。攻击者诱导用户访问带有恶意脚本的 URL。
  3. 基于 DOM 的 XSS(DOM-Based XSS):脚本在客户端执行过程中,由于操作了 DOM(文档对象模型)而导致漏洞。浏览器解析和处理 JavaScript 代码时直接注入到 DOM 中执行。

XSS 攻击的防范方法

  1. 输入验证和编码

    • 对所有用户输入的数据进行严格的验证和过滤,禁止不安全的字符。
    • 对输出到网页的数据进行编码(例如 HTML 编码),防止用户输入的代码被直接执行。
  2. 使用安全的库和框架

    • 许多现代的前端框架(如 React、Angular)在默认情况下会自动对数据进行编码,减少 XSS 攻击的风险。
    • 使用库来防范 XSS,如 OWASP AntiSamy 等。
  3. 内容安全策略(Content Security Policy, CSP)

    • 配置内容安全策略,限制加载的资源源。例如,仅允许加载站内的 JavaScript 和 CSS 资源。
    • CSP 可以阻止大多数的 XSS 攻击,即使攻击者成功插入了脚本,也无法在受限环境中执行。
  4. HTTP-only 和 Secure Cookies

    • 使用 HTTP-only 标记的 Cookie,防止 JavaScript 访问敏感的会话信息。
    • 使用 Secure 标记确保 Cookie 仅通过 HTTPS 传输。
  5. 避免直接操作 DOM

    • 避免使用 innerHTMLdocument.write 等方法来操作 DOM,改用更安全的 DOM 操作方法,例如 textContent 或安全的模板引擎。
  6. 定期更新依赖

    • 确保使用的库和框架是最新版本,修复可能的安全漏洞。

防范 XSS 攻击的关键在于:不要相信任何用户输入的数据,始终对输出内容进行编码。通过这些措施,可以大大减少 XSS 攻击的风险。

15, CSRF

CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种攻击手段:利用用户的身份认证信息对受信任网站发送伪造请求。 CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种攻击手段,攻击者通过诱导用户在未授权的情况下执行某些请求(如转账、修改密码等),利用用户已登录的身份在不知情的情况下完成攻击操作。CSRF 攻击的核心是:利用用户的身份认证信息对受信任网站发送伪造请求

CSRF 攻击的工作原理

  1. 用户登录受信任的站点,并在浏览器中生成了会话 Cookie。
  2. 在用户未登出或 Cookie 仍有效的情况下,攻击者诱导用户点击带有恶意请求的链接或访问含有恶意代码的页面。
  3. 浏览器自动携带用户的会话 Cookie 向受信任网站发送请求,服务器信任这个请求,因为它包含了合法的 Cookie。
  4. 最终,攻击者在用户不知情的情况下完成了敏感操作,例如转账、修改用户设置等。

CSRF 攻击的防范方法

  1. 使用 CSRF Token

    • 为每个请求生成一个唯一的、不可预测的 CSRF Token,将该 Token 随请求发送到服务器。
    • 服务器端验证请求中的 Token 是否有效。如果 Token 不匹配,则拒绝请求。
    • 通常在 HTML 表单中加入隐藏字段存放 CSRF Token,或者通过 HTTP 请求头部传递 Token。
  2. 检查 Referer 或 Origin Header

    • 服务器端检查请求头中的 RefererOrigin 是否来自信任的域名。
    • 如果 RefererOrigin 显示请求来自不可信的外部网站,则拒绝请求。
    • 不过需要注意,这种方法依赖于浏览器的支持,可能不适合所有场景。
  3. 要求用户重新认证或使用验证码

    • 对关键操作(如密码修改、转账)添加二次验证步骤,要求用户输入验证码或重新登录。
    • 这种方法能有效防止攻击者在用户不知情的情况下进行敏感操作。
  4. 设置 SameSite Cookie 属性

    • 通过设置 Cookie 的 SameSite 属性为 StrictLax,限制 Cookie 在跨站请求中发送。
    • SameSite=Strict:仅允许同一站点请求时发送 Cookie。
    • SameSite=Lax:允许部分跨站请求(例如 GET 请求)发送 Cookie,但 POST 等敏感请求会被阻止。
    • SameSite 属性在现代浏览器中普遍支持,是一种简单有效的防范 CSRF 的方法。

16,什么是浏览器的同源政策?

“同源”是指以下三个部分必须完全相同:

  1. 协议(如 httphttps
  2. 域名(如 example.comsub.example.com
  3. 端口(如 80、443)

同源政策的作用

同源政策限制了以下跨源操作,以防止恶意网站读取或操控其他网站的数据:

  1. DOM 访问:禁止不同源的页面之间直接操作 DOM。
  2. Cookie、LocalStorage 和 SessionStorage 访问:限制访问这些存储在客户端的敏感数据。
  3. AJAX 请求:限制 JavaScript 对不同源的服务器发送请求并读取返回的数据。

17 如何解决跨域问题?

跨域问题(CORS,Cross-Origin Resource Sharing)是指在浏览器中,某个网页访问了不同域的资源(例如 API 数据),而该资源未被浏览器允许访问时产生的安全问题。

  • 服务端设置 CORS 响应头:在服务器上设置允许跨域访问的响应头,这是最直接的解决方式。可以在服务器的 HTTP 响应中添加 Access-Control-Allow-Origin,例如 Access-Control-Allow-Origin: * 允许所有域访问,或指定某个域来限制访问。还可以配置 Access-Control-Allow-MethodsAccess-Control-Allow-Headers 等头信息,具体实现取决于服务器技术,例如使用 Node.js 中的 cors 中间件

  • JSONP(JSON with Padding) :这是传统的解决方案之一,但仅支持 GET 请求。JSONP 通过 <script> 标签请求跨域资源,将 JSON 数据包装成函数调用的格式,浏览器会将数据作为脚本处理,从而绕过 CORS 限制。不过,由于只支持 GET 请求和存在潜在的安全隐患,现在已不推荐使用​

  • 反向代理:可以在前端服务器(如 Nginx)上设置反向代理,将跨域请求转发给目标服务器。通过代理,前端向自己服务器的同源 URL 发送请求,再由代理转发到目标域,从而实现跨域。这种方式对实现跨域友好,也可增加应用的安全性​

  • 跨域资源共享策略:在现代框架(如 Angular、React)中,也可以通过配置开发服务器(例如在 Angular 的 proxy.conf.json 中配置代理)来解决开发环境中的跨域问题。生产环境中仍需通过服务端配置 CORS 或反向代理来解决​

  • 使用 WebSocket 或者 postMessage:如果是跨源通信需求,可以考虑通过 WebSocket 或 window.postMessage 实现客户端与不同源的通信。这些方法虽然不能直接解决所有的跨域请求问题,但可以用于特定场景中的跨源数据交互。

18 简单谈一下 cookie ?

Cookie 是一种在用户浏览网页时由服务器发送并存储在用户设备上的小型文本文件,用于保存用户的状态和偏好,以便在后续请求时与服务器共享。

Cookie 的基本属性

  • 名称和值(Name/Value) :每个 Cookie 都有一个名称和一个对应的值,服务器可以根据这些信息识别用户。
  • 域和路径(Domain/Path) :限制 Cookie 的作用范围,只有特定域和路径的页面可以访问 Cookie。
  • 过期时间(Expires/Max-Age) :用于设置 Cookie 的有效期。会话 Cookie 没有过期时间,在用户关闭浏览器时失效;持久性 Cookie 则有一个特定的到期时间。
  • 安全性(Secure 和 HttpOnly)Secure 属性表示 Cookie 只能通过 HTTPS 传输,增加数据传输的安全性;HttpOnly 属性则防止 JavaScript 访问 Cookie,减少 XSS 攻击的风险。

19 同步和异步的区别?

同步指的是当一个进程在执行某个请求的时候,如果这个请求需要等待一段时间才能返回,那么这个进程会一直等待下去,直到消息返回为止再继续向下执行。

异步指的是当一个进程在执行某个请求的时候,如果这个请求需要等待一段时间才能返回,这个时候进程会继续往下执行,不会阻塞等待消息的返回,当消息返回时系统再通知进程进行处理。

20 谈一谈浏览器的缓存机制?

v8 的垃圾回收机制基于分代回收机制,这个机制又基于世代假说,这个假说有两个特点,一是新生的对象容易早死,另一个是不死的对象会活得更久。基于这个假说,v8 引擎将内存分为了新生代和老生代。

新创建的对象或者只经历过一次的垃圾回收的对象被称为新生代。经历过多次垃圾回收的对象被称为老生代。

新生代被分为 From 和 To 两个空间,To 一般是闲置的。当 From 空间满了的时候会执行 Scavenge 算法进行垃圾回收。当我们执行垃圾回收算法的时候应用逻辑将会停止,等垃圾回收结束后再继续执行。

Scavenge 算法算法分为三步:

segmentfault.com/a/119000004… --这个地方讲的好

(1)首先检查 From 空间的存活对象,如果对象存活则判断对象是否满足晋升到老生代的条件,如果满足条件则晋升到老生代。如果不满足条件则移动 To 空间。

(2)如果对象不存活,则释放对象的空间。

(3)最后将 From 空间和 To 空间角色进行交换。 新生代对象晋升到老生代有两个条件:

(1)第一个是判断是对象否已经经过一次 Scavenge 回收。若经历过,则将对象从 From 空间复制到老生代中;若没有经历,则复制到 To 空间。

(2)第二个是 To 空间的内存使用占比是否超过限制。当对象从 From 空间复制到 To 空间时,若 To 空间使用超过 25%,则对象直接晋升到老生代中。设置 25% 的原因主要是因为算法结束后,两个空间结束后会交换位置,如果 To 空间的内存太小,会影响后续的内存分配。

21 什么是 Polyfill ?

22 观察者模式和发布订阅模式有什么不同?

23 如何判断当前脚本运行在浏览器还是 node 环境中?

this === window ? 'browser' : 'node';

通过判断 Global 对象是否为 window,如果不为 window,当前脚本没有运行在浏览器中。

24 简单介绍一下 V8 引擎的垃圾回收机制

25 哪些操作会造成内存泄漏?

第一种情况是我们由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。

`

function example() {  
        someLargeObject = { key: 'value' }; // 未使用 var, let 或 const 导致全局变量 
}`

第二种情况是我们设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。

const timer = setInterval(() => { console.log('Hello'); }, 1000);
// 未调用 clearInterval 来清除定时器
  

第三种情况是我们获取一个 DOM 元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回收。

const div = document.createElement('div'); 
document.body.appendChild(div); // 如果没有移除对 div 的引用,div 不能被垃圾回收
div = null;

第四种情况是不合理的使用闭包,从而导致某些变量一直被留在内存当中。