2024年前端面试中JavaScript的30个高频面试题之高级知识

基础知识
中级知识
21. 什么是执行上下文,执行堆栈,变量对象和作用域链?

执行上下文: 执行上下文是指代码执行的环境。它由作用域,变量对象和“this”关键字的值组成。

每当一个函数被执行时,就会创建一个执行上下文,并包含该函数的所有变量或属性。

JavaScript中有三种类型的执行上下文:

全局执行上下文

函数执行上下文

Eval函数执行上下文

**执行栈:**也称为“调用栈”,是一个LIFO(后进先出)数据结构,用于存储正在进行的函数调用的所有执行上下文。当调用一个函数时,会为该函数创建一个新的执行上下文并推送到栈中。当函数完成时,其上下文将从栈中弹出。

引擎执行堆栈顶部上下文对应的函数。当这个函数完成时,它的执行栈将从栈中弹出,控制权达到它下面的当前堆栈中的上下文。

执行上下文在创建阶段创建。在创建阶段会发生以下事情:

  1. LexicalEnvironment 组件被创建。

  2. VariableEnvironment 组件被创建。

变量对象: 是执行上下文的一部分,包含该上下文中定义的所有变量、函数声明和参数。

作用域链: 作用域链是解析 JavaScript 中变量值的一种机制。当引用一个变量时,JavaScript 引擎会首先在当前执行上下文的变量对象中查找该变量。如果没有找到,则继续在外层执行上下文的作用域链上查找,直到找到该变量或到达全局执行上下文。

22. 回调函数、promise、setTimeout和process.nextTick()的执行优先级是什么?

通过事件循环和不同异步操作的处理顺序,可以理解执行优先级:

  1. process.nextTick(): 使用process.nextTick()调度的回调具有最高优先级。当您使用process.nextTick()时,回调会在当前操作完成后、事件循环移动到下一阶段之前立即执行。这是确保函数在事件循环中尽可能早地执行的一种方式。

  2. Promise: Promise通常在process.nextTick()之后执行。但是,它们的优先级高于使用setTimeout()调度的回调。

  3. setTimeout(): 使用setTimeout()调度的回调被放置在事件循环的定时器阶段。它们将在当前操作、promise和任何以前调度的setTimeout()回调完成后执行。

  4. Callback: 常规回调(未使用process.nextTick()调度)具有最低优先级。它们会在事件循环处理完process.nextTick()、promise和setTimeout()回调后执行。

23. 什么是工厂函数和生成器函数?

JavaScript中的工厂函数是一个返回对象的函数。这是一种使用简单且有组织的方式创建对象的模式。工厂函数封装了对象创建过程,并返回一个新对象,而不是使用构造函数和new关键字来创建新对象。

function createPerson(name, age) {
  return {
    name: name,
    age: age,
    greet: function() {
      return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
    }
  };
}

const person1 = createPerson('Alice', 25);
const person2 = createPerson('Bob', 30);

console.log(person1.greet()); // 输出:Hello, my name is Alice and I am 25 years old.
console.log(person2.greet()); // 输出:Hello, my name is Bob and I am 30 years old.

生成器函数在 JavaScript 中是一种特殊类型的函数,可以在执行期间暂停和恢复。

生成器函数会产生一个结果序列而不是单个值。

当调用生成器函数时,它会返回一个生成器对象,可以使用 next() 方法来控制函数的执行。

可以使用 yield 关键字在函数体内暂停代码,并在以后从暂停的地方恢复执行。

function* numberGenerator() {
  let i = 0;
  while (true) {
    yield i++;
  }  
}

const gen = numberGenerator();
console.log(gen.next().value); // 输出:0  
console.log(gen.next().value); // 输出:1
console.log(gen.next().value); // 输出:2

这为创建迭代器和处理异步代码提供了强大的机制。

24. 复制(浅复制和深复制)对象的不同方法?

浅复制 是对象的一份拷贝,其引用与原始对象相同。这意味着如果改变浅复制中的属性值,也会改变原始对象中的属性值。

const user = {    
    name: "Kingsley",
    age: 28,
    job: "Web Developer"
}
const clone = user  

深复制 是对象的一份拷贝,其引用与原始对象不同。这意味着如果改变深复制中的属性值,不会改变原始对象中的属性值。

