2024.10.26 - 2024.11.07 更新前端面试问题总结(20道题)
获取更多面试相关问题可以访问
github 地址: https://github.com/pro-collection/interview-question/issues
gitee 地址: https://gitee.com/yanleweb/interview-question/issues
目录:
初级开发者相关问题【共计 4 道题】
-
- JS 里面是否会存在,对象上有一个 name 属性,但是原型链上还有一个同名的 name 属性【热度: 724】【JavaScript】
-
- JS 里面哪些类型是可以互转的【热度: 157】【JavaScript】
-
- 实现一个处理长字符串的函数【热度: 200】【代码实现/算法】
-
- 解构复制对象, 是深拷贝还是浅拷贝【热度: 411】【JavaScript】
中级开发者相关问题【共计 11 道题】
-
- z-index: 999 元素一定会置于 z-index: 0 元素之上吗【热度: 100】【CSS】
-
- 浏览器中点击 a 标签保存为文件如何做【热度: 84】【web应用场景】
-
- Object.is 与全等运算符(===)有何区别【热度: 320】【JavaScript】
-
- 创建一个禁止修改的对象, 只能通过指定方法去修改属性【热度: 410】【代码实现/算法】
-
- 详细讲一下 Reflect 内置函数【热度: 224】【JavaScript】
-
- Reflect.get() 和直接通过对象 [.] 访问获取属性, 有何区别【热度: 225】【JavaScript】
-
- 如何判定一个属性来自于对象本身, 还是来自于原型链【热度: 224】【JavaScript】
-
- 为何现在主流的图表库都是用的 canvas 方案, 而不是使用 svg, 是基于什么因素考量的呢【热度: 26】【web应用场景】
-
- 为何现在主流的图表库都是用的 canvas 方案, 而不是使用 svg, 是基于什么因素考量的呢【热度: 88】【web应用场景】
-
- [React] 如何将一个层级非常深的子组件的某一个方法, 抛出给上层组件使用【热度: 465】【web框架】
-
- 网络五层模型 具体是值的啥【热度: 116】【网络】
高级开发者相关问题【共计 5 道题】
-
- canvas 是如何处理复杂事件交互的呢【热度: 120】【web应用场景】【出题公司: TOP100互联网】
-
- 弱网检测该如何做【热度: 597】【网络、web应用场景】
-
- Performance API 主要有哪些应用场景【热度: 431】【网络、web应用场景】
-
- 统计前端请求耗时【热度: 609】【网络、web应用场景】
-
- 排查谁在修改对象【热度: 500】【web应用场景】
1043. JS 里面是否会存在,对象上有一个 name 属性,但是原型链上还有一个同名的 name 属性【热度: 724】【JavaScript】
在 JavaScript 中是可能存在对象上有一个name
属性,同时原型链上也有一个同名的name
属性的情况。
以下是一个示例:
1function Person() {}
2Person.prototype.name = 'prototype name';
3
4const person = new Person();
5person.name = 'instance name';
6
7console.log(person.name); // 'instance name'
8console.log(person.__proto__.name); // 'prototype name'
在这个例子中,首先定义了一个构造函数Person
,在其原型上设置了一个name
属性。然后创建一个Person
的实例person
,并在实例上也设置了一个同名的name
属性。当访问person.name
时,会优先访问实例上的属性,如果实例上没有该属性,才会沿着原型链向上查找。
1049. JS 里面哪些类型是可以互转的【热度: 157】【JavaScript】
关键词:JS 类型转换
在 JavaScript(JS)中,以下类型之间是可以互相转换的:
一、数字(Number)与字符串(String)
-
数字转字符串:
-
使用
toString()
方法。例如:1let num = 123; 2let str = num.toString();
-
使用字符串拼接的方式自动转换。例如:
let str = num + '';
-
-
字符串转数字:
- 使用
Number()
函数。如果字符串不能转换为有效数字,则返回NaN
。例如:let num = Number('123');
- 使用
parseInt()
函数,将字符串解析为整数。例如:let num = parseInt('123');
- 使用
parseFloat()
函数,将字符串解析为浮点数。例如:let num = parseFloat('123.45');
- 使用
二、布尔值(Boolean)与其他类型
-
布尔值转其他类型:
- 转数字时,
true
转换为 1,false
转换为 0。 - 转字符串时,
true
转换为“true”,false
转换为“false”。
- 转数字时,
-
其他类型转布尔值:
- 以下值转换为
false
:false
、0、空字符串(”)、null
、undefined
、NaN
。 - 其他值转换为
true
。
- 以下值转换为
三、对象(Object)与原始类型
-
对象转原始类型:
-
通过调用
valueOf()
和toString()
方法进行转换,具体转换结果取决于对象的实现。例如:1let obj = { 2 valueOf: function () { 3 return 42; 4 }, 5}; 6let num = Number(obj);
-
-
原始类型转对象:
- 使用包装对象,例如
new String('abc')
将字符串“abc”转换为字符串对象。
- 使用包装对象,例如
在进行类型转换时,需要注意转换的结果是否符合预期,以避免出现意外的错误。
1053. 实现一个处理长字符串的函数【热度: 200】【代码实现/算法】
描述
实现一个处理字符串的函数: 字符串长度不能超过 256,如果超过 256, 字符串中间字符 【…】处理,且处理结果字符串长度还是为 256
以下是使用 JavaScript 实现的函数:
1function processString(str) {
2 if (str.length <= 256) {
3 return str;
4 }
5 const halfLength = Math.floor((256 - 3) / 2);
6 return str.slice(0, halfLength) + "... " + str.slice(str.length - halfLength);
7}
这个函数首先检查输入字符串的长度是否超过 256。如果不超过,则直接返回原字符串。如果超过,它会计算出前后两端保留的字符长度,使得加上中间的“… ”后总长度为 256。然后,它从字符串的开头和结尾分别截取相应长度的子字符串,并将它们与“… ”拼接起来返回。
1054. 解构复制对象, 是深拷贝还是浅拷贝【热度: 411】【JavaScript】
关键词:结构复制对象
浅拷贝
举例:
1const obj = {
2 prop1: "value1",
3 prop2: {
4 nestedProp: "nestedValue",
5 },
6};
7
8// 使用扩展运算符进行复制
9const obj2 = { ...obj };
10
11console.log("原始对象 obj:", obj);
12console.log("复制后的对象 obj2:", obj2);
13
14// 修改基本类型属性
15obj2.prop1 = "newValue1";
16console.log("修改基本类型属性后:");
17console.log("原始对象 obj:", obj);
18console.log("复制后的对象 obj2:", obj2);
19
20// 修改嵌套对象的属性
21obj2.prop2.nestedProp = "newNestedValue";
22console.log("修改嵌套对象属性后:");
23console.log("原始对象 obj:", obj);
24console.log("复制后的对象 obj2:", obj2);
解释如下:
- 首先定义了一个对象
obj
,它包含一个基本类型属性prop1
和一个嵌套对象属性prop2
。 - 使用扩展运算符
{...obj}
创建了一个新的对象obj2
,这看起来像是对obj
进行了复制。 - 当修改
obj2
的基本类型属性prop1
时,原始对象obj
的prop1
不受影响。这是因为基本类型的值在复制时是按值复制的。 - 然而,当修改
obj2
的嵌套对象属性prop2.nestedProp
时,原始对象obj
的prop2.nestedProp
也被修改了。这是因为扩展运算符对于嵌套对象只是复制了引用,而不是创建一个全新的嵌套对象副本,所以这是浅拷贝的行为。
1037. z-index: 999 元素一定会置于 z-index: 0 元素之上吗【热度: 100】【CSS】
关键词:z-index 生效情况
设置了z-index: 999
的元素不一定会置于z-index: 0
的元素之上。
一、z-index 的作用机制
z-index
属性用于控制元素在 z 轴上的堆叠顺序,即决定了元素在垂直于屏幕平面的方向上的前后显示顺序。- 只有当元素的定位属性(如
position: relative
、position: absolute
、position: fixed
)被设置时,z-index
才会生效。
二、影响堆叠顺序的其他因素
-
元素的堆叠上下文:
- 元素会根据其所在的堆叠上下文进行堆叠。堆叠上下文是一个三维的概念,包含一组元素,这些元素按照特定的规则进行堆叠。
- 创建堆叠上下文的因素包括:设置了定位属性和
z-index
的元素、opacity
小于 1 的元素、transform
属性不为none
的元素等。 - 如果一个元素处于一个具有更高堆叠顺序的堆叠上下文中,即使它的
z-index
值较低,也可能会被置于另一个堆叠上下文中具有较低z-index
值的元素之下。
-
元素的 HTML 结构顺序:
- 在没有设置
z-index
或堆叠上下文的情况下,元素的堆叠顺序通常遵循 HTML 结构的顺序。后出现的元素会覆盖先出现的元素。
- 在没有设置
三、示例说明
- 以下是一个示例代码:
1<!DOCTYPE html>
2<html>
3 <head>
4 <style>
5 .parent {
6 position: relative;
7 z-index: 1;
8 }
9
10 .child1 {
11 position: absolute;
12 z-index: 999;
13 background-color: red;
14 width: 100px;
15 height: 100px;
16 }
17
18 .child2 {
19 position: absolute;
20 z-index: 0;
21 background-color: blue;
22 width: 100px;
23 height: 100px;
24 }
25 </style>
26 </head>
27
28 <body>
29 <div class="parent">
30 <div class="child1"></div>
31 <div class="child2"></div>
32 </div>
33 </body>
34</html>
在这个例子中,.child1
设置了z-index: 999
,.child2
设置了z-index: 0
,并且它们都在一个具有z-index: 1
的父元素中。由于父元素创建了一个堆叠上下文,.child1
和.child2
会在这个堆叠上下文中按照它们的z-index
值进行堆叠,所以.child1
会显示在.child2
之上。
但是,如果将父元素的z-index
值设置为 0,或者去除父元素的定位属性,那么.child1
和.child2
的堆叠顺序可能会发生变化,具体取决于浏览器的默认行为和 HTML 结构的顺序。
1038. 浏览器中点击 a 标签保存为文件如何做【热度: 84】【web应用场景】
关键词:a 标签保存文件
在浏览器中,通常情况下无法直接通过点击一个<a>
标签将其指向的内容保存为文件。但是可以通过一些特定的方法来实现类似的功能:
一、使用服务器端响应
- 服务器端生成文件:如果要让用户下载一个文件,可以在服务器端生成该文件,并设置适当的响应头,让浏览器将响应内容视为一个文件进行下载。
- 例如,在后端使用 Node.js 和 Express 框架,可以这样设置响应头来提供一个文件下载:
1const express = require("express");
2const app = express();
3const fs = require("fs");
4
5app.get("/download", (req, res) => {
6 const fileStream = fs.createReadStream("path/to/your/file");
7 res.setHeader("Content-disposition", "attachment; filename=yourFileName.ext");
8 res.setHeader("Content-type", "application/octet-stream");
9 fileStream.pipe(res);
10});
11
12app.listen(3000, () => {
13 console.log("Server running on port 3000");
14});
- 在上面的例子中,当用户访问
/download
路径时,服务器会将指定的文件以附件的形式提供给浏览器进行下载。
<a>
标签链接到服务器端路径:在前端,可以使用一个<a>
标签链接到服务器端提供文件下载的路径。
1<a href="/download">下载文件</a>
二、使用 JavaScript 和 Blob 对象
- 创建 Blob 对象:可以使用 JavaScript 创建一个 Blob 对象,该对象包含要保存的文件内容。
- 例如:
1const data = "This is the content of the file";
2const blob = new Blob([data], { type: "text/plain" });
-
创建临时 URL:使用
URL.createObjectURL()
方法创建一个临时的 URL,指向创建的 Blob 对象。const url = URL.createObjectURL(blob);
-
使用
<a>
标签和 JavaScript:创建一个隐藏的<a>
标签,设置其href
属性为临时 URL,并模拟点击该标签来触发下载。- 例如:
1const a = document.createElement("a");
2a.style.display = "none";
3a.href = url;
4a.download = "yourFileName.txt";
5document.body.appendChild(a);
6a.click();
7document.body.removeChild(a);
8URL.revokeObjectURL(url);
这种方法的局限性在于,它只能在浏览器的安全限制范围内工作,并且可能受到同源策略的限制。此外,不同浏览器对于这种方法的支持程度也可能有所不同。
1039. Object.is 与全等运算符(===)有何区别【热度: 320】【JavaScript】
关键词:等值判断
Object.is()
与全等运算符(===
)都用于比较两个值是否相等,但它们之间存在一些区别:
一、对特殊值的处理
NaN
的比较:===
认为NaN
不等于任何值,包括它自身。Object.is()
认为NaN
只等于NaN
。- 例如:
1console.log(NaN === NaN); // false
2console.log(Object.is(NaN, NaN)); // true
-0
和+0
的比较:===
认为-0
和+0
是相等的。Object.is()
可以区分-0
和+0
。- 例如:
1console.log(-0 === +0); // true
2console.log(Object.is(-0, +0)); // false
二、一般值的比较
- 对于其他值的比较,
Object.is()
和===
的行为类似:- 比较两个数字、字符串、布尔值、对象等,如果它们的值和类型都相同,则认为它们相等。
- 例如:
1console.log(5 === 5); // true
2console.log(Object.is(5, 5)); // true
3
4const obj1 = { a: 1 };
5const obj2 = { a: 1 };
6console.log(obj1 === obj1); // true
7console.log(obj1 === obj2); // false
8console.log(Object.is(obj1, obj1)); // true
9console.log(Object.is(obj1, obj2)); // false
1040. 创建一个禁止修改的对象, 只能通过指定方法去修改属性【热度: 410】【代码实现/算法】
具体描述如下
1// 实现 createObject 函数, 用例如下;
2
3// 比如:
4const obj = createObject({ name: "name" });
5
6obj.name = "name2"; // 错误, 禁止修改;
7obj.set("name", "name2"); // 正确方式, 允许修改;
8obj.set("address.info", "chongqing"); // 正确方式, 允许添加属性。
实现
以下是使用 JavaScript 实现的createObject
函数:
1function createObject(initialData) {
2 const data = initialData;
3 return new Proxy(data, {
4 set(target, property, value, receiver) {
5 if (property === 'set') {
6 const keys = property.split('.');
7 let obj = target;
8 for (let i = 0; i < keys.length - 1; i++) {
9 obj = obj[keys[i]] || (obj[keys[i]] = {});
10 }
11 obj[keys[keys.length - 1]] = value;
12 return true;
13 } else {
14 return false;
15 }
16 },
17 get(target, property, receiver) {
18 if (property === 'set') {
19 return function (key, value) {
20 const keys = key.split('.');
21 let obj = target;
22 for (let i = 0; i < keys.length - 1; i++) {
23 obj = obj[keys[i]] || (obj[keys[i]] = {});
24 }
25 obj[keys[keys.length - 1]] = value;
26 };
27 } else {
28 return target[property];
29 }
30 },
31 });
32}
你可以使用以下方式测试这个函数:
1const obj = createObject({ name: 'name' });
2
3obj.name = 'name2'; // 不会生效,禁止直接修改属性
4console.log(obj.name); // 'name'
5
6obj.set('name', 'name2');
7console.log(obj.name); // 'name2'
8
9obj.set('address.info', 'chongqing');
10console.log(obj.address.info); // 'chongqing'
1041. 详细讲一下 Reflect 内置函数【热度: 224】【JavaScript】
关键词:Reflect 函数
Reflect
是 ES6 引入的一个内置对象,它提供了一组与对象操作对应的方法,这些方法与Object
上的某些方法类似,但有一些重要的区别。
以下是对Reflect
内置函数的详细介绍:
一、获取属性(Reflect.get()
)
-
作用:
- 用于获取对象的属性值。
- 类似于传统的对象属性访问操作符(
obj.property
或obj[property]
)。
-
示例:
1const obj = { name: "John" }; 2console.log(Reflect.get(obj, "name")); // 'John'
-
与传统方式的区别:
- 返回值:如果属性不存在,
Reflect.get()
返回undefined
,而直接访问属性可能会导致错误(如果在严格模式下)或返回undefined
(非严格模式下)。 - 可接受第三个参数
receiver
,用于指定属性访问的上下文对象,这在某些情况下(如使用代理时)非常有用。
- 返回值:如果属性不存在,
二、设置属性(Reflect.set()
)
-
作用:
- 用于设置对象的属性值。
- 类似于传统的属性赋值操作符(
obj.property = value
或obj[property] = value
)。
-
示例:
1const obj = {}; 2Reflect.set(obj, "name", "Jane"); 3console.log(obj.name); // 'Jane'
-
与传统方式的区别:
- 返回值:返回一个布尔值,表示属性设置是否成功。如果目标对象不可扩展、属性不可写或属性为访问器属性且设置器函数返回
false
,则返回false
;否则返回true
。 - 同样可接受第三个参数
receiver
,用于指定属性设置的上下文对象。
- 返回值:返回一个布尔值,表示属性设置是否成功。如果目标对象不可扩展、属性不可写或属性为访问器属性且设置器函数返回
三、判断对象是否具有某个属性(Reflect.has()
)
-
作用:
- 相当于
in
操作符,用于检查对象是否具有某个属性。
- 相当于
-
示例:
1const obj = { age: 30 }; 2console.log(Reflect.has(obj, "age")); // true 3console.log(Reflect.has(obj, "gender")); // false
四、获取对象的原型(Reflect.getPrototypeOf()
)
-
作用:
- 获取对象的原型,与
Object.getPrototypeOf()
方法类似。
- 获取对象的原型,与
-
示例:
1function Person() {} 2const person = new Person(); 3console.log(Reflect.getPrototypeOf(person) === Person.prototype); // true
五、设置对象的原型(Reflect.setPrototypeOf()
)
-
作用:
- 设置对象的原型,与
Object.setPrototypeOf()
方法类似。
- 设置对象的原型,与
-
示例:
1function Person() {} 2function Employee() {} 3const person = new Person(); 4Reflect.setPrototypeOf(person, Employee.prototype); 5console.log(Reflect.getPrototypeOf(person) === Employee.prototype); // true
-
注意事项:
- 频繁地设置对象的原型可能会对性能产生负面影响,并且可能会导致代码难以理解和维护。
六、判断对象是否可扩展(Reflect.isExtensible()
)
-
作用:
- 确定一个对象是否可以添加新的属性。
-
示例:
1const obj = {}; 2console.log(Reflect.isExtensible(obj)); // true 3Object.preventExtensions(obj); 4console.log(Reflect.isExtensible(obj)); // false
七、使对象不可扩展(Reflect.preventExtensions()
)
-
作用:
- 使一个对象不可扩展,即不能再添加新的属性。
-
示例:
1const obj = {}; 2Reflect.preventExtensions(obj); 3try { 4 obj.newProperty = "value"; 5} catch (e) { 6 console.log("Cannot add new property to non-extensible object."); 7}
八、判断对象的属性是否可配置(Reflect.ownKeys()
)
-
作用:
- 返回一个对象自身的所有属性的键名,包括不可枚举属性和 Symbol 属性。
-
示例:
1const obj = { name: "John", [Symbol("secret")]: "secret value" }; 2console.log(Reflect.ownKeys(obj)); // ['name', Symbol(secret)]
总的来说,Reflect
对象提供了一种更统一、更规范的方式来进行对象操作,并且在某些情况下(如与代理一起使用时)具有特殊的用途。它的方法通常与Object
上的对应方法具有相似的功能,但在返回值和行为上可能会有所不同,这使得开发者可以更精确地控制对象的操作。
1042. Reflect.get() 和直接通过对象 [.] 访问获取属性, 有何区别【热度: 225】【JavaScript】
关键词:Reflect 函数
Reflect.get()
和直接通过对象[.]
访问获取属性有以下一些区别:
一、返回值
-
Reflect.get()
:-
如果属性不存在,返回
undefined
。 -
例如:
1const obj = {}; 2const value = Reflect.get(obj, "property"); 3console.log(value); // undefined
-
-
对象直接访问:
-
如果属性不存在,在非严格模式下返回
undefined
;在严格模式下,会抛出一个ReferenceError
错误。 -
例如:
1const obj = {}; 2// 非严格模式下 3console.log(obj.property); // undefined 4// 严格模式下 5("use strict"); 6console.log(obj.property); // ReferenceError: property is not defined
-
二、可接受的参数和功能扩展
-
Reflect.get()
:-
可以接受第三个参数
receiver
,用于指定属性访问的上下文对象,这在某些情况下非常有用,比如在使用代理时可以控制属性访问的行为。 -
例如:
1const obj = { name: "John" }; 2const proxy = new Proxy(obj, {}); 3console.log(Reflect.get(proxy, "name", { name: "Jane" })); // 'Jane'
-
-
对象直接访问:
- 没有类似的参数来指定上下文对象。
三、与代理的交互
-
Reflect.get()
:-
与代理对象配合使用时,会触发代理对象上定义的相应拦截方法,使得可以对属性访问进行更精细的控制。
-
例如:
1const obj = { name: "John" }; 2const handler = { 3 get(target, property, receiver) { 4 if (property === "name") { 5 return "Modified Name"; 6 } 7 return Reflect.get(target, property, receiver); 8 }, 9}; 10const proxy = new Proxy(obj, handler); 11console.log(proxy.name); // 'Modified Name'
-
-
对象直接访问:
- 当通过直接访问属性的方式访问代理对象时,不一定会触发代理对象上的拦截方法,具体行为取决于代理的实现和配置。
四、一致性和规范性
-
Reflect.get()
:- 作为一种更规范的方法,它与其他
Reflect
方法一起提供了一种统一的方式来进行对象操作,有助于提高代码的可读性和可维护性。
- 作为一种更规范的方法,它与其他
-
对象直接访问:
- 虽然直接访问属性的方式更加简洁,但在一些复杂的场景下可能会导致不一致的行为,并且不太容易与其他高级特性(如代理)进行良好的集成。
1044. 如何判定一个属性来自于对象本身, 还是来自于原型链【热度: 224】【JavaScript】
关键词:对象与原型链
在 JavaScript 中,可以通过以下几种方式来判断一个属性是来自对象本身还是来自原型链:
一、使用 hasOwnProperty()
方法
-
方法介绍:
hasOwnProperty()
是 JavaScript 对象的一个方法,用于判断一个对象自身是否具有指定的属性。- 它不会检查原型链上的属性,只关注对象本身是否拥有该属性。
-
示例代码:
1function Person() {} 2Person.prototype.name = "prototype name"; 3 4const person = new Person(); 5person.age = 30; 6 7console.log(person.hasOwnProperty("age")); // true,说明 age 属性是对象本身的属性 8console.log(person.hasOwnProperty("name")); // false,说明 name 属性不在对象本身,而是在原型链上
二、使用 in
操作符结合 hasOwnProperty()
-
方法介绍:
in
操作符用于检查一个对象及其原型链中是否具有指定的属性。- 可以结合
hasOwnProperty()
来判断属性的来源。
-
示例代码:
1function Person() {} 2Person.prototype.name = "prototype name"; 3 4const person = new Person(); 5person.age = 30; 6 7const propertyName = "name"; 8if (person.hasOwnProperty(propertyName)) { 9 console.log(`${propertyName} is an own property of the object.`); 10} else if (propertyName in person) { 11 console.log(`${propertyName} is inherited from the prototype.`); 12} else { 13 console.log(`${propertyName} is not found in the object or its prototype.`); 14}
三、使用 Object.getOwnPropertyDescriptor()
方法
-
方法介绍:
Object.getOwnPropertyDescriptor()
方法返回指定对象上一个自有属性的属性描述符。- 如果对象没有指定的自有属性,则返回
undefined
。
-
示例代码:
1function Person() {} 2Person.prototype.name = "prototype name"; 3 4const person = new Person(); 5person.age = 30; 6 7const ageDescriptor = Object.getOwnPropertyDescriptor(person, "age"); 8const nameDescriptor = Object.getOwnPropertyDescriptor(person, "name"); 9 10if (ageDescriptor) { 11 console.log("age is an own property of the object."); 12} 13if (!nameDescriptor) { 14 console.log("name is not an own property of the object."); 15}
1050. 为何现在主流的图表库都是用的 canvas 方案, 而不是使用 svg, 是基于什么因素考量的呢【热度: 26】【web应用场景】
关键词:SVG 与 canvas 对比
作者备注
这个是一个开放性的话题, 没有啥好讲的, 主要是看看同学们对前言技术的敏感度吧
有兴趣的话,可以去搜索一下国外的文章,有比较多的对比文章
目前主流图表库选用 canvas 方案而非 svg 主要有以下一些因素考量:
一、性能方面
-
渲染性能
- 在处理大量数据和复杂图形时,canvas 通常具有更高的渲染性能。canvas 是基于像素的即时模式绘图系统,它可以快速地绘制图形,尤其在需要频繁更新和重绘的场景下,如动态数据可视化或实时交互的图表中,能够更高效地处理大量图形的绘制,减少卡顿现象,提供更流畅的用户体验。
- SVG 在处理复杂图形时可能会因为其基于文档对象模型(DOM)的特性而导致性能下降。每一个 SVG 元素都是一个 DOM 节点,当图形数量较多时,浏览器对 DOM 的操作和管理会消耗较多的资源。
-
内存占用
- canvas 在绘制图形后,通常只保留最终的像素图像,不会像 SVG 那样保留每个图形元素的完整描述信息,因此在处理大规模数据可视化时,canvas 可能占用更少的内存。特别是对于需要在移动设备或资源受限环境中运行的应用,内存占用是一个重要的考虑因素。
二、灵活性方面
-
像素操作
- canvas 提供了对像素级别的操作能力,可以直接访问和修改图像的像素数据。这在一些特定的可视化需求中非常有用,例如图像滤镜、数据可视化中的热图绘制等。通过对像素的直接操作,可以实现更加复杂和个性化的视觉效果。
- SVG 主要是基于矢量图形的描述,对于像素级别的操作相对较为困难。
-
自定义绘图
- 使用 canvas,开发人员可以完全控制绘图的过程,通过编写自定义的绘图代码,可以实现非常复杂和独特的图表效果。这种灵活性使得在满足特定设计需求或实现特殊可视化效果时,canvas 成为更好的选择。
三、适用场景方面
-
游戏和动画
- 在开发具有复杂动画效果的可视化应用时,canvas 的性能优势更加明显。例如在数据可视化中的动态交互图表、数据驱动的动画效果等场景中,canvas 可以更好地满足实时性和流畅性的要求。而 SVG 在处理大量动画效果时可能会显得力不从心。
- 对于一些类似游戏开发的场景,canvas 的图形绘制和更新机制更适合实现快速的画面更新和交互响应。
-
大规模数据可视化
- 当处理大规模数据集的可视化时,canvas 的性能优势使得它能够更好地应对数据量的挑战。例如在地理信息系统(GIS)、金融数据可视化等领域,需要处理大量的数据点和复杂的图形,canvas 可以更高效地完成这些任务。
然而,SVG 也有其自身的优势,如可编辑性强、支持矢量缩放、对搜索引擎友好等。在一些特定的场景下,如需要用户交互编辑图形、对图形进行精确的布局控制或需要考虑可访问性等方面,SVG 可能是更好的选择。不同的图表库会根据具体的应用需求和设计目标来选择合适的技术方案。
1051. 为何现在主流的图表库都是用的 canvas 方案, 而不是使用 svg, 是基于什么因素考量的呢【热度: 88】【web应用场景】
关键词:SVG 与 canvas 对比
作者备注
比较冷门的话题, 当做一个科普吧
SVG 和 Canvas 在利用 GPU 硬件加速方面存在以下区别:
一、SVG 的 GPU 加速特点
-
有限的自动加速
- SVG 图形通常由浏览器自动决定是否使用 GPU 加速。对于一些简单的 SVG 图形,浏览器可能不会启用 GPU 加速,因为在这种情况下,使用 CPU 进行渲染可能已经足够高效。
- 只有在处理较为复杂的 SVG 场景,如包含大量图形元素、复杂的变换或动画效果时,浏览器才有可能启用 GPU 加速。但这种加速的触发机制并不完全可控,取决于浏览器的实现和判断。
-
依赖浏览器优化
- SVG 的 GPU 加速很大程度上依赖于浏览器的优化策略。不同的浏览器对 SVG 的 GPU 加速支持程度可能不同,这可能导致在不同的浏览器上 SVG 图形的性能表现不一致。
- 例如,一些现代浏览器可能会对特定的 SVG 特性(如滤镜效果、渐变等)进行优化并启用 GPU 加速,而其他浏览器可能没有这样的优化。
二、Canvas 的 GPU 加速特点
-
更明确的控制
- 在 Canvas 中,可以通过一些特定的技术手段来明确地请求 GPU 加速。例如,使用 WebGL(基于 OpenGL ES 的 JavaScript API)与 Canvas 结合,可以直接利用 GPU 进行高性能的 3D 图形渲染。
- 对于 2D 图形,一些浏览器也提供了对 Canvas 的硬件加速支持,可以通过特定的浏览器设置或使用特定的图形库来启用。开发人员可以更直接地控制是否使用 GPU 加速,以及如何优化图形渲染以充分利用 GPU 的性能。
-
高性能图形渲染
- Canvas 结合 GPU 加速可以实现非常高性能的图形渲染,尤其适用于复杂的游戏、数据可视化和动画效果。通过利用 GPU 的并行处理能力,可以快速地绘制大量的图形和进行复杂的图形变换。
- 例如,在数据可视化中,使用 Canvas 和 WebGL 可以实现大规模数据的实时渲染和交互,提供流畅的用户体验。
总的来说,Canvas 在利用 GPU 硬件加速方面通常具有更明确的控制和更高的性能潜力,而 SVG 的 GPU 加速则更多地依赖于浏览器的自动优化,且加速的范围和效果相对有限。但在实际应用中,选择使用 SVG 还是 Canvas 并考虑 GPU 加速,需要根据具体的应用场景、性能需求和开发技术栈来综合决定。
1052. [React] 如何将一个层级非常深的子组件的某一个方法, 抛出给上层组件使用【热度: 465】【web框架】
关键词:调用子组件方法
在 React 中,可以通过以下几种方式将一个层级非常深的子组件的某一个方法抛出给上层组件使用:
一、使用回调函数传递
-
在父组件中定义一个回调函数,并将其作为属性传递给子组件的父级组件,依次向下传递,直到目标子组件。
例如:
1function ParentComponent() { 2 const handleDeepMethod = () => { 3 console.log("调用了深层子组件的方法"); 4 }; 5 return <ChildComponent onDeepMethod={handleDeepMethod} />; 6} 7 8function ChildComponent({ onDeepMethod }) { 9 return <GrandChildComponent onDeepMethod={onDeepMethod} />; 10} 11 12function GrandChildComponent({ onDeepMethod }) { 13 const deepMethod = () => { 14 console.log("深层子组件的方法被执行"); 15 onDeepMethod(); 16 }; 17 return <button onClick={deepMethod}>触发深层方法</button>; 18}
-
在深层子组件中,当特定条件触发时,调用这个通过层层传递下来的回调函数,从而让父组件执行相应的操作。
二、使用 React 的 Context API
-
创建一个 Context:
1const DeepMethodContext = React.createContext();
-
在父组件中提供值:
1function ParentComponent() { 2 const handleDeepMethod = () => { 3 console.log("调用了深层子组件的方法"); 4 }; 5 return ( 6 <DeepMethodContext.Provider value={handleDeepMethod}> 7 <ChildComponent /> 8 </DeepMethodContext.Provider> 9 ); 10}
-
在深层子组件中获取 Context 值并调用:
1function GrandChildComponent() { 2 const handleDeepMethod = React.useContext(DeepMethodContext); 3 const deepMethod = () => { 4 console.log("深层子组件的方法被执行"); 5 handleDeepMethod(); 6 }; 7 return <button onClick={deepMethod}>触发深层方法</button>; 8}
三、使用 Custom Events 还有一种方法是使用自定义事件(Custom Events)。
-
在深层子组件中创建并派发自定义事件:
1function GrandChildComponent() { 2 const deepMethod = () => { 3 console.log("深层子组件的方法被执行"); 4 const event = new CustomEvent("deepMethodTriggered", { detail: {} }); 5 window.dispatchEvent(event); 6 }; 7 return <button onClick={deepMethod}>触发深层方法</button>; 8}
-
在父组件中监听这个自定义事件:
1function ParentComponent() { 2 const handleDeepMethod = () => { 3 console.log("调用了深层子组件的方法"); 4 }; 5 useEffect(() => { 6 window.addEventListener("deepMethodTriggered", handleDeepMethod); 7 return () => { 8 window.removeEventListener("deepMethodTriggered", handleDeepMethod); 9 }; 10 }, []); 11 return <ChildComponent />; 12}
1056. 网络五层模型 具体是值的啥【热度: 116】【网络】
关键词:网络模型 OSI 参考模型
网络五层模型,也称为 OSI(Open System Interconnection,开放系统互连)参考模型,它将计算机网络通信的功能划分为五个层次,每个层次都有特定的功能和职责。以下是五层模型的具体介绍:
一、物理层
-
功能:
- 物理层主要负责在物理介质上传输原始的比特流。
- 它定义了网络设备之间的物理连接标准,包括电缆类型、连接器、信号电平、传输速率等。
-
举例:
- 以太网中的双绞线、光纤等传输介质,以及 RJ45 连接器等都属于物理层的范畴。
- 物理层规定了不同的信号编码方式,如曼彻斯特编码等,确保比特流在物理介质上的准确传输。
二、数据链路层
-
功能:
- 数据链路层负责将物理层传输的比特流组织成数据帧。
- 进行数据帧的封装和解封装、差错检测和纠正、流量控制等操作。
- 通过 MAC(Media Access Control,介质访问控制)地址在局域网中唯一标识网络设备,实现设备之间的数据传输。
-
举例:
- 以太网协议、Wi-Fi 协议等都工作在数据链路层。
- 数据链路层的设备如网桥、交换机等,根据 MAC 地址转发数据帧。
三、网络层
-
功能:
- 网络层负责在不同的网络之间进行数据包的路由和转发。
- 通过 IP(Internet Protocol,网际协议)地址来标识网络中的设备,实现设备之间的逻辑通信。
- 进行网络拥塞控制、路由选择等操作,确保数据包能够准确地从源设备传输到目标设备。
-
举例:
- IP 协议是网络层的核心协议。
- 路由器是工作在网络层的设备,根据 IP 地址和路由表选择最佳路径转发数据包。
四、传输层
-
功能:
- 传输层负责端到端的数据传输控制。
- 提供可靠的数据传输服务,如 TCP(Transmission Control Protocol,传输控制协议),或者不可靠的传输服务,如 UDP(User Datagram Protocol,用户数据报协议)。
- 进行流量控制、差错控制、连接管理等操作,确保数据的正确、有序传输。
-
举例:
- 当你在浏览器中访问一个网站时,浏览器和服务器之间的 HTTP(HyperText Transfer Protocol,超文本传输协议)通信通常是基于 TCP 协议在传输层进行的。
- 一些实时应用如视频会议、在线游戏等可能会使用 UDP 协议,因为它的传输速度快,实时性好,但不保证数据的可靠性。
五、应用层
-
功能:
- 应用层是网络通信的最高层,直接为用户的应用程序提供服务。
- 定义了各种应用协议,如 HTTP、FTP(File Transfer Protocol,文件传输协议)、SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)等。
- 应用层的协议规定了应用程序之间通信的方式和数据格式。
-
举例:
- 当你在浏览器中输入网址访问网页时,浏览器使用 HTTP 协议与服务器进行通信,获取网页内容。
- 当你发送电子邮件时,邮件客户端使用 SMTP 协议将邮件发送到邮件服务器。
网络五层模型的各层之间相互独立又相互协作,每一层都为上一层提供特定的服务,同时又依赖于下一层的功能。这种分层结构使得网络通信的设计和实现更加清晰、易于管理和扩展。
1045. canvas 是如何处理复杂事件交互的呢【热度: 120】【web应用场景】【出题公司: TOP100互联网】
关键词:canvas 事件交互
在 HTML5 的canvas
中处理复杂事件交互可以通过以下方法实现:
一、基本原理
由于canvas
只是一个像素绘制区域,本身并不像常规 HTML 元素那样具有内置的事件处理机制。所以需要通过以下方式来处理事件交互:
- 监听整个文档或包含
canvas
的容器元素的事件。 - 根据事件发生的坐标位置判断是否在
canvas
内部以及与特定图形的交互。
二、具体步骤
-
获取
canvas
元素和绘图上下文:1const canvas = document.getElementById("myCanvas"); 2const ctx = canvas.getContext("2d");
-
监听容器元素的事件:
-
通常可以监听整个文档或包含
canvas
的父元素的鼠标事件(如mousemove
、mousedown
、mouseup
等)和触摸事件(如touchstart
、touchmove
、touchend
等)。 -
例如:
1document.addEventListener("mousemove", handleMouseMove);
-
-
事件处理函数:
-
在事件处理函数中,计算鼠标或触摸点在
canvas
中的坐标。 -
判断坐标是否在特定图形范围内,以确定是否发生了交互。
-
例如:
1function handleMouseMove(event) { 2 const rect = canvas.getBoundingClientRect(); 3 const mouseX = event.clientX - rect.left; 4 const mouseY = event.clientY - rect.top; 5 6 // 判断坐标是否在某个圆形范围内 7 if (isPointInCircle(mouseX, mouseY)) { 8 // 执行与圆形交互的逻辑 9 } 10}
-
-
判断坐标是否在图形内的函数:
-
根据不同的图形形状,编写相应的函数来判断坐标是否在图形内。
-
例如,对于圆形:
1function isPointInCircle(x, y, circleX, circleY, radius) { 2 const dx = x - circleX; 3 const dy = y - circleY; 4 return dx * dx + dy * dy <= radius * radius; 5}
-
三、处理复杂交互的策略
-
多个图形的交互:
-
可以维护一个图形对象的数组,在事件处理函数中遍历这个数组,判断与每个图形的交互。
-
例如:
1const shapes = [ 2 { type: "circle", x: 100, y: 100, radius: 50 }, 3 { type: "rectangle", x: 200, y: 200, width: 100, height: 50 }, 4]; 5 6function handleMouseMove(event) { 7 const rect = canvas.getBoundingClientRect(); 8 const mouseX = event.clientX - rect.left; 9 const mouseY = event.clientY - rect.top; 10 11 for (const shape of shapes) { 12 if (shape.type === "circle" && isPointInCircle(mouseX, mouseY, shape.x, shape.y, shape.radius)) { 13 // 圆形交互逻辑 14 } else if ( 15 shape.type === "rectangle" && 16 isPointInRectangle(mouseX, mouseY, shape.x, shape.y, shape.width, shape.height) 17 ) { 18 // 矩形交互逻辑 19 } 20 } 21}
-
-
动态交互效果:
-
根据交互状态改变图形的外观、位置等属性,以实现动态效果。
-
例如,当鼠标悬停在圆形上时,改变圆形的颜色:
1function handleMouseMove(event) { 2 const rect = canvas.getBoundingClientRect(); 3 const mouseX = event.clientX - rect.left; 4 const mouseY = event.clientY - rect.top; 5 6 for (const shape of shapes) { 7 if (shape.type === "circle" && isPointInCircle(mouseX, mouseY, shape.x, shape.y, shape.radius)) { 8 ctx.fillStyle = "red"; 9 } else { 10 ctx.fillStyle = "blue"; 11 } 12 drawShape(shape); 13 } 14} 15 16function drawShape(shape) { 17 if (shape.type === "circle") { 18 ctx.beginPath(); 19 ctx.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); 20 ctx.fill(); 21 } else if (shape.type === "rectangle") { 22 ctx.fillRect(shape.x, shape.y, shape.width, shape.height); 23 } 24}
-
通过以上方法,可以在canvas
中实现较为复杂的事件交互处理,为用户提供丰富的交互体验。
1046. 弱网检测该如何做【热度: 597】【网络、web应用场景】
关键词:网络状况检测
在 JavaScript 中,可以通过以下几种方式进行弱网检测:
一、监测网络连接状态
- 使用
navigator.onLine
属性:这个属性可以判断浏览器是否处于在线状态。当网络连接中断时,navigator.onLine
会变为false
;当网络连接恢复时,它会变为true
。- 示例代码:
1function checkNetworkStatus() {
2 if (navigator.onLine) {
3 console.log("网络连接正常");
4 } else {
5 console.log("网络连接中断");
6 }
7}
8
9window.addEventListener("online", checkNetworkStatus);
10window.addEventListener("offline", checkNetworkStatus);
- 使用
online
和offline
事件:可以监听online
和offline
事件来检测网络连接状态的变化。- 示例代码与上述类似,在事件处理函数中执行相应的操作。
二、测量网络延迟和带宽
- 使用
XMLHttpRequest
或fetch
进行请求:可以发送一个小的请求到服务器,测量请求的响应时间。如果响应时间较长,可能表示网络状况不佳。- 示例代码:
1function measureNetworkLatency() {
2 const startTime = performance.now();
3 fetch("small-test-file.txt")
4 .then(() => {
5 const endTime = performance.now();
6 const latency = endTime - startTime;
7 if (latency > 500) {
8 console.log("网络可能较慢");
9 } else {
10 console.log("网络状况良好");
11 }
12 })
13 .catch(() => {
14 console.log("网络连接问题");
15 });
16}
17
18measureNetworkLatency();
- 使用
Web Performance API
:可以使用window.performance
对象来获取页面加载和资源请求的性能数据,包括网络延迟和带宽信息。- 例如,可以通过
performance.getEntriesByType('navigation')[0].responseEnd - performance.getEntriesByType('navigation')[0].requestStart
来获取页面加载的总时间,包括网络延迟。
- 例如,可以通过
三、模拟弱网环境进行测试
-
使用浏览器开发者工具:现代浏览器的开发者工具通常提供了网络模拟功能,可以模拟不同的网络条件,如慢速 3G、2G 等,以测试应用在弱网环境下的表现。
-
使用第三方库:有一些专门的网络模拟库,如
Fiddler
、Charles Proxy
等,可以模拟各种网络状况,帮助进行弱网测试。
总之,弱网检测可以通过监测网络连接状态、测量网络延迟和带宽以及模拟弱网环境等方式来实现。根据具体的应用场景和需求,可以选择合适的方法来检测网络状况,并采取相应的措施来优化应用在弱网环境下的性能和用户体验。
1047. Performance API 主要有哪些应用场景【热度: 431】【网络、web应用场景】
关键词:Performance API 应用
Performance API 在前端开发中有很多应用场景,以下是一些主要的方面:
一、性能监测与优化
-
测量页面加载时间:
- 通过
performance.timing
可以获取页面加载过程中的各个关键时间点,如navigationStart
(导航开始时间)、domLoading
(开始解析 DOM 的时间)、domInteractive
(DOM 准备就绪时间)、domContentLoadedEventEnd
(DOMContentLoaded
事件结束时间)、loadEventEnd
(页面完全加载时间)等。计算这些时间点之间的差值可以得出不同阶段的加载时间,帮助开发者了解页面加载的性能瓶颈并进行优化。 - 例如,可以计算从导航开始到页面完全加载的时间,以评估整体加载性能。
1const timing = performance.timing; 2const loadTime = timing.loadEventEnd - timing.navigationStart; 3console.log(`Page load time: ${loadTime} milliseconds.`);
- 通过
-
测量资源加载时间:
- 使用
performance.getEntriesByType('resource')
可以获取所有资源(如脚本、样式表、图片等)的加载性能信息。可以分析每个资源的加载时间、发起请求的时间、响应时间等,以便优化资源的加载策略。 - 例如,可以找出加载时间较长的资源并考虑优化其大小、压缩方式或加载时机。
1const resources = performance.getEntriesByType("resource"); 2for (const resource of resources) { 3 console.log(`Resource ${resource.name} took ${resource.responseEnd - resource.startTime} milliseconds to load.`); 4}
- 使用
-
监测网络请求耗时:
- 可以结合
fetch
或XMLHttpRequest
与 Performance API 来测量特定网络请求的耗时。在请求发送前记录时间戳,在请求完成后再次记录时间戳并计算差值,同时可以利用 Performance API 的其他信息来进一步分析请求性能。 - 例如,可以统计某个 API 请求的耗时并与其他指标一起分析网络性能对应用的影响。
1const startTime = performance.now(); 2fetch("your-api-url") 3 .then((response) => { 4 const endTime = performance.now(); 5 const duration = endTime - startTime; 6 console.log(`API request took ${duration} milliseconds.`); 7 return response; 8 }) 9 .catch((error) => { 10 console.error(`Error fetching API: ${error}`); 11 });
- 可以结合
二、用户体验优化
-
检测页面交互响应时间:
- 通过记录用户操作(如点击按钮、滚动页面等)的时间戳和相应的响应事件(如按钮点击后的处理完成时间、滚动事件触发后的页面更新时间等),可以测量用户交互的响应时间。这有助于确保应用在用户操作后能够及时做出反应,提高用户体验。
- 例如,可以在用户点击一个按钮后记录开始时间,在按钮对应的操作完成后记录结束时间,计算响应时间并进行优化。
1document.getElementById("your-button").addEventListener("click", () => { 2 const startTime = performance.now(); 3 // 执行按钮对应的操作 4 //... 5 const endTime = performance.now(); 6 const responseTime = endTime - startTime; 7 console.log(`Button click response time: ${responseTime} milliseconds.`); 8});
-
分析页面卡顿和流畅度:
- Performance API 中的
performance.now()
可以提供高精度的时间戳,通过在一定时间间隔内记录时间戳并分析时间差,可以检测页面是否出现卡顿。如果连续的时间差较大,可能表示页面出现了卡顿现象。此外,还可以结合浏览器的requestAnimationFrame
来确保动画和交互的流畅性,通过在每一帧中执行特定的操作并测量时间,可以判断页面的流畅度是否符合预期。 - 例如,可以在动画循环中记录每一帧的时间戳,分析帧与帧之间的时间间隔是否稳定,以检测动画的流畅度。
1let lastFrameTime = performance.now(); 2function animate() { 3 const currentTime = performance.now(); 4 const deltaTime = currentTime - lastFrameTime; 5 if (deltaTime > 100) { 6 console.log("Possible卡顿 occurred."); 7 } 8 lastFrameTime = currentTime; 9 requestAnimationFrame(animate); 10} 11requestAnimationFrame(animate);
- Performance API 中的
三、性能分析工具开发
-
构建自定义性能分析工具:
- 利用 Performance API 提供的数据,可以开发自定义的性能分析工具,用于特定项目或团队的需求。这些工具可以收集和展示各种性能指标,提供详细的报告和分析,帮助开发者更好地理解应用的性能状况并进行针对性的优化。
- 例如,可以开发一个插件或工具,集成到开发环境中,实时监测页面性能并提供可视化的报告,包括加载时间、资源使用情况、网络请求耗时等。
1class PerformanceAnalyzer { 2 constructor() { 3 this.measurements = []; 4 } 5 startMeasurement() { 6 this.startTime = performance.now(); 7 } 8 endMeasurement(label) { 9 const endTime = performance.now(); 10 const duration = endTime - this.startTime; 11 this.measurements.push({ label, duration }); 12 } 13 generateReport() { 14 console.log("Performance Report:"); 15 for (const measurement of this.measurements) { 16 console.log(`${measurement.label}: ${measurement.duration} milliseconds.`); 17 } 18 } 19} 20const analyzer = new PerformanceAnalyzer(); 21analyzer.startMeasurement(); 22// 执行一些操作 23//... 24analyzer.endMeasurement("Operation 1"); 25analyzer.generateReport();
-
与其他性能工具集成:
- Performance API 的数据可以与其他性能分析工具(如 Lighthouse、WebPageTest 等)结合使用,提供更全面的性能分析。可以将 Performance API 收集的数据作为输入,与这些工具的报告进行对比和分析,以获得更深入的性能洞察。
- 例如,可以在使用 Lighthouse 进行性能测试的同时,利用 Performance API 记录特定操作的耗时,以便更细致地分析应用在不同方面的性能表现。
1048. 统计前端请求耗时【热度: 609】【网络、web应用场景】
关键词:请求耗时统计
在前端业务中,可以通过以下几种方法统计请求耗时:
一、使用fetch
结合时间戳
- 在发送请求前记录当前时间戳:
const startTime = performance.now();
- 使用
fetch
发送请求:fetch('your-api-url')
- 在请求的
.then()
或.catch()
中记录结束时间戳并计算耗时:
1.then(response => {
2 const endTime = performance.now();
3 const duration = endTime - startTime;
4 console.log(`Request took ${duration} milliseconds.`);
5 return response;
6 })
7.catch(error => {
8 const endTime = performance.now();
9 const duration = endTime - startTime;
10 console.log(`Request took ${duration} milliseconds with error: ${error}`);
11 });
二、使用XMLHttpRequest
结合时间戳
- 创建
XMLHttpRequest
对象并记录开始时间:
1const xhr = new XMLHttpRequest();
2const startTime = performance.now();
- 配置请求并发送:
xhr.open('GET', 'your-api-url'); xhr.send();
- 在请求的
onload
、onerror
等事件处理函数中记录结束时间并计算耗时:
1xhr.onload = function () {
2 const endTime = performance.now();
3 const duration = endTime - startTime;
4 console.log(`Request took ${duration} milliseconds.`);
5};
6xhr.onerror = function () {
7 const endTime = performance.now();
8 const duration = endTime - startTime;
9 console.log(`Request took ${duration} milliseconds with error.`);
10};
三、利用拦截器(axios
)
- 如果使用
axios
或类似的库,可以设置请求拦截器和响应拦截器:
- 在请求拦截器中记录开始时间,在响应拦截器中记录结束时间并计算耗时。
1axios.interceptors.request.use((config) => {
2 config.startTime = performance.now();
3 return config;
4});
5axios.interceptors.response.use(
6 (response) => {
7 const endTime = performance.now();
8 const duration = endTime - response.config.startTime;
9 console.log(`Request to ${response.config.url} took ${duration} milliseconds.`);
10 return response;
11 },
12 (error) => {
13 const endTime = performance.now();
14 const duration = endTime - error.config.startTime;
15 console.log(`Request to ${error.config.url} took ${duration} milliseconds with error.`);
16 return Promise.reject(error);
17 }
18);
总结
上面都属于一些初级手段,因为还是在浏览器进程里面, 一旦出现长任务阻塞了浏览器, 这个统计就不太准确了。
Performance API 可以用来统计请求耗时。
Performance API 提供了一系列的性能测量工具,可以测量网页加载和运行过程中的各种性能指标。其中,可以通过以下方式来统计网络请求的耗时:
-
使用
performance.timing
:performance.timing
对象包含了网页加载过程中的各个时间点信息。可以通过计算不同时间点之间的差值来得到特定阶段的耗时。- 例如,可以计算
responseEnd
(服务器响应结束的时间)和requestStart
(开始请求的时间)之间的差值来得到请求的耗时。
-
使用
performance.getEntriesByType('resource')
:- 这个方法可以获取所有资源加载的性能条目。对于每个资源条目,可以获取其
startTime
(开始时间)和responseEnd
(响应结束时间)等属性,从而计算出资源加载的耗时。 - 可以遍历这些条目,找到特定的网络请求资源,并计算其耗时。
- 这个方法可以获取所有资源加载的性能条目。对于每个资源条目,可以获取其
以下是一个示例代码:
1// 计算页面加载过程中第一个请求的耗时
2const timing = performance.timing;
3const requestDuration = timing.responseEnd - timing.requestStart;
4console.log(`First request took ${requestDuration} milliseconds.`);
5
6// 遍历所有资源加载条目,找到特定请求并计算耗时
7const resources = performance.getEntriesByType("resource");
8for (const resource of resources) {
9 if (resource.name === "https://example.com/specific-resource") {
10 const resourceDuration = resource.responseEnd - resource.startTime;
11 console.log(`Specific resource request took ${resourceDuration} milliseconds.`);
12 break;
13 }
14}
Web Worker 可以用于统计请求耗时。
以下是一种使用 Web Worker 统计请求耗时的方法:
- 创建一个 Web Worker 文件,例如
worker.js
:
1self.onmessage = function (event) {
2 const url = event.data.url;
3 const startTime = performance.now();
4 fetch(url)
5 .then((response) => {
6 const endTime = performance.now();
7 const duration = endTime - startTime;
8 self.postMessage({ duration });
9 })
10 .catch((error) => {
11 self.postMessage({ error: `Error fetching ${url}: ${error}` });
12 });
13};
- 在主页面中使用 Web Worker:
1const worker = new Worker("worker.js");
2const url = "your-api-url";
3worker.postMessage({ url });
4worker.onmessage = function (event) {
5 if (event.data.duration) {
6 console.log(`Request to ${url} took ${event.data.duration} milliseconds.`);
7 } else {
8 console.error(event.data.error);
9 }
10};
在这个例子中,Web Worker 负责发送请求并计算耗时,然后将结果发送回主页面。这样可以在不阻塞主页面 UI 线程的情况下进行请求耗时统计。
1055. 排查谁在修改对象【热度: 500】【web应用场景】
这是一个话题性值得问题:
例如:redux 申请了一份数据 store , 整个应用有特别多的地方在修改这个 store , 现在数据出现异常, 如何排查是哪儿场景修改的这份 store 导致的异常;
不仅仅是针对 redux , 我们面临可能是全局对象等场景,对被多个调用方修改的现象。
这个没有一个标准答案, 可以咨询去探索。
作者给几个意见:
-
首先不允许直接修改对象, 必须要经过一个封装函数去修改, 修改的时候, 必须要传递
actionUser
信息, 这个actionUser
表示调用方是谁; -
怎么约束这个事儿呢?就比如我封装了一个函数去修改全局对象,但是就是有人要手动去改这个全局对象。 那么处理方式就是将这个全局对象冻结, 或者 proxy 劫持。 例如 formily 表单里面的 value 就是 proxy 劫持的, 只能允许用户去通过 onChange 修改。
-
那如何保证调用方就一定会传递
actionUser
信息呢;因为是一个函数, 所以要是调用方不传递这个会咋样; 解决办法最强硬的就是, 如果不传递actionUser
就直接不执行, 同时throw error
;稍微温和一点儿就是写一个 eslint 插件, 给予 error 提示;最温和的方式, 就是用 TS interface 去约束入参;