面試時,你被要求手寫常見原理了嗎?

如今前端工程師的技術要求越來越高,會使用常見的API已經不能滿足現如今前端日益快速發展的腳步。現在中大廠基本都會要求面試者手寫前端常見API的原理,以此來證明你對該知識點的理解程度。接下來,我將列舉我面試時以及自認爲比較重要的CSS部分、JS部分常見手寫原理題!

CSS部分

經典Flex佈局

如今Flex佈局不管是移動端還是PC端的應用已經非常廣泛了,下面我列舉幾個平時項目中非常常見的幾個需求。以下例子我們都以Vue項目爲例~

flex佈局均勻分佈後換行問題

需求一:ul下有多個li,每三個li排一列,多餘的換行顯示。

很顯然,絕大部分的小夥伴都會使用Flex佈局,很顯然會出現一個問題就是如果li是3的倍數的話就能正常顯示,若不是的話,佈局就不是產品經理滿意的結果。

display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;

解決方案

我們在ul的底部新增li,個數爲數組總長度%3的餘數即可。

<li class="item" style="border: none;" v-for="(ite, idx) in list.length%3" :key="idx"></li>

兩欄佈局

兩欄佈局:左右兩欄,左邊固定,右邊自適應

效果圖

  1. 第一種方式 — 浮動

    HTML部分:

    <div class="outer outer1">
        <div class="left">1-left</div>
        <div class="right">1-right</div>
    </div>
    

    CSS部分:

    .outer1 .left {
        width: 200px;
        float: left;
    }
    .outer1 .right {
        width: auto;
        margin-left: 200px;
    }
    
  2. 第二種方式 — flex

    HTML部分:

    <div class="outer outer2">
        <div class="left">2-left</div>
        <div class="right">2-right</div>
    </div>
    

    CSS部分:

    .outer2 {
       display: flex;
    }
    .outer2 .left {
       flex: 0 0 200px;
       /* flex-grow: 0;
       flex-shrink:0;
       flex-basis:200px; */
    }
    .outer2 .right {
       flex: auto;
    }
    
    注意:flex: 0 0 200px是flex: flex-grow flex-shrink flex-basis的簡寫
    
  3. 第三種方式 — position

    HTML部分:

    <div class="outer outer3">
       <div class="left">3-left</div>
       <div class="right">3-right</div>
    </div>
    

    CSS部分:

    .outer3 {
       position: relative;
    }
    .outer3 .left {
       position: absolute;
       width: 200px;
    }
    .outer3 .right {
       margin-left: 200px;
    }
    
  4. 第四種方式 — position again

    HTML部分:

    <div class="outer outer4">
        <div class="left">4-left</div>
        <div class="right">4-right</div>
    </div>
    

    CSS部分:

    .outer4 {
        position: relative;
    }
    .outer4 .left {
        width: 200px;
    } 
    .outer4 .right {
        position: absolute;
        top: 0;
        left: 200px;
        right: 0;
    }
    

三欄佈局

三欄佈局: 中間列自適應寬度,旁邊兩側固定寬度

效果圖

  1. 第一種方式 — 定位

    HTML部分:

    <div class="outer outer1">
       <div class="left">1-left</div>
       <div class="middle">1-middle</div>
       <div class="right">1-right</div>
    </div>
    

    CSS部分:

    .outer1 {
       position: relative;
    }
    .outer1 .left {
       position: absolute;
       width: 100px;
    }
    .outer1 .middle {
       margin: 0 200px 0 100px;
    }
    .outer1 .right {
       position: absolute;
       width: 200px;
       top: 0;
       right: 0;
    }
    注意:左右分別使用絕對定位,中間設置外邊距
    
  2. 第二種方式 — flex佈局

    HTML部分:

    <div class="outer outer2">
       <div class="left">2-left</div>
       <div class="middle">2-middle</div>
       <div class="right">2-right</div>
    </div>
    

    CSS部分:

    .outer2 {
       display: flex;
    }
    .outer2 .left {
       flex: 0 0 100px;
    }
    .outer2 .middle {
       flex: auto;
    }
    .outer2 .right {
       flex: 0 0 200px;
    }
    
  3. 第三種方式 — 浮動原理

    HTML部分:

    <div class="outer outer3">
       <div class="left">3-left</div>
       <div class="right">3-right</div>
       <div class="middle">3-middle</div>
    </div>
    

    CSS部分:

    .outer3 .left{
       float: left;
       width: 100px;
    }
    .outer3 .right {
       float: right;
       width: 200px;
    }
    .outer3 .middle {
       margin: 0 200px 0 100px;
    }
    

