2024 全新 Javascript 面试题目高级篇

今天是咱们Javascript面试题的高级篇,也是最后一篇。

20. 执行上下文、执行栈、变量对象和作用域链究竟是啥?

执行上下文:这指的是代码执行的环境,包括作用域、变量对象以及“this”关键字的值。每当一个函数被执行时,就会创建一个执行上下文,里面包含了该函数的所有变量或属性。

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

  • 全局执行上下文
  • 函数执行上下文
  • eval函数执行上下文

执行栈:也被称为“调用栈”,是一个遵循LIFO(后进先出)原则的数据结构,存储了所有正在进行中的函数调用的执行上下文。当函数被调用时,会创建一个新的执行上下文并压入栈中。当函数执行完毕,其上下文就会从栈中弹出。

引擎会执行位于栈顶的执行上下文中的函数。当这个函数执行完毕,它的执行栈就会从当前栈中弹出,控制权转移到当前栈中下一个上下文中。

执行上下文在创建阶段形成,此阶段会发生以下事情:

  1. 创建词法环境组件。
  2. 创建变量环境组件。

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

作用域链:这是JavaScript中用于解析变量值的一种机制。当引用一个变量时,JavaScript引擎首先在当前执行上下文的变量对象中查找该变量。如果在那里找不到,它会继续向外层执行上下文搜索,沿着作用域链进行,直到找到该变量或到达全局执行上下文为止。

21. 回调、Promise、setTimeout、process.nextTick() 的执行优先级是怎样的?

根据事件循环和不同异步操作处理的顺序,我们可以理解它们的执行优先级:

  1. process.nextTick():使用process.nextTick()安排的回调具有最高优先级。当你使用process.nextTick()时,回调会在当前操作完成后但事件循环进入下一阶段之前立即执行,确保函数能以最早可能的时间点在事件循环中执行。
  2. Promise:Promise通常在process.nextTick()之后执行,但它们的优先级高于通过setTimeout()安排的回调。
  3. setTimeout():通过setTimeout()安排的回调被放置在事件循环的计时器阶段。它们将在当前操作、Promise以及任何先前安排的setTimeout()回调完成之后执行。
  4. 回调:常规回调(非process.nextTick()安排的)具有最低优先级。它们在事件循环处理完process.nextTick()、Promise和setTimeout()回调之后执行。

22. 工厂函数和生成器函数是啥东东?

工厂函数在JavaScript中是指返回一个对象的函数。这是一种直接且有序创建对象的模式。与使用构造函数和new关键字创建新对象不同,工厂函数封装了对象创建过程并返回新对象

function createPerson(name, age) {
    return {
        name: name,
        age: age,
        greet: function() {
            return `哈喽,我是${this.name},今年${this.age}岁。`;
        }
    };
}

const person1 = createPerson('小明', 25);
const person2 = createPerson('小红', 30);

console.log(person1.greet()); // 输出:哈喽,我是小明,今年25岁。
console.log(person2.greet()); // 输出:哈喽,我是小红,今年30岁。

生成器函数是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

这种方式为创建迭代器和处理异步代码提供了强大工具。

23. 复制(浅拷贝和深拷贝)对象的不同方法有哪些?

浅拷贝是指创建的对象其引用与原对象相同。这意味着如果你修改了浅拷贝对象的某个属性值,原对象相应属性的值也会改变。

const user = { name: "小王", age: 28, job: "网页开发者" };
const clone = user;

深拷贝则是指创建的对象其引用与原对象不同。即使你修改了深拷贝对象的属性值,也不会影响到原对象的属性值。

实现深拷贝的方法有多种:

a)JSON.parse和JSON.stringify:适用于嵌套对象,但不能处理函数和循环引用。

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

b)structuredClone

const myDeepCopy = structuredClone(myOriginal);

c)展开运算符(…):不适用于包含嵌套对象的情况。

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

deepCopy.name = "小刚";
console.log("originalObject", originalObject.name); // 小张

d)Object.assign():适合于没有嵌套对象的情况。

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

e)递归

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: "小王", nested: { age: 25 } };
const deepCopy = deepCopy(originalObject);

24. 如何使对象变得不可变?(使用seal和freeze方法)

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

Object.freeze():(完全不可变)此方法冻结一个对象,使其密封并标记所有属性为只读。一旦对象被冻结,其属性便无法修改、添加或删除。

const obj = { name: '小王', age: 25 };
Object.freeze(obj);
obj.name = '小李'; // 不允许
obj.address = '123 街道'; // 不允许(不能添加新属性)
delete obj.age; // 不允许(不能删除现有属性)

Object.seal():(部分不可变)此方法密封一个对象,阻止添加新属性并标记所有现有属性为不可配置。不过,你仍然可以修改那些可写的现有属性值。

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