有不同的方法可以深复制一个对象。

a)JSON.parse 和 JSON.stringify:对嵌套对象也有用。

   const originalObject = { name: "Alice", age: 25 };
   const deepCopy = JSON.parse(JSON.stringify(originalObject));

b)structuredClone:

const myDeepCopy = structuredClone(myOriginal); 

c)Spread Operator(…): 任何包含嵌套对象的对象都不会深度复制。

const originalObject = { name: "Alice", age: 25 };
const deepCopy =  {...originalObject};  

deepCopy.name = "ravi"
console.log("originalObject", originalObject.name) // Alice

d)Object.assign(): 应该使用 Object.assign()方法来深复制没有嵌套对象的对象。

const originalObject = { name: "Alice", age: 25 };
const shallowCopy = Object.assign({}, originalObject);

e)Recursion:

   function deepCopy(obj) {
       if (typeof obj !== 'object' || obj === null) {
           return obj;
       }
       const newObj = Array.isArray(obj) ? [] : {};
       for (let key in obj) {
           if (Object.hasOwnProperty.call(obj, key)) {
               newObj[key] = deepCopy(obj[key]);
           }
       }
       return newObj;
   }
   const originalObject = { name: "Alice", nested: { age: 25 } };
   const deepCopy = deepCopy(originalObject);

25. 如何使一个对象不可变?(seal 和 freeze 方法)?

在 JavaScript 中,可以使用 Object.seal() 和 Object.freeze() 方法使对象不可变。

Object.freeze() 完全不可变) 这个方法冻结了一个对象,既封闭了该对象,又将其所有属性标记为只读。在冻结对象之后,其属性不能被修改、添加或删除。

     const obj = { name: 'Alice', age: 25 };
     Object.freeze(obj); 
     obj.name = 'Bob'; // 不允许  
     obj.address = '123 Street'; // 不允许
     delete obj.age; // 不允许

Object.seal():(部分不可变) 这个方法封闭了一个对象,防止添加新属性并将所有现有属性标记为不可配置。但是,您仍然可以修改那些可写的现有属性的值。

     const obj = { name: 'Alice', age: 25 }; 
     Object.seal(obj);
     obj.name = 'Bob'; // 允许
     obj.address = '123 Street'; // 不允许(不能添加新属性) 
     delete obj.age; // 不允许(不能删除现有属性)

26. 什么是事件和事件流?事件冒泡和事件捕获是什么?

在 JavaScript 中,事件流是指类似点击或按键等事件在网页或浏览器中被接收或处理的顺序。事件流中有两个阶段:事件捕获和事件冒泡。

当您点击嵌套在各种其他元素中的元素时,在点击实际到达其目标或目标元素之前,它必须首先触发其每个父元素的点击事件,从顶部的全局窗口对象开始。

现在,让我们用这个例子来解释事件流:

  1. 事件捕获阶段: 当您单击按钮时,事件从顶部开始(文档的根部),向下移动到目标元素。在这种情况下,它从文档的根目录遍历到

    (父元素),然后到
  2. 事件目标阶段: 事件到达目标元素,在这种情况下是

  3. 事件冒泡阶段: 在到达目标后,事件开始向上冒泡。它从

以下是简单的 JavaScript 代码片段,可见此过程的实际运行:

document.getElementById('parent').addEventListener('click', function() {
  console.log('Div clicked (capturing phase)'); 
}, true); // 这里的 'true' 表示捕获阶段。 
document.getElementById('child').addEventListener('click', function() {
  console.log('Button clicked (target phase)'); 
});

document.getElementById('parent').addEventListener('click', function() {
  console.log('Div clicked (bubbling phase)');
});

当您单击按钮时,控制台中会按以下顺序显示这些消息:

  1. “Div clicked (capturing phase)”
  2. “Button clicked (target phase)”
  3. “Div clicked (bubbling phase)”

27. 什么是事件委托?

事件委托是一种优化多个元素的事件处理的 JavaScript编程技术。

它不是为每个单独的元素附加事件侦听器,而是将单个事件监听器附加到 DOM (文档对象模型)层次结构中更高级别的公共祖先元素。