聖盃佈局

聖盃佈局: 中間的優先渲染,獨立的左中右結構

具體實現聖盃佈局的步驟:

  1. 讓左右浮動在一行顯示,相對定位
  2. 讓中間模塊的middle寬度爲100%
  3. 讓左邊的色塊移動到middle前面,margin-left:-100%
  4. 讓右邊的色塊移動到middle的後面,margin-left:-寬度
  5. 給三個小塊的父元素加一個內填充的屬性padding,爲的是填充擠到中間
  6. 給左邊的塊移動到左邊left:-200px, 給右邊的塊移動到右邊right:-200px

效果圖

HTML部分:

<header>header</header>
   <div class="container">
      <div class="middle">midlle</div>
      <div class="left">left</div>
      <div class="right">right</div>
   </div>
<footer>footer</footer>

CSS部分:

header, footer {
   height: 100px;
   width: 100%;
   background-color: antiquewhite;
}
.container {
   height: 200px;
   padding-left: 200px;
   padding-right: 300px;
}
.container > div {
   float: left;
   position: relative;
   height: 100%;
}
.left {
   width: 200px;
   height: 200px;
   background-color: burlywood;
   margin-left: -100%;
   left: -200px;
}
.right {
   width: 300px;
   height: 200px;
   background-color: burlywood;
   margin-left: -300px;
   right: -300px;
}
.middle {
   width: 100%;
   height: 200px;
   background-color: #b0f9c2;
}

雙飛翼佈局

雙飛翼佈局

具體實現雙飛翼佈局的步驟:

  1. 給左,中,右 加浮動,在一行顯示
  2. 給middle寬度爲100%
  3. 讓左邊的模塊移動middle的左邊 margin-left:-100%
  4. 讓右邊的模塊移動middle的右邊 margin-left:-自己寬度
  5. 給middle裏面的容器添加外間距 margin: 左右

效果:

html部分

<div class="main">
    <div class="middle">
			<div class="middle-inner">中間</div>
    </div>
    <div class="left">左邊</div>
    <div class="right">右邊</div>
</div>

css部分

.main>div { 
    float:left;
    position: relative;
    height: 300px; 
}
.middle { 
    width: 100%;
    background-color: lightgreen 
}
.left { 
   width:200px;
   margin-left:-100%;
   background-color:#b0f9c2 
}
.right { 
   width: 200px;
   margin-left:-200px;
   background-color:pink 
}
.middle-inner{
   margin:0 200px; 
   background-color: burlywood; 
   height:300px;
}

水平垂直居中

html部分

<div class="box" id="box">
   石小明
</div>

css部分

公共部分

body {
    width: 100vw;
    height: 100vh;
    overflow: hidden;
}
.box {
    box-sizing: border-box;
    width: 100px;
    height: 50px;
    line-height: 50px;
    text-align: center;
    font-size: 16px;
    border: 1px solid lightblue;
    background: lightcyan;
}

第一種:定位

.box {
    position: absolute;
    top: 50%;
    left: 50%;
    margin-left: -50px;
    margin-top: -25px;
}

注意:上面的方式是一定要知道具體的寬高。但下面的方式是知道寬高,但是沒有用到寬高。

第二種:flex

body {
    display: flex;
    justify-content: center;
    align-items: center;
}

注意:這種方式也是兼容性不是很好

第三種:JavaScript

