【JavaScript】
一、防抖和節流
1. 防抖函數的實現
一個需要頻繁觸發的函數,在規定時間內只讓最後一次生效,前面不生效。就是指觸發事件後在 n 秒內函數只能執行一次,如果在 n 秒內又觸發了事件,則會重新計算函數執行時間。
<!DOCTYPE html>
<html>
<head>
<title>防抖函數</title>
<style>
</style>
</head>
<body>
<div>
<button id='btn'>防抖</button>
</div>
<script>
function debounce(fn,delay){
let timeout = null;
return function(){
if(timeout !== null){
clearTimeout(timeout);
}
timeout = setTimeout(fn.apply(this),delay);
}
}
function fn(){
console.log("觸發時間:" + new Date());
}
document.getElementById('btn').addEventListener('click',debounce(fn,500));
</script>
</body>
</html>
2. 節流函數的實現
有個需要頻繁觸發的函數,出於性能優化角度,在規定的時間內,只讓函數觸發的第一次生效,後面的不生效。
<!DOCTYPE html>
<html>
<head>
<title>節流函數</title>
<style>
</style>
</head>
<body>
<div>
<button id='btn'>節流</button>
</div>
<script>
function throttle(fn,delay){
// 記錄上一次觸發的時間
var lasttime = 0 ;
// 通過閉包的方式使用lasttime變量,每次都是上次的時間
return function(){
//
var nowtime = Date.now();
if(nowtime-lasttime>delay){
// 修正this函數問題
fn.call(this);
// 同步時間
lasttime = nowtime;
}
}
}
function fn(){
console.log("觸發時間:" + new Date());
}
document.getElementById('btn').addEventListener('click',throttle(fn,2000));
</script>
</body>
</html>
3. 應用場景
函數防抖的應用場景:
連續的事件,只需觸發一次回調的場景有:
- 搜索框搜索輸入。只需用戶最後一次輸入完,再發送請求。
- 窗口大小Resize。只需窗口調整完成後,計算窗口大小。防止重複渲染。
- 表單驗證、手機號、郵箱驗證輸入檢測。
- 按鈕點擊事件。
函數節流的應用場景:
間隔一段時間執行一次回調的場景有:
- 按鈕點擊事件。高頻點擊提交,表單重複提交。
- 滾動加載,加載更多或滾到底部監聽。
- 拖拽事件。
- 計算鼠標移動的距離 (mousemove)。
https://blog.csdn.net/w1418899532/article/details/98358491
https://www.cnblogs.com/piaobodewu/p/10395858.html
二、DFS 和 BFS
深度優先搜索就是自上而下的遍歷,廣度優先搜索則是逐層遍歷,如圖所示:
深度優先搜索:1,2-1,3-1,3-2,2-2,3-3,3-4,2-3,3-5,3-6
廣度優先搜索:1,2-1,2-2,2-3,3-1,3-2,3-3,3-4,3-5,3-6
示例一:
const data = [
{
name: 'a',
children: [
{ name: 'b', children: [{ name: 'e' }] },
{ name: 'c', children: [{ name: 'f' }] },
{ name: 'd', children: [{ name: 'g' }] },
],
},
{
name: 'a2',
children: [
{ name: 'b2', children: [{ name: 'e2' }] },
{ name: 'c2', children: [{ name: 'f2' }] },
{ name: 'd2', children: [{ name: 'g2' }] },
],
}
]
1. 深度優先 DFS
function dfs(data) {
const result = [];
data.forEach(item => {
const map = data => {
result.push(data.name);
if(data.children){
data.children.forEach(child => map(child));
}
}
map(item);
})
return result.join(',');
}
console.log(dfs(data)); // a,b,e,c,f,d,g,a2,b2,e2,c2,f2,d2,g2
2. 廣度優先 BFS
// 廣度遍歷, 創建一個執行隊列, 當隊列爲空的時候則結束
function bfs(data) {
let result = [];
let queue = data;
while (queue.length > 0) {
[...queue].forEach(child => {
queue.shift();
result.push(child.name);
if(child.children){
queue.push(...child.children);
};
});
}
return result.join(',');
}
console.log(bfs(data)); // a,a2,b,c,d,b2,c2,d2,e,f,g,e2,f2,g2
示例二:
const tree = {
value: "-",
left: {
value: '+',
left: {
value: 'a',
},
right: {
value: '*',
left: {
value: 'b',
},
right: {
value: 'c',
}
}
},
right: {
value: '/',
left: {
value: 'd',
},
right: {
value: 'e',
}
}
}
1. 深度優先 DFS(也是二叉樹的先序遍歷)
// 深度優先搜索 棧
// 遞歸
function dfs(tree) {
let result = [];
let dg = function(node){
if(node){
result.push(node.value);
if(node.left) dg(node.left);
if(node.right) dg(node.right);
}
}
dg(tree);
return result.join(',');
}
console.log(dfs(tree)); // -,+,a,*,b,c,/,d,e
// 深度優先搜索 棧
// 非遞歸
function dfs(tree) {
let result = [];
let stack = [tree];
while(stack.length){
let node = stack.pop();
result.push(node.value);
if(node.right) stack.push(node.right);
if(node.left) stack.push(node.left);
}
return result.join(',');
}
console.log(dfs(tree)); // -,+,a,*,b,c,/,d,e
2. 廣度優先 BFS
// 廣度優先搜索 隊列
// 使用shift修改數組
function bfs(tree) {
let result = [];
let queue = [tree];
while(queue.length){
let node = queue.shift();
result.push(node.value);
if(node.left) queue.push(node.left);
if(node.right) queue.push(node.right);
}
return result.join(',');
}
console.log(bfs(tree)); // -,+,/,a,*,d,e,b,c
// 廣度優先搜索 隊列
// 使用指針
function bfs(tree) {
let result = [];
let queue = [tree];
let pointer = 0;
while(pointer < queue.length){
let node = queue[pointer++];
result.push(node.value);
if(node.left) queue.push(node.left);
if(node.right) queue.push(node.right);
}
return result.join(',');
}
console.log(bfs(tree)); // -,+,/,a,*,d,e,b,c
兩者的區別:
對於算法來說,無非就是時間換空間,空間換時間:
- 深度優先不需要記住所有的節點,所以佔用空間小,而廣度優先需要先記錄所有的節點佔用空間大
- 深度優先有回溯的操作(沒有路走了需要回頭),所以相對而言時間會長一點
- 深度優先採用的是堆棧的形式, 即先進後出,廣度優先則採用的是隊列的形式, 即先進先出
https://www.cnblogs.com/zzsdream/p/11322060.html
https://www.jianshu.com/p/5e9ea25a1aae
https://blog.csdn.net/m0_37686205/article/details/100044144
三、setTimeout 實現重複定時器(即實現 setIntevel)
setTimeout(function(){
// 處理中
setTimeout(arguments.callee, interval);
// arguments.callee 用來獲取對當前執行的函數的引用
},interval)
// 完整代碼及其調用
function defineSetInterval(fn,interval){
setTimeout(function(){
// 執行內容
fn();
// arguments.callee 用來獲取對當前執行的函數的引用
setTimeout(arguments.callee, interval);
},interval)
}
// 調用
defineSetInterval(fn,interval);
好處:在前一個定時器代碼執行之前,不會向隊列插入新的定時器代碼,確保不會有任何缺失的間隔。而且,它可以保證在下一次定時器代碼執行之前,至少要等待指定的間隔,避免了連續的運行。
應用:將一個<div>元素向右移動,當左座標在200像素的時候停止。
setTimeout(function(){
var div = document.getElementById("myDiv");
left = parseInt(div.style.left) + 5;
div.style.left = left +"px";
if(left < 200){
setTimeout(arguments.callee, 50);
}
},50)
應用拓展:將一個<div>元素左右移動。
<!DOCTYPE html>
<html>
<head>
<title>重複定時器的應用</title>
<style>
#main{
width: 100%;
height: 200px;
position: relative;
border: 1px solid black;
}
#myDiv{
width: 100px;
height: 100px;
position: absolute;
top: 50px;
left: 0;
border: 1px solid red;
}
</style>
</head>
<body>
<div id="main">
<div id='myDiv'>左右移動</div>
</div>
<script>
let distance = 10;
let bool = true;
setTimeout(function(){
let div = document.getElementById("myDiv");
let left = parseInt(div.currentStyle ? div.currentStyle.left : getComputedStyle(div,null).left);
left = bool ? left + distance : left - distance;
div.style.cssText = 'left:'+ left +'px;'
let main = document.getElementById("main");
let mainWidth = parseInt(main.clientWidth);
let width = parseInt(div.currentStyle ? div.currentStyle.width : getComputedStyle(div,null).width);
let t = mainWidth - width - distance;
bool = left < t ? (left === 0 ? true : bool) : false;
setTimeout(arguments.callee, 50);
},50)
</script>
</body>
</html>
四、Ajax 請求
// 創建對象
var xhr = new XMLHttpRequest();
// 綁定監聽對象
xhr.onreadystatechange = function () {
// 監聽readyState和status
if (xhr.readyState === 4 && xhr.status === 200) {
var value = xhr.responseText; // 獲取數據
alert(value);
}
}
// 調用open方法,
// 指定請求的路徑,
// 是否是異步,true:異步,false:同步
// get方法,參數放在url的?後面
xhr.open("get", url, true);
// post方法
// xhr.open("post", url, true);
// xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
// 發送請求
xhr.send();
關於 xhr 的超時設定、加載事件及進度事件:
各個瀏覽器雖然都支持 xhr,但還是有些差異。
1、超時設定
IE8 爲 xhr 對象添加了一個 timeout 屬性,表示請求在等待響應多少毫秒後就終止。在給 timeout 設置一個數值後,如果在規定的時間內瀏覽器還沒有接收到響應,那麼就會觸發 timeout 事件,進而會調用 ontimeout 事件處理程序。
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(event){
try {
if(xhr.readyState == 4){
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful:" + xhr.status);
}
}
} catch(ex){
// 假設ontimeout事件處理程序處理
}
};
xhr.open("get", url, true);
xhr.timeout = 1000;
xhr.ontimeout = function(){
alert("Request did not return in a second.");
};
xhr.send(null);
2、加載事件
Firfox 在實現 xhr 對象的某個版本時,曾致力於簡化異步交互模型。於是引入 load 事件,用以代替 readystatechange 事件。響應接收完畢後將觸發 load 事件,因此也就沒有必要去檢查 readyState 屬性了。
var xhr = new XMLHttpRequest();
xhr.onload = function(event){
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful:" + xhr.status);
}
};
xhr.open("get", url, true);
xhr.send(null);
只要瀏覽器接收到服務器的響應,不管其狀態如何,都會觸發load事件。而這意味着你必須檢查 status 屬性,才能確定數據是否真的已經可用了。
3、進度事件
Mozilla 對 xhr 對象的另一個革新是添加了 progress 事件,這個事件會在瀏覽器接受新數據期間週期性的觸發,而 onprogress事件處理程序 會接收到一個 event 對象,其 target 屬性是 xhr 對象,但包含着兩個額外的屬性:position 和 totalSize。其中 position 表示已經接受的字節數,totleSize 表示根據 Content-Length 響應頭部確定的預期字節數。
var xhr = new XMLHttpRequest();
xhr.onload = function(event){
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful:" + xhr.status);
}
};
xhr.onprogress = function(event){
var divStatus = document.getElementById("status");
divStatus.innerHTML = "Received" + event.position + "of" + event.totalSize +"bytes";
};
xhr.open("get", url, true);
xhr.send(null);
END