1. 程式人生 > >月薪2w+的前端程序員是這樣答題的!

月薪2w+的前端程序員是這樣答題的!

else 同時 back arc 執行順序 為什麽 macro console 之前

為什麽 0.1 + 0.2 != 0.3,請詳述理由

因為 JS 采用 IEEE 754 雙精度版本(64位),並且只要采用 IEEE 754 的語言都有該問題。

我們都知道計算機表示十進制是采用二進制表示的,所以 0.1 在二進制表示為

// (0011) 表示循環
0.1 = 2^-4 * 1.10011(0011)

那麽如何得到這個二進制的呢,我們可以來演算下

技術分享圖片

小數算二進制和整數不同。乘法計算時,只計算小數位,整數位用作每一位的二進制,並且得到的第一位為最高位。所以我們得出 0.1 = 2^-4 1.10011(0011),那麽 0.2 的演算也基本如上所示,只需要去掉第一步乘法,所以得出 0.2 = 2^-3 1.10011(0011)。

回來繼續說 IEEE 754 雙精度。六十四位中符號位占一位,整數位占十一位,其余五十二位都為小數位。因為 0.1 和 0.2 都是無限循環的二進制了,所以在小數位末尾處需要判斷是否進位(就和十進制的四舍五入一樣)。

所以 2^-4 1.10011...001 進位後就變成了 2^-4 1.10011(0011 12次)010 。那麽把這兩個二進制加起來會得出 2^-2 1.0011(0011 * 11次)0100 , 這個值算成十進制就是 0.30000000000000004

下面說一下原生解決辦法,如下代碼所示

parseFloat((0.1 + 0.2).toFixed(10))

10 個 Ajax 同時發起請求,全部返回展示結果,並且至多允許三次失敗,說出設計思路

這個問題相信很多人會第一時間想到 Promise.all ,但是這個函數有一個局限在於如果失敗一次就返回了,直接這樣實現會有點問題,需要變通下。以下是兩種實現思路

// 以下是不完整代碼,著重於思路 非 Promise 寫法
let successCount = 0
let errorCount = 0
let datas = []
ajax(url, (res) => {
     if (success) {
         success++
         if (success + errorCount === 10) {
             console.log(datas)
         } else {
             datas.push(res.data)
         }
     } else {
         errorCount++
         if (errorCount > 3) {
            // 失敗次數大於3次就應該報錯了
             throw Error(‘失敗三次‘)
         }
     }
})
// Promise 寫法
let errorCount = 0
let p = new Promise((resolve, reject) => {
    if (success) {
         resolve(res.data)
     } else {
         errorCount++
         if (errorCount > 3) {
            // 失敗次數大於3次就應該報錯了
            reject(error)
         } else {
             resolve(error)
         }
     }
})
Promise.all([p]).then(v => {
  console.log(v);
});

基於 Localstorage 設計一個 1M 的緩存系統,需要實現緩存淘汰機制

設計思路如下:

  • [ ] 存儲的每個對象需要添加兩個屬性:分別是過期時間和存儲時間。
  • [ ] 利用一個屬性保存系統中目前所占空間大小,每次存儲都增加該屬性。當該屬性值大於 1M
    時,需要按照時間排序系統中的數據,刪除一定量的數據保證能夠存儲下目前需要存儲的數據。
  • [ ] 每次取數據時,需要判斷該緩存數據是否過期,如果過期就刪除。

以下是代碼實現,實現了思路,但是可能會存在 Bug,但是這種設計題一般是給出設計思路和部分代碼,不會需要寫出一個無問題的代碼