let html = document.documentElement,
            winW = html.clientWidth,
            winH = html.clientHeight,
            boxW = box.offsetWidth, // offsetWidth帶邊框
            boxH = box.offsetHeight;

            box.style.position = 'absolute';
            box.style.left = (winW - boxW) / 2 + 'px';
            box.style.top = (winH - boxH) / 2 + 'px';

第四種:table-cell

body {
    display: table-cell;
    vertical-align: middle;
    text-align: center;
}

JS 部分

統計網頁中出現的標籤

實現步驟

  1. 獲取所有的DOM節點
  2. NodeList集合轉化爲數組
  3. 獲取數組每個元素的標籤名
  4. 去重
new Set([...document.querySelectorAll('*')].map(ele=>ele.tagName)).size

JS深淺拷貝

對象深淺拷貝,是面試常見的面試題之一。

原對象:

let obj = {
   a: 100,
   b: [100, 200, 300],
   c: {
      x: 10
   },
   d: /^\d+$/
}

淺克隆

淺克隆 只克隆第一層

方法一:

let obj2 = {...obj};

方法二:

let obj2 = {};
for(let key in obj) {
   if(!obj.hasOwnProperty(key)) break;
   obj2[key] = obj[key];
}

深克隆

注意:在函數、日期、正則表達式時,JSON.stringify時,都會被轉換成對象{}

方法一:

let obj3 = JSON.parse(JSON.stringify(obj));

方法二:

function deepClone(obj) {
    // 過濾一些特殊情況
    if(obj === null) return null;
    if(typeof obj !== "object") return obj;
    if (typeof window !== 'undefined' && window.JSON) { // 瀏覽器環境下 並支持window.JSON 則使用 JSON
        return JSON.parse(JSON.stringify(obj));
    }
    if(obj instanceof RegExp) { // 正則
         return new RegExp(obj);
    }
    if(obj instanceof Date) { // 日期
         return new Date(obj);
    }
    // let newObj = {}
    // let newObj = new Object()
    let newObj = new obj.constructor; // 不直接創建空對象的目的:克隆的結果和之前保持所屬類  =》 即能克隆普通對象,又能克隆某個實例對象
    for(let key in obj) {
        if(obj.hasOwnProperty(key)) {
             newObj[key] = deepClone(obj[key]);
        }
    }
    // let newObj = obj.constructor === Array ? [] : {};
    //for(let key in obj) {
    //    newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : //obj[key];
    //}
    return newObj;
}

原生Ajax

一個完整的 ajax 請求一般包括以下步驟:

  • 實例化 XMLHttpRequest 對象
  • 連接服務器
  • 發送請求
  • 介紹
function ajax(options) {
  let method = options.method || 'GET', // 不傳則默認爲GET請求
      params = options.params, // GET請求攜帶的參數
      data   = options.data, // POST請求傳遞的參數
      url    = options.url + (params ? '?' + Object.keys(params).map(key => key + '=' + params[key]).join('&') : ''),
      async  = options.async === false ? false : true,
      success = options.success,
      headers = options.headers;

  let xhr;
  // 創建xhr對象
  if(window.XMLHttpRequest) {
    xhr = new XMLHttpRequest();
  } else {
    xhr = new ActiveXObject('Microsoft.XMLHTTP');
  }

  xhr.onreadystatechange = function() {
    if(xhr.readyState === 4 && xhr.status === 200) {
      success && success(xhr.responseText);
    }
  }

  xhr.open(method, url, async);
  
  if(headers) {
    Object.keys(Headers).forEach(key => xhr.setRequestHeader(key, headers[key]))
  }

  method === 'GET' ? xhr.send() : xhr.send(data)
}

注意:IE5、6不兼容XMLHttpRequest,所以要使用ActiveXObject()對象,並傳入 ‘Microsoft.XMLHTTP’,達到兼容目的。

  • readyState的五種狀態詳解:

    0 - (未初始化)還沒有調用send()方法

    1 - (載入)已調用send()方法,正在發送請求

    2 - (載入完成)send()方法執行完成,已經接收到全部響應內容

    3 - (交互)正在解析響應內容

    4 - (完成)響應內容解析完成,可以在客戶端調用了

