此文章目录,来源于印客学院的资料,具体内容是从网上查找的补充。
这里只是分享,便于学习。
诸君可以根据自己实际情况,自行衡量,看看哪里需要加强。
概述如下:
- 怎么判断两个对象相等?
- 项目做过哪些性能优化?
- 浏览器缓存
- WebSocket
- 尽可能多的说出你对 Electron 的理解
- Javascript 深浅拷贝 ,及举例
- 防抖/节流,举例说明
- 谈谈Javascript变量提升?
- Javascript为什么是单线程,以及如何解决单线程问题
- 前端面试之hybrid
更多精彩内容,请微信搜索“前端爱好者
“, 戳我 查看 。
怎么判断两个对象相等?
在JavaScript中,对象的相等性比较有两种情况,分别为 浅相等 和 深相等。
浅相等(Shallow Equality):判断两个对象是否指向同一内存地址
,即它们是同一个对象。
可以使用===
运算符来进行浅相等的比较。例如:
const obj1 = { name: 'Alice' };
const obj2 = { name: 'Alice' };
console.log(obj1 === obj2); // false
以上例子中,obj1
和obj2
虽然包含相同的键值对,但它们指向不同的内存地址,因此浅相等比较的结果为 false
。
深相等(Deep Equality):判断两个对象的属性值
是否完全相等。
可以使用递归遍历对象
的所有属性,对每个属性进行相等性比较。你可以自己编写递归函数或使用一些库来实现深相等比较。
下面给出一个自定义的深相等比较函数的示例:
function deepEqual(obj1, obj2) {
if (obj1 === obj2) {
return true;
}
if (typeof obj1 !== 'object' || obj1 === null ||
typeof obj2 !== 'object' || obj2 === null) {
return false;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) {
return false;
}
for (let key of keys1) {
if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
const obj1 = { name: 'Alice', age: 25 };
const obj2 = { name: 'Alice', age: 25 };
console.log(deepEqual(obj1, obj2)); // true
以上示例中,deepEqual
函数首先进行浅相等性判断,然后递归地遍历对象的所有属性进行深相等性比较。在这个示例中,obj1
和obj2
的属性值完全相等,因此深相等比较的结果为 true
。
上述代码是一个简化版本的深相等比较函数,只适用于基本的对象情况。对于包含特殊类型(如Date、RegExp等)或循环引用的复杂对象,可能需要额外的处理。
深相等(Deep Equality): 转换为字符串来判断
obj={
a:1,
b:2
}
obj2={
a:1,
b:2
}
obj3={
a:1,
b: '2'
}
JSON. stringify( obj) = = JSON. stringify( obj2 ) ; // true
JSON.stringify(obj)==JSON.stringify(obj3);//false
如果你使用的是流行的JavaScript库,例如Lodash或Underscore.js,它们通常提供了更强大和全面的深相等比较方法,可以方便地处理各种复杂情况。
项目做过哪些性能优化?
在项目中,我们进行了多项性能优化措施。以下是一些主要的性能优化技术和方法:
-
代码优化:我们对关键代码进行了分析和重构,以减少不必要的计算、内存占用和网络请求等。通过减少冗余操作和优化算法,提高了代码的执行效率。
-
缓存策略:我们使用了缓存机制来减少数据查询和计算的时间。通过将频繁访问的数据存储在缓存中,可以避免每次都从数据库或其他存储介质中读取,从而大大提高了响应速度。
-
并发处理:通过采用并发编程技术,充分利用多核处理器和多线程/多进程机制,提高了系统的并发处理能力和吞吐量。
-
数据库优化:我们对数据库进行了性能优化,包括合理设计数据库索引、优化查询语句、使用合适的数据类型和规范化数据结构等,以提高数据读写效率和查询性能。
-
前端加载优化:对于前端页面,我们进行了资源压缩、合并和缓存等优化操作,减少了页面加载时间,提高了用户体验。
-
负载均衡和扩展:通过使用负载均衡技术和横向扩展,将请求分发到多台服务器上处理,提高了系统的并发处理能力和可扩展性。
-
响应式设计:我们采用了响应式设计技术,使系统能够适应不同终端设备的屏幕尺寸和分辨率,提供更好的用户界面和交互体验。
这些性能优化措施的实施使得系统在各方面都得到了改进,包括响应速度、并发处理能力、资源利用效率和用户体验等。
浏览器缓存
浏览器缓存分为 强缓存 和 协商缓存。
当客户端请求某个资源时, 获取缓存的流程如下
- 先根据这个资源的⼀些
http header
判断它是否命中强缓存, 如果命中,则直接从本地 获取缓存资源,不会发请求到服务器; - 当强缓存没有命中时,客户端会发送请求到服务器, 服务器通过另⼀些
request header
验证这个资源是否命中协商缓存,称为http
再验证, 如果命中, 服务器将请求返回,但不返回资源, 而是告诉客户端直接从缓存中获取,客户端收到返回后就会从缓存中获取资源; - 强缓存和协商缓存共同之处在于, 如果命中缓存, 服务器都不会返回资源; 区别是,强缓 存不对发送请求到服务器,但协商缓存会。
- 当协商缓存也没命中时, 服务器就会将资源发送回客户端。
- 当
ctrl+f5
强制刷新网页时, 直接从服务器加载,跳过强缓存和协商缓存; - 当
f5
刷新网页时,跳过强缓存,但是会检查协商缓存;
强缓存
Expires
( 该字段是 http1.0时的规范,值为⼀个绝对时间的 GMT 格式的时间字符串,代表缓存资源的过期时间)Cache-Control:max-age
( 该字段是 http1.1 的规范, 强缓存利用其 max-age 值来 判断缓存资源的最大生命周期, 它的值单位为秒)
协商缓存
Last-Modified
( 值为资源最后更新时间, 随服务器response返回)If-Modified-Since
( 通过比较两个时间来判断资源在两次请求期间是否有过修改, 如 果没有修改,则命中协商缓存)ETag
(表示资源内容的唯⼀标识, 随服务器response
返回)If-None-Match
( 服务器通过比较请求头部的If-None-Match
与当前资源的 ETag 是 否⼀致来判断资源是否在两次请求之间有过修改, 如果没有修改,则命中协商缓存)
WebSocket
WebSocket是一种网络通信协议,它提供了在服务器和客户端之间进行全双工、实时的通信
的能力。
传统的HTTP协议是一种请求-应答模式的协议,当客户端发送请求后,服务器返回响应,然后连接就会关闭。而WebSocket允许建立一个持久的连接,使得服务器和客户端可以随时通过该连接进行双向的实时通信。
WebSocket协议的设计目标是在Web浏览器和服务器之间传递数据,但它也可以在其他应用中使用。WebSocket通过在标准的HTTP握手过程中添加一些额外的头部信息来启用它的通信通道,并使用自定义的协议进行数据交换。一旦WebSocket连接建立成功,服务器和客户端就可以以类似于TCP的方式发送和接收数据,而不需要每次都发送HTTP请求。
WebSocket的优点包括:
- 低延迟、
- 实时性高、
- 双向通信、
- 减少网络流量等。
它广泛应用于实时聊天、在线游戏、股票行情、推送服务等领域。在具体的使用中,可以通过JavaScript等编程语言的WebSocket API来实现WebSocket的功能。
由于 http 存在⼀个明显的弊端 ( 消息只能有客户端推送到服务器端, 而服务器端不能主动推送到客户端), 导致如果服务器如果有连续的变化, 这时只能使用轮询, 而轮询效率过低, 并不适合 。于是 WebSocket 被发明出来
相比与 http 具有以下有点
- ⽀持双向通信, 实时性更强;
- 可以发送⽂本,也可以⼆进制⽂件;
- 协议标识符是 ws ,加密后是 wss ;
- 较少的控制开销 。连接创建后, ws 客户端 、服务端进行数据交换时,协议控制的数据包 头部较⼩ 。在不包含头部的情况下, 服务端到客户端的包头只有 2~10 字节 ( 取决于数据 包长度), 客户端到服务端的的话, 需要加上额外的4字节的掩码 。而 HTTP 协议每次通信 都需要携带完整的头部;
- ⽀持扩展 。ws协议定义了扩展,用户可以扩展协议, 或者实现自定义的⼦协议 。 ( 比如⽀ 持自定义压缩算法等)
- ⽆跨域问题。
- 实现比较简单, 服务端库如 socket.io 、 ws , 可以很好的帮助我们⼊门。 而客户端也只需要参照 api 实现即可
总结:WebSocket和HTTP是两种不同的网络通信协议,它们在很多方面有着显著的区别:
-
连接方式:HTTP是一种请求-应答模式的协议,客户端发送请求,服务器返回响应后连接就会关闭。而WebSocket允许建立一个持久的连接,使得服务器和客户端可以随时通过该连接进行双向的实时通信。
-
数据传输方式:HTTP是无状态的,每个请求都是独立的,服务器不会保留任何客户端的状态信息。每次请求都需要重新建立连接,并且会带有较多的头部信息,导致一定的数据开销。WebSocket则是全双工通信,可以在已经建立的连接上随时发送和接收数据,减少了数据开销。
-
支持的数据格式:HTTP通常使用JSON、XML等格式来传输数据,数据量较大时会产生较多的开销。WebSocket可以传输任意类型的数据,包括二进制数据,更加灵活。
-
实时性和性能:由于WebSocket的特性,它具备更低的延迟和更高的实时性。HTTP协议需要频繁地发起请求和接收响应,对于实时性要求较高的应用场景,如聊天室、在线游戏等,WebSocket更适合。
HTTP适用于客户端向服务器发送请求,获得服务器响应的场景,而WebSocket适用于实时双向通信的场景。选择使用哪种协议要根据具体的需求和场景来决定。
尽可能多的说出你对 Electron 的理解
最最重要的⼀点, electron 实际上是⼀个套了 Chrome 的 nodeJS 程序
Electron是一个开源的框架,它用于构建跨平台的桌面应用程序。它使用了Web技术,包括HTML、CSS和JavaScript,来构建应用程序界面,并通过将应用程序打包成可执行文件在不同的操作系统上运行。
所以应该是从两个方面说: Chrome ( ⽆各种兼容性问题)
和 NodeJS ( NodeJS 能做的它也能做)
;
以下是对Electron的理解:
-
跨平台性:Electron可以在多个主流操作系统(如Windows、macOS和Linux)上运行,只需编写一次代码,就能在不同的平台上提供相同的用户体验。
-
基于Web技术:Electron使用Web技术作为应用程序的开发语言,包括HTML、CSS和JavaScript。这使得开发者可以利用已有的Web开发知识和工具来构建桌面应用程序。
-
主进程与渲染进程:Electron使用了主进程和渲染进程的架构。主进程负责管理应用程序的生命周期、与底层操作系统进行通信,并处理系统级别的任务。渲染进程则负责展示应用程序的界面和处理用户交互。这种架构使得Electron应用程序可以同时运行多个独立的窗口,并且能够充分利用多核处理器的优势。
-
强大的扩展性:Electron支持使用第三方模块和插件来扩展应用程序的功能。开发者可以利用这些模块和插件来访问底层系统资源、操作文件系统、实现网络通信等。
-
社区支持和生态系统:Electron拥有一个庞大的开源社区,提供了大量的文档、示例代码和教程,以及丰富的第三方库和工具。这使得开发者能够快速入门并迅速解决问题。
Electron是一个强大的框架,使开发者能够使用Web技术构建跨平台的桌面应用程序,并且具备良好的扩展性和强大的社区支持。
Javascript 深浅拷贝 ,及举例
在JavaScript中,对象的复制可以分为深拷贝和浅拷贝两种方式。
浅拷贝是指创建一个新对象,然后将原始对象的属性值复制到新对象中,如果属性值是基本类型,则直接复制其值;如果属性值是引用类型,则复制引用的地址,也就是新对象和原始对象引用的是同一块内存地址。浅拷贝只复制对象的一层属性。
使用Object.assign()进行浅拷贝
或者
使用展开运算符(...)进行浅拷贝
深拷贝则是在浅拷贝的基础上,递归地复制所有嵌套的子对象,确保每个对象都是通过复制创建的,并且修改其中一个对象的属性不会影响其他对象。
使用JSON.parse(JSON.stringify())进行深拷贝(注意:该方法无法拷贝函数和循环引用)
或者
使用第三方库如lodash的cloneDeep()方法进行深拷贝
下面是使用不同方法实现深拷贝和浅拷贝的示例:
浅拷贝示例:
// 使用Object.assign()进行浅拷贝
const obj = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, obj);
console.log(shallowCopy); // { a: 1, b: { c: 2 } }
// 使用展开运算符(...)进行浅拷贝
const shallowCopy2 = { ...obj };
console.log(shallowCopy2); // { a: 1, b: { c: 2 } }
深拷贝示例:
// 使用JSON.parse(JSON.stringify())进行深拷贝(注意:该方法无法拷贝函数和循环引用)
const obj = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj));
console.log(deepCopy); // { a: 1, b: { c: 2 } }
// 使用第三方库如lodash的cloneDeep()方法进行深拷贝
const _ = require('lodash');
const deepCopy2 = _.cloneDeep(obj);
console.log(deepCopy2); // { a: 1, b: { c: 2 } }
请注意,使用JSON.parse(JSON.stringify())
进行深拷贝时,会忽略函数和循环引用,因此在某些情况下可能无法满足需求。
防抖/节流,举例说明
防抖和节流是在前端开发中常用的技术,用于控制函数的执行频率,提高页面性能和用户体验。
- 防抖(Debounce):
防抖的原理是在函数被连续调用时,只有在一定的间隔时间内没有再次触发时,才会执行函数。常见的应用场景包括输入框搜索自动补全、窗口大小变化时的响应等。
例如,当用户在搜索框输入关键词时,我们不希望每次输入都发送请求,而是在用户停止输入一段时间后才发送请求。这个时候就可以使用防抖函数来实现:
function debounce(func, delay) {
let timerId;
return function() {
const context = this;
const args = arguments;
clearTimeout(timerId);
timerId = setTimeout(function() {
func.apply(context, args);
}, delay);
};
}
// 假设我们有个函数用于发送搜索请求
function search(keyword) {
// 发送搜索请求的代码
console.log('搜索关键词:', keyword);
}
const debouncedSearch = debounce(search, 300); // 创建一个防抖函数
// 绑定输入框的输入事件
const inputElement = document.querySelector('input');
inputElement.addEventListener('input', function(event) {
const keyword = event.target.value;
debouncedSearch(keyword); // 调用防抖函数
});
上述例子中,当用户输入时,防抖函数会在用户停止输入 300 毫秒后才调用真正的搜索函数,避免了频繁发送请求。
- 节流(Throttle):
节流的原理是规定一个函数只能在一定的时间间隔内执行一次,不管触发频率多高,都会保证在固定时间间隔后执行。常见的应用场景包括页面滚动监听、按钮连续点击等。
例如,当用户点击一个按钮时,我们希望每隔一段时间执行一次相应的操作,而不是立即执行。这个时候就可以使用节流函数来实现:
function throttle(func, interval) {
let lastTime = 0;
return function() {
const context = this;
const args = arguments;
const now = Date.now();
if (now - lastTime >= interval) {
func.apply(context, args);
lastTime = now;
}
};
}
// 假设我们有个函数用于处理按钮点击
function handleClick() {
// 处理按钮点击的代码
console.log('按钮被点击了');
}
const throttledClick = throttle(handleClick, 1000); // 创建一个节流函数
// 绑定按钮的点击事件
const buttonElement = document.querySelector('button');
buttonElement.addEventListener('click', throttledClick); // 调用节流函数
上述例子中,当用户点击按钮时,节流函数会在每隔 1000 毫秒后执行一次按钮点击处理函数,确保在指定的时间间隔内只执行一次操作,避免了过多的点击导致的问题。
谈谈Javascript变量提升?
在JavaScript中,变量提升是一种特殊的行为,它允许在变量声明之前就可以访问和使用这些变量。这是由于JavaScript在代码执行之前会将变量声明提升到作用域的顶部。
具体来说,JavaScript的变量提升主要有两个方面:
- 变量声明提升:在作用域中,所有的变量声明(使用
var
、let
或const
关键字声明的变量)都会被提升到作用域的顶部。这意味着我们可以在变量声明之前使用这些变量。但是,变量的赋值操作并不会被提升,只有变量的声明会被提升。例如:
console.log(x); // undefined
var x = 5;
在上面的例子中,变量 x
的声明会被提升,所以 console.log(x)
不会报错,但是 x
的赋值操作并没有被提升,所以输出结果是 undefined
。
- 函数声明提升:与变量声明类似,使用函数声明的方式(而不是函数表达式)创建的函数也会被提升到作用域的顶部。这意味着我们可以在函数声明之前调用这些函数。例如:
myFunction(); // "Hello"
function myFunction() {
console.log("Hello");
}
在上面的例子中,函数 myFunction
的声明会被提升,所以我们可以在函数声明之前调用它,输出结果是 “Hello”。
需要注意的是,使用 let
和 const
关键字声明的变量在块级作用域中(如条件语句或循环语句)具有块级作用域,不会存在变量提升的情况,只有在声明之后才能访问它们。
JavaScript中的变量提升是一种特殊行为,它允许在变量声明之前使用这些变量。但是要注意,虽然变量提升使得代码在语法上正确,但在逻辑上可能产生意想不到的结果。因此,为了代码的可读性和可维护性,建议在使用变量之前先进行声明。
Javascript为什么是单线程,以及如何解决单线程问题
JavaScript 之所以被设计为单线程的语言,主要基于其最初的用途和应用场景。JavaScript 最初是作为浏览器中的脚本语言而创建的,用于增强网页的交互性和动态性。在这种场景下,单线程的设计可以简化编程模型,并防止多个脚本同时修改同一份数据导致的竞争条件和不一致性问题。
然而,由于 JavaScript 是单线程的,它在处理大量计算、耗时操作或者阻塞任务时容易出现性能瓶颈和响应延迟。为了解决这些问题,JavaScript 开发者通常使用以下技术来克服单线程带来的限制:
-
异步编程:通过使用回调函数、Promise、async/await 等机制,可以将耗时的操作交给后台线程处理,然后在操作完成后再返回结果。这样可以避免阻塞主线程。
-
Web Workers:Web Workers 是一种在后台运行的 JavaScript 线程,可以执行复杂的计算任务,而不阻塞主线程。通过将这些任务委托给 Web Workers 来实现并行计算,从而提高 JavaScript 的性能。
-
定时器:通过使用定时器函数(如setTimeout和setInterval),可以在指定的时间间隔执行代码。这可以用来模拟并行处理或者定期执行任务。
-
Event Loop:JavaScript 运行时使用事件循环机制来处理异步任务。在主线程空闲时,它从任务队列中取出待执行的任务并执行,这样可以保证一部分代码的顺序执行,并且不会阻塞主线程。
需要注意的是,尽管以上技术可以提高 JavaScript 的性能和响应能力,但它们仅仅是对单线程的弥补,并不真正将 JavaScript 变成多线程。因此,在编写 JavaScript 代码时,仍然需要注意避免执行长时间的计算或者阻塞操作,以免影响用户体验。
异步编程 是一种机制,允许代码在后台执行,而不会阻塞主线程。
通过使用:
- 回调函数、
- Promise、
- async/await 等方式,
JavaScript 可以将耗时的操作交给后台执行,并设置回调函数,在操作完成后再返回结果。这样,代码可以继续执行后续任务,而不必等待耗时操作的完成。这种异步的特性使得 JavaScript 在执行网络请求、文件读写、定时器等任务时能够更加高效地利用 CPU 时间,提升程序的相应速度和性能。
前端面试之hybrid
Hybrid前端开发是一种结合Web技术和原生移动应用开发技术的方法。它可以使用Web技术(如HTML、CSS和JavaScript)来构建移动应用程序,并通过封装器或容器以原生应用的形式在移动设备上运行。
下面是一些关于Hybrid前端开发的常见问题和答案,可能会在面试中遇到:
-
什么是Hybrid应用?
Hybrid应用是指同时使用Web技术和原生应用技术构建的移动应用。它可以在移动设备上作为一个独立的应用安装和运行,同时又能够访问设备的底层功能和特性。 -
Hybrid应用与原生应用的区别是什么?
Hybrid应用使用Web技术进行开发,具有跨平台的优势,可以在多个平台上运行。而原生应用是为特定平台(如iOS或Android)进行开发,通常会针对特定平台的特性进行优化。 -
Hybrid应用的优势是什么?
- 跨平台支持:Hybrid应用可以在多个平台上运行,无需分别为每个平台开发应用。
- 开发效率高:使用Web技术进行开发,开发人员可以重用已有的Web开发技能和工具。
- 快速迭代:Hybrid应用可以通过Web技术进行实时更新,无需用户手动更新应用程序。
- 灵活性:Hybrid应用可以充分利用设备的底层功能和特性,如相机、地理位置等。
-
Hybrid应用的缺点是什么?
- 性能较低:相比原生应用,Hybrid应用的性能可能会更低,尤其是对于复杂的图形或计算密集型操作。
- 受限制的访问权限:由于安全方面的考虑,Hybrid应用可能无法访问设备的某些底层功能和特性。
- UI限制:Hybrid应用通常使用WebView来呈现内容,因此可能受到WebView的UI限制。
-
Hybrid应用开发中常用的框架是什么?
常见的Hybrid应用开发框架包括Ionic、React Native和Flutter。这些框架提供了一套用于构建Hybrid应用的工具和组件,能够简化开发过程并提高开发效率。
以下是几个常见的Hybrid应用的例子:
-
Facebook应用:Facebook应用是一个经典的Hybrid应用。它使用React Native框架构建,通过JavaScript和React组件来创建用户界面,并在移动设备上以原生应用的形式展示。这样,用户可以使用Web技术来开发应用程序,并且能够获得接近原生应用的性能和体验。
-
Instagram应用:Instagram应用也是一个使用React Native构建的Hybrid应用。类似于Facebook应用,它使用Web技术来开发用户界面,并通过封装器将其打包为原生应用。
-
Airbnb应用:Airbnb应用是一个使用多种Hybrid技术构建的应用。它使用React Native来构建核心功能,并使用WebView来呈现一些特定页面或内容。这样,开发人员可以充分利用React Native的优势,同时也可以使用Web技术来实现更灵活的内容呈现。
-
Uber应用:Uber应用也是一个Hybrid应用的例子。它使用React Native来构建主要的用户界面,并通过原生集成来访问设备的位置服务、地图和导航功能。这样,开发人员可以使用Web技术来快速构建应用程序的界面,并且能够直接访问设备的原生功能。
这些例子说明了Hybrid应用的强大之处,它们可以结合Web技术和原生应用技术,以跨平台的方式开发应用程序,并提供接近原生应用的性能和用户体验。