天天看點

前端基礎知識總結(二)

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 進行算法優化;
  1. 如果是同一類型的元件,按照原政策繼續比較 virtual DOM tree。
  2. 如果不是,則将該元件判斷為 dirty component,進而替換整個元件下的所有子節點。
  3. 對于同一類型的元件,有可能其 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

回流:元素的大小或者位置發生了變化,觸發了重新布局,導緻渲染樹重新計算布局和渲染。頁面第一次加載的時候,至少發生一次回流。

  1. 添加或删除可見的DOM元素;
  2. 元素的位置發生變化;
  3. 元素的尺寸發生變化;
  4. 内容發生變化(比如文本變化或圖檔被另一個不同尺寸的圖檔所替代);
  5. 頁面一開始渲染的時候(這個無法避免);
  6. 浏覽器的視窗尺寸變化, 因為回流是根據視口的大小來計算元素的位置和大小的;

重繪:元素的外觀,風格改變,而不會影響布局(不包含寬高、大小、位置等不變)

  1. 如:outline, visibility, color, background-color......等

Reflow 的成本比 Repaint 高得多的多。DOM Tree 裡的每個結點都會有 reflow 方法,一個結點的 reflow 很有可能導緻子結點,甚至父點以及同級結點的 reflow。。回流一定會觸發重繪,而重繪不一定會回流

減少重繪與回流

  1. 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觸發硬體加速的屬性:

  1. transform
  2. opacity
  3. filter
  4. will-change
http請求方法

HTTP1.0定義了三種請求方法: GET, POST 和 HEAD方法。

HTTP1.1新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法

  1. OPTIONS: 即預檢請求,可用于檢測伺服器允許的http方法。當發起跨域請求時,由于安全原因,觸發一定條件時浏覽器會在正式請求之前自動先發起OPTIONS請求,即CORS預檢請求,伺服器若接受該跨域請求,浏覽器才繼續發起正式請求。
  2. HEAD: 向伺服器索與GET請求相一緻的響應,隻不過響應體将不會被傳回,用于擷取報頭。
  3. GET:向特定的資源送出請求。注意:GET方法不應當被用于産生“副作用”的操作中
  4. POST:向指定資源送出資料進行處理請求(例如送出表單或者上傳檔案)。資料被包含在請求體中。POST請求可能會導緻新的資源的建立和/或已有資源的修改。
  5. PUT:向指定資源位置上傳其最新内容
  6. DELETE:請求伺服器删除Request-URL所辨別的資源
  7. TRACE:回顯伺服器收到的請求,主要用于測試或診斷
  8. CONNECT:HTTP/1.1協定中預留給能夠将連接配接改為管道方式的代理伺服器
js判斷資料類型
  1. typeof 操作符
  • 對于基本類型,除 null 以外,均可以傳回正确的結果。
  • 對于引用類型,除 function 以外,一律傳回 object 類型。
  • 對于 null ,傳回 object 類型。
  • 對于 function 傳回  function 類型。
  1. instanceof :用來判斷 A 是否為 B 的執行個體,檢測的是原型。instanceof 隻能用來判斷兩個對象是否屬于執行個體關系, 而不能判斷一個對象執行個體具體屬于哪種類型。

instanceof 主要的實作原理就是隻要右邊變量的 prototype 在左邊變量的原型鍊上即可。

  1. constructor
  • null 和 undefined 是無效的對象,不會有 constructor 存在的
  • 函數的 constructor 是不穩定的,這個主要展現在自定義對象上,當開發者重寫 prototype 後,原有的 constructor 引用會丢失,constructor 會預設為 Object。為了規範開發,在重寫對象原型時一般都需要重新給 constructor 指派。

    為什麼變成了 Object?

    因為 prototype 被重新指派的是一個 { }, { } 是 new Object() 的字面量,是以 new Object() 會将 Object 原型上的 constructor 傳遞給 { },也就是 Object 本身。

  1. 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緩存: 強制緩存和協商緩存

良好的緩存政策可以降低資源的重複加載提高網頁的整體加載速度。緩存原理:

  1. 浏覽器在加載資源時,根據請求頭的expires和cache-control判斷是否命中強緩存,是則直接從緩存讀取資源,不會發請求到伺服器。
  2. 如果沒有命中強緩存,浏覽器會發送一個請求到伺服器,通過last-modified和etag驗證是否命中協商緩存。當向服務端發起緩存校驗的請求時,服務端會傳回 200 ok表示傳回正常的結果或者 304 Not Modified(不傳回body)表示浏覽器可以使用本地緩存檔案。304的響應頭也可以同時更新緩存文檔的過期時間
  3. 如果前面兩者都沒有命中,直接從伺服器加載資源。

強緩存通過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攻擊。

同源政策的具體表現:

  1. http請求不能向不同源的伺服器發起HTTP請求
  2. 非同源的網頁不能共享Cookie、LocalStorage、IndexDB
  3. 禁止對不同源頁面的DOM進行操作。主要場景是iframe跨域的情況,不同域名的iframe限制互相通路

解決方案:

  1. JSONP

實作原理:<script>标簽不受浏覽器同源政策的影響, 允許跨域引用資源。

實作方式: 動态建立script标簽,  利用src屬性進行跨域,通過回調函數處理請求結果。

優點: 相容性好

缺點:

隻能支援GET請求, JSONP在調用失敗時不會傳回各種HTTP狀态碼

安全性。如果提供JSONP的服務被人控制,那麼所有調用這個JSONP的網站都存在漏洞。

  1. CORS

跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴浏覽器 讓運作在一個 origin (domain) 上的Web應用被準許通路來自不同源伺服器上的指定的資源

虛拟dom原理

Virtual DOM是對DOM的抽象,本質上是JavaScript對象,這個對象就是更加輕量級的對DOM的描述.

箭頭函數與普通函數差別
  • 文法更加簡潔、清晰
  • 不綁定this,會捕獲其所在的上下文的this值,作為自己的this值
  • 箭頭函數繼承而來的this指向永遠不變
  • .call()/.apply()/.bind()無法改變箭頭函數中this的指向
  • 不能作為構造函數使用, 因為:
  1. 沒有自己的 this,無法調用 call,apply。
  2. 沒有 prototype 屬性 ,而 new 指令在執行時需要将構造函數的 prototype 指派給新的對象的__prpto__
  • 沒有自己的arguments,在箭頭函數中通路arguments實際上獲得的是外層局部(函數)執行環境中的值。如果要用,可以用 rest 參數代替。
  • 沒有原型prototype, 指向 undefined
  • 不能用作Generator函數,不能使用yeild關鍵字
new

new 關鍵字會進行如下的操作:

  1. 建立一個空的簡單JavaScript對象(即{});
  2. 連結該對象(設定該對象的__proto__)到構造器函數的原型 ;
  3. 将新建立的對象作為this的上下文 ;
  4. 傳回。如果該函數沒有傳回對象,則傳回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();
});