防抖和節流

如今前端界面效果越來越複雜,有一些頻繁操作會導致頁面性能和用戶體驗度低。像:輸入框搜索會頻繁調端口接口、放大縮小窗口等。

防抖 - debounce 當持續觸發事件時,一定時間段內沒有再觸發事件,事件處理函數纔會執行一次,如果設定的時間到來之前,又一次觸發了事件,就重新開始延時。

const debounce = (fn, delay) => {
  let timer = null;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
};

節流 - throttle 當持續觸發事件時,保證一定時間段內只調用一次事件處理函數。

const throttle = (fn, delay = 500) => {
  let flag = true;
  return (...args) => {
    if (!flag) return;
    flag = false;
    setTimeout(() => {
      fn.apply(this, args);
      flag = true;
    }, delay);
  };
};

解析 URL 參數

function parseParam(url) {
    // 將瀏覽器地址中 ‘?’ 後面的字符串取出來
    const paramsStr = /.+\?(.+)$/.exec(url)[1];
    // 將截取的字符串以 ‘&’ 分割後存到數組中
    const paramsArr = paramsStr.split('&');
    // 定義存放解析後的對象
    let paramsObj = {};
    // 遍歷
    paramsArr.forEach(param => {
      // 判斷是否含有key和value
      if (/=/.test(param)) {
        // 結構獲取對象的key和value
        let [key, val] = param.split('=');
        // 解碼
        val = decodeURIComponent(val);
        // 判斷是否轉爲數字
        val = /^\d+$/.test(val) ? parseFloat(val) : val;
        // 判斷存放對象中是否存在key屬性
        if (paramsObj.hasOwnProperty(key)) {
          // 存在的話就存放一個數組
          paramsObj[key] = [].concat(paramsObj[key], val);
        } else {
          // 不存在就存放一個對象
          paramsObj[key] = val;
        }
      } else {
        // 沒有value的情況
        paramsObj[param] = true;
      }
    })
    return paramsObj;
}
let url = 'https://www.baidu.com?username=%22tmc%22&password=%22123456%22&dutiy=%E5%89%8D%E7%AB%AF%E6%94%BB%E5%9F%8E%E7%8B%AE&flag';
console.log(parseParam(url))

{ username: '"tmc"',
  password: '"123456"',
  dutiy: '前端攻城獅',
  flag: true 
}

Jsonp的原理

function jsonp({url, params, cb}) { 
   return new Promise((resolve, reject) => {
     window[cb] = function (data) {  // 聲明全局變量
        resolve(data)
        document.body.removeChild(script)
      }
      params = {...params, cb}
      let arrs = []
      for(let key in params) {
         arrs.push(`${key}=${params[key]}`)
      }
      let script = document.createElement('script')
      script.src = `${url}?${arrs.join('&')}`
      document.body.appendChild(script)
   })
}

jsonp的缺點

  1. 只能發送Get請求 不支持post put delete
  2. 不安全 xss攻擊

apply的原理

apply 的實現原理和 call 的實現原理差不多,只是參數形式不一樣。— 數組

Function.prototype.apply = function(content = window) {
    content.fn = this;
    let result;
    // 判斷是否有第二個參數
    if(arguments[1]) {
        result = content.fn(...arguments[1]);
    } else {
        result = content.fn();
    }
    delete content.fn;
    return result;
}

注意:當apply傳入的第一個參數爲null時,函數體內的this會指向window。

bind的原理

bind 方法會創建一個新函數。當這個新函數被調用時,bind() 的第一個參數將作爲它運行時的 this,之後的一序列參數將會在傳遞的實參前傳入作爲它的參數。

Function.prototype.bind = function(content) {
   if(typeof this != 'function') {
      throw Error('not a function');
   }
   let _this = this;
   let args = [...arguments].slice(1);
   return function F() {
      // 判斷是否被當做構造函數使用
      if(this instanceof F) {
         return _this.apply(this, args.concat([...arguments]))
      }
      return _this.apply(content, args.concat([...arguments]))
   }
}