class Store {
  constructor() {
    let store = localStorage.getItem(‘cache‘)
    if (!store) {
      store = {
        maxSize: 1024 * 1024,
        size: 0
      }
      this.store = store
    } else {
      this.store = JSON.parse(store)
    }
  }
  set(key, value, expire) {
    this.store[key] = {
      date: Date.now(),
      expire,
      value
    }
    let size = this.sizeOf(JSON.stringify(this.store[key]))
    if (this.store.maxSize < size + this.store.size) {
      console.log(‘超了-----------‘);
      var keys = Object.keys(this.store);
      // 時間排序
      keys = keys.sort((a, b) => {
        let item1 = this.store[a], item2 = this.store[b];
        return item2.date - item1.date;
      });
      while (size + this.store.size > this.store.maxSize) {
        let index = keys[keys.length - 1]
        this.store.size -= this.sizeOf(JSON.stringify(this.store[index]))
        delete this.store[index]
      }
    }
    this.store.size += size

    localStorage.setItem(‘cache‘, JSON.stringify(this.store))
  }
  get(key) {
    let d = this.store[key]
    if (!d) {
      console.log(‘找不到該屬性‘);
      return
    }
    if (d.expire > Date.now) {
      console.log(‘過期刪除‘);
      delete this.store[key]
      localStorage.setItem(‘cache‘, JSON.stringify(this.store))
    } else {
      return d.value
    }
  }
  sizeOf(str, charset) {
    var total = 0,
      charCode,
      i,
      len;
    charset = charset ? charset.toLowerCase() : ‘‘;
    if (charset === ‘utf-16‘ || charset === ‘utf16‘) {
      for (i = 0, len = str.length; i < len; i++) {
        charCode = str.charCodeAt(i);
        if (charCode <= 0xffff) {
          total += 2;
        } else {
          total += 4;
        }
      }
    } else {
      for (i = 0, len = str.length; i < len; i++) {
        charCode = str.charCodeAt(i);
        if (charCode <= 0x007f) {
          total += 1;
        } else if (charCode <= 0x07ff) {
          total += 2;
        } else if (charCode <= 0xffff) {
          total += 3;
        } else {
          total += 4;
        }
      }
    }
    return total;
  }
}

詳細說明 Event loop

眾所周知 JS 是門非阻塞單線程語言,因為在最初 JS 就是為了和瀏覽器交互而誕生的。如果 JS 是門多線程的語言話,我們在多個線程中處理 DOM 就可能會發生問題(一個線程中新加節點,另一個線程中刪除節點),當然可以引入讀寫鎖解決這個問題。

JS 在執行的過程中會產生執行環境,這些執行環境會被順序的加入到執行棧中。如果遇到異步的代碼,會被掛起並加入到 Task(有多種 task) 隊列中。一旦執行棧為空,Event Loop 就會從 Task 隊列中拿出需要執行的代碼並放入執行棧中執行,所以本質上來說 JS 中的異步還是同步行為。

console.log(‘script start‘);

setTimeout(function() {
  console.log(‘setTimeout‘);
}, 0);

console.log(‘script end‘);

以上代碼雖然 setTimeout 延時為 0,其實還是異步。這是因為 HTML5 標準規定這個函數第二個參數不得小於 4 毫秒,不足會自動增加。所以 setTimeout 還是會在 script end 之後打印。

不同的任務源會被分配到不同的 Task 隊列中,任務源可以分為 微任務(microtask) 和 宏任務(macrotask)。在 ES6 規範中,microtask 稱為 jobs,macrotask 稱為 task。

console.log(‘script start‘);

setTimeout(function() {
  console.log(‘setTimeout‘);
}, 0);

new Promise((resolve) => {
    console.log(‘Promise‘)
    resolve()
}).then(function() {
  console.log(‘promise1‘);
}).then(function() {
  console.log(‘promise2‘);
});

console.log(‘script end‘);
// script start => Promise => script end => promise1 => promise2 => setTimeout

以上代碼雖然 setTimeout 寫在 Promise 之前,但是因為 Promise 屬於微任務而 setTimeout 屬於宏任務,所以會有以上的打印。

微任務包括 process.nextTick ,promise ,Object.observe ,MutationObserver

宏任務包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering

很多人有個誤區,認為微任務快於宏任務,其實是錯誤的。因為宏任務中包括了 script ,瀏覽器會先執行一個宏任務,接下來有異步代碼的話就先執行微任務。

所以正確的一次 Event loop 順序是這樣的

