HTML5的本地存储,区别?
-
sessionStorage
对象- 页面刷新或者。
- sessionStorage对象与服务器会话紧密相关,所以在运行本地文件时不能使用。存储在sessionStorage对象中的数据只能由最初存储数据的页面使用,在多页应用程序中的用处有限。
- 通过setItem()和getItem()方法可以设置sessionStorage的名/值对。
-
localStorage
对象- localStorage对象是客户端持久存储数据的机制。要访问同一个localStorage对象,页面必须来自同一个域(子域不行)、在相同的端口上使用相同的协议。同样可以通过setItem()和getItem()方法给localStorage设置名/值对。
-
Cookie
- 储存在用户本地终端上的数据,是网站为了识别用户和跟踪会话而存储在用户本地终端中的文本数据。
- Cookie是保存在客户端的纯文本文件。比如txt文件。所谓的客户端就是我们自己的本地电脑。
区别
-
页面通信:
localstorage、sessionstorage不能用来与服务器进行通信,
cookie会随请求发送到服务器,作为会话表示,服务器可修改cookie,但是cookie太多会降低页面性能 -
能够存储数据的大小:
localstorage和sessionstorage为5M
cookie为4k -
时效性:
localstorage默认永久有效,除非你手动删除
sessionstorage关闭页面会删除
cookie默认没有时效性,给定失效时间,在失效时间以内有效 -
localstorage和sessionstorage的作用域:
不同浏览器无法共用localstorage和sessionstorage的信息
相同浏览器的不同页面可以共用localstorage的信息,但是不可以共用sessionstorage的信息
有一个cookie种在www.toudiao.com,那在mp.toudiao.com能访问这个cookie吗,为什么
在默认情况下,Cookie 遵循同源策略(Same-Origin Policy),这意味着只有在同一域名、协议和端口的网页才能访问共享 Cookie。如果一个 Cookie 是在 www.toudiao.com 域名下设置的,那么它通常不会被 mp.toudiao.com 域名下的页面访问到。
这是因为域名 www.toudiao.com 和 mp.toudiao.com 在同源策略的定义下被认为是不同的域名,它们有不同的子域名(www 和 mp),因此 Cookie 不会自动在它们之间共享。
如果您希望在不同子域名之间共享 Cookie,您可以采取以下几种方法之一:
-
设置 Cookie 的 Domain 属性:当设置 Cookie 时,可以通过设置其 Domain 属性来指定哪些子域名可以访问该 Cookie。例如,您可以将 Domain 设置为 .toudiao.com,以使 Cookie 在所有 toudiao.com 子域名下都可用。
document.cookie = "name=value; Domain=.toudiao.com; path=/";
-
使用主域名:在设置 Cookie 时,可以使用主域名(例如 toudiao.com)而不是特定的子域名,这将使 Cookie 在所有子域名下都可用。
document.cookie = "name=value; Domain=toudiao.com; path=/";
cookie跨域受到的限制有哪些
同源策略(Same-Origin Policy):默认情况下,Cookie 受到同源策略的限制,即只有在同一域名、协议和端口的网页才能访问共享 Cookie。这意味着一个网站的 Cookie 通常无法被另一个不同域名的网站所访问。
域名限制:Cookie 的域名属性 (Domain) 决定了哪些域名可以访问该 Cookie。通常情况下,Cookie 的域名属性被设置为当前网页所在的域名,但可以通过明确设置域名属性来允许子域名之间共享 Cookie。
安全限制:对于带有 Secure 属性的 Cookie,浏览器要求它们只能通过安全的 HTTPS 连接传输,这有助于确保在传输过程中不会被窃取。因此,如果一个网页是通过 HTTP 加载的,那么它无法访问带有 Secure 属性的 Cookie。
路径限制:Cookie 的路径属性 (path) 可以限制哪些路径下的网页可以访问该 Cookie。通常情况下,Cookie 的路径属性被设置为当前网页的路径,但可以通过明确设置路径属性来限制访问。
HttpOnly 属性:设置 Cookie 的 HttpOnly 属性可以禁止客户端 JavaScript 访问该 Cookie,以增加安全性,防止跨站点脚本攻击 (XSS)。
过期时间:Cookie 可以设置一个过期时间,一旦过期,浏览器将不再发送该 Cookie。这有助于确保敏感信息不会长时间存在于客户端。
限制数量:浏览器通常会限制每个域名下的 Cookie 数量和总大小,以防止滥用和过多的存储。
SameSite 属性:SameSite 属性用于控制 Cookie 是否可以在跨站点请求中传递。通过设置 SameSite 属性,可以减少跨站点请求伪造 (CSRF) 攻击的风险。
收到状态码200,一定经过网络传输吗,有其他case吗
本地缓存:如果之前的请求已经缓存了请求的响应,并且缓存仍然有效(未过期或未失效),客户端可以从本地缓存中获取响应,而不需要进行网络传输。这种情况下也可能收到状态码200,但没有实际的网络传输。
代理服务器缓存:在一些网络环境中,存在代理服务器(如CDN或反向代理服务器),代理服务器可以缓存响应并根据缓存策略返回响应。如果客户端的请求被代理服务器的缓存命中,客户端会收到状态码200,但代理服务器可能会根据自身的缓存策略返回缓存的响应,而不需要从原始服务器获取数据。
HTTP请求的协议吗,比如状态码
-
1xx(Informational):请求已接收,继续处理。
- 100 Continue:客户端可以继续发送请求体。
- 101 Switching Protocols:服务器正在切换协议,客户端需要切换协议以继续。
-
2xx(Successful):请求已成功处理。
- 200 OK:请求成功,服务器返回所请求的数据。
- 201 Created:请求成功,服务器创建了新资源。
- 204 No Content:请求成功,但响应中没有返回内容。
- 206 Partial Content:服务器成功响应部分 GET 请求,通常用于分段下载。
-
3xx(Redirection):需要客户端执行进一步的操作才能完成请求。
- 301 Moved Permanently:资源永久性移动,客户端需要更新链接。
- 302 Found:资源临时移动,客户端应继续使用原始链接。
- 304 Not Modified:资源未修改,客户端可以使用缓存的版本。
- 307 Temporary Redirect:资源临时移动,客户端应继续使用原始链接。
-
4xx(Client Error):客户端请求包含错误或无法完成请求。
- 400 Bad Request:客户端请求有语法错误。
- 401 Unauthorized:请求需要身份验证。
- 403 Forbidden:请求被服务器拒绝,通常因为权限问题。
- 404 Not Found:请求的资源不存在。
- 405 Method Not Allowed:请求中使用了不允许的方法。
- 408 Request Timeout:请求超时。
-
5xx(Server Error):服务器在处理请求时发生错误。
- 500 Internal Server Error:服务器遇到内部错误。
- 501 Not Implemented:服务器不支持请求的功能。
- 503 Service Unavailable:服务器暂时无法处理请求,通常是因为过载或维护。
其他HTTP协议头?TCP跟HTTP什么关系
TCP(Transmission Control Protocol)和HTTP(Hypertext Transfer Protocol)是两个不同但密切相关的协议,它们一起构成了互联网通信的基础。
TCP(Transmission Control Protocol):TCP是一种面向连接的、可靠的传输层协议。它负责在网络上可靠地传输数据,确保数据的完整性、有序性和可靠性。TCP建立了一个可靠的双向通信通道,通过三次握手建立连接,使用序号和确认号来跟踪数据的传输和接收。TCP提供了流量控制、拥塞控制和错误检测等功能,以确保数据能够可靠地从一个点传输到另一个点。TCP是一个底层协议,几乎所有的应用层协议都依赖于它来进行数据传输,包括HTTP。
HTTP(Hypertext Transfer Protocol):HTTP是一种应用层协议,用于在互联网上传输超文本文档,通常用于在Web浏览器和Web服务器之间传递HTML页面、图像、视频、音频和其他资源。HTTP是基于TCP协议的,它使用TCP连接来传输数据。当您在Web浏览器中输入一个URL并请求一个网页时,浏览器会使用HTTP来与Web服务器进行通信,发送HTTP请求并接收HTTP响应,然后在浏览器中呈现网页内容。
简而言之,HTTP是应用层协议,它建立在TCP协议之上,用于在客户端和服务器之间传输Web内容。TCP负责在网络上传输数据的可靠性,而HTTP负责定义如何组织和传递数据,以便在Web上呈现内容。这两个协议一起构成了Web通信的基础架构。
做题:给一个数组,输出所有元素排列组合的结果
PC端和移动端开发的区别
屏幕尺寸和分辨率:
- PC端通常具有较大的屏幕尺寸和高分辨率,而移动端设备(如智能手机和平板电脑)通常拥有较小的屏幕和不同的分辨率。这需要开发人员在移动端上适应不同的屏幕大小和分辨率。
用户交互方式:
- PC端通常使用鼠标和键盘进行交互,而移动端设备使用触摸屏、手势和虚拟键盘。这意味着用户界面和交互模式需要在两者之间进行调整。
浏览器兼容性:
- 在PC端,主要的Web浏览器(如Chrome、Firefox、Edge、Safari)通常具有较高的兼容性,但移动端浏览器(如Mobile Safari、Chrome for Android)可能会有不同的行为和兼容性问题,需要进行额外的测试和处理。
性能和资源限制:
- 移动设备通常具有有限的计算能力、内存和网络带宽,因此需要更加注重性能优化,以确保应用程序在移动端上运行流畅。
操作系统:
- PC端通常运行Windows、macOS或Linux等桌面操作系统,而移动端设备运行Android或iOS等移动操作系统。不同的操作系统有不同的API、生命周期和应用分发渠道。
屏幕方向和响应式设计:
- 移动设备可以以不同的方向(纵向和横向)使用,因此需要支持屏幕旋转和响应式设计,以适应不同的屏幕方向。
导航和页面布局:
- 移动端应用通常采用简化的导航和页面布局,以提供更好的用户体验,而PC端应用可能具有更多的页面元素和复杂的导航。
本地功能:
- 移动设备通常具有许多本地功能,如摄像头、地理定位、传感器等,可以用于增强应用程序的功能,而PC端应用可能没有这些功能或只有有限的本地功能。
总之,PC端和移动端开发之间存在许多区别,开发人员需要根据目标平台的特点进行适当的调整和优化,以提供最佳的用户体验。通常情况下,跨平台开发工具和框架也可以帮助简化在不同平台上开发应用程序的过程。
手写轮询 里面写了一个定时器 问为什么要设置一个定时器
function pollServer() {
// 向服务器发送请求
fetch('https://example.com/api/data')
.then(response => response.json())
.then(data => {
// 处理从服务器获取的数据
console.log('服务器返回的数据:', data);
// 在此可以更新页面或执行其他操作
})
.catch(error => {
// 处理请求失败的情况
console.error('请求失败:', error);
});
}
// 设置轮询的时间间隔(毫秒)
const pollInterval = 5000; // 5 秒
// 启动轮询
setInterval(pollServer, pollInterval);
继承和原型链
在JavaScript中,每个对象都有一个原型对象(prototype)。原型对象就是一个普通的对象,在创建新对象时,可以将该对象作为新对象的原型。原型对象可以包含共享的属性和方法,这些属性和方法可以被新对象继承和访问。对象之间通过原型链(prototype chain)互相关联,形成了一个原型的链条。
当访问对象的属性或方法时,JavaScript会首先在对象本身查找,如果找不到,就会沿着原型链向上查找,直到找到对应的属性或方法,或者到达原型链的顶层(Object.prototype)
- 原型链继承
让一个构造函数的原型是另一个类型的实例,那么这个构造函数new出来的实例就具有该实例的属性。
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
function Parent1() {
this.name = 'parent1';
this.play = [1, 2, 3]
}
function Child1() {
this.type = 'child2';
}
Child1.prototype = new Parent1();
console.log(new Child1());
优点:写法方便简洁,容易理解。
缺点:对象实例共享所有继承的属性和方法。传教子类型实例的时候,不能传递参数,因为这个对象是一次性创建的(没办法定制化)。
- 借用构造函数继承
在子类型构造函数的内部调用父类型构造函数;使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上。
function Parent1(){
this.name = 'parent1';
}
Parent1.prototype.getName = function () {
return this.name;
}
function Child1(){
Parent1.call(this);
this.type = 'child1'
}
let child = new Child1();
console.log(child); // 没问题
console.log(child.getName()); // 会报错
优点:解决了原型链实现继承的不能传参的问题和父类的原型共享的问题。
缺点:借用构造函数的缺点是方法都在构造函数中定义,因此无法实现函数复用。无法机场父类实例上的方法。
- 组合继承(经典继承)
将 原型链 和 借用构造函数 的组合到一块。使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
Parent3.prototype.getName = function () {
return this.name;
}
function Child3() {
// 第二次调用 Parent3()
Parent3.call(this);
this.type = 'child3';
}
// 第一次调用 Parent3()
Child3.prototype = new Parent3();
// 手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play); // 不互相影响
console.log(s3.getName()); // 正常输出'parent3'
console.log(s4.getName()); // 正常输出'parent3'
优点: 解决了原型链继承和借用构造函数继承造成的影响。
缺点: 无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
- 原型式继承
这里不得不提到的就是 ES5 里面的 Object.create 方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。
let parent4 = {
name: "parent4",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
let person4 = Object.create(parent4);
优点是:不需要单独创建构造函数。
缺点是:属性中包含的引用值始终会在相关对象间共享,子类实例不能向父类传参
手写判断A是不是继承B
function isPrototypeOf(parent, child) {
// 使用Object.getPrototypeOf获取child的原型
let prototype = Object.getPrototypeOf(child);
// 递归检查prototype是否等于parent
if (prototype === null) {
return false; // 已经达到原型链的末端,没有找到parent
} else if (prototype === parent) {
return true; // 找到了parent
} else {
// 继续向上查找原型链
return isPrototypeOf(parent, prototype);
}
}
// 示例用法:
const parent = { name: "Parent" };
const child = Object.create(parent);
console.log(isPrototypeOf(parent, child)); // true,child继承自parent
console.log(isPrototypeOf({}, child)); // false,{}不是child的原型
JS的数据类型?为什么bigint有争议
基本数据类型
- 数字(Number):用于表示数值。可以是整数或浮点数。
- 字符串(String):用于表示文本。可以包含任何字符,用单引号(')或双引号(")括起来。
- 布尔(Boolean):表示真(true)或假(false)的值,用于逻辑运算。
- null:表示一个空值或不存在的对象。当你想表示一个不存在的或空的值时,可以使用 null。
- undefined:表示一个未定义的变量或属性。通常在声明变量但未赋值时,默认为 undefined。
- Symbol:Symbol 是一种唯一标识符的数据类型,通常用于对象的属性键,以确保属性的唯一性。
- BigInt:用于表示任意精度的整数,可以处理非常大或非常小的整数值。
引用数据类型
- 对象(Object):对象是 JavaScript 中最常见的引用数据类型之一。它们用于表示复杂的数据结构,可以包含属性和方法。对象可以是普通对象、数组、函数、日期、正则表达式等。
- 数组(Array):数组是一种特殊的对象,用于存储有序的数据集合。数组中的元素可以是任何数据类型,包括基本数据类型和其他引用数据类型。
- 函数(Function):函数是 JavaScript 中的一等公民,也是一种引用数据类型。函数可以存储和传递代码,允许将代码作为参数传递给其他函数,或将函数作为值返回。
- 日期(Date):日期对象用于处理日期和时间。它允许执行日期和时间的各种操作,如日期解析、格式化和计算。
- 正则表达式(RegExp):正则表达式是用于字符串匹配和搜索的模式。它们允许你定义一种文本模式,然后搜索或替换匹配该模式的文本。
- Map 和 Set:Map 是一种键-值对的数据结构,Set 是一种集合,用于存储唯一的值。它们在 ES6 中引入,用于更高级的数据操作。
- Promise:Promise 是用于处理异步操作的对象,它代表了一个未来的值或事件,允许你以更具可读性的方式编写异步代码。
bigInt缺点
- 性能开销:由于 BigInt 是任意精度的,它需要更多的计算资源来执行基本数学运算。在处理大整数时,可能会导致性能下降,特别是与普通整数相比。因此,在性能关键的应用中,需要小心使用 BigInt。
- 语法不直观:BigInt 的语法相对复杂,需要在整数后面添加 n,这与传统整数操作有所不同。这可能会导致代码可读性较低,因为不是所有开发者都熟悉 BigInt 的语法。
- 不适用于所有场景:BigInt 主要用于处理大整数或需要更高精度的数学计算。在大多数情况下,普通整数类型足够满足需求,而不需要引入 BigInt。因此,过度使用 BigInt 可能会使代码变得复杂和难以维护。
- 浏览器兼容性:尽管现代浏览器大多支持 BigInt,但在旧版本的浏览器中可能不受支持。这可能需要考虑向后兼容性问题,或者在不受支持的环境中提供替代方案。
- 内存占用:由于 BigInt 可以表示非常大的整数,它可能会占用大量内存。在处理大量大整数时,需要小心内存占用问题。
场景题 微信手机扫电脑二维码, 说出手机 电脑 服务器三者的交互
手机与电脑之间的交互:
- 用户使用微信手机客户端打开扫描功能,并扫描电脑屏幕上显示的二维码。
- 电脑上的二维码包含一个唯一的标识符,通常是一个令牌(token)或会话 ID,用于标识这次扫描。
- 手机将扫描到的二维码信息发送给微信服务器,包括标识符以及用户手机的相关信息。
手机与服务器之间的交互:
- 微信服务器接收到手机发送的扫描信息,包括二维码的标识符。
- 微信服务器会检查这个标识符,验证其有效性,并确定它是一个电脑端登录请求。
- 如果验证成功,微信服务器将生成一个临时的令牌或会话 ID,并将它发送回到用户的手机客户端。
电脑与服务器之间的交互:
- 电脑端的应用程序(例如微信网页版)会不断轮询微信服务器,询问用户的登录状态。
- 一旦微信服务器确认用户已经在手机端授权登录,它会告知电脑端应用程序,同时提供用户的相关信息。
- 电脑端应用程序使用获得的用户信息,以及与微信服务器建立的临时会话,来进行用户登录和操作。
总结一下,微信手机扫电脑二维码的过程涉及到手机、电脑和微信服务器之间的多轮交互。手机扫描电脑上的二维码后,与微信服务器进行通信,微信服务器验证并授权用户登录,然后将授权信息传递给电脑端应用程序,最终完成用户的登录过程。这种流程可以用于实现电脑端登录,而不需要手动输入用户名和密码。
算法题:构建一个有向图,判断里面是否有环,解释思路
class Graph {
constructor(vertices) {
this.vertices = vertices;
this.map = new Map();
}
addEdge(v, w) {
if (!this.map.has(v)) {
this.map.set(v, []);
}
this.map.get(v).push(w);
}
hasCycle() {
const visited = new Set(); // 已访问的节点
const set = new Set();
for (const vertex of this.map.keys()) { // 遍历图中的每个顶点
if (this.isCyclicUtil(vertex, visited, set)) {
return true; // 如果发现存在环,立即返回 true
}
}
return false;
}
isCyclicUtil(v, visited, set) { // 递归函数,用于深度优先搜索
visited.add(v); // 将当前节点标记为已访问
set.add(v); // 将当前节点添加到递归调用栈
if (this.map.has(v)) { // 遍历当前节点的所有邻居节点
for (const neighbor of this.map.get(v)) {
if (!visited.has(neighbor)) {
if (this.isCyclicUtil(neighbor, visited, set)) { // 如果邻居节点未被访问过,则继续递归访问
return true; // 如果在递归中发现环,返回 true
}
} else if (set.has(neighbor)) { // 如果邻居节点在当前递归调用栈上,说明存在环,返回 true
return true;
}
}
}
set.delete(v); // 递归结束后,将节点从递归调用栈删除
return false;
}
}
// 使用示例
const graph = new Graph(4);
graph.addEdge(0, 1);
graph.addEdge(0, 2);
graph.addEdge(1, 2);
graph.addEdge(2, 0);
// graph.addEdge(2, 3);
// graph.addEdge(3, 3);
console.log(graph)
if (graph.hasCycle()) {
console.log('图中存在环');
} else {
console.log('图中不存在环');
}
websocket刷新页面之后如何恢复连接
WebSocket 是一种基于 TCP 的双向通信协议,通常用于实现实时数据交换。当页面刷新后,WebSocket 连接会断开,需要在页面重新加载后重新建立连接。为了实现在页面刷新后恢复 WebSocket 连接,你可以采取以下方法:
- 使用 Session 或 Cookie 存储标识符:
- 在客户端,将 WebSocket 连接的标识符(例如会话 ID)存储在浏览器的 Session Storage、Local Storage 或 Cookie 中。
- 当页面重新加载后,检查这些存储中是否存在 WebSocket 连接的标识符。
- 如果存在标识符,说明用户之前已经建立了 WebSocket 连接,因此可以使用存储的标识符重新建立连接。
示例代码(使用 Local Storage 存储 WebSocket 标识符):
// 建立 WebSocket 连接
const socket = new WebSocket('ws://example.com');
// 存储 WebSocket 标识符
socket.addEventListener('open', () => {
localStorage.setItem('websocketIdentifier', 'your_identifier_here');
});
// 在页面加载后恢复连接
const savedIdentifier = localStorage.getItem('websocketIdentifier');
if (savedIdentifier) {
const socket = new WebSocket('ws://example.com');
// 使用 savedIdentifier 进行连接恢复操作
}
- 使用服务端状态存储:
- 在服务器端,维护一个映射,将用户的标识符与其 WebSocket 连接关联起来。
- 当用户建立 WebSocket 连接时,将标识符存储在服务端。
- 当用户刷新页面后,通过标识符在服务端查找并恢复与用户关联的 WebSocket 连接。
这两种方法都可以用于在页面刷新后恢复 WebSocket 连接。选择哪种方法取决于你的应用程序需求和架构。不管选择哪种方式,都需要确保能够正确地关联用户和其之前建立的 WebSocket 连接,以便在页面重新加载后重新建立连接并继续通信。
如果想给localstorage添加有效时间,有没有什么思路去实现?
怎么解决跨域(八股)
跨域是如何形成的?
当我们请求一个url的协议、域名、端口三者之间任意一个与当前页面url的协议、域名、端口不同这种现象我们把它称之为跨域。
浏览器在解析JavaScript出于安全方面的考虑,只允许在同域名下页面进行相互资源请求调用,不允许调用其他域名下的页面的对象;简单的理解就是因为JavaScript同源策略的限制。
注意:跨域并不是请求发不出去,请求能发出去,服务器能收到请求并正常返回结果,只是结果被浏览器拦截了,所以页面无法正常使用数据。
跨域会导致:
- 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
- 无法接触非同源网页的 DOM
- 无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)
导致跨域的根本原因是请求浏览器的同源策略导致的 ,而跨域请求报错的原因是: 浏览器同源策略 && 请求是ajax类型 && 请求确实跨域了。
页面发起跨域请求后,浏览器会先发起预检请求,预检通过后,在发起正式请求。如果预检请求不过,浏览器就会停止后面的业务请求,导致访问失败。
解决方法: jsonp,cors,代理转发
点击查看更多
-
jsonp
利用<script src="xxx">
元素的这个天然支持跨域的策略,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。一般是在请求的url中添加一个回调函数参数,服务器的响应将被包裹在这个参数中,从而实现跨域读取数据。<script type="text/javascript"> function handleResponse(data){ alert(data.msg); } </script> <script type="text/javascript" src="http://crossdomain.com/jsonServerResponse?callback=handleResponse">
服务端支持的代码:服务端获取回调函数的名称,然后将数据包裹在回调函数中作为响应返回给客户端。
const express = require('express'); const app = express(); app.get('/data', (req, res) => { const data = { message: 'Hello, World!' }; const callbackName = req.query.callback; // 获取回调函数名称 // 返回数据,并将数据包裹在回调函数中 res.send(`${callbackName}(${JSON.stringify(data)})`); }); app.listen(80, () => { console.log('Server started'); });
-
CORS(Cross-Origin Resource Sharing)
实现CORS通信的关键是服务器,需要在服务器端做一些小小的改造。
只要服务器实现了CORS接口,就可以跨源通信。
在响应头上添加Access-Control-Allow-Origin属性,指定同源策略的地址。同源策略默认地址是网页的本身。只要浏览器检测到响应头带上了CORS,并且允许的源包括了本网站,那么就不会拦截请求响应。用Express框架,配置了一个中间件来处理CORS。设置了允许跨域的源
http://www.example.com
const express = require('express'); const app = express(); // 配置CORS中间件 app.use(function(req, res, next) { // 允许特定域的跨域访问 res.header('Access-Control-Allow-Origin', 'http://www.example.com'); // 允许发送跨域请求的HTTP方法 res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); // 允许的请求头 res.header('Access-Control-Allow-Headers', 'Content-Type'); // 是否允许发送Cookie res.header('Access-Control-Allow-Credentials', 'true'); next(); }); // 跨域请求处理 app.get('/data', (req, res) => { const data = { message: 'Hello, World!' }; res.send(data); }); app.listen(80, () => { console.log('Server started'); });
-
代理服务器
在客户端和服务端中间加一个代理服务器,使得客户端向代理服务器发送请求,再由代理服务器向真正的服务器发送请求,并将结果转发给客户端。vue 在 vue.config.js 配置代理服务器
// vue.config.js module.exports = { devServer: { proxy: { '/api': { target: 'http://api.example.com', // 目标服务器的URL changeOrigin: true, pathRewrite: { '^/api': '', // 可选,用于重写路径 }, }, }, }, };
讲一下对JSONP的理解
JSONP(JSON with Padding)是一种用于在跨域请求数据的技术,它允许在不受同源策略限制的情况下从不同域的服务器获取数据。JSONP 的工作原理如下:
-
请求方式:通常,使用 JSONP 时,不使用常规的 XMLHttpRequest 或 Fetch 请求。相反,它是通过动态创建一个
<script>
标签来实现的。 -
回调函数:在客户端代码中定义一个回调函数(callback function),该函数将在服务器响应时被调用。这个回调函数的名称通常作为请求的一个参数,以便服务器知道如何包装数据以供客户端解析。
-
服务器响应:服务器收到 JSONP 请求后,会将数据包装在回调函数中,然后作为 JavaScript 代码返回给客户端。这意味着响应内容将被视为 JavaScript 代码而不是 JSON 数据。
-
客户端解析:客户端浏览器接收到响应后,会执行包含数据的 JavaScript 代码,从而触发回调函数,并将数据传递给这个回调函数。这使得客户端能够访问和处理来自不同域的数据。
JSONP 的优点包括:
- 跨域请求:JSONP 允许从不同域获取数据,克服了浏览器的同源策略限制。
- 简单易用:使用 JSONP 相对简单,只需定义回调函数并创建一个 script 标签来发出请求。
- 广泛支持:JSONP 可以在几乎所有浏览器中使用,并且不需要特殊的配置或库。
JSONP 的缺点和安全风险:
- 安全性问题:由于 JSONP 是通过
script
标签加载的 JavaScript 代码,因此存在安全风险,可能会被用于潜在的跨站脚本攻击(XSS)。 - 仅支持 GET 请求:JSONP 通常只支持 GET 请求,因此不适用于需要进行更复杂操作(例如 POST 请求)的情况。
- 依赖于服务器支持:JSONP 的服务器响应需要包含特定的回调函数包装,因此需要服务器端的支持。
有用过web worker吗
Web Worker 是一项用于在浏览器中进行多线程 JavaScript 编程的技术,它允许你将一些计算密集型或长时间运行的任务在后台线程中执行,以避免阻塞主线程,从而提高 Web 应用的性能和响应性。
以下是一些关于 Web Worker 的主要概念和用法:
-
主线程和 Worker 线程:Web Worker 有两种类型,分别是主线程和 Worker 线程。主线程是浏览器的主要线程,负责处理用户界面和与用户的交互。Worker 线程是在后台运行的线程,可以执行一些计算密集型任务。
-
线程间通信:主线程和 Worker 线程之间可以通过消息传递进行通信。主线程可以将消息发送给 Worker 线程,Worker 线程可以将消息发送回主线程。这种通信方式是异步的。
-
不共享内存:主线程和 Worker 线程之间不共享内存,这意味着它们不能直接访问彼此的变量和数据。通信是通过消息传递实现的。
-
限制:Worker 线程有一些限制,例如不能访问 DOM、全局变量、window 对象等。它们只能执行纯粹的 JavaScript 代码,没有浏览器环境的特性。
-
创建 Worker:要创建一个 Worker 线程,你可以使用 new Worker(‘worker.js’),其中 ‘worker.js’ 是包含 Worker 线程代码的 JavaScript 文件的 URL。
-
事件监听:在 Worker 线程中,你可以监听 message 事件来接收来自主线程的消息,并使用 postMessage() 方法向主线程发送消息。
Web Worker 在以下情况下特别有用:
- 执行复杂的计算任务,以避免阻塞用户界面。
- 处理大量数据的计算或操作。
- 加载和解析大型文件,如图像或视频。
- 在后台执行网络请求,以减轻主线程的负担。
- 需要注意的是,Web Worker 并不适用于所有场景,因为它们引入了一些复杂性和通信开销。你需要根据具体的应- 用需求来决定是否使用 Web Worker。此外,Web Worker 在不同浏览器中的支持情况也有所不同,需要进行兼容性测试。
为什么浏览器是单线程
浏览器是单线程的原因与其设计和用途有关,主要有以下几个方面的考虑:
-
简化设计:单线程模型使浏览器的设计更加简单和易于实现。多线程模型可能会引入复杂的同步和竞争条件问题,增加了浏览器的复杂性和难度。
-
安全性:多线程模型可能会引发安全性问题,如数据竞争和跨线程脚本执行问题。单线程模型可以减轻这些安全问题的发生。
-
避免死锁:在多线程环境中,可能会发生死锁的情况,其中多个线程相互等待对方释放资源。单线程模型避免了这种问题。
-
JavaScript 单线程:JavaScript 是浏览器的核心语言之一,它是单线程执行的。浏览器的主要任务之一是解析和执行 JavaScript 代码,因此浏览器整体上采用了单线程模型以保持一致性。
虽然浏览器是单线程的,但它通过使用异步编程技术(例如事件循环和回调函数)来实现并发性。这意味着浏览器可以在单个线程上同时处理多个任务,而不会阻塞用户界面的响应。例如,浏览器可以同时处理用户的输入、网络请求、渲染页面等任务,通过异步操作来提高用户体验。
此外,HTML5 引入的 Web Worker 技术允许在后台运行脚本,从而在某种程度上实现了多线程的效果,但这些 Worker 线程无法直接访问 DOM,因此主线程仍然负责管理用户界面。总体而言,单线程模型是浏览器的核心设计之一,它在简化和保证浏览器的稳定性和安全性方面发挥了关键作用。
css position的定位方式(八股)
Static(默认值):
默认值,元素按照正常文档流进行排列,不进行特殊定位。
Relative(相对定位):
元素相对于其正常位置进行偏移,但不会影响其他元素的位置。
使用 top、right、bottom 和 left 属性来指定相对偏移的距离。
Absolute(绝对定位):
元素相对于其最近的具有定位属性(非 static)的祖先元素进行定位。
使用 top、right、bottom 和 left 属性来指定相对于祖先元素的偏移距离。
Fixed(固定定位):
元素相对于浏览器窗口进行定位,不随页面滚动而移动。
使用 top、right、bottom 和 left 属性来指定相对于窗口的偏移距离。
Sticky(粘性定位):
元素在滚动到达特定位置时变为固定定位,然后在滚动超出一定范围后变回相对定位。
使用 top、right、bottom 和 left 属性来指定定位位置和切换的阈值。
css动画写过吗,写一个从左往右移动的动画
CSS动画主要使用@keyframes规则以及transition和animation属性来创建。@keyframes规则用于创建动画,而transition和animation属性用于控制动画的效果和播放。
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.box {
width: 100px;
height: 100px;
background-color: #3498db;
animation: moveRight 2s ease-in-out infinite;
}
@keyframes moveRight {
0% {
transform: translateX(0);
}
50% {
transform: translateX(200px); /* 设置移动的距离 */
}
100% {
transform: translateX(0);
}
}
</style>
<body>
<div class="box"></div>
</body>
</html>
什么时候用OPTION请求
OPTIONS请求即预检请求,可用于检测服务器允许的http方法。当发起跨域请求时,由于安全原因,触发一定条件时浏览器会在正式请求之前自动先发起OPTIONS请求,即CORS预检请求,服务器若接受该跨域请求,浏览器才继续发起正式请求。
规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。
总结触发option条件:
-
使用自定义请求头:如果在实际请求中使用了自定义的请求头(如 Authorization、Content-Type 为某些特殊值等),浏览器会发送预检请求来确认服务器是否支持这些自定义头部。
-
使用某些请求方法:对于某些非简单请求方法(例如,使用了 PUT、DELETE 等方法),浏览器也会发送预检请求。
-
跨域调用。使用了 CORS 安全头部:如果在实际请求中使用了某些需要服务器支持的 CORS 安全头部(如 withCredentials),浏览器会发送预检请求。
-
其他复杂的请求条件:根据浏览器的实现,还可能根据其他复杂的请求条件触发预检请求。
跨域的场景下一定会发OPTION吗
不会,如果是get方法,直接发送
TCP是如何保证传输的稳定性和可靠性的?
-
三次握手(Three-Way Handshake):在建立连接时,客户端和服务器之间会执行三次握手的过程,以确保双方都准备好进行通信。这个过程包括以下步骤:
- 客户端向服务器发送一个同步(SYN)请求。
- 服务器接收到请求后,回复一个确认(ACK)和同步(SYN)。
- 客户端接收到服务器的响应后,再次回复一个确认(ACK)。
- 通过三次握手,双方确保了连接的建立,避免了不必要的数据传输。
-
数据分段和序号:TCP将数据划分为小的数据段(segment),每个数据段都包含一个序号,用于指示数据在整个数据流中的位置。这样可以确保数据在传输过程中不会丢失或混淆。
-
确认和重传:接收方会对接收到的数据段发送确认(ACK)信号,告知发送方数据已成功接收。如果发送方在一定时间内未收到确认,它会认为数据丢失,然后重新发送数据。
-
流量控制:TCP使用滑动窗口(sliding window)机制来控制数据流量,确保发送方不会发送太多数据导致接收方无法处理。接收方可以调整窗口大小,告诉发送方可以接收的数据量。
-
拥塞控制:TCP会检测网络中的拥塞情况,如果发现拥塞,它会减慢数据的发送速度以避免进一步加重网络负担。拥塞控制通过使用拥塞窗口和慢启动等算法来实现。
- 慢开始
- 拥塞避免
- 拥塞发生
- 快恢复
-
超时和重试:如果数据段丢失或延迟太高,TCP会设置一个超时时间,如果在超时时间内未收到确认,它会重新发送数据。这确保了即使出现网络问题,数据也会被可靠地传输。
-
有序交付:TCP会确保数据按照正确的顺序交付给应用程序,即使数据段到达的顺序与发送的顺序不同。
-
连接维护和释放:TCP在连接建立后,会维护连接状态,确保双方可以随时通信。连接终止时,会执行四次挥手的过程来优雅地关闭连接。
为什么要进行四次挥手?
TCP(Transmission Control Protocol)连接的四次挥手是用于安全、可靠地关闭一个已经建立的连接。四次挥手的目的是确保双方在关闭连接时都有机会完成未完成的数据传输和处理。
以下是四次挥手的过程:
- 第一次挥手(FIN1):一方(通常是客户端)首先决定要关闭连接,它发送一个带有 FIN 标志的TCP报文段给另一方,表示它不再发送数据,但仍然可以接收数据。
- 第二次挥手(ACK1):另一方(通常是服务器)接收到第一次挥手后,会发送一个带有 ACK 标志的TCP报文段作为响应,确认收到了关闭请求。
- 第三次挥手(FIN2):一方(通常是服务器)决定关闭连接,它发送一个带有 FIN 标志的TCP报文段给对方,表示它也不再发送数据,但仍然可以接收数据。
- 第四次挥手(ACK2):另一方(通常是客户端)接收到第三次挥手后,会发送一个带有 ACK 标志的TCP报文段作为响应,确认收到了关闭请求。
四次挥手之所以需要分为四个步骤,是为了确保数据的可靠传输和完整性。具体原因如下:
- 允许双方完成未完成的数据传输:在关闭连接之前,双方有可能还有未完成的数据传输,四次挥手允许双方传输剩余的数据。
- 确保双方都知道对方要关闭连接:通过四次挥手,双方都能够明确地知道对方已经完成了数据传输并要关闭连接,避免了不必要的等待和超时。
- 防止连接的一方处于半关闭状态:四次挥手确保双方都能够完全关闭连接,避免了连接的一方处于半关闭状态,仍然等待接收数据。
总之,四次挥手是为了保证连接的可靠关闭,确保数据完整性,防止连接的一方陷入等待状态。它是TCP连接管理的重要部分,确保了网络通信的可靠性。
Https的加密过程?
建立连接:
- 客户端向服务器发起连接请求,请求建立HTTPS连接。
- 服务器收到请求后,准备与客户端建立加密连接。
服务器证书:
- 服务器将自己的数字证书发送给客户端。这个证书包含了服务器的公钥、服务器的域名信息以及证书的颁发机构(CA)签名。
证书验证:
- 客户端接收到服务器的证书后,会验证证书的有效性。这包括检查证书是否过期、是否由受信任的CA签发以及证书中的域名信息是否匹配访问的域名。
- 如果证书验证失败,客户端会警告用户或中止连接。
生成共享密钥:
- 客户端生成一个随机的对称密钥(称为会话密钥或对话密钥)。
- 客户端使用服务器的公钥对会话密钥进行加密,然后将加密后的会话密钥发送给服务器。
解密会话密钥:
- 服务器使用自己的私钥解密客户端发送的会话密钥,从而获得该密钥。
加密通信:
- 客户端和服务器现在都拥有了相同的会话密钥,他们使用这个密钥来加密和解密通信的数据。
- 数据在传输过程中使用对称加密算法进行加密和解密,对称加密速度较快,因此适用于大部分通信。
安全通信:
- 客户端和服务器之间的所有数据传输都是加密的,即使被拦截,也无法轻松解密。
维持连接:
- 客户端和服务器之间的连接可以持续维护,以进行后续的通信。
HTTPS通过使用非对称加密(公钥加密)来安全地传输对称密钥,然后使用对称加密来加密实际数据传输,实现了数据的机密性和完整性。这种双重加密保证了通信的安全性,使得恶意用户无法轻松地截取或篡改传输的数据。同时,证书的使用确保了客户端正在连接到预期的服务器,而不是恶意的中间人。这使得HTTPS成为了保护敏感信息的标准协议,广泛用于在线交易、登录、个人信息传输等安全性要求较高的场景。
抓包的流程是什么?
抓包工具通过监听计算机网络接口来获取数据包,从而实现数据的捕获。下面是抓包工具获取数据的一般流程:
-
网络接口监听:抓包工具会选择一个或多个网络接口(例如,以太网、Wi-Fi、虚拟网卡等)进行监听。这些接口允许工具捕获通过它们传输的数据包。
-
数据包捕获:一旦网络接口被选择并启动监听,抓包工具开始捕获从该接口收到的所有数据包。这包括来自本机发出和接收的所有网络流量,不仅限于特定应用程序或协议。
-
数据包分析:捕获的数据包被传送到抓包工具的内部引擎,该引擎会分析数据包的内容、协议类型、源和目标地址、端口号等信息。
-
数据包存储:抓包工具通常会将捕获的数据包存储在内存中或磁盘上,以供后续分析和查看。保存数据包的方式和格式因工具而异。
-
数据包展示:抓包工具提供一个用户界面,允许用户查看捕获的数据包,查看详细信息,筛选和过滤数据包,以及进行分析。
-
数据包过滤:抓包工具通常允许用户根据协议、源或目标地址、端口号等条件来筛选和过滤数据包,以便专注于感兴趣的流量。
-
数据包分析:用户可以使用抓包工具来分析数据包,检查协议头、数据内容、响应时间等信息,以便解决网络问题、分析性能、调试应用程序等。
总的来说,抓包工具通过监听网络接口、捕获数据包、分析数据包的内容和元数据,然后展示和存储这些信息,使用户能够深入了解网络通信和解决问题。抓包工具的功能和特性会因工具的类型和用途而有所不同,但其基本原理是类似的。
证书的作用是什么?
- 数据加密:HTTPS 证书用于加密在客户端和服务器之间传输的数据。这意味着在数据传输过程中,即使有人拦截了数据包,也无法轻松解密其中的内容。数据加密确保用户的敏感信息(如登录凭据、信用卡号码等)在传输过程中得到保护。
- 客户端接收到服务器的证书后,会验证证书的有效性。这包括检查证书是否过期、是否由受信任的CA签发以及证书中的域名信息是否匹配访问的域名。
- 如果证书验证失败,客户端会警告用户或中止连接。
写一下消息订阅模式的代码
有了解过其他设计模式吗
单例模式(Singleton Pattern):单例模式确保一个类只有一个实例,并提供一个全局访问点。这在需要限制某个类的实例数量时非常有用,例如数据库连接池、日志记录器等。
- 数据库连接池:确保只有一个数据库连接池的实例,以节省资源和提高性能。
- 配置管理器:管理应用程序的配置信息,以避免重复的配置对象。
工厂模式(Factory Pattern):工厂模式用于创建对象的过程,通过将对象的创建抽象化,客户端代码不需要了解对象的具体创建方式。工厂模式分为简单工厂、工厂方法和抽象工厂等不同变种。
- UI组件库:根据不同的输入参数动态创建不同类型的UI组件。
- 日志记录器工厂:根据不同的日志记录需求创建不同类型的日志记录器。
观察者模式(Observer Pattern):观察者模式定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。这在事件处理和UI组件通信中常见。
- 事件处理:在UI组件中注册事件监听器,以便在事件发生时自动执行特定操作。
- 消息通知系统:在多个模块之间传递状态变化通知,以便它们可以响应状态的变化。
策略模式(Strategy Pattern):策略模式定义了一系列算法,将它们封装起来,并使它们可以相互替换。这使得算法可以独立于客户端代码变化,从而提高了代码的灵活性。
- 排序算法:允许在运行时选择不同的排序算法,而不改变客户端代码。
- 支付方式:根据用户选择的支付方式,使用不同的支付策略进行支付。
装饰器模式(Decorator Pattern):装饰器模式用于在不改变对象接口的情况下动态地扩展对象的功能。这允许你在运行时添加新的行为,同时保持代码的开放-封闭原则。
- 文件流操作:在文件读写操作上叠加缓冲、加密、压缩等功能。
- 图形界面控件:在UI组件上添加滚动条、边框、阴影等装饰。
观察者模式原理是怎么样的
观察者模式(Observer Pattern)是一种行为设计模式,用于定义一对多的依赖关系,其中一个对象(称为主题或可观察者)维护一组依赖于它的对象(称为观察者),当主题的状态发生变化时,它会通知所有的观察者,使它们能够自动更新。
观察者模式的原理如下:
-
主题(Subject):主题是被观察的对象,它维护一个观察者列表,提供方法来添加、删除和通知观察者。主题通常包含一个状态,当状态发生变化时,会通知观察者。
-
观察者(Observer):观察者是依赖于主题的对象,它定义一个更新方法(通常称为回调方法或通知方法),主题在状态变化时会调用观察者的更新方法。
-
注册和解注册:观察者可以通过注册(订阅)主题来接收通知,也可以通过解注册(取消订阅)主题来停止接收通知。
-
通知机制:主题维护一个观察者列表,并提供方法来通知观察者。当主题的状态发生变化时,它会遍历观察者列表,调用每个观察者的更新方法,将状态变化通知给它们。
观察者模式的优点包括:
分离主题和观察者:主题和观察者之间的耦合度较低,可以独立扩展和修改。
支持广播通知:主题可以通知多个观察者,实现了一对多的关系。
开放-封闭原则:可以很容易地添加新的观察者,而无需修改主题的代码。
观察者模式的典型应用包括事件处理系统、图形用户界面(GUI)、消息通知系统、发布-订阅系统等。在这些应用中,观察者模式可以用于实现事件处理、用户界面元素的更新、消息传递等功能。
你知道js在观察数据和状态是否发生变化是怎么做的吗
-
手动轮询:最简单的方法是定期(例如,每隔一定时间间隔)检查数据或状态的变化。这可以通过 setInterval 函数来实现。然而,这种方法效率较低,不适用于需要实时性的情况,且会占用不必要的计算资源。
-
事件监听器:JavaScript 提供了事件机制,你可以通过添加事件监听器来监听数据或状态的变化。例如,对于 DOM 元素,可以使用 addEventListener 来监听事件,对于自定义对象,可以实现自定义事件,并使用 addEventListener 进行订阅。
-
观察者模式:观察者模式是一种设计模式,它允许对象(被观察者)维护一组观察者,当被观察者的状态发生变化时,通知观察者执行相应的操作。这种方式更灵活,适用于复杂的数据和状态管理。
-
使用框架和库:现代的 JavaScript 框架和库(如 Vue.js、React、Angular 等)提供了内置的数据绑定和状态管理机制。通过这些框架,你可以声明性地定义数据和视图之间的关系,并自动处理状态的变化检测和更新。
轮播图怎么实现的,手写原生代码(可以用伪代码)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
*{
margin: 0;
padding: 0;
}
.banner{
width: 1300px;
height: 700px;
margin: auto;
margin-top: 100px;
border: 0px ;
position: relative;
overflow: hidden;
text-align: center;
}
.banner:hover{
width:1300px;
height: 700px;
}
.banner .banner_pic .pic{
display: none;
}
.banner .banner_pic .current{
display: block;
}
.banner .banner_pic img{
width: 1200px;
height: 600px;
}
</style>
<script>
window.onload = function(){
var current_index = 0 ;
var pic_div = document.getElementById("banner_pic").getElementsByTagName("div");
var timer = window.setInterval(autoChange,3000);
function autoChange(){
++current_index;
for(var i = 0 ; i < pic_div.length ; i++ ){
if(i == current_index){
pic_div[i].className="current";
}else{
pic_div[i].className="pic";
}
if(current_index==pic_div.length){
current_index=0;
i=-1;
}
}
}
}
</script>
</head>
<body>
<div class="banner">
<!-- 图片部分 -->
<div id="banner_pic" class="banner_pic">
<div class="current"><a href="#">
<img src="https://figurebed-ladidol.oss-cn-chengdu.aliyuncs.com/img/202205051426174.jpeg" alt=""></a></div>
<div class="pic">
<img src="https://figurebed-ladidol.oss-cn-chengdu.aliyuncs.com/img/202205051426398.jpeg" alt=""></div>
<div class="pic">
<img src="https://figurebed-ladidol.oss-cn-chengdu.aliyuncs.com/img/202205051427281.jpeg" alt=""></div>
</div>
</div>
</body>
</html>
排序算法了解过哪些,它们的区别是什么,使用场景是怎么样的
冒泡排序(Bubble Sort):
- 基本思想:比较相邻的元素,如果顺序不对则交换它们,每次外层循环都会将最大的元素移到最后。
- 时间复杂度:最坏情况 O(n^2),平均情况 O(n^2),最好情况 O(n)。
- 适用场景:小型数据集,对稳定性排序有要求。
function bubbleSort(array) {
const len = array.length
if (len < 2) return array
for (let i = 0; i < len; i++) {
for (let j = 0; j < i; j++) {
if (array[j] > array[i]) {
const temp = array[j]
array[j] = array[i]
array[i] = temp
}
}
}
return array
}
选择排序(Selection Sort):
- 基本思想:找到数组中的最小值,选中它并将其放置在第一位,接着找到第二个最小值,选中它并将其放置到第二位,然后继续寻找。
- 时间复杂度:最坏情况 O(n^2),平均情况 O(n^2),最好情况 O(n^2)。
- 适用场景:小型数据集,不稳定排序。
const selectionSort = function(arr) {
for(let i = 0; i < arr.length - 1; ++i) {
// 假设最小的值是当前的下标
let indexMin = i;
//遍历剩余长度找到最小下标
for(let j = i; j < arr.length; ++j) {
if(arr[j] < arr[indexMin] ) {
indexMin = j;
}
}
if(indexMin !== i) {
//交换当前下标i与最小下标的值,重复this.length次
const temp = arr[i];
arr[i] = arr[indexMin];
arr[indexMin] = temp;
}
}
};
插入排序(Insertion Sort):
- 基本思想:将元素逐个插入到已排序的部分中,保持已排序部分的有序性。
- 时间复杂度:最坏情况 O(n^2),平均情况 O(n^2),最好情况 O(n)。
- 适用场景:小型数据集,对稳定性排序有要求,对于几乎已经有序的数据表现较好。
function insertSort(array) {
const len = array.length
let current
let prev
for (let i = 1; i < len; i++) {
current = array[i]
prev = i - 1
while (prev >= 0 && array[prev] > current) {
array[prev + 1] = array[prev]
prev--
}
array[prev + 1] = current
}
return array
}
快速排序(Quick Sort):
- 基本思想:选择一个基准元素,将比基准小的元素移到左边,比基准大的元素移到右边,然后递归地对左右两侧进行排序。
- 时间复杂度:最坏情况 O(n^2),平均情况 O(n log n),最好情况 O(n log n)。
- 适用场景:大型数据集,通常用于实际应用中。
let quickSort = (arr) => {
if (arr.length < 2) {
return arr;
}
let left = [];
let right = [];
let numIndex = Math.floor(arr.length / 2);
let num = arr.splice(numIndex, 1);
//这里注意不能像下面这样直接写,因为splice会直接改变数组,这样在最后面连上num,才没问题
// let num = arr[Math.floor(arr.length / 2)];
arr.forEach((item) => {
if (item < num) {
left.push(item);
} else if (item > num) {
right.push(item);
}
});
return quickSort(left).concat(num, quickSort(right));
};
console.log(quickSort(a));
原地快排
function quickSort(arr,left,right){
let len = arr.length;
left = left!==undefined ? left:0;
right = right!==undefined ? right:len-1;
// 递归,终止条件是left>=right
if(left<right){
let index = sortAndFindIndex(arr,left,right);
quickSort(arr,left,index-1);
quickSort(arr,index+1,right);
}
return arr
}
// 小于基准点的放在前面,大于基准点的放在后面,并获取基准点的索引
function sortAndFindIndex(arr,left,right){
// 获取当前基准点索引对应的值
const baseValue = arr[left];
let index = left+1;
for(let i =index;i<=right;i++){
if(baseValue>arr[i]){
swap(arr,index,i);
index++;
}
}
// -1是因为最后依次符合条件的i加1了
swap(arr,index-1,left);
return index-1;
}
function swap(arr,i,j){
let temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
归并排序(Merge Sort):
- 基本思想:将数组分为两半,分别对两半进行排序,然后将两个有序的子数组合并成一个有序的数组。
- 时间复杂度:最坏情况 O(n log n),平均情况 O(n log n),最好情况 O(n log n)。
- 适用场景:大型数据集,对稳定性排序有要求。
function fn(arr) {
function sort(left, right) {
let i = 0;
let j = 0;
let result = [];
while (left[i] && right[j]) {
if (left[i] < right[j]) {
result.push(left[i++]);
} else {
result.push(right[j++]);
}
}
while (left[i]) {
result.push(left[i++]);
}
while (right[j]) {
result.push(right[j++]);
}
return result;
}
function fen(arr) {
if (arr.length < 2) return arr;
let midIndex = Math.floor(arr.length / 2);
let left = arr.slice(0, midIndex);
let right = arr.slice(midIndex, arr.length);
return sort(fen(left), fen(right));
}
return fen(arr);
}
堆排序(Heap Sort):
- 基本思想:将数组构建成一个最大堆(或最小堆),然后将堆顶元素与堆底元素交换,然后重新构建堆,重复此过程直到排序完成。
- 时间复杂度:最坏情况 O(n log n),平均情况 O(n log n),最好情况 O(n log n)。
- 适用场景:大型数据集,原地排序,不需要稳定性。
var a = [1, 3, 6, 3, 23, 76, 1, 34, 222, 6, 456, 221];
function heap_sort(arr) {
var len = arr.length
var k = 0
function swap(i, j) {
var temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
function max_heapify(start, end) {
var dad = start
var son = dad * 2 + 1
if (son >= end) return
if (son + 1 < end && arr[son] < arr[son + 1]) {
son++
}
if (arr[dad] <= arr[son]) {
swap(dad, son)
max_heapify(son, end)
}
}
for (var i = Math.floor(len / 2) - 1; i >= 0; i--) {
max_heapify(i, len)
}
for (var j = len - 1; j > k; j--) {
swap(0, j)
max_heapify(0, j)
}
return arr
}
heap_sort(a); // [1, 1, 3, 3, 6, 6, 23, 34, 76, 221, 222, 456]
希尔排序(Shell Sort):
- 基本思想:将数组分成若干子数组,对每个子数组进行插入排序,然后逐渐减小子数组的大小,最终整个数组变成有序。
- 时间复杂度:最坏情况 O(n^2),平均情况取决于间隔序列的选择。
- 适用场景:大型数据集,对原地排序有要求。
计数排序(Counting Sort):
- 基本思想:统计每个元素出现的次数,然后根据计数结果将元素放回原数组的正确位置。
- 时间复杂度:最坏情况 O(n + k),其中 k 是非负整数的范围。
- 适用场景:适用于非负整数排序,适用于数据范围不大的情况。
桶排序(Bucket Sort):
- 基本思想:将数据分割成若干个桶,每个桶内部使用其他排序算法进行排序,然后合并桶中的数据。
- 时间复杂度:取决于桶的数量和每个桶内部排序的复杂度。
- 适用场景:适用于均匀分布的数据,数据范围不大。