call的原理

call語法:fun.call(thisArg, arg1, arg2, arg3, …)

call 的核心原理:

  • 將函數設爲對象的屬性
  • 執行和刪除這個函數
  • 指定this到函數並傳入給定參數執行函數
  • 如果不傳參數,默認指向window
Function.prototype.call2 = function(content = window) {
    // 判斷是否是underfine和null
    // if(typeof content === 'undefined' || typeof content === null){
    //     content = window
    // }
    content.fn = this;
    let args = [...arguments].slice(1);
    let result = content.fn(...args);
    delete content.fn;
    return result;
}

注意:當call傳入的第一個參數爲null時,函數體內的this會指向window。

new的原理

實現一個new操作符的具體實現步驟:

  • 首先函數接受不定量的參數,第一個參數爲構造函數,接下來的參數被構造函數使用
  • 然後內部創建一個空對象 obj
  • 因爲 obj 對象需要訪問到構造函數原型鏈上的屬性,所以我們通過 setPrototypeOf 將兩者聯繫起來。這段代碼等同於 obj.proto = Con.prototype
  • 將 obj 綁定到構造函數上,並且傳入剩餘的參數
  • 判斷構造函數返回值是否爲對象,如果爲對象就使用構造函數返回的值,否則使用 obj,這樣就實現了忽略構造函數返回的原始值
/**
 * 創建一個new操作符
 * @param {*} Con 構造函數
 * @param  {...any} args 忘構造函數中傳的參數
 */
  function createNew(Con, ...args) {
    let obj = {} // 創建一個對象,因爲new操作符會返回一個對象
    Object.setPrototypeOf(obj, Con.prototype) // 將對象與構造函數原型鏈接起來
    // obj.__proto__ = Con.prototype // 等價於上面的寫法
    let result = Con.apply(obj, args) // 將構造函數中的this指向這個對象,並傳遞參數
    return result instanceof Object ? result : obj
}

注意

一、new操作符的幾個作用:

  1. new操作符返回一個對象,所以我們需要在內部創建一個對象
  2. 這個對象,也就是構造函數中的this,可以訪問到掛載在this上的任意屬性
  3. 這個對象可以訪問到構造函數原型鏈上的屬性,所以需要將對象與構造函數鏈接起來
  4. 返回原始值需要忽略,返回對象需要正常處理

二、new操作符的特點:

  1. new通過構造函數Test創建處理的實例可以訪問構造函數中的屬性也可以訪問構造函數原型鏈上的屬性,所以:通過new操作符,實例與構造函數通過原型鏈連接了起來
  2. 構造函數如果返回原始值,那麼這個返回值毫無意義
  3. 構造函數如果返回對象,那麼這個返回值會被正常的使用,導致new操作符沒有作用

instanceof的原理

instanceof 用來檢測一個對象在其原型鏈中是否存在一個構造函數的 prototype 屬性

function instanceOf(left,right) {
    let proto = left.__proto__;
    let prototype = right.prototype
    while(true) {
        if(proto === null) return false
        if(proto === prototype) return true
        proto = proto.__proto__;
    }
}

Promise A+規範原理

在面試中高級前端時。要求被手寫Promise A+規範源碼是必考題了。如果想詳細瞭解,請參考 一步步教你實現Promise/A+ 規範 完整版