25. 事件和事件流、事件冒泡及事件捕获是啥?

在JavaScript中,事件流定义了一个事件(如点击或按键)在网页上或由浏览器处理的接收顺序。事件流包含两个阶段:事件捕获和事件冒泡。

当你点击嵌套在多个其他元素中的一个元素时,在你的点击实际上到达目标元素之前,它必须先为每个父元素触发点击事件,从顶层的全局window对象开始。

以这个例子说明事件流:

  1. 事件捕获阶段:点击按钮时,事件从顶部(文档根部)开始向下传播至目标元素。在这个例子中,事件从文档根部传播到<div>(父元素),再到<button>(子元素)。这就是捕获阶段。
  2. 事件目标阶段:事件到达目标元素,即本例中的<button>
  3. 事件冒泡阶段:到达目标后,事件开始向上冒泡。它从<button>回到<div>,最终回到文档根部。这称为冒泡阶段。

以下是一个简单的JavaScript代码示例来演示这一过程:

document.getElementById('parent').addEventListener('click', function() {
  console.log('Div clicked (capture 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 (bubble phase)');
});

点击按钮时,控制台将按以下顺序显示这些消息:

  1. “Div clicked (capture phase)”
  2. “Button clicked (target phase)”
  3. “Div clicked (bubble phase)”

26. 事件委托是啥?

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

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

当后代元素上的事件发生时,它会“冒泡”到该共同祖先处,那里事件监听器正在等待。

事件委托是监听事件的一种方式,你将一个父元素作为所有子元素内发生的事件的监听者

var list = document.getElementById('items');

list.addEventListener('click', function(event) {
  if(event.target.tagName.toLowerCase() === 'li') {
    console.log('Clicked on ' + event.target.textContent);
  }
}, false);

27. 服务器发送事件(Server-Sent Events, SSE)是啥?

服务器发送事件(SSE)是一种简单而高效的技术,能够通过单一HTTP连接从服务器向客户端提供实时更新

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

  • SSE提供了从服务器到客户端的单向数据流。服务器发起通信,发送更新给客户端。
  • SSE使用基于文本的协议,意味着服务器发送给客户端的数据通常是文本格式(通常是JSON或纯文本)。
  • SSE自动处理重新连接
  • SSE建立客户端与服务器之间的持久连接,允许服务器向客户端发送事件流。每个事件都可以有唯一类型和相关联的数据。
  • EventSource对象用于接收服务器发送的事件通知。例如,可以通过以下方式接收服务器的消息:
if (typeof EventSource !== "undefined") {
  var source = new EventSource("updates.example.com");
  source.onmessage = function(event) {
    document.getElementById("updateArea").innerHTML += event.data + "<br>";
  };
}
  • SSE还提供了一系列事件(如onopen、onmessage、onerror)供使用。

28. JavaScript中的Web Worker或Service Worker是啥?

Web Worker和Service Worker是JavaScript中的两种不同概念,

Web Worker设计用于后台的并发JavaScript执行,而Service Worker则用于构建具有离线功能的渐进式Web应用(Progressive Web App, PWA)及其他高级特性。两者都是提升Web应用性能和功能的关键工具。

各自在Web开发中扮演着不同的角色:

Web Worker:
  1. 并发性:Web Worker是浏览器功能,允许你在后台运行独立于主浏览器线程的JavaScript代码。这使得任务可以并发执行,而不阻塞用户界面。
  2. 应用场景:Web Worker常用于计算密集型或耗时的任务,如数据处理、图像操作或复杂计算。通过在单独的线程中运行这些任务,它们不会影响网页的响应性。
  3. 通信:Web Worker通过消息系统与主线程通信,可以发送和接收消息,实现主线程和Worker之间的协调。
  4. 浏览器支持:大多数现代浏览器都支持Web Worker。
Service Worker:
  1. 离线能力:Service Worker是更高级的功能,用于创建具有离线功能的渐进式Web应用。它们充当代理服务器,在后台运行,可以拦截和缓存网络请求,从而实现离线内容提供等功能。
  2. 应用场景:Service Worker主要用于实现离线访问推送通知后台同步等特性。它们让Web应用即便在没有互联网连接的情况下也能运行。
  3. 生命周期:Service Worker有自己的生命周期,包含installactivatefetch等事件。它们通常在Web应用启动时注册。
  4. 浏览器支持:Service Worker得到现代浏览器的支持,是创建可靠且吸引人的Web应用的关键技术。

29. 在JavaScript中如何比较两个JSON对象?

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

function isEqual(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(isEqual(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)); // 输出: true

希望您喜欢这篇文章。非常感谢您的阅读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值