7个JavaScript面试题全面解析,一文搞定技术面试

JavaScript是构建网络的主要基石之一。这个强大的语言也有自己的怪癖。例如,您知道0 === -0计算为true,或者Number("")产生0吗?

问题在于,这些怪癖有时会让你抓耳挠腮,甚至质疑Brendon Eich发明JavaScript的那一天是不是high了。当然,这里的重点不是说JavaScript是一种糟糕的编程语言,也不是像它的批评者所说的那样它是邪恶的。所有编程语言都或多或少地有一些奇怪之处,JavaScript也不例外。

在这篇博文中,我们将深入解析一些重要的JavaScript面试题。我的目标是全面地解释这些面试题,以便我们可以理解其中的基本概念,并希望能解决面试中类似的其他问题。

1 +和-运算符更近一步的探讨

可以猜测像上面这样的情况中,JavaScript的+和-运算符的行为吗?

当JavaScript遇到1 + '1'时,它使用+运算符处理该表达式。 +运算符的一个有趣特性是,当其中一个操作数是字符串时,它更倾向于字符串连接。在我们的例子中,'1’是一个字符串,所以JavaScript隐式地将数字值1强制转换为字符串。因此,1 + '1'变成了'1' + '1',结果是字符串'11'

现在,我们的方程是'11' - 1。 -运算符的行为正好相反。无论操作数的类型如何,它都优先进行数字减法。当操作数不是数字类型时,JavaScript执行隐式强制类型转换将它们转换为数字。在这种情况下,'11'被转换为数字值11,表达式简化为11 - 1

把它们结合起来:

'11' - 1 = 11 - 1 = 10

2 数组元素的复制

请考虑下面的JavaScript代码,并尝试在代码中找到任何问题:

function duplicate(array) {
  for (var i = 0; i < array.length; i++) {
    array.push(array[i]);
  }
  return array;
}

const arr = [1, 2, 3];
const newArr = duplicate(arr);
console.log(newArr);

在这段代码中,我们需要创建一个包含输入数组中重复元素的新数组。初步检查,代码似乎通过从原始数组arr中复制每个元素来创建一个新数组newArr。但是,在duplicate函数本身出现了一个关键问题。

duplicate函数使用一个循环遍历给定数组中的每个项目。 但在循环内部,它使用push()方法在数组的末尾添加一个新元素。 每次这样做都会使数组变长,从而创建一个循环永远不会停止的问题。 循环条件(i <array.length)始终为真,因为数组不断变大。 这使得循环永远继续下去,导致程序卡住。

为了解决不断增长的数组长度导致的无限循环问题,可以在进入循环之前将数组的初始长度存储在一个变量中。 然后,可以将这一初始长度用作循环迭代的限制。 这样,循环将只针对数组中的原始元素运行,不会受到由于添加副本而导致数组增长的影响。 这是代码的修改版本:

function duplicate(array) {
  var initialLength = array.length; // Store the initial length
  for (var i = 0; i < initialLength; i++) {
    array.push(array[i]); // Push a duplicate of each element
  }
  return array;
}

const arr = [1, 2, 3];
const newArr = duplicate(arr);
console.log(newArr);

通过这种修改,你将获得预期的输出:

[1, 2, 3, 1, 2, 3]

3 prototype和__proto__的区别

prototype属性是与JavaScript中的构造函数相关联的属性。 构造函数用于在JavaScript中创建对象。 定义构造函数时,你还可以将属性和方法附加到其prototype属性上。 然后这些属性和方法对从该构造函数创建的所有实例对象都可访问。 因此,prototype属性充当实例之间共享的公共存储库,用于方法和属性。

// Constructor function
function Person(name) {
  this.name = name;
}