class Promise {
    constructor(executor) {
        this.status = 'pending' // 初始化狀態
        this.value = undefined // 初始化成功返回的值
        this.reason = undefined // 初始化失敗返回的原因

        // 解決處理異步的resolve
        this.onResolvedCallbacks = [] // 存放所有成功的resolve
        this.onRejectedCallbacks = [] // 存放所有失敗的reject

        /**
         * @param {*} value 成功返回值
         * 定義resolve方法
         * 注意:狀態只能從pending->fulfilled和pending->rejected兩個
         */
        const resolve = (value) => { 
            if(this.status === 'pending') {
                this.status = 'fulfilled' // 成功時將狀態轉換爲成功態fulfilled
                this.value = value // 將成功返回的值賦值給promise
                // 爲了解決異步resolve以及返回多層promise
                this.onResolvedCallbacks.forEach(fn => {
                    fn() // 當狀態變爲成功態依次執行所有的resolve函數
                })
            }
        }
        const reject = (reason) => {
            if(this.status === 'pending') {
                this.status = 'rejected' // 失敗時將狀態轉換爲成功態失敗態rejected
                this.reason = reason // 將失敗返回的原因賦值給promise
                this.onRejectedCallbacks.forEach(fn => {
                    fn() // 當狀態變爲失敗態依次執行所有的reject函數
                })
            }
        }
        executor(resolve, reject) // 執行promise傳的回調函數
    }
    /**
     * 定義promise的then方法 
     * @param {*} onFulfilled 成功的回調
     * @param {*} onRejected 失敗的回調
     */
    then(onFulfilled, onRejected) {
        // 爲了解決then方法返回Promise的情況
        const promise2 = new Promise((resolve, reject) => {
            if(this.status === 'fulfilled') { // 如果狀態爲fulfilled時則將值傳給這個成功的回調
                setTimeout(() => {
                    const x = onFulfilled(this.value) // x的值有可能爲 promise || 123 || '123'...
                    // 注意:此時調用promise2時還沒有返回值,要用setTimeout模擬進入第二次事件循環;先有雞先有蛋
                    resolvePromise(promise2, x, resolve, reject) 
                }, 0)
            }
            if(this.status === 'rejected') {
                setTimeout(() => {
                    const x = onRejected(this.reason) // 如果狀態爲rejected時則將視頻的原因傳給失敗的回調
                    resolvePromise(promise2, x, resolve, reject) 
                }, 0)
            }
            if(this.status === 'pending') { // 記錄-》解決異步
                this.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                        const x = onFulfilled(this.value)
                        resolvePromise(promise2, x, resolve, reject) 
                    }, 0)
                })
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        const x = onRejected(this.reason)
                        resolvePromise(promise2, x, resolve, reject) 
                    }, 0)
                })
            }
        })
        return promise2; // 解決多次鏈式調用的問題
    }
}

const resolvePromise = (promise2, x, resolve, reject) => {
    // console.log(promise2, x, resolve, reject)
    if(promise2 === x) { // 如果返回的值與then方法返回的值相同時
        throw TypeError('循環引用')
    }
    // 判斷x是不是promise;注意:null的typeof也是object要排除
    if(typeof x === 'function' || (typeof x === 'object' && x !== null)) {
        try {
            const then = x.then // 獲取返回值x上的then方法;注意方法會報錯要捕獲異常;原因111
            if(typeof then === 'function') { // 就認爲是promise
                then.call(x, y => {
                    // resolve(y)
                    // 遞歸解析 ; 有可能返回多個嵌套的promise
                    resolvePromise(promise2, y, resolve, reject)
                }, r => {
                    reject(r)
                })
            }
        } catch(e) {
            reject(e)
        }
    } else {
        resolve(x);
    }
}
module.exports = Promise;

JS數組

去重

普通項

let arr2 = [1, 2, 3, 2, 33, 55, 66, 3, 55];

第一種:

let newArr = [];
   arr2.forEach(item => {
       if(newArr.indexOf(item) == '-1') {
           newArr.push(item);
       }
   })
console.log(newArr);

// (6) [1, 2, 3, 33, 55, 66]

第二種:

let newArr = [...new Set(arr2)];
console.log(newArr);

// (6) [1, 2, 3, 33, 55, 66]

注意:Array.from()、filter()、for()等方法都可以完成上面數組去重。

對象項

let arr1 = [
    {id: 1, name: '湯小夢'},
    {id: 2, name: '石小明'},
    {id: 3, name: '前端開發'},
    {id: 1, name: 'web前端'}
];

實現方法:

const unique = (arr, key) => {
    return [...new Map(arr.map(item => [item[key], item])).values()]
}
console.log(unique(arr1, 'id'));

