共计 34171 个字符,预计需要花费 86 分钟才能阅读完成。
2023.03.14 – 2023.03.24 更新前端面试问题总结(45 道题)– 上部
获取更多面试问题能够拜访
github 地址: https://github.com/pro-collection/interview-question/issues
gitee 地址: https://gitee.com/yanleweb/interview-question/issues
目录:
-
高级开发者相干问题【共计 1 道题】
- 111.null 和 undefined 的区别,如何让一个属性变为 null?【JavaScript】
-
中级开发者相干问题【共计 20 道题】
- 73.express middleware(中间件) 工作原理是什么??【Nodejs】
- 104. 说一说 cookie sessionStorage localStorage 区别?【JavaScript】
- 105.promise.race、promise.all、promise.allSettled 有哪些区别?【JavaScript】
- 106. 手写代码实现 promise.race【JavaScript】
- 109.JavaScript 有几种办法判断变量的类型?【JavaScript】
- 110. 款式优先级的规定是什么?【CSS】
- 115.Proxy 和 Object.defineProperty 的区别是啥?【JavaScript】
- 117.css 中 三栏布局的实现计划 的实现计划有哪些?【CSS】
- 119.vue 的 keep-alive 的原理是啥?【web 框架】
- 125. 当应用 new 关键字创建对象时, 会经验哪些步骤?【JavaScript】
- 126.es5 和 es6 应用 new 关键字实例化对象的流程是一样的吗?【JavaScript】
- 127. 如何实现可过期的 localstorage 数据?【JavaScript】
- 132.React setState 是同步还是异步的?【web 框架】
- 133.react 18 版本中 setState 是同步还是异步的?【web 框架】
- 134.【React】合成事件理解多少【web 框架】
- 135.【React】绑定事件的原理是什么?【web 框架】
- 139.pnpm 和 npm 的区别?【工程化】
- 142. 事件循环原理?【JavaScript】
- 143.[vue] 双向数据绑定原理?【web 框架】
- 146.nodejs 过程间如何通信?【Nodejs】
-
高级开发者相干问题【共计 22 道题】
- 77. 虚构 dom 原理是啥,手写一个简略的虚构 dom 实现?【JavaScript】
- 107. 手写代码实现 promise.all【JavaScript】
- 108. 手写实现 Promise.allSettled【JavaScript】
- 112.CSS 尺寸单位有哪些?【CSS】
- 113.React Router 中 HashRouter 和 BrowserRouter 的区别和原理?【web 框架】
- 114.Vue3.0 实现数据双向绑定的办法是什么?【web 框架】
- 118. 浏览器垃圾回收机制?【浏览器】
- 120. 常见的 web 前端网路攻打有哪些?【网络】
- 121. 如何避免 跨站脚本攻打(Cross-Site Scripting, XSS)?【网络】
- 122. 跨站申请伪造(Cross-Site Request Forgery, CSRF)具体实现步骤是啥,如何避免?【网络】
- 123.script 标签 defer 和 async 区别?【浏览器】
- 124.Vue 中 $nextTick 作用与原理是啥?【web 框架】
- 128.axios 的拦截器原理及利用、简略手写外围逻辑?【web 框架】
- 129. 有什么办法能够放弃前后端实时通信?【网络】
- 130.react 遍历渲染节点列表,为什么要加 key ?【web 框架】
- 131.react lazy import 实现懒加载的原理是什么?【web 框架】
- 136. 如何剖析页面加载慢?【工程化】
- 137.【性能】以用户为核心的前端性能指标有哪些?【工程化】
- 138. 浏览器渲染过程理解多少?【浏览器】
- 140.pnpm 理解多少?【工程化】
- 141. 如何组织 monorepo 工程?【工程化】
- 144.[vue] 是怎么解析 template 的?【web 框架】
-
资深开发者相干问题【共计 2 道题】
- 116.React Diff 算法是怎么实现的?【JavaScript】
- 145. 实现 JS 沙盒的形式有哪些?【工程化】
高级开发者相干问题【共计 1 道题】
111.null 和 undefined 的区别,如何让一个属性变为 null?【JavaScript】
null
和 undefined
都是 JavaScript 中示意缺失或无值的非凡值。
undefined
是一个变量没有被赋值时的默认值,或者在拜访对象属性或数组元素不存在时返回的值。例如:
let a;
console.log(a); // 输入 undefined
const obj = {};
console.log(obj.nonexistentProp); // 输入 undefined
而 null
示意一个变量被明确地赋值为没有值。例如:
const a = null;
console.log(a); // 输入 null
要将一个属性的值设置为null
,能够像这样:
const obj = {prop: 'value'};
obj.prop = null;
console.log(obj.prop); // 输入 null
如果要删除对象的属性并将其值设置为 null
,能够应用delete
操作符:
const obj = {prop: 'value'};
delete obj.prop;
obj.prop = null;
console.log(obj.prop); // 输入 null
请留神,尝试拜访一个已删除的属性将返回 undefined
而不是null
。
中级开发者相干问题【共计 20 道题】
73.express middleware(中间件) 工作原理是什么??【Nodejs】
express middleware 工作原理是什么?
Express middleware 的工作原理是通过拦挡 HTTP 申请,对申请进行解决,而后将申请传递给下一个中间件或应用程序的路由解决。在 Express 中,中间件能够是一个或多个函数,每个函数都能够对申请进行操作或响应,从而实现对申请的解决和过滤。
当 Express 应用程序接管到一个 HTTP 申请时,申请将首先被传递给第一个注册的中间件函数。这个中间件函数能够对申请进行操作,例如批改申请的头信息、查看申请是否蕴含无效的身份验证令牌等等。当这个中间件函数实现操作后,它能够抉择将申请传递给下一个中间件函数,或者间接将响应返回给客户端。
如果中间件函数抉择将申请传递给下一个中间件函数,它能够调用 next() 函数来将控制权传递给下一个中间件。这个过程能够始终继续到所有中间件函数都被执行结束,最初将申请传递给应用程序的路由处理函数。
通过应用中间件,开发人员能够将应用程序的性能模块化,从而实现更好的代码组织和可维护性。同时,中间件还能够实现各种性能,例如身份验证、日志记录、错误处理等等,从而为应用程序提供更丰盛的性能和更好的用户体验。
它的设计模式是啥?写一个简略的示例呢
Express middleware 的设计模式是基于责任链模式。在责任链模式中,每个对象都有机会解决申请,并将其传递给下一个对象,直到申请被齐全解决为止。在 Express 中,每个中间件函数都有机会对申请进行解决,并能够抉择将申请传递给下一个中间件函数或应用程序的路由处理函数。
以下是一个简略的示例,演示如何应用 Express middleware 实现身份验证:
const express = require('express');
const app = express();
// 定义一个中间件函数,用于验证用户身份
function authenticate(req, res, next) {
const token = req.headers.authorization;
if (token === 'secret-token') {
// 如果令牌无效,则将控制权传递给下一个中间件函数
next();} else {
// 否则,返回 401 谬误
res.status(401).send('Unauthorized');
}
}
// 注册中间件函数,用于验证用户身份
app.use(authenticate);
// 定义一个路由处理函数,用于返回受爱护的资源
app.get('/protected', (req, res) => {res.send('Protected resource');
});
// 启动应用程序
app.listen(3000, () => {console.log('Server is running on port 3000');
});
在下面的示例中,咱们定义了一个名为 authenticate 的中间件函数,它用于验证用户的身份。在这个函数中,咱们查看申请头中是否蕴含无效的身份验证令牌。如果令牌无效,则将控制权传递给下一个中间件函数或路由处理函数。否则,返回 401 谬误。
而后,咱们通过调用 app.use() 办法来注册这个中间件函数,以便在每个申请中都进行身份验证。最初,咱们定义一个名为 /protected 的路由处理函数,它示意受爱护的资源。只有在身份验证通过后,能力拜访这个路由处理函数。
通过这个简略的示例,咱们能够看到如何应用 Express middleware 实现根本的身份验证性能。中间件函数充当责任链中的一个环节,通过对申请进行解决和过滤,为应用程序提供更好的安全性和用户体验。
104. 说一说 cookie sessionStorage localStorage 区别?【JavaScript】
cookie、sessionStorage 和 localStorage 都是存储在浏览器端的客户端存储形式,用于存储一些客户端数据。
它们之间的区别如下:
- 生命周期
cookie 的生命周期由 Expires 和 Max-Age 两个属性管制。当设置了 Expires 属性时,cookie 的生命周期为设置的过期工夫;当设置了 Max-Age 属性时,cookie 的生命周期为设置的秒数。cookie 在浏览器敞开时也会过期。而 sessionStorage 和 localStorage 的生命周期则与浏览器窗口相干,当窗口被敞开时,sessionStorage 数据也会被清空,而 localStorage 数据则会始终存在,直到用户手动删除。
- 存储容量
cookie 的存储容量限度为 4KB,而 sessionStorage 和 localStorage 的存储容量则较大,能够达到 5MB 或更高。
- 数据共享
cookie 能够被所有同源窗口(指协定、域名、端口雷同)拜访,而 sessionStorage 和 localStorage 只能被创立它们的窗口拜访。
- 传输方式
cookie 会随着 http 申请发送到服务器,而 sessionStorage 和 localStorage 不会发送到服务器,只存在于浏览器端。
- 数据类型
cookie 只能存储字符串类型的数据,而 sessionStorage 和 localStorage 能够存储除了对象以外的数据类型,如数字、布尔值、数组、甚至是其余简单的数据结构。然而,它们都能够通过 JSON.stringify 和 JSON.parse 办法将数据转化为字符串进行存储和读取。
综上所述,这三种存储形式都有各自的优缺点和实用场景。在理论利用中,咱们须要依据理论状况抉择适合的存储形式。
105.promise.race、promise.all、promise.allSettled 有哪些区别?【JavaScript】
Promise.race()
、Promise.all()
、Promise.allSettled()
都是 JavaScript 中的 Promise 相干 API,它们的区别如下:
Promise.race()
Promise.race()
接管一个蕴含多个 Promise 的数组作为参数,返回一个新的 Promise。该 Promise 将会在数组中的任意一个 Promise 状态变为 fulfilled
或 rejected
时被解决,且以第一个解决的 Promise 的后果作为其后果返回。
如果数组中所有 Promise 都被回绝,则返回的 Promise 将会以最先被回绝的 Promise 的起因作为其起因回绝。
Promise.all()
Promise.all()
接管一个蕴含多个 Promise 的数组作为参数,返回一个新的 Promise。该 Promise 将会在数组中所有 Promise 状态均为 fulfilled
时被解决,并且以数组模式返回所有 Promise 的后果。
如果数组中有任何一个 Promise 被回绝,则返回的 Promise 将会以最先被回绝的 Promise 的起因作为其起因回绝。
Promise.allSettled()
Promise.allSettled()
接管一个蕴含多个 Promise 的数组作为参数,返回一个新的 Promise。该 Promise 将会在数组中所有 Promise 状态都被解决时被解决,并且以数组模式返回所有 Promise 的后果。和 Promise.all()
不同,Promise.allSettled()
不会在有 Promise 被回绝时回绝该 Promise。
返回的 Promise 的数组中的每个元素都是一个对象,该对象示意原始 Promise 的后果。每个对象都有一个 status
属性,示意原始 Promise 的状态,其值为字符串 'fulfilled'
或 'rejected'
。如果 Promise 被解决,对象还会蕴含一个 value
属性,示意 Promise 的解决值。如果 Promise 被回绝,对象还会蕴含一个 reason
属性,示意 Promise 的回绝起因。
综上所述,Promise.race()
、Promise.all()
和 Promise.allSettled()
的次要区别在于它们对多个 Promise 的状态解决形式不同,以及返回的 Promise 所蕴含的数据类型和构造不同。
106. 手写代码实现 promise.race【JavaScript】
上面是手写实现 Promise.race()
办法的代码:
Promise.race = function (promises) {return new Promise((resolve, reject) => {promises.forEach((promise) => {Promise.resolve(promise).then(resolve, reject);
});
});
};
实现原理:
Promise.race()
办法接管一个蕴含多个 Promise 的数组作为参数,并返回一个新的 Promise。该 Promise 将会在数组中的任意一个 Promise 状态变为 fulfilled
或 rejected
时被解决,且以第一个解决的 Promise 的后果作为其后果返回。
咱们能够通过创立一个新的 Promise,而后遍历 Promise 数组并将每个 Promise 包装在一个 Promise.resolve()
中,而后应用 .then()
办法将它们的解决值和回绝起因别离传递给新的 Promise 的 resolve()
和 reject()
办法。因为 Promise 的状态只能扭转一次,所以一旦第一个 Promise 被解决,新的 Promise 的状态也将被解决,并且以第一个解决的 Promise 的后果作为其后果返回。
109.JavaScript 有几种办法判断变量的类型?【JavaScript】
JavaScript 中有以下几种办法能够判断变量的类型
- typeof 运算符:能够用于判断根本数据类型(如字符串、数字、布尔值、Undefined 等)和函数类型,但对于对象类型(如数组、日期、正则表达式等)不能精确判断。
- instanceof 运算符:能够用于判断一个对象是否为某个构造函数的实例,但不能判断根本数据类型。
- Object.prototype.toString() 办法:能够返回一个对象的具体类型字符串,能够判断所有数据类型,但须要留神的是须要应用 call 或 apply 办法将要判断的对象传递给 toString 办法。
- Array.isArray() 办法:能够判断一个对象是否为数组类型。
- constructor 属性:能够返回一个对象的构造函数,但须要留神的是 constructor 属性能够被批改,因而不能保障准确性。
举例 Object.prototype.toString() 是如何判断 js 类型的
Object.prototype.toString() 办法是用来返回以后对象的类型字符串,其实现形式是返回一个相似 “[object Type]” 的字符串,其中 Type 是以后对象的类型。
Object.prototype.toString.call("hello") // "[object String]"
Object.prototype.toString.call(123) // "[object Number]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(null) // "[object Null]"
var arr = [1, 2, 3];
Object.prototype.toString.call(arr) // "[object Array]"
var date = new Date();
Object.prototype.toString.call(date) // "[object Date]"
var reg = /abc/;
Object.prototype.toString.call(reg) // "[object RegExp]"
var func = function() {};
Object.prototype.toString.call(func) // "[object Function]"
通过这种形式,能够准确判断变量的类型,包含根本数据类型和对象类型。
110. 款式优先级的规定是什么?【CSS】
在 CSS 中,当多个选择器利用于同一个元素并设置了雷同的属性时,就会呈现款式抵触的问题。此时,CSS 会依据肯定的规定来决定哪个款式具备更高的优先级,从而确定最终的款式成果。CSS 款式优先级的规定如下:
- !important:具备最高优先级,用于强制笼罩其它款式。
- 内联款式:间接在 HTML 元素的 style 属性中定义的款式,其优先级高于前面提到的其它选择器。
- ID 选择器:通过 #id 定义的款式,其优先级高于前面提到的 class 选择器和标签选择器。
- 类选择器、属性选择器、伪类选择器:通过 .class、[attribute. 或 :pseudo 定义的款式,其优先级高于前面提到的标签选择器。
- 标签选择器、伪元素选择器:通过 tagname 或 ::pseudo 定义的款式,优先级最低。
须要留神的是,当呈现多个选择器具备雷同的优先级时,CSS 会依照样式表中呈现的程序来决定款式的优先级,越后呈现的款式会笼罩后面呈现的款式。此外,继承自父元素的款式的优先级比上述任何选择器都低。
115.Proxy 和 Object.defineProperty 的区别是啥?【JavaScript】
Proxy
和 Object.defineProperty
是 JavaScript 中两个不同的个性,它们的作用也不完全相同。
Object.defineProperty
容许你在一个对象上定义一个新属性或者批改一个已有属性。通过这个办法你能够准确地定义属性的特色,比方它是否可写、可枚举、可配置等。该办法的应用场景通常是须要在一个对象上创立一个属性,而后管制这个属性的行为。
Proxy
也能够用来代理一个对象,然而相比于 Object.defineProperty
,它提供了更加弱小的性能。应用 Proxy
能够截获并重定义对象的基本操作,比方拜访属性、赋值、函数调用等等。在这些操作被执行之前,能够通过拦截器函数对这些操作进行拦挡和批改。因而,通过 Proxy
,你能够齐全重写一个对象的默认行为。该办法的应用场景通常是须要对一个对象的行为进行定制化,或者须要在对象上增加额定的性能。
总结来说,Object.defineProperty
是用来定义对象的属性,而 Proxy
则是用来代理对象并对其进行操作拦挡和批改。两者的利用场景不同,但都能够用来对对象的行为进行定制化。
以下是 Proxy
和 Object.defineProperty
的一些具体利用场景的不同:
Object.defineProperty
实用于须要准确地管制对象属性行为的场景,比方管制属性是否可写、可枚举、可配置等。它的利用场景包含但不限于:
- 对象属性拜访权限管制;
- 对象属性计算;
- 对象属性懒加载。
Proxy
实用于须要代理对象并对其进行操作拦挡和批改的场景。它的利用场景包含但不限于:
- 对象属性访问控制;
- 对象属性批改管制;
- 对象属性缓存;
- 对象属性计算;
- 对象属性监听;
- 对象属性校验;
- 对象属性劫持等。
总的来说,Object.defineProperty
次要用于单个对象属性的管制和批改,而 Proxy
则实用于对整个对象或对象的多个属性进行管制和批改。因为 Proxy
的性能更加弱小,它在一些高级利用场景中比 Object.defineProperty
更加实用。然而在一些简略场景下,应用 Object.defineProperty
可能更加不便和实用。
117.css 中 三栏布局的实现计划 的实现计划有哪些?【CSS】
三栏布局指的是在一个页面中,将内容分为左、中、右三栏,并使它们具备相应的宽度和高度。实现三栏布局有多种计划,以下是其中的几种:
- 应用浮动
cssCopy code.left {
float: left;
width: 200px;
height: 100%;
background-color: #ccc;
}
.right {
float: right;
width: 200px;
height: 100%;
background-color: #ccc;
}
.middle {
margin: 0 200px;
height: 100%;
background-color: #eee;
}
在这个计划中,左栏和右栏应用浮动来实现,两头栏应用 margin
来占据残余的宽度。
- 应用相对定位
cssCopy code.container {
position: relative;
height: 100%;
}
.left {
position: absolute;
left: 0;
width: 200px;
height: 100%;
background-color: #ccc;
}
.right {
position: absolute;
right: 0;
width: 200px;
height: 100%;
background-color: #ccc;
}
.middle {
position: absolute;
left: 200px;
right: 200px;
height: 100%;
background-color: #eee;
}
在这个计划中,左栏和右栏应用相对定位来实现,两头栏应用左右 padding
来占据残余的宽度。
- 应用 Flexbox 布局
cssCopy code.container {
display: flex;
height: 100%;
}
.left {
width: 200px;
height: 100%;
background-color: #ccc;
}
.right {
width: 200px;
height: 100%;
background-color: #ccc;
}
.middle {
flex: 1;
height: 100%;
background-color: #eee;
}
在这个计划中,父容器应用 Flexbox 布局,左、中、右三栏都是 Flex 项,两头栏应用 flex: 1
来占据残余的宽度。
这些计划都能够实现三栏布局,但每种计划都有本人的优缺点。在抉择计划时,须要思考浏览器兼容性、性能、可维护性和可扩展性等因素。
119.vue 的 keep-alive 的原理是啥?【web 框架】
<keep-alive>
是 Vue.js 提供的一个形象组件,它能够使被蕴含的组件保留在内存中,而不是每次从新渲染的时候销毁并重建,从而进步了利用的性能。
具体来说,<keep-alive>
的实现原理如下:
- 当一个组件被包裹在
<keep-alive>
组件内时,它会被缓存起来,而不是被销毁。 - 如果这个组件被包裹的父组件从它的视图中移除,那么这个组件不会被销毁,而是被缓存起来。
- 如果这个组件再次被包裹的父组件增加回视图中,那么它会被从新激活,而不是从新创立。
<keep-alive>
组件通过一个外部的缓存对象来缓存组件实例,这个缓存对象会在组件被包裹在 <keep-alive>
组件中时创立。当一个被缓存的组件须要被激活时,<keep-alive>
组件会从缓存中取出该组件的实例并将其挂载到视图上,从而实现了组件的复用。
须要留神的是,被缓存的组件并不是始终存在于内存中,它们会在肯定条件下被销毁,比方缓存的组件数量超过了肯定的阈值,或者零碎内存占用过低等。
125. 当应用 new 关键字创建对象时, 会经验哪些步骤?【JavaScript】
在 JavaScript 中,new
关键字用于创立一个对象实例。当应用 new
关键字创建对象时,会产生以下几个步骤:
- 创立一个空的对象。
- 将这个空对象的
[[Prototype]]
属性设置为构造函数的prototype
属性。 - 将这个空对象赋值给构造函数外部的
this
关键字,用于初始化属性和办法。 - 如果构造函数返回一个对象,那么返回这个对象;否则,返回第一步创立的对象实例。
以下是一个示例,演示如何应用 new
关键字创立一个对象实例:
function Person(name, age) {
this.name = name;
this.age = age;
}
const person = new Person("John", 30);
console.log(person.name); // "John"
console.log(person.age); // 30
在下面的示例中,new Person("John", 30)
会创立一个新的对象实例。在构造函数 Person
中,this.name
和 this.age
会被赋值为 "John"
和 30
。最终,new
关键字会返回这个新的对象实例。
须要留神的是,在 JavaScript 中,函数也是对象。因而,咱们能够向对象一样定义属性和办法。当咱们应用 new
关键字调用一个函数时,这个函数会被视为构造函数,从而创立一个新的对象实例。
126.es5 和 es6 应用 new 关键字实例化对象的流程是一样的吗?【JavaScript】
ES5 和 ES6 应用 new
关键字实例化对象的流程基本上是一样的,只是在细节上存在一些差别。
在 ES5 中,当应用 new
关键字调用一个函数时,会创立一个新的对象,并将这个新对象的 [[Prototype]]
属性指向构造函数的 prototype
属性。此外,new
关键字还会将构造函数外部的 this
关键字绑定到新创建的对象上,从而容许咱们在构造函数外部增加属性和办法。
在 ES6 中,这些根本的流程也是雷同的。然而,ES6 引入了类(class)的概念,从而为面向对象编程提供了更加便当的语法。应用类定义一个对象时,须要应用 constructor
办法作为构造函数,而不是一般的函数。类定义的语法糖实际上是对函数的封装,应用 new
关键字创立类的实例时,实际上也是在调用类的 constructor
办法。
在 ES6 中,能够应用类的继承来创立更简单的对象。当应用 new
关键字创立一个继承自另一个类的类的实例时,会先调用父类的 constructor
办法,再调用子类的 constructor
办法,从而实现对象实例的创立过程。
须要留神的是,尽管 ES6 的类看起来像是其余面向对象语言中的类,但在 JavaScript 中,类依然是基于原型继承的。在创立一个类的实例时,实际上是在创立一个新对象,并将这个新对象的原型指向类的原型。因而,实例化对象的流程与应用一般函数或类定义的对象的流程基本上是雷同的。
127. 如何实现可过期的 localstorage 数据?【JavaScript】
要实现可过期的 localStorage
数据,能够联合应用 localStorage
和 Date
对象。
首先,在存储数据时,须要将数据和过期工夫一起存储在 localStorage
中。能够应用 JSON 格局来将数据和过期工夫打包存储。代码示例如下:
function setWithExpiry(key, value, ttl) {const now = new Date()
const item = {
value: value,
expiry: now.getTime() + ttl}
localStorage.setItem(key, JSON.stringify(item))
}
在下面的代码中,setWithExpiry
函数承受三个参数:key
示意存储数据的键名,value
示意要存储的数据,ttl
示意数据的过期工夫(单位为毫秒)。在函数外部,咱们首先获取以后工夫 now
,而后将过期工夫 expiry
计算为以后工夫加上过期工夫。最初,咱们将数据和过期工夫打包成一个对象,并应用 JSON.stringify
办法将其转换为字符串,存储在 localStorage
中。
接下来,咱们能够在获取数据时检查数据是否已过期。如果数据已过期,则须要将其从 localStorage
中删除并返回 null
。代码示例如下:
function getWithExpiry(key) {const item = localStorage.getItem(key)
if (!item) return null
const parsedItem = JSON.parse(item)
const now = new Date()
if (now.getTime() > parsedItem.expiry) {localStorage.removeItem(key)
return null
}
return parsedItem.value
}
在下面的代码中,getWithExpiry
函数承受一个参数 key
,示意要获取的数据的键名。在函数外部,咱们首先应用 localStorage.getItem
办法获取存储在 localStorage
中的数据,如果不存在则返回 null
。而后,咱们将存储的字符串数据解析为对象,并获取过期工夫。如果以后工夫曾经超过了过期工夫,则须要将数据从 localStorage
中删除并返回 null
。否则,咱们返回存储的数据。
通过联合应用 localStorage
和 Date
对象,咱们能够实现可过期的 localStorage
数据。这种办法简略易用,实用于须要在客户端存储数据,并且须要在肯定工夫后将其主动删除的场景。
132.React setState 是同步还是异步的?【web 框架】
在 React 中,setState
办法有时是异步的,有时是同步的,具体取决于应用形式和环境。
当咱们在 React 中调用 setState
办法时,React 会将新状态合并到组件的状态队列中,并在将来的某个工夫更新组件的状态。这个更新过程是异步的,即不能保障在 setState
调用后立刻更新状态,而是会将状态更新推延到下一个渲染周期中。这种状况下,setState
办法会返回一个 Promise
对象,但该对象不蕴含任何有用的信息。
然而,当 setState
办法被间接调用,而不是在事件处理函数、异步代码或生命周期函数中被调用时,更新过程是同步的。在这种状况下,React 会立刻更新组件的状态,并在更新后立刻执行一次从新渲染。这种状况下,setState
办法不会返回任何信息。
须要留神的是,在 React 中异步更新状态的机制能够进步性能和优化页面渲染速度,因为它能够让 React 在适当的时候批量更新组件状态,从而防止过多的渲染和浏览器的性能问题。如果咱们须要在更新状态后立刻执行某些操作,能够应用 setState
办法的回调函数或生命周期函数 componentDidUpdate
来实现。例如:
jsCopy codeclass MyComponent extends React.Component {constructor(props) {super(props);
this.state = {count: 0};
}
handleClick() {this.setState({ count: this.state.count + 1}, () => {console.log('New count:', this.state.count);
});
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.handleClick()}>Increment</button>
</div>
);
}
}
这里的 setState
办法承受一个回调函数作为第二个参数,在状态更新实现后调用该函数并传递更新后的状态作为参数。在这个回调函数中能够执行任何须要在状态更新后立刻执行的操作,例如输入调试信息、发送网络申请等。
133.react 18 版本中 setState 是同步还是异步的?【web 框架】
在 React 18 中,setState
的行为依然是异步的。React 团队并没有扭转 setState
的默认行为,因为这会带来一些潜在的性能问题和不确定性。异步更新状态能够让 React 在适当的时候批量更新组件状态,从而防止过多的渲染和浏览器的性能问题。
然而,React 18 引入了一个新的个性:批量更新(Batching)。当咱们在事件处理函数、异步代码或生命周期函数中调用 setState
时,React 会将多个状态更新合并到同一个批次中,从而缩小渲染的次数,进步利用的性能。这意味着,即便咱们在多个中央调用 setState
办法,React 也会将这些调用合并到同一个更新队列中,并在下一个渲染周期中一次性更新所有状态。
在 React 18 中,咱们能够应用新的 startTransition
API 来通知 React,咱们正在进行一次可中断的异步操作,并且心愿在操作实现后批量更新组件状态。这个 API 的用法如下:
jsCopy codeimport {startTransition} from 'react';
function handleClick() {startTransition(() => {setState({ count: count + 1});
// 执行其余异步操作
});
}
在这个例子中,咱们通过 startTransition
API 包装 setState
和其余异步操作,通知 React 咱们正在进行一次可中断的异步操作,并且心愿在操作实现后批量更新组件状态。这样做能够让咱们的利用更加晦涩和响应,并且能够进步用户体验。
须要留神的是,startTransition
API 并不是必须的,如果咱们不应用这个 API,React 依然会在适当的时候批量更新组件状态。这个 API 只是为了让咱们更加准确地管制更新的机会,并在必要时进行优化。
134.【React】合成事件理解多少【web 框架】
在 React 中,合成事件是一种封装了浏览器原生事件对象的高级事件机制。它是由 React 提供的一种用于处理事件的形象层,能够让开发者更不便地解决和治理事件。
React 的合成事件机制提供了一些优良的个性:
- 跨浏览器兼容性:React 的合成事件能够屏蔽浏览器的差别,保障在各种浏览器上运行统一。
- 性能优化:React 的合成事件能够对事件进行池化解决,重用事件对象,防止创立大量的事件对象,从而进步性能。
- 事件委托:React 的合成事件能够实现事件委托机制,将事件处理程序绑定在组件树的根节点上,对立治理和解决组件外部和内部的事件,从而防止屡次绑定事件处理程序的问题。
- 反对自定义事件:React 的合成事件能够反对自定义事件,开发者能够自定义组件事件,提供更多的自定义能力。
React 的合成事件机制通过事件冒泡和事件委托来实现。当在组件中触发事件时,React 会将该事件包装成一个合成事件对象,并在组件树中冒泡传递,直到根节点处。在组件树中,React 应用事件委托机制将事件处理程序绑定到根节点上,对立解决所有组件的事件。
在解决合成事件时,React 提供了一些罕用的事件处理函数,例如 onClick
、onMouseOver
、onSubmit
等,能够在组件中间接应用。此外,开发者还能够自定义事件处理函数,通过 on
前缀加上事件名称的形式来绑定自定义事件。例如,咱们能够定义一个 onCustomEvent
办法来解决自定义事件:
jsxCopy codeclass MyComponent extends React.Component {handleCustomEvent() {// 解决自定义事件}
render() {
return (
<div>
<button onClick={this.handleCustomEvent}> 触发自定义事件 </button>
</div>
);
}
}
在这个例子中,咱们定义了一个名为 handleCustomEvent
的办法来解决自定义事件,而后在组件中通过 onClick
属性来绑定该办法。当用户点击按钮时,React 会将该事件包装成一个合成事件对象,并调用 handleCustomEvent
办法来处理事件。
135.【React】绑定事件的原理是什么?【web 框架】
在 React 中,绑定事件的原理是基于合成事件(SyntheticEvent)的机制。合成事件是一种由 React 本人实现的事件零碎,它是对原生 DOM 事件的封装和优化,提供了一种对立的事件处理机制,能够跨浏览器保持一致的行为。
当咱们在 React 组件中应用 onClick
等事件处理函数时,实际上是在应用合成事件。React 应用一种称为“事件委托”的技术,在组件的最外层容器上注册事件监听器,而后依据事件的指标元素和事件类型来触发适合的事件处理函数。这种机制能够大大减少事件监听器的数量,进步事件处理的性能和效率。
在应用合成事件时,React 会将事件处理函数包装成一个合成事件对象(SyntheticEvent),并将其传递给事件处理函数。合成事件对象蕴含了与原生 DOM 事件雷同的属性和办法,例如 target
、currentTarget
、preventDefault()
等,然而它是由 React 实现的,并不是原生的 DOM 事件对象。因而,咱们不能在合成事件对象上调用 stopPropagation()
或 stopImmediatePropagation()
等办法,而应该应用 nativeEvent
属性来拜访原生 DOM 事件对象。
绑定事件的实现原理也波及到 React 的更新机制。当组件的状态或属性发生变化时,React 会对组件进行从新渲染,同时从新注册事件监听器。为了防止不必要的事件处理函数的创立和注册,React 会对事件处理函数进行缓存和复用,只有在事件处理函数发生变化时才会从新创立和注册新的事件处理函数。这种机制能够大大提高组件的性能和效率,尤其是在解决大量事件和频繁更新状态的状况下。
139.pnpm 和 npm 的区别?【工程化】
pnpm
和 npm
是两个不同的 JavaScript 包管理工具,它们有以下区别:
- 包的存储形式:
npm
将每个包都下载到我的项目的node_modules
目录中,而pnpm
会在全局装置一个存储库,并在我的项目中创立一个符号链接到该存储库中的每个包。 - 空间占用: 因为
pnpm
应用符号链接,它的空间占用通常比npm
小,因为它防止了在多个我的项目中反复存储雷同的依赖项。 - 装置速度: 因为
pnpm
在全局装置中共享依赖项,因而装置速度通常比npm
更快。 - 命令行接口:
pnpm
的命令行接口与npm
不同,但它们都提供了一组类似的命令来治理包。 - 兼容性: 因为
pnpm
的存储形式不同于npm
,因而某些与npm
兼容的工具可能无奈与pnpm
一起应用。
总的来说,pnpm
与 npm
相比具备更小的空间占用和更快的装置速度,但因为其不同的存储形式可能会导致与某些工具的不兼容。
142. 事件循环原理?【JavaScript】
通过一道题进入浏览器事件循环原理:
console.log('script start')
setTimeout(function () {console.log('setTimeout')
}, 0);
Promise.resolve().then(function () {console.log('promise1')
}).then(function () {console.log('promise2')
})
console.log('script end')
能够先试一下,手写出执行后果,而后看完这篇文章当前,在运行一下这段代码,看后果和预期是否一样
单线程
定义
单线程意味着所有的工作须要排队,前一个工作完结,才可能执行后一个工作。如果前一个工作耗时很长,前面一个工作不得不始终等着。
起因
javascript
的单线程,与它的用处无关。作为浏览器脚本语言,javascript
的主要用途是与用户互动,以及操作 DOM
。这决定了它只能是单线程,否则会带来很简单的同步问题。比方,假设javascript
同时有两个线程,一个在增加 DOM
节点,另外一个是删除 DOM
节点,那浏览器应该应该以哪个为准,如果在减少一个线程进行治理多个线程,尽管解决了问题,然而减少了复杂度,为什么不应用单线程呢,执行有个先后顺序,某个工夫只执行单个事件。
为了利用多核 CPU
的计算能力,HTML5
提出 Web Worker
规范,运行 javascript
创立多个线程,然而子线程齐全受主线程管制,且不得操作 DOM
。所以,这个规范并没有扭转javascript
单线程的实质
浏览器中的Event Loop
事件循环这个名字来源于它往往这么实现:
while(queue.waitForMessage()) {queue.processNextMessage();
}
这个模型的劣势在于它必须解决完一个音讯(run to completion), 才会解决下一个音讯, 使程序可追溯性更强。不像 C 语言可能随时从一个线程切换到另一个线程。然而毛病也在于此, 若同步代码阻塞则会影响用户交互
macroTask
和microTask
宏队列,macroTask
也叫 tasks
。蕴含同步工作,和一些异步工作的回调会顺次进入macro task queue
中,macroTask
蕴含:
- script 代码块
- setTimeout
- requestAnimationFrame
- I/O
- UI rendering
微队列, microtask
,也叫jobs
。另外一些异步工作的回调会顺次进入micro task queue
,期待后续被调用,这些异步工作蕴含:
- Promise.then
- MutationObserver
上面是 Event Loop
的示意图
一段 javascript
执行的具体流程就是如下:
- 首先执行宏队列中取出第一个,一段
script
就是相当于一个macrotask
, 所以他先会执行同步代码,当遇到例如setTimeout
的时候,就会把这个异步工作推送到宏队列队尾中。 - 以后
macrotask
执行实现当前,就会从微队列中取出位于头部的异步工作进行执行,而后微队列中工作的长度减一。 - 而后持续从微队列中取出工作,直到整个队列中没有工作。如果在执行微队列工作的过程中,又产生了
microtask
,那么会退出整个队列的队尾,也会在以后的周期中执行 - 当微队列的工作为空了,那么就须要执行下一个
macrotask
,执行实现当前再执行微队列,以此重复。
总结下来就是一直从task
队列中按程序取task
执行,每执行完一个task
都会查看microtask
是否为空,不让过不为空就执行队列中的所有microtask
。而后在取下一个task
以此循环
调用栈和工作队列
调用栈是一个栈构造,函数调用会造成一个栈帧。栈帧:调用栈中每个实体被称为栈帧,帧中蕴含了以后执行函数的参数和局部变量等上下文信息,函数执行实现后,它的执行上下文会从栈中弹出。上面是调用栈和工作队列的关系:
剖析文章结尾的题目,能够通过在题目后面增加 debugger
,联合chrome
的call stack
进行剖析:
(这里不晓得怎么画动图,在早晨找的一张图,小伙伴们有好的工具,求分享); 上面借助三个数组来剖析一下这段代码的执行流程,call stack
示意调用栈,macroTasks
示意宏队列,microTasks
示意微队列:
- 首先代码执行之前都是三个队列都是空的:
callStack: []
macroTasks: [main]
microTasks: []
在后面提到,整个代码块就相当于一个 macroTask
,所以首先向callStack
中压入 main()
,main
相当于整个代码块
- 执行
main
,输入同步代码后果:
callStack: [main]
macroTasks: []
microTasks: []
在遇到 setTimeout
和promise
的时候会向 macroTasks
与microTasks
中别离推入
- 此时的三个队列别离是:
callStack: [main]
macroTasks: [setTimeout]
microTasks: [promise]
当这段代码执行实现当前,会输入:
script start
script end
- 当
main
执行实现当前,会取microTasks
中的工作,放入callStack
中,此时的三个队列为:
callStack: [promise]
macroTasks: [setTimeout]
microTask: []
当这个 promise
执行实现后会输入
promise1
前面又有一个 then
,在后面提到如果还有microtask
就在微队列队尾中退出这个工作,并且在以后 tick
执行。所以紧接着输入promise2
- 以后的
tick
也就实现了,最初在从macroTasks
取出task
,此时三个队列的状态如下:
callStack: [setTimeout]
macroTasks: []
microTask: []
最初输入的后果就是 setTimeout
。
所谓的事件循环就是从两个队列中一直取出事件,而后执行,重复循环就是事件循环。通过下面的示例,了解起来是不是比较简单
143.[vue] 双向数据绑定原理?【web 框架】
在目前的前端面试中,vue 的双向数据绑定曾经成为了一个非常容易考到的点,即便不能当场写进去,至多也要能说出原理。本篇文章中我将会仿照 vue 写一个双向数据绑定的实例,名字就叫 myVue 吧。联合正文,心愿能让大家有所播种。
1、原理
Vue 的双向数据绑定的原理置信大家也都非常理解了,次要是通过Object 对象的 defineProperty 属性,重写 data 的 set 和 get 函数来实现的
, 这里对原理不做过多形容,次要还是来实现一个实例。为了使代码更加的清晰,这里只会实现最根本的内容,次要实现 v -model,v-bind 和 v -click 三个命令,其余命令也能够自行补充。
增加网上的一张图
2、实现
页面构造很简略,如下
<div id="app">
<form>
<input type="text" v-model="number">
<button type="button" v-click="increment"> 减少 </button>
</form>
<h3 v-bind="number"></h3>
</div>
蕴含:
1. 一个 input,应用 v -model 指令
2. 一个 button,应用 v -click 指令
3. 一个 h3,应用 v -bind 指令。
咱们最初会通过相似于 vue 的形式来应用咱们的双向数据绑定,联合咱们的数据结构增加正文
var app = new myVue({
el:'#app',
data: {number: 0},
methods: {increment: function() {this.number ++;},
}
})
首先咱们须要定义一个 myVue 构造函数:
function myVue(options) {
}
为了初始化这个构造函数,给它增加一 个 \_init 属性
function myVue(options) {this._init(options);
}
myVue.prototype._init = function (options) {
this.$options = options; // options 为下面应用时传入的构造体,包含 el,data,methods
this.$el = document.querySelector(options.el); // el 是 #app, this.$el 是 id 为 app 的 Element 元素
this.$data = options.data; // this.$data = {number: 0}
this.$methods = options.methods; // this.$methods = {increment: function(){}}
}
接下来实现 \_obverse 函数,对 data 进行解决,重写 data 的 set 和 get 函数
并革新 \_init 函数
myVue.prototype._obverse = function (obj) {// obj = {number: 0}
var value;
for (key in obj) { // 遍历 obj 对象
if (obj.hasOwnProperty(key)) {value = obj[key];
if (typeof value === 'object') { // 如果值还是对象,则遍历解决
this._obverse(value);
}
Object.defineProperty(this.$data, key, { // 要害
enumerable: true,
configurable: true,
get: function () {console.log(` 获取 ${value}`);
return value;
},
set: function (newVal) {console.log(` 更新 ${newVal}`);
if (value !== newVal) {value = newVal;}
}
})
}
}
}
myVue.prototype._init = function (options) {
this.$options = options;
this.$el = document.querySelector(options.el);
this.$data = options.data;
this.$methods = options.methods;
this._obverse(this.$data);
}
接下来咱们写一个指令类 Watcher,用来绑定更新函数,实现对 DOM 元素的更新
function Watcher(name, el, vm, exp, attr) {
this.name = name; // 指令名称,例如文本节点,该值设为 "text"
this.el = el; // 指令对应的 DOM 元素
this.vm = vm; // 指令所属 myVue 实例
this.exp = exp; // 指令对应的值,本例如 "number"
this.attr = attr; // 绑定的属性值,本例为 "innerHTML"
this.update();}
Watcher.prototype.update = function () {this.el[this.attr] = this.vm.$data[this.exp]; // 比方 H3.innerHTML = this.data.number; 当 number 扭转时,会触发这个 update 函数,保障对应的 DOM 内容进行了更新。}
更新 \_init 函数以及 \_obverse 函数
myVue.prototype._init = function (options) {
//...
this._binding = {}; //_binding 保留着 model 与 view 的映射关系,也就是咱们后面定义的 Watcher 的实例。当 model 扭转时,咱们会触发其中的指令类更新,保障 view 也能实时更新
//...
}
myVue.prototype._obverse = function (obj) {
//...
if (obj.hasOwnProperty(key)) {this._binding[key] = {// 依照后面的数据,_binding = {number: _directives: []}
_directives: []};
//...
var binding = this._binding[key];
Object.defineProperty(this.$data, key, {
//...
set: function (newVal) {console.log(` 更新 ${newVal}`);
if (value !== newVal) {
value = newVal;
binding._directives.forEach(function (item) {// 当 number 扭转时,触发_binding[number]._directives 中的绑定的 Watcher 类的更新
item.update();})
}
}
})
}
}
}
那么如何将 view 与 model 进行绑定呢?接下来咱们定义一个 \_compile 函数,用来解析咱们的指令(v-bind,v-model,v-clickde)等,并在这个过程中对 view 与 model 进行绑定。
myVue.prototype._init = function (options) {
//...
this._complie(this.$el);
}
myVue.prototype._complie = function (root) { root 为 id 为 app 的 Element 元素,也就是咱们的根元素
var _this = this;
var nodes = root.children;
for (var i = 0; i < nodes.length; i++) {var node = nodes[i];
if (node.children.length) { // 对所有元素进行遍历,并进行解决
this._complie(node);
}
if (node.hasAttribute('v-click')) { // 如果有 v -click 属性,咱们监听它的 onclick 事件,触发 increment 事件,即 number++
node.onclick = (function () {var attrVal = nodes[i].getAttribute('v-click');
return _this.$methods[attrVal].bind(_this.$data); //bind 是使 data 的作用域与 method 函数的作用域保持一致
})();}
if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) { // 如果有 v -model 属性,并且元素是 INPUT 或者 TEXTAREA,咱们监听它的 input 事件
node.addEventListener('input', (function(key) {var attrVal = node.getAttribute('v-model');
//_this._binding['number']._directives = [一个 Watcher 实例]
// 其中 Watcher.prototype.update = function () {// node['vaule'] = _this.$data['number']; 这就将 node 的值放弃与 number 统一
// }
_this._binding[attrVal]._directives.push(new Watcher(
'input',
node,
_this,
attrVal,
'value'
))
return function() {_this.$data[attrVal] = nodes[key].value; // 使 number 的值与 node 的 value 保持一致,曾经实现了双向绑定
}
})(i));
}
if (node.hasAttribute('v-bind')) { // 如果有 v -bind 属性,咱们只有使 node 的值及时更新为 data 中 number 的值即可
var attrVal = node.getAttribute('v-bind');
_this._binding[attrVal]._directives.push(new Watcher(
'text',
node,
_this,
attrVal,
'innerHTML'
))
}
}
}
至此,咱们曾经实现了一个简略 vue 的双向绑定性能,包含 v -bind, v-model, v-click 三个指令。成果如下图
附上全副代码,不到 150 行
<!DOCTYPE html>
<head>
<title>myVue</title>
</head>
<style>
#app {text-align: center;}
</style>
<body>
<div id="app">
<form>
<input type="text" v-model="number">
<button type="button" v-click="increment"> 减少 </button>
</form>
<h3 v-bind="number"></h3>
<form>
<input type="text" v-model="count">
<button type="button" v-click="incre"> 减少 </button>
</form>
<h3 v-bind="count"></h3>
</div>
</body>
<script>
function myVue(options) {this._init(options);
}
myVue.prototype._init = function (options) {
this.$options = options;
this.$el = document.querySelector(options.el);
this.$data = options.data;
this.$methods = options.methods;
this._binding = {};
this._obverse(this.$data);
this._complie(this.$el);
}
myVue.prototype._obverse = function (obj) {
var _this = this;
Object.keys(obj).forEach(function (key) {if (obj.hasOwnProperty(key)) {_this._binding[key] = {_directives: []
};
console.log(_this._binding[key])
var value = obj[key];
if (typeof value === 'object') {_this._obverse(value);
}
var binding = _this._binding[key];
Object.defineProperty(_this.$data, key, {
enumerable: true,
configurable: true,
get: function () {console.log(`${key}获取 ${value}`);
return value;
},
set: function (newVal) {console.log(`${key}更新 ${newVal}`);
if (value !== newVal) {
value = newVal;
binding._directives.forEach(function (item) {item.update();
})
}
}
})
}
})
}
myVue.prototype._complie = function (root) {
var _this = this;
var nodes = root.children;
for (var i = 0; i < nodes.length; i++) {var node = nodes[i];
if (node.children.length) {this._complie(node);
}
if (node.hasAttribute('v-click')) {node.onclick = (function () {var attrVal = nodes[i].getAttribute('v-click');
return _this.$methods[attrVal].bind(_this.$data);
})();}
if (node.hasAttribute('v-model') && (node.tagName = 'INPUT' || node.tagName == 'TEXTAREA')) {node.addEventListener('input', (function(key) {var attrVal = node.getAttribute('v-model');
_this._binding[attrVal]._directives.push(new Watcher(
'input',
node,
_this,
attrVal,
'value'
))
return function() {_this.$data[attrVal] = nodes[key].value;
}
})(i));
}
if (node.hasAttribute('v-bind')) {var attrVal = node.getAttribute('v-bind');
_this._binding[attrVal]._directives.push(new Watcher(
'text',
node,
_this,
attrVal,
'innerHTML'
))
}
}
}
function Watcher(name, el, vm, exp, attr) {
this.name = name; // 指令名称,例如文本节点,该值设为 "text"
this.el = el; // 指令对应的 DOM 元素
this.vm = vm; // 指令所属 myVue 实例
this.exp = exp; // 指令对应的值,本例如 "number"
this.attr = attr; // 绑定的属性值,本例为 "innerHTML"
this.update();}
Watcher.prototype.update = function () {this.el[this.attr] = this.vm.$data[this.exp];
}
window.onload = function() {
var app = new myVue({
el:'#app',
data: {
number: 0,
count: 0,
},
methods: {increment: function() {this.number ++;},
incre: function() {this.count ++;}
}
})
}
</script>
如果喜爱请关注我的 Github,给个 Star 吧,我会定期分享一些 JS 中的常识,^\_^
146.nodejs 过程间如何通信?【Nodejs】
在 Node.js 中,过程间通信(IPC)能够通过以下几种形式进行:
- 应用子过程模块:能够应用 Node.js 的子过程模块(child\_process)来创立子过程,并应用过程间通信机制(如过程间管道)来实现通信。
- 应用共享内存:Node.js 中的共享内存模块(sharedArrayBuffer)能够在多个过程间共享内存,从而实现过程间通信。
- 应用过程间消息传递:Node.js 提供了一个内置的过程间通信机制,能够应用 process.send() 办法在不同的过程之间发送音讯。
- 应用过程间的 TCP 通信:能够应用 Node.js 的 net 模块建设 TCP 服务器和客户端,从而在不同的过程之间进行通信。
须要留神的是,不同的过程之间通信可能会导致一些并发问题,例如竞态条件和死锁。因而,在设计过程间通信计划时,须要认真思考并发问题,并采取相应的措施来保障并发平安。
高级开发者相干问题【共计 22 道题】
77. 虚构 dom 原理是啥,手写一个简略的虚构 dom 实现?【JavaScript】
vdom 概念
用 JS 模仿 DOM 构造。
DOM 变动的比照,放在 JS 层来做。
晋升重绘性能。
比方有 abc 三个 dom,如果咱们要删除 b dom, 以前浏览器的做法是 全副删除 abc dom,而后 在增加 b dom。这样做的老本会十分高。
用 JS 模仿 dom
例如上面的一个 dom 构造:
<ul id="list">
<li class="item">item1</li>
<li class="item">item2</li>
</ul>
这样的 dom 构造,能够模仿为上面的 JS :
let dom = {
tag: 'ul',
attrs: {id: 'list'},
children: [
{
tag: 'li',
attrs: {className: 'item'},
children: ['item1']
},
{
tag: 'li',
attrs: {className: 'item'},
children: ['item2']
}
]
}
浏览器操作 dom 是花销十分大的。执行 JS 花销要小十分多,所以这就是为什么虚构 dom 呈现的一个根本原因。
jquery 实现 virtual-dom
一个需要场景
1、数据生成表格。2、轻易批改一个信息,表格也会跟着批改。
<body>
<div id="container"></div>
<br>
<button id="btn-change">change</button>
<script>
let data = [
{
name: 'yanle',
age: '20',
address: '重庆'
},
{
name: 'yanle2',
age: '25',
address: '成都'
},
{
name: 'yanle3',
age: '27',
address: '深圳'
}
];
// 渲染函数
function render(data) {let $container = document.getElementById('container');
$container.innerHTML = '';
let $table = document.createElement('table');
$table.setAttribute('border', true);
$table.insertAdjacentHTML('beforeEnd', `<tr>
<td>name</td>
<td>age</td>
<td>address</td>
</tr>`);
data.forEach(function (item) {
$table.insertAdjacentHTML('beforeEnd',
`<tr>
<td>${item.name}</td>
<td>${item.age}</td>
<td>${item.address}</td>
</tr>`
)
});
$container.appendChild($table);
}
// 批改信息
let button = document.getElementById('btn-change');
button.addEventListener('click', function () {data[1].name = '徐老毕';
data[1].age = 30;
data[1].address = '深圳';
render(data);
});
render(data);
</script>
</body>
实际上下面的这段代码也是不合乎预期的,因为每次应用 render 办法,都会全副渲染整个 table, 然而并未没有只渲染咱们想要的第二行。
遇到的问题:
DOM 操作是十分 “ 低廉 ” 的,JS 运行效率高。虚构 dom 的外围就是 diff 算法,比照出不同的 dom 数据,定点渲染不同的数据。
107. 手写代码实现 promise.all【JavaScript】
上面是手写实现 Promise.all()
办法的代码:
Promise.all = function (promises) {return new Promise((resolve, reject) => {let results = [];
let count = 0;
promises.forEach((promise, index) => {Promise.resolve(promise).then((result) => {results[index] = result;
count++;
if (count === promises.length) {resolve(results);
}
},
(reason) => {reject(reason);
}
);
});
});
};
实现原理:
Promise.all()
办法接管一个蕴含多个 Promise 的数组作为参数,并返回一个新的 Promise。该 Promise 将会在数组中所有 Promise 状态均为 fulfilled
时被解决,并且以数组模式返回所有 Promise 的后果。
咱们能够通过创立一个新的 Promise,而后遍历 Promise 数组并将每个 Promise 包装在一个 Promise.resolve()
中,而后应用 .then()
办法将它们的解决值和回绝起因别离传递给新的 Promise 的 resolve()
和 reject()
办法。咱们还须要保护一个计数器和一个后果数组来跟踪所有 Promise 的状态。每当一个 Promise 被解决时,咱们将其后果存储在后果数组中,而后将计数器减少 1。当计数器等于 Promise 数组的长度时,阐明所有 Promise 均已被解决,此时咱们能够应用 resolve()
办法并将后果数组作为参数传递给它。如果有任何一个 Promise 被回绝,则应用 reject()
办法并将其回绝起因作为参数传递给它。
须要留神的是,如果 Promise 数组为空,则 Promise.all()
将立刻被解决,并返回一个空数组。
108. 手写实现 Promise.allSettled【JavaScript】
Promise.allSettled
办法会接管一个 Promise 数组,并返回一个新的 Promise 对象。该新 Promise 对象会在所有输出的 Promise 都被 resolved 或 rejected 后变为 settled 状态,并且它的值是一个蕴含所有 Promise 状态的对象数组。
以下是手写实现 Promise.allSettled
办法的代码:
function allSettled(promises) {return new Promise((resolve) => {const results = [];
let settledCount = 0;
promises.forEach((promise, index) => {Promise.resolve(promise).then((value) => {results[index] = {status: 'fulfilled', value};
},
(reason) => {results[index] = {status: 'rejected', reason};
}
).finally(() => {
settledCount++;
if (settledCount === promises.length) {resolve(results);
}
});
});
});
}
上述代码中,咱们首先创立一个新的 Promise 对象,并在其中执行了一个异步操作。而后咱们遍历了传入的 Promise 数组,并为每个 Promise 增加了一个 then
办法的回调函数,以便在 Promise 状态发生变化时收集 Promise 的后果。对于每个 Promise,咱们都应用 Promise.resolve
办法将其转换为 Promise 对象,以确保咱们解决的是 Promise 对象。咱们应用一个 finally
办法来在 Promise settled 时更新 settledCount,以确保在所有 Promise settled 后咱们只会执行一次 resolve
办法。
最终,咱们将所有 Promise 的状态都收集到了 results
数组中,并将该数组作为 Promise 的值解析。这样,咱们就实现了 Promise.allSettled
办法的性能。
112.CSS 尺寸单位有哪些?【CSS】
CSS 尺寸设置的单位包含:
- 像素(Pixel,缩写为 px):是最罕用的单位,示意屏幕上的一个点,能够准确地指定元素的大小。
- 百分比(Percentage,缩写为 %):绝对于父元素的大小,能够依据父元素的大小来设置子元素的大小。
- em:绝对于以后元素的字体大小,用于设置字体大小时很罕用。
- rem:绝对于根元素(即 html 元素)的字体大小。
- vh/vw:绝对于视口(Viewport)的高度和宽度。
- cm、mm、in、pt、pc 等长度单位:用于打印样式表,不倡议在 Web 开发中应用。
- 自定义单位:能够通过 CSS 的 calc()函数自定义单位,比方应用“1fr”作为网格布局中的单位。
须要留神的是,不同的浏览器可能会有不同的计算形式和默认值,因而在设置尺寸时须要进行充沛的测试和兼容性解决。
113.React Router 中 HashRouter 和 BrowserRouter 的区别和原理?【web 框架】
React Router 是一个风行的第三方库,它容许在 React 应用程序中实现路由性能。React Router 反对两种路由形式:HashRouter 和 BrowserRouter。
- HashRouter
HashRouter 应用 URL 中的 hash 局部(即 #)来实现路由。在 React 中,能够应用 <HashRouter>
组件来创立 HashRouter。例如:
jsxCopy codeimport {HashRouter, Route, Link} from 'react-router-dom';
function App() {
return (
<HashRouter>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</HashRouter>
);
}
在应用 HashRouter 时,URL 中的门路看起来像这样:http://example.com/#/about
。HashRouter 不会向服务器发送申请,因为 # 符号前面的内容被浏览器认为是 URL 的一部分,而不是服务器申请的一部分。这意味着在应用 HashRouter 时,React 应用程序能够在客户端上运行,而无需服务器反对。
- BrowserRouter
BrowserRouter 应用 HTML5 的 history API 来实现路由。在 React 中,能够应用 <BrowserRouter>
组件来创立 BrowserRouter。例如:
jsxCopy codeimport {BrowserRouter, Route, Link} from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</BrowserRouter>
);
}
在应用 BrowserRouter 时,URL 中的门路看起来像这样:http://example.com/about
。BrowserRouter 通过 history API 在客户端和服务器之间发送申请,因而须要服务器反对。
- 区别
HashRouter 和 BrowserRouter 的次要区别在于它们如何解决 URL。HashRouter 应用 URL 中的 # 局部来实现路由,而 BrowserRouter 应用 HTML5 的 history API 来实现路由。HashRouter 不须要服务器反对,而 BrowserRouter 须要服务器反对。
- 原理
HashRouter 的原理是通过监听 window.location.hash
的变动来实现路由。当用户点击链接时,React Router 会依据链接的门路渲染相应的组件,并将门路增加到 URL 中的 # 局部。当用户点击浏览器的“后退”按钮时,React Router 会依据上一个 URL 中的 # 局部来渲染相应的组件。
BrowserRouter 的原理是通过 HTML5 的 history API 来实现路由。当用户点击链接时,React Router 会应用 history API 将门路增加到浏览器的历史记录中,并渲染相应的组件。当用户点击浏览器的“后退”
114.Vue3.0 实现数据双向绑定的办法是什么?【web 框架】
Vue3.0 通过应用 Composition API 中的 reactive
和 ref
函数来实现数据双向绑定。
- reactive 函数
reactive
函数是 Vue3.0 中用来创立响应式对象的函数。将一个 JavaScript 对象传递给 reactive
函数,它会返回一个新的响应式对象。响应式对象是一个 Proxy 对象,能够在应用程序中应用它来主动追踪数据的变动。
例如,咱们能够这样应用 reactive
函数来创立一个响应式对象:
import {reactive} from 'vue';
const state = reactive({message: 'Hello, world!'});
在下面的示例中,咱们应用 reactive
函数创立了一个蕴含一个 message
属性的响应式对象。
- ref 函数
ref
函数是 Vue3.0 中用来创立一个蕴含单个值的响应式对象的函数。将一个初始值传递给 ref
函数,它会返回一个新的响应式对象。响应式对象是一个一般对象,它有一个名为 value
的属性,该属性保留了以后值。当 value
属性的值产生扭转时,Vue3.0 会自动更新应用程序的界面。
例如,咱们能够这样应用 ref
函数来创立一个响应式对象:
import {ref} from 'vue';
const count = ref(0);
在下面的示例中,咱们应用 ref
函数创立了一个蕴含初始值为 0 的响应式对象。
- 双向绑定的实现
Vue3.0 中的双向绑定能够通过在模板中应用 v-model
指令来实现。v-model
指令是 Vue3.0 中用来实现表单元素和组件的双向数据绑定的指令。例如,咱们能够这样应用 v-model
指令来实现一个表单输入框的双向绑定:
htmlCopy code<template>
<input v-model="message" />
<p>{{message}}</p>
</template>
<script>
import {ref} from 'vue';
export default {setup() {const message = ref('');
return {message};
}
};
</script>
在下面的示例中,咱们在模板中应用 v-model
指令将输入框和 message
响应式对象进行双向绑定。当用户在输入框中输出文本时,message
响应式对象的值会自动更新,当 message
响应式对象的值产生扭转时,界面上的文本也会自动更新。
总之,Vue3.0 应用 reactive
和 ref
函数来实现数据双向绑定。应用 reactive
函数能够创立蕴含多个属性的响应式对象,应用 ref
函数能够创立蕴含单个值的响应式对象。通过在模板中应用 \`v-model
指令能够实现表单元素和组件的双向数据绑定,将表单元素的值绑定到响应式对象的属性上,当响应式对象的属性值变动时,自动更新绑定的表单元素的值。
除了应用 v-model
指令实现双向绑定,Vue3.0 也提供了 watch
函数和 watchEffect
函数来实现响应式数据的监听和副作用函数的执行。这些函数能够用来监听响应式数据的变动,从而执行特定的操作。上面是一个应用 watch
函数监听响应式数据变动的示例:
htmlCopy code<template>
<div>{{count}}</div>
<button @click="increment">Increment</button>
</template>
<script>
import {ref, watch} from 'vue';
export default {setup() {const count = ref(0);
watch(count, (newVal, oldVal) => {console.log(`count changed from ${oldVal} to ${newVal}`);
});
const increment = () => {count.value++;};
return {
count,
increment
};
}
};
</script>
在下面的示例中,咱们应用 watch
函数监听 count
响应式对象的变动,当 count
响应式对象的值发生变化时,会主动调用回调函数,打印出 count
变动前和变动后的值。
另外,Vue3.0 中还提供了 computed
函数用来计算一个响应式对象的值,toRefs
函数用来将一个响应式对象转换为一般的对象,并且在 TypeScript 中应用时能够应用 defineComponent
函数来定义组件的类型,从而进步代码的可读性和可维护性。
118. 浏览器垃圾回收机制?【浏览器】
浏览器垃圾回收机制是指浏览器在运行时主动回收不再应用的内存空间的过程。以下是浏览器垃圾回收机制的几个方面:
- 标记革除:这是一种最罕用的垃圾回收机制。它的工作原理是标记所有以后正在应用的对象,而后革除未标记的对象。这种办法的长处是效率高,毛病是可能会导致内存碎片。
- 援用计数:这种垃圾回收机制会跟踪每个对象被援用的次数,当援用计数为零时,就会回收该对象。这种办法的长处是能够立刻回收不再应用的对象,毛病是无奈解决循环援用。
- 分代回收:这是一种联合了标记革除和援用计数的垃圾回收机制。它将对象分为几代,而后在不同的代上应用不同的回收策略。新创建的对象会被调配到第一代,随着工夫的推移,如果它们依然存活,它们会被转移到下一代。这种办法的长处是能够更精密地管制回收策略。
浏览器垃圾回收机制能够帮忙开发人员防止内存透露和缩小程序解体的危险。不同的浏览器和不同的 JavaScript 引擎实现可能有不同的垃圾回收机制,但它们的基本原理是类似的。
120. 常见的 web 前端网路攻打有哪些?【网络】
以下是一些常见的 web 前端网络攻击类型:
- 跨站脚本攻打(Cross-Site Scripting, XSS):XSS 攻打利用了 Web 应用程序对用户输出的不当解决,以将恶意代码注入到 Web 页面中。当用户拜访蕴含恶意代码的页面时,攻击者能够利用这些代码窃取用户的敏感信息、劫持用户会话等。
- 跨站申请伪造(Cross-Site Request Forgery, CSRF):CSRF 攻打利用了用户曾经登录了受信赖网站的身份,通过在受害者的浏览器中执行恶意代码,将伪造的申请发送到受信赖网站上,从而执行某些操作或者获取某些信息。
- 点击劫持(Clickjacking):点击劫持是一种利用通明 iframe 层来笼罩网页上的其余内容,坑骗用户点击不可见的按钮或链接,以执行攻击者所需的操作。
- HTML 注入攻打:HTML 注入攻打利用了 Web 应用程序对用户输出的不当解决,以将歹意的 HTML 代码插入到 Web 页面中。这种攻打通常被用来批改页面内容、坑骗用户或者施行其余歹意行为。
- 敏感数据泄露(Sensitive Data Leakage):敏感数据泄露可能会产生在 Web 应用程序中,其中攻击者能够通过暴力破解、SQL 注入等攻击方式,获取存储在数据库中的敏感数据(如用户名、明码、信用卡信息等)。
- 带宽滥用(Bandwidth Abuse):带宽滥用是指攻击者利用 Web 应用程序或服务器的破绽来耗费服务器的资源和带宽,从而使服务器变得迟缓或无奈失常工作。
- HTTP 申请坑骗(HTTP Request Spoofing):HTTP 申请坑骗是一种利用 Web 应用程序对输出的不当解决,以篡改 HTTP 申请的攻击方式。攻击者能够通过伪造 HTTP 申请头信息、批改 HTTP 申请办法等形式,坑骗 Web 应用程序执行攻击者所需的操作。
须要留神的是,这些攻打类型通常会联合应用,攻击者会利用多种攻击方式,以更好地实现攻打指标。