1.執行同步代碼,這屬於宏任務
2.執行棧為空,查詢是否有微任務需要執行
3.執行所有微任務
4.必要的話渲染 UI
5.然後開始下一輪 Event loop,執行宏任務中的異步代碼

通過上述的 Event loop 順序可知,如果宏任務中的異步代碼有大量的計算並且需要操作 DOM 的話,為了更快的 界面響應,我們可以把操作 DOM 放入微任務中。

Node 中的 Event loop

Node 中的 Event loop 和瀏覽器中的不相同。
Node 的 Event loop 分為6個階段,它們會按照順序反復運行

┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<──connections───     │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘

timer

timers 階段會執行 setTimeout 和 setInterval

一個 timer 指定的時間並不是準確時間,而是在達到這個時間後盡快執行回調,可能會因為系統正在執行別的事務而延遲。

下限的時間有一個範圍:[1, 2147483647] ,如果設定的時間不在這個範圍,將被設置為1。

I/O

I/O 階段會執行除了 close 事件,定時器和 setImmediate 的回調

idle, prepare

idle, prepare 階段內部實現

poll

poll 階段很重要,這一階段中,系統會做兩件事情

1.執行到點的定時器
2.執行 poll 隊列中的事件

並且當 poll 中沒有定時器的情況下,會發現以下兩件事情

  • [ ] 如果 poll 隊列不為空,會遍歷回調隊列並同步執行,直到隊列為空或者系統限制
  • [ ] 如果 poll 隊列為空,會有兩件事發生
  • [ ] 如果有 setImmediate 需要執行,poll 階段會停止並且進入到 check 階段執行 setImmediate
    如果沒有 setImmediate 需要執行,會等待回調被加入到隊列中並立即執行回調

如果有別的定時器需要被執行,會回到 timer 階段執行回調。

check

check 階段執行 setImmediate

close callbacks

close callbacks 階段執行 close 事件

並且在 Node 中,有些情況下的定時器執行順序是隨機的

setTimeout(() => {
    console.log(‘setTimeout‘);
}, 0);
setImmediate(() => {
    console.log(‘setImmediate‘);
})
// 這裏可能會輸出 setTimeout,setImmediate
// 可能也會相反的輸出,這取決於性能
// 因為可能進入 event loop 用了不到 1 毫秒,這時候會執行 setImmediate
// 否則會執行 setTimeout

當然在這種情況下,執行順序是相同的

var fs = require(‘fs‘)

fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log(‘timeout‘);
    }, 0);
    setImmediate(() => {
        console.log(‘immediate‘);
    });
});
// 因為 readFile 的回調在 poll 中執行
// 發現有 setImmediate ,所以會立即跳到 check 階段執行回調
// 再去 timer 階段執行 setTimeout
// 所以以上輸出一定是 setImmediate,setTimeout

上面介紹的都是 macrotask 的執行情況,microtask 會在以上每個階段完成後立即執行。

setTimeout(()=>{
    console.log(‘timer1‘)

    Promise.resolve().then(function() {
        console.log(‘promise1‘)
    })
}, 0)

setTimeout(()=>{
    console.log(‘timer2‘)

    Promise.resolve().then(function() {
        console.log(‘promise2‘)
    })
}, 0)

// 以上代碼在瀏覽器和 node 中打印情況是不同的
// 瀏覽器中打印 timer1, promise1, timer2, promise2
// node 中打印 timer1, timer2, promise1, promise2

最後

自己從事前端五年了

如果你依然在編程的世界裏迷茫,不知道自己的未來規劃,可以加入web前端學習交流群:731771211 裏面可以與大神一起交流並走出迷茫。新手可進群免費領取學習資料,看看前輩們是如何在編程的世界裏傲然前行!群裏不停更新最新的教程和學習方法(進群送web前端系統學習路線,詳細的前端項目實戰教學視頻),有想學習web前端的,或是轉行,或是大學生,還有工作中想提升自己能力的,正在學習的小夥伴歡迎加入

點擊:加入

月薪2w+的前端程序員是這樣答題的!