当发生在后代元素之一的事件时,它会“冒泡”到公共祖先元素,事件侦听器正在那里等待。

事件委托是一种监听事件的技术,其中您将父元素委托为其中发生的所有事件的侦听器。

var form = document.querySelector("#registration-form");
// 监听表单内部的字段更改
form.addEventListener(  
  "input",
  function (event) {
    // 记录更改的字段  
    console.log(event.target);
  },
  false
);

28. 什么是服务器发送的事件?

服务器发送事件(SSE)是一种简单高效的技术,可以通过单个 HTTP 连接从服务器端实时更新客户端。

SSE 允许服务器在有新信息可用时立即将数据推送到 Web 客户端(通常是浏览器),非常适合需要实时更新而不依赖复杂协议或第三方库的场景。

a)SSE 提供从服务器到客户端的单向数据流。服务器发起通信,向客户端发送更新。

b)SSE **使用基于文本的协议,**这意味着从服务器发送到客户端的数据通常以文本格式(通常是 JSON 或纯文本)。

c)SSE 自动处理重连。

d)SSE 在客户端和服务器端建立持久连接,允许服务器向客户端发送一串事件。每个事件都可以具有唯一的类型和相关的数据。

e)EventSource 对象用于接收服务器发送的事件通知。 例如,您可以如下接收来自服务器的消息:

if (typeof EventSource !== "undefined") {
  var source = new EventSource("sse_generator.js");
  source.onmessage = function (event) {
    document.getElementById("output").innerHTML += event.data + "
"; }; }

f)以下是服务器发送事件的事件列表(onopen、onmessage、onerror)。

29. 什么是 web worker 或 service worker?

Web Worker 和 Service Worker 是 JavaScript 中的两个不同概念:

Web Worker 用于并发 JavaScript 后台执行,而 Service Worker 用于创建具有离线功能的渐进式 Web 应用程序和高级功能。这两者都是增强 Web 应用程序性能和功能的重要工具。

它们各自在 Web 开发中起distinct 作用:

Web Worker:

  1. 并发: Web Worker 是浏览器功能,允许您在主浏览器线程之外的后台运行 JavaScript 代码。这实现了任务的并发执行,而不阻塞用户界面。

  2. 使用案例: Web Worker 常用于计算密集型或时间消耗型任务,如数据处理、图像操作或复杂计算。通过在单独的线程中运行这些任务,它们不会影响网页的响应能力。

  3. 通信: Web Worker 可以使用消息传递系统与主线程通信。它们可以发送和接收消息,允许主线程和 Worker 协调。

  4. 浏览器支持: 大多数现代浏览器都支持 Web Worker。

Service Worker:

  1. 脱机功能: Service Worker 是用于创建渐进式 Web 应用程序 (PWA) 的更高级功能。它们充当代理服务器,在后台运行并可拦截和缓存网络请求。这支持脱机功能,例如在用户脱机时提供缓存内容。

  2. 使用案例: Service Worker 主要用于实现脱机访问推送通知和后台同步等功能。它们使 Web App 即使在没有互联网连接的情况下也能正常运行。

  3. 生命周期:Service Worker 有自己的生命周期,具有 installactivatefetch 等事件。它们通常在 Web App 的生命周期开始时注册。

  4. 浏览器支持:Service Worker 在现代浏览器中得到支持,是创建可靠和引人入胜的 Web 应用程序的关键技术。

30. 如何比较两个 JSON 对象?

a) 一种简单的方法是使用 JSON.stringify 将它们转换为字符串,然后比较字符串。

function areEqual(obj1, obj2) {
  return JSON.stringify(obj1) === JSON.stringify(obj2);
}

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };  
console.log(areEqual(obj1, obj2)); // 输出:true

b) 您也可以使用 Ramda 库来比较两个 JSON 对象。Ramda 提供了一个名为 equals 的函数。

const R = require('ramda');

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };

console.log(R.equals(obj1, obj2)); // 输出:true 

c) 另一种选择是使用一个库,比如 Lodash,它提供了一种对象进行深度比较的方法。

const _ = require('lodash');

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };
console.log(_.isEqual(obj1, obj2)); // Output: true

你可能感兴趣的:(前端,面试,javascript)