// [
	{id: 1, name: "web前端"},
	{id: 2, name: "石小明"},
	{id: 3, name: "前端開發"}
]

合併

let arr3 = ['a', 'b']
let arr4 = ['c', 'd']

方法一:ES5

let arr5 = arr3.concat(arr4);
console.log(arr5);

// ['a', 'b', 'c', 'd']

方法一:ES6

let arr6 = [...arr3, ...arr4];
console.log(arr6);

// ['a', 'b', 'c', 'd']

展平

let arr7 = [1, 2, [3, 4], [5, 6, [7, 8, 9]]];

第一種:

let arrNew = arr7.flat(Infinity);
console.log(arrNew);

// (9) [1, 2, 3, 4, 5, 6, 7, 8, 9]

第二種:

let arrNew = arr7.join().split(',').map(Number);
console.log(arrNew);

// (9) [1, 2, 3, 4, 5, 6, 7, 8, 9]

第三種:

let arrNew = arr7.toString().split(',').map(Number);
console.log(arrNew);

// (9) [1, 2, 3, 4, 5, 6, 7, 8, 9]

第四種:

const flattern = (arr) => {
     const result = []
     arr.forEach((item) => {
         if (Array.isArray(item)) {
              result.push(...flattern(item))
         } else {
              result.push(item)
         }
    })
    return result
}
flattern(arr7);

// (9) [1, 2, 3, 4, 5, 6, 7, 8, 9]

第五種:

function flatten(arr) {
    return [].concat(
        ...arr.map(x => Array.isArray(x) ? flatten(x) : x)
    )
}
flattern(arr7);

// (9) [1, 2, 3, 4, 5, 6, 7, 8, 9]

是否爲數組

let arr = []

第一種:instanceof

console.log(arr instanceof Array)

第二種:constructor

console.log(arr.constructor === Array)

第三種:判斷對象是否有 push 等數組的一些方法

console.log(!!arr.push && !!arr.concat)

第四種:toString

console.log(Object.prototype.toString.call(arr) === '[object Array]')

第五種:Array.isArray

console.log(Array.isArray(arr))

注意:第五種方式最優~

冒泡排序

let arr = [1, 44, 6, 77, 3, 7, 99, 12];
  • 冒泡排序算法的原理如下:
  1. 比較兩個相鄰的元素,若前一個比後一個大,則交換位置
  2. 第一輪的時候最後一個元素應該是最大的一個
  3. 對所有的元素重複以上的步驟,除了最後一個
function bubbleSort(arr) {
    for(let i=0; i<arr.length; i++) {
        for(let j=0; j<arr.length - i - 1; j++) {
            if(arr[j+1] < arr[j]) {
                let temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
    return arr;
}
console.log(bubbleSort(arr));

// [ 1, 3, 6, 7, 12, 44, 77, 99 ]

注意:最後一個元素不用比較。

快速排序

let arr = [1, 44, 6, 77, 3, 7, 99, 12];
  • 快速排序算法的原理如下:
  1. 找基準(一般是以中間項爲基準)
  2. 遍歷數組,小於基準的放在left,大於基準的放在right
  3. 遞歸
function quickSort(arr) {
    if(arr.length <= 1) return arr;
    let mid = Math.floor(arr.length / 2);
    let midItem = arr.splice(mid, 1)[0];
    let leftArr = [];
    let rightArr = [];
    for(let i=0; i<arr.length; i++) {
        let current = arr[i];
        if(current >= midItem) {
            rightArr.push(current);
        } else {
            leftArr.push(current);
        }
    }
    return quickSort(leftArr).concat([midItem], quickSort(rightArr));
}

console.log(quickSort(arr));

// [ 1, 3, 6, 7, 12, 44, 77, 99 ]

總結

上面總結我面試或面試別人常見的CSS、JS部分手寫原理題。希望有小夥伴需要的請認真思考閱讀,必有收穫。希望您取得滿意的offer~❤️

最後

如果本文對你有幫助得話,給本文點個贊❤️❤️❤️

歡迎大家加入,一起學習前端,共同進步!
cmd-markdown-logo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章