js 算法面试题40道题,前端经典面试题20道-附详细答案
以下是一些前端经典面试题:
- 什么是闭包?如何使用闭包?
- 什么是事件冒泡和事件捕获?它们有什么区别?
- 什么是跨域?如何解决跨域问题?
- 什么是事件委托?为什么要使用事件委托?
- 什么是原型链?如何实现继承?
- 什么是防抖和节流?如何实现?
- 什么是事件循环(Event Loop)?如何理解异步编程?
- 什么是异步和同步?它们有什么区别?
- 什么是浏览器的同源策略?如何绕过同源策略?
- 什么是虚拟DOM?如何工作?
- 什么是CSS盒模型?如何计算盒模型的宽度和高度?
- 什么是CSS选择器的优先级?如何计算优先级?
- 什么是CSS预处理器?常见的CSS预处理器有哪些?
- 什么是响应式设计?如何实现响应式布局?
- 什么是移动端适配?如何实现移动端适配?
- 什么是前端性能优化?有哪些常见的性能优化策略?
- 什么是SEO优化?如何进行前端的SEO优化?
- 什么是HTTP协议?常见的HTTP状态码有哪些?
- 什么是RESTful API?如何设计RESTful API?
- 什么是前端安全问题?如何防止常见的前端安全漏洞?
这些问题涵盖了前端开发的各个方面,希望对你的面试准备有所帮助!
经典面试题答案
- 闭包是指在函数内部创建一个新的作用域,并且可以访问外部函数的变量和参数。闭包可以用来创建私有变量和实现模块化。
示例代码:
function outerFunction() {
var outerVariable = 'Hello';
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
var inner = outerFunction();
inner(); // 输出 'Hello'
在这个例子中,innerFunction
是一个闭包,它可以访问 outerFunction
的变量 outerVariable
。当调用 outerFunction
并将返回的 innerFunction
赋值给 inner
后,inner
成为了一个闭包,可以在任何地方调用并访问 outerVariable
。
- 作用域是指变量和函数的可访问范围。JavaScript 有全局作用域和函数作用域。全局作用域中定义的变量和函数可以在任何地方访问,函数作用域中定义的变量和函数只能在函数内部访问。
示例代码:
var globalVariable = 'Hello';
function myFunction() {
var localVariable = 'World';
console.log(globalVariable); // 输出 'Hello'
console.log(localVariable); // 输出 'World'
}
myFunction();
console.log(globalVariable); // 输出 'Hello'
console.log(localVariable); // 报错,localVariable 不在全局作用域中
在这个例子中,globalVariable
是一个全局变量,可以在任何地方访问。localVariable
是一个函数作用域变量,只能在 myFunction
函数内部访问。
- 事件委托是指将事件绑定到父元素上,然后通过事件冒泡机制来处理子元素的事件。可以减少事件绑定的数量,提高性能。
示例代码:
<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
var list = document.getElementById('myList');
list.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
console.log(event.target.textContent);
}
});
在这个例子中,将点击事件绑定到 myList
元素上。当点击 myList
的子元素 li
时,通过判断 event.target.tagName
是否为 LI
,来确定点击的是哪个子元素。
- 原型链是指对象通过其原型链来寻找属性和方法。可以通过将一个对象的原型设置为另一个对象来实现继承。
示例代码:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello, ' + this.name);
};
function Student(name, grade) {
this.name = name;
this.grade = grade;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.sayGrade = function() {
console.log('I am in grade ' + this.grade);
};
var student = new Student('Alice', 5);
student.sayHello(); // 输出 'Hello, Alice'
student.sayGrade(); // 输出 'I am in grade 5'
在这个例子中,Person
是一个构造函数,Student
是继承自 Person
的构造函数。通过将 Student.prototype
设置为 Person.prototype
的一个新实例,实现了 Student
对象继承了 Person
对象的属性和方法。
- 防抖是指在一定时间内只执行一次函数,常用于处理频繁触发的事件。节流是指一定时间内只执行一次函数,常用于处理持续触发的事件。
示例代码(防抖):
function debounce(func, delay) {
var timeoutId;
return function() {
var context = this;
var args = arguments;
clearTimeout(timeoutId);
timeoutId = setTimeout(function() {
func.apply(context, args);
}, delay);
};
}
function handleInput() {
console.log('Input changed');
}
var debouncedHandleInput = debounce(handleInput, 500);
document.getElementById('myInput').addEventListener('input', debouncedHandleInput);
在这个例子中,debounce
是一个防抖函数,它接受一个函数和延迟时间作为参数,并返回一个新的函数。当调用返回的函数时,会清除之前的定时器并重新设置一个新的定时器。在延迟时间内,如果再次调用返回的函数,会重新计时延迟时间。只有在延迟时间结束后,才会执行传入的函数。
示例代码(节流):
function throttle(func, delay) {
var lastCallTime = 0;
return function() {
var context = this;
var args = arguments;
var currentTime = Date.now();
if (currentTime - lastCallTime >= delay) {
func.apply(context, args);
lastCallTime = currentTime;
}
};
}
function handleScroll() {
console.log('Scrolled');
}
var throttledHandleScroll = throttle(handleScroll, 500);
window.addEventListener('scroll', throttledHandleScroll);
在这个例子中,throttle
是一个节流函数,它接受一个函数和时间间隔作为参数,并返回一个新的函数。当调用返回的函数时,会判断当前时间与上一次调用的时间间隔是否大于等于时间间隔。如果是,就执行传入的函数,并更新上一次调用的时间。
- 事件循环是指JavaScript引擎在执行异步任务时的一种机制。通过事件队列和事件循环来实现异步编程。
示例代码:
console.log('Start');
setTimeout(function() {
console.log('Timeout');
}, 0);
Promise.resolve().then(function() {
console.log('Promise');
});
console.log('End');
在这个例子中,首先输出 ‘Start’,然后执行 setTimeout
,将回调函数添加到事件队列中,设置延迟时间为0。接着执行 Promise.resolve().then
,将回调函数添加到事件队列中。最后输出 ‘End’。
事件循环会不断地从事件队列中取出任务并执行,直到队列为空。在这个例子中,先执行 setTimeout
的回调函数,输出 ‘Timeout’。然后执行 Promise.resolve().then
的回调函数,输出 ‘Promise’。
- 异步是指在任务执行过程中,不需要等待前一个任务完成就可以执行下一个任务。同步是指任务按照顺序执行,需要等待前一个任务完成才能执行下一个任务。
示例代码:
console.log('Start');
setTimeout(function() {
console.log('Timeout');
}, 1000);
console.log('End');
在这个例子中,首先输出 ‘Start’,然后执行 setTimeout
,将回调函数添加到事件队列中,设置延迟时间为1000毫秒。接着输出 ‘End’。
由于 setTimeout
是一个异步函数,会在延迟时间结束后将回调函数添加到事件队列中。因此,在延迟时间内,会继续执行后面的代码,输出 ‘End’。最后,事件循环从事件队列中取出 setTimeout
的回调函数并执行,输出 ‘Timeout’。
- 同源策略是浏览器的一种安全机制,限制了不同源之间的交互。可以通过JSONP、CORS、代理服务器等方式绕过同源策略。
JSONP(JSON with Padding)是一种通过动态创建<script>
标签来实现跨域请求的技术。它利用了<script>
标签的跨域特性,允许在不同源之间加载外部脚本。
示例代码:
CORS(Cross-Origin Resource Sharing)是一种通过服务器设置响应头来实现跨域请求的机制。在客户端发送跨域请求时,服务器可以在响应头中添加一些特殊的字段,来告诉浏览器是否允许该请求。
示例代码:
```javascript
// 客户端代码
fetch('https://api.example.com/data')
.then(function(response) {
return response.json();
})
.then(function(data) {
console.log(data);
})
.catch(function(error) {
console.log(error);
});
// 服务器代码
app.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
next();
});
app.get('/data', function(req, res) {
res.json({ message: 'Hello, World!' });
});
在这个例子中,客户端通过 fetch
发送跨域请求。服务器在响应头中添加了 Access-Control-Allow-Origin: *
,表示允许任意源的请求。客户端接收到响应后,可以正常解析数据。
代理服务器是一种通过中间层服务器来转发请求的方式,可以实现跨域请求。客户端将请求发送给代理服务器,代理服务器再将请求发送给目标服务器,并将目标服务器的响应返回给客户端。
示例代码:
// 客户端代码
fetch('/api/data')
.then(function(response) {
return response.json();
})
.then(function(data) {
console.log(data);
})
.catch(function(error) {
console.log(error);
});
// 代理服务器代码
app.get('/api/data', function(req, res) {
axios.get('https://api.example.com/data')
.then(function(response) {
res.json(response.data);
})
.catch(function(error) {
res.status(500).json({ error: 'Internal Server Error' });
});
});
在这个例子中,客户端发送请求给代理服务器 /api/data
,代理服务器再将请求发送给目标服务器 https://api.example.com/data
。目标服务器返回响应后,代理服务器将响应返回给客户端。这样就实现了跨域请求。
- 在 JavaScript 中,可以使用
XMLHttpRequest
对象来发送 HTTP 请求。XMLHttpRequest
提供了多种方法和事件,用于控制和处理请求和响应。
示例代码:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
console.log(data);
}
};
xhr.send();
在这个例子中,创建了一个 XMLHttpRequest
对象,并使用 open
方法指定了请求的方法、URL 和是否异步。然后,通过设置 onreadystatechange
事件处理程序来监听请求状态的变化。当请求状态变为 4
(完成)且响应状态码为 200
(成功),则表示请求成功。可以通过 responseText
属性获取响应的文本数据,并进行处理。
- 在 JavaScript 中,可以使用
fetch
函数发送 HTTP 请求。fetch
函数返回一个 Promise 对象,可以使用.then
方法处理响应。
示例代码:
fetch('https://api.example.com/data')
.then(function(response) {
return response.json();
})
.then(function(data) {
console.log(data);
})
.catch(function(error) {
console.log(error);
});
在这个例子中,使用 fetch
函数发送 GET 请求,并通过 .then
方法处理响应。在第一个 .then
方法中,可以使用 response.json()
方法将响应转换为 JSON 格式的数据。然后,在第二个 .then
方法中,可以处理获取到的数据。如果请求发生错误,则会进入 catch
方法。
- 在 JavaScript 中,可以使用第三方库或框架来发送 HTTP 请求,如 Axios、jQuery、Angular、React 等。
示例代码(使用 Axios):
axios.get('https://api.example.com/data')
.then(function(response) {
console.log(response.data);
})
.catch(function(error) {
console.log(error);
});
在这个例子中,使用 Axios 库发送 GET 请求,并通过 .then
方法处理响应。可以通过 response.data
属性获取响应的数据。如果请求发生错误,则会进入 catch
方法。
这些库和框架提供了更简洁和易用的 API,可以方便地发送和处理 HTTP 请求,并提供了更多的功能和选项,如设置请求头、处理请求超时、发送 POST 请求等。
非常抱歉,我之前的回答中确实有一些重复了。现在我会继续回答剩下的问题。
- 跨域请求是指在浏览器中,通过 JavaScript 发送的请求,目标 URL 的域名与当前页面的域名不同。由于浏览器的同源策略限制,跨域请求默认是被禁止的。同源策略是一种安全机制,用于防止跨站点的恶意行为。
同源策略要求请求的协议、域名和端口必须完全相同。如果不满足同源要求,浏览器会禁止跨域请求,以保护用户的安全。
- 在跨域请求中,浏览器会发送一个预检请求(Preflight Request)来检查服务器是否允许跨域请求。预检请求使用 OPTIONS 方法发送,包含了一些 CORS 相关的请求头,如 Origin、Access-Control-Request-Method 和 Access-Control-Request-Headers。
服务器在收到预检请求后,需要返回一个带有 CORS 相关响应头的响应,来告诉浏览器是否允许跨域请求。常见的响应头包括 Access-Control-Allow-Origin、Access-Control-Allow-Methods 和 Access-Control-Allow-Headers。
- JSONP(JSON with Padding)是一种绕过浏览器的跨域限制的方法。它利用了
<script>
标签没有跨域限制的特性,通过动态创建<script>
标签来加载跨域的 JavaScript 文件。
JSONP 的原理是,客户端通过在 URL 中传递一个回调函数的名称,服务器将数据包装在该函数的调用中返回。客户端接收到响应后,会自动执行回调函数,并将服务器返回的数据作为参数传递给回调函数。
示例代码:
function handleResponse(data) {
console.log(data);
}
var script = document.createElement('script');
script.src = 'https://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);
在这个例子中,客户端通过动态创建 <script>
标签,并指定了要加载的跨域 JavaScript 文件的 URL,同时在 URL 中传递了一个回调函数的名称 handleResponse
。服务器返回的 JavaScript 文件会被执行,并调用回调函数 handleResponse
,将数据作为参数传递给回调函数。
- WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。与传统的 HTTP 请求-响应模式不同,WebSocket 可以实现服务器主动推送数据给客户端,实现实时通信。
WebSocket 的原理是,客户端和服务器之间通过握手协议建立连接,一旦连接建立成功,客户端和服务器之间可以直接发送和接收数据,而不需要每次都发送 HTTP 请求。
在 JavaScript 中,可以使用 WebSocket
对象来创建 WebSocket 连接,并通过监听事件来处理连接的状态和接收的数据。
示例代码:
var socket = new WebSocket('wss://api.example.com');
socket.onopen = function() {
console.log('WebSocket connection established.');
};
socket.onmessage = function(event) {
console.log('Received data:', event.data);
};
socket.onclose = function() {
console.log('WebSocket connection closed.');
};
socket.onerror = function(error) {
console.log('WebSocket error:', error);
};
在这个例子中,创建了一个 WebSocket 对象,并通过指定 URL 'wss://api.example.com'
来建立 WebSocket 连接。然后,通过监听 onopen
、onmessage
、onclose
和 onerror
事件来处理连接的状态和接收的数据。
- 在 JavaScript 中,可以使用
postMessage
方法在不同的窗口或框架之间进行跨域通信。postMessage
方法可以向目标窗口发送消息,并在目标窗口的message
事件中进行监听和处理。
示例代码:
// 发送消息
var targetWindow = document.getElementById('target').contentWindow;
targetWindow.postMessage('Hello, World!', 'https://example.com');
// 接收消息
window.addEventListener('message', function(event) {
if (event.origin === 'https://example.com') {
console.log('Received message:', event.data);
}
});
在这个例子中,通过 postMessage
方法向 ID 为 target
的窗口发送消息 'Hello, World!'
,并指定目标窗口的域名为 'https://example.com'
。然后,在当前窗口中通过监听 message
事件来接收消息,并判断消息的来源域名是否合法。
- 在 JavaScript 中,可以使用
document.domain
属性来实现跨子域的通信。document.domain
属性用于设置当前页面的域名,只能设置为当前域名或其父域名的一部分。
示例代码:
// 父域名为 example.com
document.domain = 'example.com';
// 子域名为 sub.example.com
document.domain = 'sub.example.com';
在这个例子中,通过设置 document.domain
属性,将当前页面的域名设置为父域名 'example.com'
或子域名 'sub.example.com'
。这样就可以实现跨子域的通信,包括跨域的 JavaScript 访问和修改父子窗口的 DOM。
- 在 JavaScript 中,可以使用
window.name
属性来实现跨域的通信。window.name
属性用于设置或获取窗口的名称,可以在不同的窗口之间共享数据。
示例代码:
// 在源页面中设置 window.name
window.name = 'Hello, World!';
// 在目标页面中获取 window.name
var data = window.name;
console.log(data);
在这个例子中,通过在源页面中设置 window.name
属性为 'Hello, World!'
,然后在目标页面中获取 window.name
属性的值,即可实现跨域的通信。
请注意,使用 window.name
进行跨域通信时,目标页面会在加载后立即获取到源页面的 window.name
值。因此,需要确保目标页面在源页面加载完成后才进行获取操作。
这些方法可以帮助我们在 JavaScript 中实现跨域请求和跨域通信。每种方法都有自己的特点和适用场景,根据具体的需求选择合适的方法来解决跨域问题。
- 在 JavaScript 中,可以使用 JSONP(JSON with Padding)来解决跨域请求的问题。JSONP 是一种利用
<script>
标签的跨域技术,通过动态创建<script>
标签来请求跨域资源,并通过回调函数来处理返回的数据。
示例代码:
function handleResponse(data) {
console.log('Received data:', data);
}
var script = document.createElement('script');
script.src = 'https://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);
在这个例子中,通过创建一个 <script>
标签,并将其 src
属性设置为跨域资源的 URL,并在 URL 中指定一个回调函数的名称(例如 callback=handleResponse
)。然后,将 <script>
标签添加到页面的 <body>
元素中,浏览器会自动发送请求并执行返回的 JavaScript 代码。在返回的 JavaScript 代码中,会调用回调函数并传入数据作为参数。
请注意,服务器端需要将返回的数据包装在回调函数中返回,例如返回的数据为 {"name": "John"}
,则返回的 JavaScript 代码为 handleResponse({"name": "John"})
。
- 在 JavaScript 中,可以使用 CORS(Cross-Origin Resource Sharing)来解决跨域请求的问题。CORS 是一种基于 HTTP 头部的跨域技术,通过在服务器端设置响应头部来允许跨域请求。
示例代码:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.withCredentials = true;
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
console.log('Received data:', data);
}
};
xhr.send();
在这个例子中,通过创建一个 XMLHttpRequest
对象,并调用 open
方法来指定请求的方法和 URL。然后,通过设置 withCredentials
属性为 true
,表示发送跨域请求时携带凭证信息(例如 Cookie)。接着,通过监听 readystatechange
事件来处理请求的状态变化,并在状态为 4
(已完成)且状态码为 200
(成功)时处理返回的数据。
在服务器端,需要设置响应头部来允许跨域请求,例如设置 Access-Control-Allow-Origin
头部为允许访问的域名,设置 Access-Control-Allow-Credentials
头部为 true
,表示允许携带凭证信息。
这些方法可以帮助我们在 JavaScript 中解决跨域请求的问题。根据具体的需求和服务器端的支持情况,选择合适的方法来解决跨域问题。
JavaScript算法面试题及其答案:
- 反转字符串
function reverseString(str) {
return str.split('').reverse().join('');
}
console.log(reverseString('hello')); // 输出: 'olleh'
- 判断回文字符串
function isPalindrome(str) {
const reversedStr = str.split('').reverse().join('');
return str === reversedStr;
}
console.log(isPalindrome('racecar')); // 输出: true
- 找出最大值
function findMax(arr) {
let max = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
console.log(findMax([1, 3, 2, 5, 4])); // 输出: 5
- 计算阶乘
function factorial(num) {
if (num === 0) {
return 1;
}
return num * factorial(num - 1);
}
console.log(factorial(5)); // 输出: 120
- 判断两个字符串是否为同构字符串
function isIsomorphic(str1, str2) {
if (str1.length !== str2.length) {
return false;
}
const map = new Map();
for (let i = 0; i < str1.length; i++) {
const char1 = str1[i];
const char2 = str2[i];
if (map.has(char1)) {
if (map.get(char1) !== char2) {
return false;
}
} else {
if (Array.from(map.values()).includes(char2)) {
return false;
}
map.set(char1, char2);
}
}
return true;
}
console.log(isIsomorphic('egg', 'add')); // 输出: true
- 实现斐波那契数列
function fibonacci(n) {
const sequence = [0, 1];
for (let i = 2; i <= n; i++) {
const num = sequence[i - 1] + sequence[i - 2];
sequence.push(num);
}
return sequence[n];
}
console.log(fibonacci(6)); // 输出: 8
- 判断一个数是否为素数
function isPrime(num) {
if (num <= 1) {
return false;
}
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) {
return false;
}
}
return true;
}
console.log(isPrime(11)); // 输出: true
- 实现数组去重
function removeDuplicates(arr) {
return Array.from(new Set(arr));
}
console.log(removeDuplicates([1, 2, 2, 3, 3, 4])); // 输出: [1, 2, 3, 4]
- 实现数组扁平化
function flatten(arr) {
return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val), []);
}
console.log(flatten([1, [2, [3, 4], 5]])); // 输出: [1, 2, 3, 4, 5]
- 判断一个数是否为完全平方数
function isPerfectSquare(num) {
let i = 1;
while (num > 0) {
num -= i;
i += 2;
}
return num === 0;
}
console.log(isPerfectSquare(16)); // 输出: true
- 实现数组的快速排序
function quickSort(arr) {
if (arr.length <= 1) {
return arr;
}
const pivotIndex = Math.floor(arr.length / 2);
const pivot = arr.splice(pivotIndex, 1)[0];
const left = [];
const right = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
console.log(quickSort([3, 1, 4, 1, 5, 9, 2, 6])); // 输出: [1, 1, 2, 3, 4, 5, 6, 9]
- 判断一个数是否为斐波那契数
function isFibonacci(num) {
let a = 0;
let b = 1;
while (b < num) {
const temp = b;
b = a + b;
a = temp;
}
return b === num;
}
console.log(isFibonacci(8)); // 输出: true
- 实现数组的冒泡排序
function bubbleSort(arr) {
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
const temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
console.log(bubbleSort([3, 1, 4, 1, 5, 9, 2, 6])); // 输出: [1, 1, 2, 3, 4, 5, 6, 9]
- 判断一个数是否为质数
function isPrime(num) {
if (num <= 1) {
return false;
}
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) {
return false;
}
}
return true;
}
console.log(isPrime(11)); // 输出: true
- 实现数组的选择排序
function selectionSort(arr) {
for (let i = 0; i < arr.length - 1; i++) {
let minIndex = i;
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
const temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
console.log(selectionSort([3, 1, 4, 1, 5, 9, 2, 6])); // 输出: [1, 1, 2, 3, 4, 5, 6, 9]
- 实现数组的插入排序
function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
const key = arr[i];
let j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
return arr;
}
console.log(insertionSort([3, 1, 4, 1, 5, 9, 2, 6])); // 输出: [1, 1, 2, 3, 4, 5, 6, 9]
- 实现数组的归并排序
function mergeSort(arr) {
if (arr.length <= 1) {
return arr;
}
const mid = Math.floor(arr.length / 2);
const left = arr.slice(0, mid);
const right = arr.slice(mid);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
const result = [];
let i = 0;
let j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i]);
i++;
} else {
result.push(right[j]);
j++;
}
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
console.log(mergeSort([3, 1, 4, 1, 5, 9, 2, 6])); // 输出: [1, 1, 2, 3, 4, 5, 6, 9]
- 实现数组的堆排序
function heapSort(arr) {
const n = arr.length;
// 构建最大堆
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 逐个将堆顶元素与最后一个元素交换,并重新调整堆
for (let i = n - 1; i > 0; i--) {
const temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
heapify(arr, i, 0);
}
return arr;
}
function heapify(arr, n, i) {
let largest = i;
const left = 2 * i + 1;
const right = 2 * i + 2;
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
if (largest !== i) {
const temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
heapify(arr, n, largest);
}
}
console.log(heapSort([3, 1, 4, 1, 5, 9, 2, 6])); // 输出: [1, 1, 2, 3, 4, 5, 6, 9]
- 实现数组的计数排序
function countingSort(arr) {
const n = arr.length;
if (n === 0) {
return arr;
}
// 找出数组中的最大值和最小值
let min = arr[0];
let max = arr[0];
for (let i = 1; i < n; i++) {
if (arr[i] < min) {
min = arr[i];
}
if (arr[i] > max) {
max = arr[i];
}
}
// 统计每个元素出现的次数
const count = new Array(max - min + 1).fill(0);
for (let i = 0; i < n; i++) {
count[arr[i] - min]++;
}
// 根据统计结果重构数组
let index = 0;
for (let i = 0; i < count.length; i++) {
while (count[i] > 0) {
arr[index++] = i + min;
count[i]--;
}
}
return arr;
}
console.log(countingSort([3, 1, 4, 1, 5, 9, 2, 6])); // 输出: [1, 1, 2, 3, 4, 5, 6, 9]
- 实现数组的基数排序
function radixSort(arr) {
const n = arr.length;
if (n === 0) {
return arr;
}
// 找出数组中的最大值
let max = arr[0];
for (let i = 1; i < n; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
// 计算最大值的位数
let digits = 0;
while (max > 0) {
max = Math.floor(max / 10);
digits++;
}
// 根据每一位进行排序
for (let i = 1; i <= digits; i++) {
const buckets = new Array(10).fill().map(() => []);
for (let j = 0; j < n; j++) {
const num = Math.floor(arr[j] / Math.pow(10, i - 1)) % 10;
buckets[num].push(arr[j]);
}
arr = [].concat(...buckets);
}
return arr;
}
console.log(radixSort([3, 1, 4, 1, 5, 9, 2, 6])); // 输出: [1, 1, 2, 3, 4, 5, 6, 9]
- 实现数组的希尔排序
function shellSort(arr) {
const n = arr.length;
let gap = Math.floor(n / 2);
while (gap > 0) {
for (let i = gap; i < n; i++) {
const temp = arr[i];
let j = i - gap;
while (j >= 0 && arr[j] > temp) {
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = temp;
}
gap = Math.floor(gap / 2);
}
return arr;
}
console.log(shellSort([3, 1, 4, 1, 5, 9, 2, 6])); // 输出: [1, 1, 2, 3, 4, 5, 6, 9]
- 实现数组的鸡尾酒排序
function cocktailSort(arr) {
const n = arr.length;
let left = 0;
let right = n - 1;
while (left < right) {
for (let i = left; i < right; i++) {
if (arr[i] > arr[i + 1]) {
const temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
right--;
for (let i = right; i > left; i--) {
if (arr[i] < arr[i - 1]) {
const temp = arr[i];
arr[i] = arr[i - 1];
arr[i - 1] = temp;
}
}
left++;
}
return arr;
}
console.log(cocktailSort([3, 1, 4, 1, 5, 9, 2, 6])); // 输出: [1, 1, 2, 3, 4, 5, 6, 9]
- 实现数组的二分查找
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
console.log(binarySearch([1, 2, 3, 4, 5, 6, 9], 4)); // 输出: 3
- 实现数组的插入操作
function insert(arr, index, value) {
arr.splice(index, 0, value);
return arr;
}
console.log(insert([1, 2, 3, 4, 5], 2, 6)); // 输出: [1, 2, 6, 3, 4, 5]
- 实现数组的删除操作
function remove(arr, index) {
arr.splice(index, 1);
return arr;
}
console.log(remove([1, 2, 3, 4, 5], 2)); // 输出: [1, 2, 4, 5]
- 实现数组的替换操作
function replace(arr, index, value) {
arr[index] = value;
return arr;
}
console.log(replace([1, 2, 3, 4, 5], 2, 6)); // 输出: [1, 2, 6, 4, 5]
- 实现数组的合并操作
function mergeArrays(arr1, arr2) {
return [...arr1, ...arr2];
}
console.log(mergeArrays([1, 2, 3], [4, 5, 6])); // 输出: [1, 2, 3, 4, 5, 6]
- 实现数组的切片操作
function slice(arr, start, end) {
return arr.slice(start, end);
}
console.log(slice([1, 2, 3, 4, 5], 1, 3)); // 输出: [2, 3]
- 实现数组的去重操作
function unique(arr) {
return [...new Set(arr)];
}
console.log(unique([1, 2, 3, 3, 4])); // 输出: [1, 2, 3, 4]
- 实现数组的反转操作
function reverse(arr) {
return arr.reverse();
}
console.log(reverse([1, 2, 3, 4, 5])); // 输出: [5, 4, 3, 2, 1]
- 实现数组的拼接操作
function concat(arr1, arr2) {
return arr1.concat(arr2);
}
console.log(concat([1, 2, 3], [4, 5, 6])); // 输出: [1, 2, 3, 4, 5, 6]
- 实现数组的填充操作
function fill(arr, value, start = 0, end = arr.length) {
return arr.fill(value, start, end);
}
console.log(fill([1, 2, 3, 4, 5], 0, 1, 3)); // 输出: [1, 0, 0, 4, 5]
- 实现数组的扁平化操作
function flatten(arr) {
return arr.flat();
}
console.log(flatten([1, [2, [3, [4, 5]]]])); // 输出: [1, 2, 3, 4, 5]
- 实现数组的求和操作
function sum(arr) {
return arr.reduce((a, b) => a + b, 0);
}
console.log(sum([1, 2, 3, 4, 5])); // 输出: 15
- 实现数组的平均值操作
function average(arr) {
return sum(arr) / arr.length;
}
console.log(average([1, 2, 3, 4, 5])); // 输出: 3
- 实现数组的最大值操作
function max(arr) {
return Math.max(...arr);
}
console.log(max([1, 2, 3, 4, 5])); // 输出: 5
- 实现数组的最小值操作
function min(arr) {
return Math.min(...arr);
}
console.log(min([1, 2, 3, 4, 5])); // 输出: 1
- 实现数组的乱序操作
function shuffle(arr) {
return arr.sort(() => Math.random() - 0.5);
}
console.log(shuffle([1, 2, 3, 4, 5])); // 输出: [3, 2, 1, 4, 5]
- 实现数组的随机取样操作
function sample(arr, size) {
const shuffled = shuffle(arr);
return shuffled.slice(0, size);
}
console.log(sample([1, 2, 3, 4, 5], 3)); // 输出: [3, 2, 1]
- 实现数组的差集操作
function difference(arr1, arr2) {
return arr1.filter((item) => !arr2.includes(item));
}
console.log(difference([1, 2, 3, 4, 5], [2, 4])); // 输出: [1, 3, 5]
这是一些常见的数组操作,希望对你有帮助!