Call, bind, apply實作
// call
Function.prototype.myCall = function (context) {
context = context ? Object(context) : window
context.fn = this;
let args = [...arguments].slice(1);
const result = context.fn(...args);
delete context.fn;
return result;
}
// apply
Function.prototype.myApply = function (context) {
context = context ? Object(context) : window;
context.fn = this;
let args = [...arguments][1];
let result;
if (args.length === 0) {
result = context.fn();
} else {
result = context.fn(args);
}
delete context.fn;
return result;
}
// bind
Function.prototype.myBind = function (context) {
let self = this;
let args = [...arguments].slice(1);
return function() {
let newArgs = [...arguments];
return self.apply(context, args.concat(newArgs));
}
}
原型與原型鍊
每一個JavaScript對象(null除外)在建立的時候會關聯另一個對象,這個對象就是原型,每一個對象都會從原型"繼承"屬性。
每一個JavaScript對象(除了 null )都具有的一個屬性,叫__proto__,這個屬性會指向該對象的原型。
執行個體對象和構造函數都可以指向原型, 原型可以指向構造函數,不能指向執行個體(因為可以有多個執行個體)。
原型有兩個屬性,constructor 和 __proto__。
JavaScript中所有的對象都是由它的原型對象繼承而來。而原型對象自身也是一個對象,它也有自己的原型對象,這樣層層上溯,就形成了一個類似連結清單的結構,這就是原型鍊。
function Person() {}
var person = new Person();
// 執行個體原型 === 構造函數原型
person.__proto__ === Person.prototype // true
// 原型構造函數 === 構造函數
Person.prototype.constructor === Person // true
react diff
- React 通過制定大膽的 diff 政策,将 O(n3) 複雜度的問題轉換成 O(n) 複雜度的問題;
-
React 通過分層求異的政策,對 tree diff 進行算法優化;
對樹進行分層比較,兩棵樹隻會對同一層次的節點進行比較。
- React 通過相同類生成相似樹形結構,不同類生成不同樹形結構的政策,對 component diff 進行算法優化;
- 如果是同一類型的元件,按照原政策繼續比較 virtual DOM tree。
- 如果不是,則将該元件判斷為 dirty component,進而替換整個元件下的所有子節點。
- 對于同一類型的元件,有可能其 Virtual DOM 沒有任何變化,如果能夠确切的知道這點那可以節省大量的 diff 運算時間,是以 React 允許使用者通過 shouldComponentUpdate() 來判斷該元件是否需要進行 diff。
React 通過設定唯一 key的政策,對 element diff 進行算法優化;建議,在開發元件時,保持穩定的 DOM 結構會有助于性能的提升;
對象方法
對象周遊方法總結:
- for...in:周遊對象自身, 包含繼承, 可枚舉,不含 Symbol 的屬性。
- Object.keys(obj):周遊對象自身, 不含繼承,可枚舉,不含 Symbol 的屬性。【values, entries】
- Object.getOwnPropertyNames(obj):周遊對象自身, 不含繼承, 不含 Symbol 的屬性, 不管是否可枚舉
- Object.getOwnPropertySymbols(obj): 周遊對象自身, 不含繼承, 所有 Symbol 的屬性, 不管是否可枚舉
-
Reflect.ownKeys(obj): 周遊對象自身,不含繼承,所有鍵名,不管是否Symbol 和可枚舉。
對象其他方法:
- JSON.stringify():隻串行化對象自身,不含繼承,可枚舉,不含 Symbol屬性。【function,undefined, Symbol會丢失, set、map會處理成空對象】
- Object.assign(): 隻拷貝對象自身,不含繼承, 可枚舉屬性, 不管是否是Symbol 。【全部資料類型屬性值】
方法 | 自身屬性 | 繼承屬性 | 可枚舉屬性 | Symbol屬性 |
---|---|---|---|---|
for...in.. | 是 | 必須 | 否 | |
Object.keys() | ||||
Object.getOwnPropertyNames(obj) | 非必須 | |||
Object.getOwnPropertySymbols(obj) | ||||
Reflect.ownKeys(obj) | ||||
JSON.stringify() | ||||
Object.assign() |
加載腳本<script>
預設情況下,浏覽器是同步加載 JavaScript 腳本,即渲染引擎遇到<script>标簽就會停下來,等到執行完腳本,再繼續向下渲染。如果是外部腳本,還必須加入腳本下載下傳的時間。
異步加載腳本方法:defer與async。
defer與async的差別是:defer要等到整個頁面在記憶體中正常渲染結束(DOM 結構完全生成,以及其他腳本執行完成),才會執行;async一旦下載下傳完,渲染引擎就會中斷渲染,執行這個腳本以後,再繼續渲染。一句話,defer是“渲染完再執行”,async是“下載下傳完就執行”。另外,如果有多個defer腳本,會按照它們在頁面出現的順序加載,而多個async腳本是不能保證加載順序的。
浏覽器對于帶有type="module"的<script>,都是異步加載,不會造成堵塞浏覽器,即等到整個頁面渲染完,再執行子產品腳本,等同于打開了<script>标簽的defer屬性。
ES6 子產品與 CommonJS 子產品的差異
- CommonJS 子產品輸出的是一個值的拷貝,ES6 子產品輸出的是值的引用。
-
CommonJS 子產品是運作時加載,ES6 子產品是編譯時輸出接口。
因為 CommonJS 加載的是一個對象(即module.exports屬性),該對象隻有在腳本運作完才會生成。而 ES6 子產品不是對象,它的對外接口隻是一種靜态定義,在代碼靜态解析階段就會生成。
- CommonJS 子產品的require()是同步加載子產品,ES6 子產品的import指令是異步加載,有一個獨立的子產品依賴的解析階段。
回流Reflow與重繪Repaint
回流:元素的大小或者位置發生了變化,觸發了重新布局,導緻渲染樹重新計算布局和渲染。頁面第一次加載的時候,至少發生一次回流。
- 添加或删除可見的DOM元素;
- 元素的位置發生變化;
- 元素的尺寸發生變化;
- 内容發生變化(比如文本變化或圖檔被另一個不同尺寸的圖檔所替代);
- 頁面一開始渲染的時候(這個無法避免);
- 浏覽器的視窗尺寸變化, 因為回流是根據視口的大小來計算元素的位置和大小的;
重繪:元素的外觀,風格改變,而不會影響布局(不包含寬高、大小、位置等不變)
- 如:outline, visibility, color, background-color......等
Reflow 的成本比 Repaint 高得多的多。DOM Tree 裡的每個結點都會有 reflow 方法,一個結點的 reflow 很有可能導緻子結點,甚至父點以及同級結點的 reflow。。回流一定會觸發重繪,而重繪不一定會回流
減少重繪與回流
- CSS
- 使用 visibility 替換 display: none ,因為前者隻會引起重繪,後者會引發回流
- 避免使用table布局,可能很小的一個小改動會造成整個 table 的重新布局。
- 避免設定多層内聯樣式,CSS 選擇符從右往左比對查找,避免節點層級過多。
- 将動畫效果應用到position屬性為absolute或fixed的元素上,避免影響其他元素的布局,這樣隻是一個重繪,而不是回流,同時,控制動畫速度可以選擇 requestAnimationFrame
- 避免使用CSS表達式,可能會引發回流。
- 将頻繁重繪或者回流的節點設定為圖層,圖層能夠阻止該節點的渲染行為影響别的節點,例如will-change、video、iframe等标簽,浏覽器會自動将該節點變為圖層。
- CSS3 硬體加速(GPU加速),使用css3硬體加速,可以讓transform、opacity、filters這些動畫不會引起回流重繪 。但是對于動畫的其它屬性,比如background-color這些,還是會引起回流重繪的,不過它還是可以提升這些動畫的性能。
JavaScript
- 避免頻繁操作樣式,最好一次性重寫style屬性,或者将樣式清單定義為class并一次性更改class屬性。
- 避免頻繁操作DOM,建立一個documentFragment,在它上面應用所有DOM操作,最後再把它添加到文檔中。
- 避免頻繁讀取會引發回流/重繪的屬性,如果确實需要多次使用,就用一個變量緩存起來。
CSS3 硬體加速(GPU 加速)
CSS3 硬體加速又叫做 GPU 加速,是利用 GPU 進行渲染,減少 CPU 操作的一種優化方案。
render tree -> 渲染元素 -> 圖層 -> GPU 渲染 -> 浏覽器複合圖層 -> 生成最終的螢幕圖像。
浏覽器在擷取 render tree後,渲染樹中包含了大量的渲染元素,每一個渲染元素會被分到一個個圖層中,每個圖層又會被加載到 GPU 形成渲染紋理。GPU 中 transform 是不會觸發 repaint ,最終這些使用 transform 的圖層都會由獨立的合成器程序進行處理。
CSS3觸發硬體加速的屬性:
- transform
- opacity
- filter
- will-change
http請求方法
HTTP1.0定義了三種請求方法: GET, POST 和 HEAD方法。
HTTP1.1新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法
- OPTIONS: 即預檢請求,可用于檢測伺服器允許的http方法。當發起跨域請求時,由于安全原因,觸發一定條件時浏覽器會在正式請求之前自動先發起OPTIONS請求,即CORS預檢請求,伺服器若接受該跨域請求,浏覽器才繼續發起正式請求。
- HEAD: 向伺服器索與GET請求相一緻的響應,隻不過響應體将不會被傳回,用于擷取報頭。
- GET:向特定的資源送出請求。注意:GET方法不應當被用于産生“副作用”的操作中
- POST:向指定資源送出資料進行處理請求(例如送出表單或者上傳檔案)。資料被包含在請求體中。POST請求可能會導緻新的資源的建立和/或已有資源的修改。
- PUT:向指定資源位置上傳其最新内容
- DELETE:請求伺服器删除Request-URL所辨別的資源
- TRACE:回顯伺服器收到的請求,主要用于測試或診斷
- CONNECT:HTTP/1.1協定中預留給能夠将連接配接改為管道方式的代理伺服器
js判斷資料類型
- typeof 操作符
- 對于基本類型,除 null 以外,均可以傳回正确的結果。
- 對于引用類型,除 function 以外,一律傳回 object 類型。
- 對于 null ,傳回 object 類型。
- 對于 function 傳回 function 類型。
- instanceof :用來判斷 A 是否為 B 的執行個體,檢測的是原型。instanceof 隻能用來判斷兩個對象是否屬于執行個體關系, 而不能判斷一個對象執行個體具體屬于哪種類型。
instanceof 主要的實作原理就是隻要右邊變量的 prototype 在左邊變量的原型鍊上即可。
- constructor
- null 和 undefined 是無效的對象,不會有 constructor 存在的
-
函數的 constructor 是不穩定的,這個主要展現在自定義對象上,當開發者重寫 prototype 後,原有的 constructor 引用會丢失,constructor 會預設為 Object。為了規範開發,在重寫對象原型時一般都需要重新給 constructor 指派。
為什麼變成了 Object?
因為 prototype 被重新指派的是一個 { }, { } 是 new Object() 的字面量,是以 new Object() 會将 Object 原型上的 constructor 傳遞給 { },也就是 Object 本身。
-
toString
toString() 是 Object 的原型方法,調用該方法,預設傳回目前對象的 [[Class]] 。這是一個内部屬性,其格式為 [object Xxx] ,其中 Xxx 就是對象的類型。
浏覽器事件模型
DOM事件流(event flow )存在三個階段:事件捕獲階段、處于目标階段、事件冒泡階段。
// useCapture: true, 即采用事件捕獲方式
window.addEventListener("click", function(e){
console.log("window 捕獲");
}, true);
// useCapture: false【預設為false】,即采用事件冒泡方式
window.addEventListener("click", function(e){
console.log("window 冒泡");
}, false);
目标元素(被點選的元素)綁定的事件都會發生在目标階段,在綁定捕獲代碼之前寫了綁定的冒泡階段的代碼,是以在目标元素上就不會遵守先發生捕獲後發生冒泡這一規則,而是先綁定的事件先發生。
不是目标元素,它上面綁定的事件會遵守先發生捕獲後發生冒泡的規則。
e.stopPropagation():阻止事件傳播。不僅可以阻止事件在冒泡階段的傳播,還能阻止事件在捕獲階段的傳播。
e.preventDefault(): 阻止事件的預設行為。預設行為是指:點選a标簽就轉跳到其他頁面、拖拽一個圖檔到浏覽器會自動打開、點選表單的送出按鈕會送出表單等
http緩存: 強制緩存和協商緩存
良好的緩存政策可以降低資源的重複加載提高網頁的整體加載速度。緩存原理:
- 浏覽器在加載資源時,根據請求頭的expires和cache-control判斷是否命中強緩存,是則直接從緩存讀取資源,不會發請求到伺服器。
- 如果沒有命中強緩存,浏覽器會發送一個請求到伺服器,通過last-modified和etag驗證是否命中協商緩存。當向服務端發起緩存校驗的請求時,服務端會傳回 200 ok表示傳回正常的結果或者 304 Not Modified(不傳回body)表示浏覽器可以使用本地緩存檔案。304的響應頭也可以同時更新緩存文檔的過期時間
- 如果前面兩者都沒有命中,直接從伺服器加載資源。
強緩存通過Expires和Cache-Control實作。
協商緩存是利用的是【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】這兩對Header來管理的。
Expires
Expires是http1.0提出的一個表示資源過期時間的header,它是一個絕對時間,由伺服器傳回。Expires 受限于本地時間,如果修改了本地時間,可能會造成緩存失效。
Expires: Wed, 11 May 2018 07:20:00 GMT
Cache-Control
Cache-Control 出現于 HTTP / 1.1,優先級高于 Expires , 表示的是相對時間。
no-store: 沒有緩存。緩存中不得存儲任何關于用戶端請求和服務端響應的内容。每次由用戶端發起的請求都會下載下傳完整的響應内容。
no-cache: 緩存但重新驗證。每次有請求發出時,緩存會将此請求發到伺服器(譯者注:該請求應該會帶有與本地緩存相關的驗證字段),伺服器端會驗證請求中所描述的緩存是否過期,若未過期(傳回304),則緩存才使用本地緩存副本。
private:隻允許用戶端浏覽器緩存。
public: 允許所有使用者緩存。例如中間代理、CDN等
max-age=<seconds>:表示資源能夠被緩存的最大時間。相對Expires而言,max-age是距離請求發起的時間的秒數。針對應用中那些不會改變的檔案,通常可以手動設定一定的時長以保證緩存有效,例如圖檔、css、js等靜态資源。
must-revalidate:觸發緩存驗證。驗證它的狀态,已過期的緩存将不被使用
Last-Modified,If-Modified-Since
Last-Modifie表示本地檔案最後修改日期,浏覽器會在request header加 If-Modified-Since(上次傳回的Last-Modified的值),詢問伺服器在該日期後資源是否有更新,有更新的話就會将新的資源發送回來。
但是如果在本地打開緩存檔案,就會造成 Last-Modified 被修改,是以在 HTTP / 1.1 出現了 ETag。
ETag、If-None-Match
Etag就像一個指紋,資源變化都會導緻ETag變化,跟最後修改時間沒有關系,ETag可以保證每一個資源是唯一的。
If-None-Match的header會将上次傳回的Etag發送給伺服器,詢問該資源的Etag是否有更新,有變動就會發送新的資源回來。
ETag的優先級比Last-Modified更高。
具體為什麼要用ETag,主要出于下面幾種情況考慮:
- 一些檔案也許會周期性的更改,但是他的内容并不改變(僅僅改變的修改時間),這個時候我們并不希望用戶端認為這個檔案被修改了,而重新GET;
- 某些檔案修改非常頻繁,比如在秒以下的時間内進行修改,(比方說1s内修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改無法判斷(或者說UNIX記錄MTIME隻能精确到秒);
- 某些伺服器不能精确的得到檔案的最後修改時間。
防抖和節流
防抖:當你在頻繁調用方法時,并不會執行,隻有當你在指定間隔内沒有再調用,才會執行函數。
節流:在一個機關時間内,隻能觸發一次函數。如果這個機關時間内觸發多次函數,隻有一次生效。
// 防抖
function debounce(fn, wait) {
let time = null;
return (function() {
const context = this;
const args = arguments;
if (time) {
clearTimeout(time);
time = null;
}
time = setTimeout(() => {
fn.call(context, args);
}, wait);
});
}
// 節流
function throttle(fn, wait) {
let lastTime;
return (
function() {
const context = this;
const args = arguments;
let nowTime = + new Date();
if (nowTime > lastTime + wait || !lastTime) {
fn.call(context, args);
lastTime = nowTime;
}
}
);
}
大小機關差別
px:像素。
em:參考物是父元素的font-size,具有繼承的特點。如果自身定義了font-size按自身來計算,整個頁面内1em不是一個固定的值。
rem: 相對于根元素html的font-size計算,不會像em那樣,依賴于父元素的字型大小,而造成混亂。
vw: 視窗寬度,1vw等于視窗寬度的1%。
vh:視窗高度,1vh等于視窗高度的1%。
vm:min(vw, vh)。
%: 是相對于父元素的大小設定的比率,position:absolute;的元素是相對于已經定位的父元素,position:fixed;的元素是相對可視視窗
- 浏覽器預設字型是16px, body設定font-size:62.5%, 那麼1rem =62.5% * 16=10px 。
- 谷歌浏覽器強制最小字型為12号,即使設定成 10px 最終都會顯示成 12px,當把html的font-size設定成10px,子節點rem的計算還是以12px為基準。
Box-sizing
- content-box:這是預設情況,整個元素的高度和寬度就是元素内容
- border-box:這種情況下,你設定的width和height屬性值就是針對整個元素,包括了border,padding,和元素内容。
函數聲明和函數表達式
// 函數聲明
function wscat(type){
return 'wscat';
}
// 函數表達式
var oaoafly = function(type){
return "oaoafly";
}
- JavaScript 解釋器中存在一種變量聲明被提升的機制,也就是說函數聲明會被提升到作用域的最前面,即使寫代碼的時候是寫在最後面,也還是會被提升至最前面。
- 用函數表達式建立的函數是在運作時進行指派,且要等到表達式指派完成後才能調用
函數聲明在JS解析時進行函數提升,是以在同一個作用域内,不管函數聲明在哪裡定義,該函數都可以進行調用。而函數表達式的值是在JS運作時确定,并且在表達式指派完成後,該函數才能調用。這個微小的差別,可能會導緻JS代碼出現意想不到的bug,讓你陷入莫名的陷阱中。
事件循環EventLoop
JavaScript是一個單線程的腳本語言。
所有同步任務都在主線程上執行,形成一個執行棧 (Execution Context Stack);而異步任務會被放置到 Task Table(異步處理子產品),當異步任務有了運作結果,就将注冊的回調函數移入任務隊列(兩種隊列)。一旦執行棧中的所有同步任務執行完畢,引擎就會讀取任務隊列,然後将任務隊列中的第一個任務取出放到執行棧中運作。(所有會進入的異步都是指的事件回調中的那部分代碼)
隻要主線程空了,就會去讀取任務隊列,該過程不斷重複,這就是所謂的 事件循環。
宏任務和微任務
宏任務會進入一個隊列,微任務會進入到另一個隊列,且微任務要優于宏任務執行。
- 宏任務:script(整體代碼)、setTimeout、setInterval、I/O、事件、postMessage、 MessageChannel、setImmediate (Node.js)
- 微任務:Promise.then、 MutaionObserver、process.nextTick (Node.js)
宏任務會進入一個隊列,而微任務會進入到另一個不同的隊列,且微任務要優于宏任務執行。
Promise
1. Promise.all: 全部fulfilled, 才進入then, 否則 catch
2. Promise.race: 任一個傳回,不管是fulfilled還是rejected, 進入相應的then/catch
3. Promise.allSettled: 全部傳回,不管是fulfilled還是rejected, 進入then
4. Promise.any: 任一個傳回fulfilled, 就進入then, 否則 catch
跨域
為什麼跨域?因為浏覽器同源政策。所謂同源是指"協定+域名+端口"三者相同,即便兩個不同的域名指向同一個ip位址,也非同源。浏覽器引入同源政策主要是為了防止XSS,CSRF攻擊。
同源政策的具體表現:
- http請求不能向不同源的伺服器發起HTTP請求
- 非同源的網頁不能共享Cookie、LocalStorage、IndexDB
- 禁止對不同源頁面的DOM進行操作。主要場景是iframe跨域的情況,不同域名的iframe限制互相通路
解決方案:
- JSONP
實作原理:<script>标簽不受浏覽器同源政策的影響, 允許跨域引用資源。
實作方式: 動态建立script标簽, 利用src屬性進行跨域,通過回調函數處理請求結果。
優點: 相容性好
缺點:
隻能支援GET請求, JSONP在調用失敗時不會傳回各種HTTP狀态碼
安全性。如果提供JSONP的服務被人控制,那麼所有調用這個JSONP的網站都存在漏洞。
- CORS
跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴浏覽器 讓運作在一個 origin (domain) 上的Web應用被準許通路來自不同源伺服器上的指定的資源
虛拟dom原理
Virtual DOM是對DOM的抽象,本質上是JavaScript對象,這個對象就是更加輕量級的對DOM的描述.
箭頭函數與普通函數差別
- 文法更加簡潔、清晰
- 不綁定this,會捕獲其所在的上下文的this值,作為自己的this值
- 箭頭函數繼承而來的this指向永遠不變
- .call()/.apply()/.bind()無法改變箭頭函數中this的指向
- 不能作為構造函數使用, 因為:
- 沒有自己的 this,無法調用 call,apply。
- 沒有 prototype 屬性 ,而 new 指令在執行時需要将構造函數的 prototype 指派給新的對象的__prpto__
- 沒有自己的arguments,在箭頭函數中通路arguments實際上獲得的是外層局部(函數)執行環境中的值。如果要用,可以用 rest 參數代替。
- 沒有原型prototype, 指向 undefined
- 不能用作Generator函數,不能使用yeild關鍵字
new
new 關鍵字會進行如下的操作:
- 建立一個空的簡單JavaScript對象(即{});
- 連結該對象(設定該對象的__proto__)到構造器函數的原型 ;
- 将新建立的對象作為this的上下文 ;
- 傳回。如果該函數沒有傳回對象,則傳回this。
function newFunc(father, ...rest) {
// 首先建立一個空對象
var result = {};
// 将空對象的原型指派為構造器函數的原型
result.__proto__ = father.prototype;
// 更改構造器函數内部this,将其指向新建立的空對象
var result2 = father.apply(result, rest);
if ((typeof result2 === 'object' || typeof result2 === 'function') && result2 !== null) {
return result2;
}
return result;
}
水準與垂直居中的實作方式有哪些
水準居中
- text-align: center; 行内元素适用
- margin: 0 auto; 适用塊級元素
- width: fit-content; 若子元素包含 float:left 屬性, 為了讓子元素水準居中, 則可讓父元素寬度設定為fit-content, 并且配合margin。
.parent {
width:fit-content;
margin:0 auto;
}
- flex
.parent {
display: flex;
justify-content: center;
}
- 盒模型, 使用flex 2009年版本
.parent {
display: box;
box-orient: horizontal;
box-pack: center;
}
.son {
position: absolute;
left: 50%;
transform: translate(-50%, 0);
}
- 兩種不同的絕對定位方法
.son {
position: absolute;
width: 固定;
left: 50%;
margin-left: -0.5 * 寬度;
}
.son {
position: absolute;
width: 固定;
top: 0;
right: 0;
margin: 0 auto;
}
垂直居中
- 單行文本, line-height
- 行内塊級元素, 使用 display: inline-block, vertical-align: middle; 加上僞元素輔助實作
.parent::after, .son {
display:inline-block;
vertical-align:middle;
}
.parent::after {
content:'';
height:100%;
}
- vertical-align。vertical-align隻有在父層為 td 或者 th 時, 才會生效, 對于其他塊級元素, 例如 div、p 等, 預設情況是不支援的. 為了使用vertical-align, 我們需要設定父元素display:table, 子元素 display:table-cell;vertical-align:middle;
.parent {
display: flex;
align-items: center;
}
- 盒模型
.parent {
display: box;
box-orient: vertical;
box-pack: center;
}
.son {
position: absolute;
top: 50%;
transform: translate(0, -50%);
}
.son {
position: absolute;
height: 固定;
top: 50%;
margin-top: -0.5 * height;
}
.son {
position: absolute;
height: 固定;
top: 0;
bottom: 0;
margin: auto 0;
}
flex, 盒模型, transform, 絕對定位, 這幾種方法同時适用于水準居中和垂直居中
冒泡排序
function bubbleSort(arr) {
const len = arr.length;
for (let i = 0; i < len - 1; i++) {
for (let j = i + 1; j < len; j++) {
if (arr[j] < arr[i]) {
[arr[j], arr[i]] = [arr[i], arr[j]];
}
}
}
return arr;
}
選擇排序
選擇排序(Selection-sort)是一種簡單直覺的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
function selectionSort(arr) {
const len = arr.length;
for (let i = 0; i < len - 1; i++) {
let index = i;
for (let j = i + 1; j < len; j++) {
if (arr[j] < arr[index]) {
index = j;
}
}
if (index !== i) {
[arr[i], arr[index]] = [arr[index], arr[i]];
}
}
return arr;
}
插入排序
function insertionSort(arr) {
const len = arr.length;
for (let i = 1; i < len; i++) {
let j = i - 1;
const value = arr[i];
while (arr[j] > value && j >= 0) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = value;
}
return arr;
}
歸并排序
function mergeSort(arr) { //采用自上而下的遞歸方法
var len = arr.length;
if (len < 2) return arr;
const middle = Math.floor(len / 2);
let left = arr.slice(0, middle);
let right = arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
let result = [];
while (left.length && right.length) {
if (left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
return result.concat(left).concat(right);
}
快速排序
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivotIndex = Math.floor(arr.length / 2);
const pivot = arr[pivotIndex];
let left = [];
let right = [];
for (var i = 0; i < arr.length; i++){
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
};
洗牌算法
function shuffle(arr){
const length = arr.length;
while (length > 0) {
const random = Math.floor(Math.random() * length);
length--;
[arr[length], arr[random]] = [arr[random], arr[length]];
}
return arr;
}
// 或
arr.sort(function(){
return .5 - Math.random();
});