// Adding a method to the prototype
Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}.`);
};

// Creating instances
const person1 = new Person("Haider Wain");
const person2 = new Person("Omer Asif");

// Calling the shared method
person1.sayHello();  // Output: Hello, my name is Haider Wain.
person2.sayHello();  // Output: Hello, my name is Omer Asif.

另一方面,__proto__属性通常发音为“dunder proto”,存在于每个JavaScript对象中。 在JavaScript中,除了原始类型之外的所有内容都可以被视为对象。 这些对象中的每一个都有一个原型,该原型充当对另一个对象的引用。 __proto__属性仅仅是对该原型对象的引用。 当原始对象不具备属性或方法时,将使用原型对象作为回退来源。 默认情况下,创建对象时,其原型设置为Object.prototype

当尝试访问对象上的属性或方法时,JavaScript遵循查找流程来查找它。 此过程涉及两个主要步骤:

  1. 对象自身的属性: JavaScript首先检查对象本身是否直接拥有所需的属性或方法。 如果在对象中找到该属性,则直接访问和使用该属性。

  2. 原型链查找:如果没有在对象本身中找到该属性,则JavaScript查看对象的原型(由__proto__属性引用),并在其中搜索该属性。 此过程对原型链进行递归,直到找到该属性或直到查找达到Object.prototype为止。

4 作用域

编写JavaScript代码时,理解作用域的概念很重要。 作用域是指变量在代码不同部分中的可访问性或可见性。 在使用示例之前,如果您不熟悉变量提升以及JavaScript代码的执行方式,可以从此链接了解它。 这将帮助您更详细地了解JavaScript代码的工作方式。

我们来仔细看看代码段:

function foo() {
    console.log(a);
}
  
function bar() {
    var a = 3;
    foo();
}

var a = 5;
bar();

这段代码定义了2个函数 foo()bar(),以及一个值为 5 的变量 a。 所有这些声明都发生在全局作用域中。 在 bar() 函数中,声明并为变量 a 赋值 3。 那么,您认为当调用 bar() 函数时,它会打印出的值 a 是多少呢?

当JavaScript引擎执行这段代码时,全局变量a被声明和赋值为5。 然后调用 bar() 函数。 在 bar() 函数中,声明一个局部变量 a 并将其赋值为 3。 这个局部变量a与全局变量a不同。 在那之后,在 bar() 函数内部调用 foo() 函数。

foo() 函数内部,console.log(a) 语句试图记录 a 的值。 由于在 foo() 函数的作用域内没有定义局部变量 a,所以JavaScript查找作用域链以查找名为 a 的最近变量。 作用域链是指函数尝试查找和使用变量时可以访问的所有不同作用域。

现在,让我们来讨论JavaScript会在哪里搜索变量 a。 它会在 bar 函数的作用域内查找,还是会在全局作用域中探索?事实证明,JavaScript将在全局作用域中进行搜索,这种行为是由词法作用域的概念驱动的。

词法作用域是指函数或变量在代码中编写时的作用域。 当我们定义 foo 函数时,它可以访问自己的局部作用域和全局作用域。 这个特性与我们在哪里调用 foo 函数无关——无论是在 bar 函数内部还是将其导出到另一个模块并在那里运行。 词法作用域不是由我们调用该函数的位置确定的。

其结果是输出将始终相同:在全局作用域中找到的 a 的值,在本例中为5

但是,如果我们在 bar 函数中定义了 foo 函数,则会出现不同的情况:

function bar() {
  var a = 3;

  function foo() {
    console.log(a);
  }
  
  foo();
}

var a = 5;
bar();

在这种情况下,foo 的词法作用域将涵盖三个不同的作用域:它自己的局部作用域、bar 函数的作用域和全局作用域。 词法作用域由您在源代码编译时放置代码的位置确定。

当这段代码运行时,foo 位于 bar 函数中。这种安排改变了作用域动态。 现在,当 foo 试图访问变量 a 时,它将首先在自己的局部作用域中进行搜索。 由于它没有在那里找到 a,因此它将扩大搜索范围到 bar 函数的作用域。 不出所料,a 存在于那里,其值为 3。 因此,控制台语句将打印 3

5 对象强制

一个有趣的方面是探索JavaScript如何将对象转换为基元值,例如字符串、数字或布尔值。 当涉及诸如字符串连接或算术运算等情况下使用对象时,这是一个有趣的问题,可以测试您是否知道对象的强制工作原理。

要实现这一点,JavaScript依赖于两个特殊方法:valueOftoString

valueOf 方法是 JavaScript 对象转换机制的基本部分。 当在需要基元值的上下文中使用对象时,JavaScript 首先在对象中查找 valueOf 方法。 如果 valueOf 方法不存在或没有返回适当的基元值,则 JavaScript 会回退到 toString 方法。 这个方法负责提供对象的字符串表示。

回到我们的原始代码段:

const obj = {
  valueOf: () => 42,
  toString: () => 27
};

console.log(obj + '');

当我们运行这段代码时,对象 obj 被转换为基元值。 在这种情况下,valueOf 方法返回 42,然后由于与空字符串连接而隐式转换为字符串。 因此,代码的输出将是 42

然而,在 valueOf 方法不存在或没有返回适当基元值的情况下,JavaScript 会回退到 toString 方法。 让我们修改之前的示例:

const obj = {
  toString: () => 27
};

console.log(obj + '');

这里,我们删除了 valueOf 方法,只留下返回数字 27toString 方法。 在这种情况下,JavaScript 将使用 toString 方法转换对象。

6 理解对象键

在使用 JavaScript 的对象时,掌握键在其他对象上下文中如何处理和分配非常重要。 考虑以下代码段,并花些时间猜测输出:

乍一看,这段代码似乎应该产生一个具有两个不同键值对的对象 a。 然而,由于 JavaScript 处理对象键的方式,结果与预期相去甚远。

JavaScript 使用默认的 toString() 方法将对象键转换为字符串。但是为什么呢? 在 JavaScript 中,对象键始终是字符串(或符号),或者它们通过隐式强制转换自动转换为字符串。 当将字符串(例如数字、对象或符号)之外的值用作对象中的键时,JavaScript 将在将该值用作键之前在内部将该值转换为其字符串表示。

因此,当我们在对象 a 中使用对象 bc 作为键时,两者都变为相同的字符串表示:[object Object]。 由于这种行为,第二次分配 a[b] = '123'; 将覆盖第一次分配 a[c] = '456';。 让我们一步一步地分解代码:

  1. let a = {};: 初始化一个空对象 a
  2. let b = { key: 'test' };: 创建一个具有属性 key 的对象 b,该属性的值为 'test'
  3. let c = { key: 'test' };: 定义与 b 具有相同结构的另一个对象 c
  4. a[b] = '123';: 将值 '123' 设置为对象 a 中键为 [object Object] 的属性。
  5. a[c] = '456';: 使用相同的键字符串 [object Object] 更新对象 a 中相同属性的值,替换之前的值。

当我们记录对象 a 时,我们观察到以下输出:

{ '[object Object]': '456' }

7 双等号运算符

这是一个有点复杂的问题。那么,你认为输出会是什么?让我们一步一步来评估。首先,让我们开始查看两个操作数的类型:

typeof([]) // "object"  
typeof(![]) // "boolean"

对于 [] 它是一个 object,这是可以理解的。因为在 JavaScript 中所有的都是对象,包括数组和函数。但是操作数 ![] 如何具有 boolean 类型呢?让我们试着理解这一点。当你对一个基本值使用 ! 时,会发生以下转换:

  1. 假值: 如果原始值是一个假值(如 false0nullundefinedNaN 或空字符串 ''),应用 ! 会将其转换为 true
  2. 真值: 如果原始值是一个真值(任何非假的值),应用 ! 会将其转换为 false

在我们的例子中,[] 是一个空数组,在 JavaScript 中它是一个真值。由于 [] 是真值,所以 ![] 变为 false。所以我们的表达式变成了:

[] == ![]
[] == false

现在让我们继续理解 == 操作符。当用 == 操作符比较两个值时,JavaScript 会执行抽象相等比较算法。该算法有以下步骤:

抽象相等比较算法

如你所见,该算法考虑被比较值的类型并进行必要的转换。

对于我们的例子,让我们用 x 表示 [],用 y 表示 ![]。我们检查了 xy 的类型,发现 x 是一个对象,y 是一个布尔值。由于 y 是布尔值,x 是对象,抽象相等比较算法条件 7 被应用:

如果 Type(y) 是 Boolean,返回比较 x == ToNumber(y)的结果。

这意味着如果其中一个类型是布尔值,在比较之前需要将其转换为数字。ToNumber(y) 的值是什么?如我们所见,[]是一个真值,否定后变为 false。因此,Number(false)0

[] == false
[] == Number(false)  
[] == 0  

现在我们有了比较 [] == 0,这时候条件 8 起作用:

如果 Type(x) 是 String 或 Number 其中之一,且 Type(y) 是 Object,返回比较 x == ToPrimitive(y) 的结果。

根据这个条件,如果其中一个操作数是一个对象,我们必须将其转换为基本值。这就是 ToPrimitive 算法发挥作用的地方。我们需要将 x 也就是 [] 转换为一个基本值。数组在 JavaScript 中是对象。如我们之前看到的,在将对象转换为基本值时,valueOftoString 方法会发挥作用。在这种情况下,valueOf 返回数组本身,这不是一个有效的基本值。因此,我们转到 toString 来获取输出。对空数组应用 toString 方法会得到一个空字符串,这是一个有效的基本值:

[] == 0  
[].toString() == 0
"" == 0

将空数组转换为字符串给我们一个空字符串 "",现在我们面临比较:"" == 0

现在其中一个操作数是 string 类型,另一个是 number 类型,条件 5 成立:

如果 Type(x) 是 String,Type(y) 是 Number,返回比较 ToNumber(x) == y 的结果。

因此,我们需要将空字符串 "" 转换为数字,这给我们 0

"" == 0
ToNumber("") == 0 
0 == 0

最后,两个操作数都有相同的类型,条件 1 成立。因为它们的值相同,最终输出是:

0 == 0 // true

到目前为止,我们在过去几个问题中都使用了强制转换,这是掌握 JavaScript 以及在面试中解决这种问题的一个重要概念,这类问题往往会被问到很多。我真的建议你查看我关于强制转换的详细博文。它以清晰透彻的方式解释了这个概念。这里是链接。

8-闭包

这是与闭包相关的最著名的面试题之一:

const arr = [10, 12, 15, 21];  
for (var i = 0; i < arr.length; i++) {
  setTimeout(function() {
    console.log('Index: ' + i + ', element: ' + arr[i]); 
  }, 3000);
}

如果你知道输出结果,那很好。所以让我们试着理解这段代码。从表面上看,这段代码会给我们以下输出:

Index: 0, element: 10
Index: 1, element: 12  
Index: 2, element: 15
Index: 3, element: 21

但事实并非如此。由于闭包的概念以及 JavaScript 处理变量作用域的方式,实际输出将不同。当 setTimeout 回调在 3000 毫秒的延迟后执行时,它们都会引用同一个变量 i,该变量在循环完成后会有一个最终值为 4。因此,代码的输出将是:

Index: 4, element: undefined  
Index: 4, element: undefined
Index: 4, element: undefined 
Index: 4, element: undefined

这种行为发生的原因是 var 关键字没有块作用域,setTimeout 回调捕获对同一个 i 变量的引用。当回调执行时,它们都看到 i 的最终值 4,并试图访问 arr[4],即 undefined

为了达到期望的输出,你可以使用 let 关键字为循环的每次迭代创建一个新作用域,确保每个回调捕获正确的值 i:

const arr = [10, 12, 15, 21];
for (let i = 0; i < arr.length; i++) { 
  setTimeout(function() {
    console.log('Index: ' + i + ', element: ' + arr[i]);
  }, 3000); 
}

通过这种修改,你将得到预期的输出:

Index: 0, element: 10
Index: 1, element: 12  
Index: 2, element: 15  
Index: 3, element: 21

使用 leti 的每个迭代创建一个新的绑定,确保每个回调引用正确的值。

通常,开发人员已经熟悉了使用 let 关键字的解决方案。然而,面试有时会进一步挑战你,在不使用 let 的情况下解决问题。在这种情况下,另一种方法是通过在循环内立即调用函数(IIFE)来创建闭包。这样,每个函数调用都有自己的 i 副本。这里是如何做到这一点:

const arr = [10, 12, 15, 21]; 
for (var i = 0; i < arr.length; i++) {
  (function(index) {
    setTimeout(function() {
      console.log('Index: ' + index + ', element: ' + arr[index]);
    }, 3000);
  })(i); 
}

在这段代码中,立即调用的函数 (function(index) { ... })(i); 为每次迭代创建了一个新作用域,捕获当前的 i 值并作为 index 参数传递。这确保了每个回调函数都获得自己独立的 index 值,防止了与闭包相关的问题,并给你预期的输出:

Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15 
Index: 3, element: 21

感谢您的阅读。我希望这篇文章对你的面试准备有所帮助

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/309414.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

金融帝国实验室(Capitalism Lab)V10版本游戏平衡性优化与改进

即将推出的V10版本中的各种游戏平衡性优化与改进&#xff1a; ————————————— 一、当玩家被提议收购一家即将破产的公司时&#xff0c;显示商业秘密。 当一家公司濒临破产&#xff0c;玩家被提议收购该公司时&#xff0c;如果玩家有兴趣评估该公司&#xff0c;则无…

【Axure高保真原型】树控制内联框架

今天和大家分享树控制内联框架的原型模板&#xff0c;点击树的箭头可以打开或者收起子节点&#xff0c;点击最后一级人物节点&#xff0c;可以切换右侧内联框到对应的页面&#xff0c;左侧的树是通过中继器制作的&#xff0c;使用简单&#xff0c;只需要按要求填写中继器表格即…

【二】为Python Tk GUI窗口添加一些组件和绑定一些组件事件

文章目录 背景系统环境添加一些组件添加一个Tab标签Frame标签内添加两个单选框、按钮为按钮添加事件&#xff08;预览图片、生成图片按钮和事件&#xff09; 运行示例添加notebook组件和frame组件&#xff08;见标题【添加一个Tab标签】&#xff09;在frame组件上添加单选框和按…

dpdk20.11.9 编译arm版本以及在arm 应用中引用dpdk20.11.9

以往19版本的dpdk 都是可以直接用make 的方式进行编译, e.g, make Tx86_64-native-linux-gcc install 为了和客户那边用的DPDK 版本一致, 这次要用dpdk20.11.9, 并且要把之前跑在X86 版本的服务器上的程序跑在ARM 版本上. 目前有两个问题: 1. 编译出arm 版本的dpdk. 2. 把…

Spark与云存储的集成:S3、Azure Blob Storage

在现代数据处理中&#xff0c;云存储服务如Amazon S3和Azure Blob Storage已成为存储和管理数据的热门选择。与此同时&#xff0c;Apache Spark作为大数据处理框架也备受欢迎。本文将深入探讨如何在Spark中集成云存储服务&#xff0c;并演示如何与S3和Azure Blob Storage进行互…

R语言安装教程(附安装包链接)

R是用于统计分析、绘图的语言和操作环境。R是属于GNU系统的一个自由、免费、源代码开放的软件&#xff0c;它是一个用于统计计算和统计制图的优秀工具。 Mac支持M1芯片&#xff0c;下载其中的arm即可&#xff0c;其余下载另一个文件 下载链接&#xff1a; 链接: https://pan…

Python如何使用Excel文件

使用Python操作Office——EXCEL 首先介绍下office win32 com接口&#xff0c;这个是MS为自动化提供的操作接口&#xff0c;比如我们打开一个EXCEL文档&#xff0c;就可以在里面编辑VB脚本&#xff0c;实现我们自己的效果。对于这种一本万利的买卖&#xff0c;Python怎么能放过…

怎么样检查自己系统上的Python环境中是否有某个包(扩展库)?

比如我们这里想看下有没有库pytz 很简单&#xff0c;进入Python的命令行&#xff0c;然后输入下面的命令&#xff1a; import pytz如果有这个库&#xff0c;则不会报错&#xff0c;否则会报错。 Windows的测试结果如下&#xff1a; Centos的测试结果如下&#xff1a;

【LV12 DAY12-13 GPIO C 语言与寄存器封装】

GPIO 通用型输入输出&#xff0c;GPIO可以控制连接在其引脚实现信号的输入和输出 芯片的引脚和外部设备相连从而实现与外部硬件的通讯&#xff0c;控制&#xff0c;信号采集的功能。 控制CHG_COK引脚输出为高电平&#xff0c;LED亮&#xff0c;输出为低电平&#xff0c;LED熄灭…

麦芯(MachCore)开发教程1 --- 设备软件中间件

黄国强 2024/1/10 acloud163.com 对任何公司来说&#xff0c;在短时间内开发一款高质量设备专用软件&#xff0c;是一件不太容易做到的事情。麦芯是笔者发明的一款设备软件中间件产品。麦芯致力于给设备厂商提供一个开发工具和平台&#xff0c;让客户快速高效的开发自己的设备专…

Unity Delaunay三角剖分算法 动态生成

Unity Delaunay三角剖分算法 动态生成 Delaunay三角剖分Delaunay三角剖分 定义Delaunay 边Delaunay 空圆特性 Delaunay 三角形Delaunay 最大化最小角特性 Delaunay 三角形特征Delaunay 算法Delaunay Lawson算法Delaunay Bowyer-Watson算法 Unity Delaunay三角剖分 应用Unity 工…

SpringBoot3 WebFlux 可观测最佳实践

前言 链路追踪是可观测性软件系统的一个非常好的工具。它使开发人员能够了解应用程序中和应用程序之间不同交互发生的时间、地点和方式。同时让观测复杂的软件系统变得更加容易。 从Spring Boot 3开始&#xff0c;Spring Boot 中用于链路追踪的旧 Spring Cloud Sleuth 解决方…

08、Kafka ------ 消息存储相关的配置-->消息过期时间设置、查看主题下的消息存活时间等配置

目录 消息存储相关的配置★ 消息的存储介绍★ 消息过期时间及处理方式演示&#xff1a;log.cleanup.policy 属性配置 ★ 修改指定主题的消息保存时间演示&#xff1a;将 test2 主题下的消息的保存时间设为10个小时1、先查看test2主题下的配置2、然后设置消息的保存时间3、然后再…

JavaScript基础课程

JavaScript 基础 - 第1天 了解变量、数据类型、运算符等基础概念&#xff0c;能够实现数据类型的转换&#xff0c;结合四则运算体会如何编程。 体会现实世界中的事物与计算机的关系 理解什么是数据并知道数据的分类 理解变量存储数据的“容器” 掌握常见运算符的使用&#x…

SpringSecurity集成JWT实现后端认证授权保姆级教程-授权配置篇

&#x1f341; 作者&#xff1a;知识浅谈&#xff0c;CSDN签约讲师&#xff0c;CSDN博客专家&#xff0c;华为云云享专家&#xff0c;阿里云专家博主 &#x1f4cc; 擅长领域&#xff1a;全栈工程师、爬虫、ACM算法 &#x1f492; 公众号&#xff1a;知识浅谈 &#x1f525;网站…

【Spring Boot】SpringMVC入门

1.什么是springMVC MVC就是把一个项目分成了三部分&#xff1a; MVC是一种思想。Spring进行了实现,称为Spring MVC。SpringBoot是创建SpringMVC项目的一种方式而已。springMVC对于MVC做出了一些改变&#xff1a; 当前阶段,MVC的概念又发生了一些变化,后端开发人员不涉及前端页…

【JaveWeb教程】(18) MySQL数据库开发之 MySQL数据库设计-DDL 如何查询、创建、使用、删除数据库数据表 详细代码示例讲解

目录 2. 数据库设计-DDL2.1 项目开发流程2.2 数据库操作2.2.1 查询数据库2.2.2 创建数据库2.2.3 使用数据库2.2.4 删除数据库 2.3 图形化工具2.3.1 介绍2.3.2 安装2.3.3 使用2.2.3.1 连接数据库2.2.3.2 操作数据库 2.3 表操作2.3.1 创建2.3.1.1 语法2.3.1.2 约束2.3.1.3 数据类…

从0到1实现html文件转换为markdown文档(进度0.1)

Spider-Man 前言准备环境1、node.js2、git 执行指令顺序报错及其解决方案一、npm 错误&#xff01;可以在以下位置找到此运行的完整日志解决方案 二、没有修改权限解决方案&#xff1a; 注意事项总结 前言 当我们处理文档时&#xff0c;常常会遇到将HTML文档转换为Markdown文档…

微信小程序:发送小程序订阅消息

文档&#xff1a;小程序订阅消息&#xff08;用户通过弹窗订阅&#xff09;开发指南 目录 步骤一&#xff1a;获取模板 ID步骤二&#xff1a;小程序端获取参数2.1、获取消息下发权限2.2、获取登录凭证&#xff08;code&#xff09; 步骤三&#xff1a;后端调用接口下发订阅消息…

从生活入手学编程(1):Edge浏览器设置自动刷新专业教程

一、前言 我们都知道&#xff0c;Edge浏览器运行时的速度卡的实在是感人…… 于是今天&#xff0c;我就突发奇想&#xff0c;来看一看怎么刷新并且还能保留页面内容。 二、探索 首先&#xff0c;我在此提醒您&#xff0c;在使用这种方法时要非常小心。因为更改网页源代…