本週我們 55 人學了什麼

yck發表於2019-07-29

程式設計師這行如果想一直做下去,那麼持續學習是必不可少的。

大家找工作通常會喜歡技術氛圍好點的團隊,因為這樣能夠幫助自己更好的成長,但是並不是每個團隊都擁有這樣的氛圍。於是萌發一個念頭,想建立一個地方,讓一些人能在這塊地方記錄自己學習到的內容。這些內容通常會是一個小點,可能並不足以寫成一篇文章。但是這個知識點可能很多人也不知道,那麼通過這種記錄的方式讓別人同樣也學習到這個知識點就是一個很棒的事情了。

如果你也想參與這個記錄的事情,歡迎貢獻你的一份力量,地址在這裡

本週總共有 55 人貢獻了他們所學到的知識,以下是一些整合後的內容,更詳細的內容推薦前往倉庫閱讀。

JS

解決鍵盤彈出後擋表單的問題

window.addEventListener('resize', function () {
if (
  document.activeElement.tagName === 'INPUT' ||
  document.activeElement.tagName === 'TEXTAREA' ||
  document.activeElement.getAttribute('contenteditable') == 'true'
) {
  window.setTimeout(function () {
    if ('scrollIntoView' in document.activeElement) {
      document.activeElement.scrollIntoView();
    } else {
      // @ts-ignore
      document.activeElement.scrollIntoViewIfNeeded();
    }
  }, 0);
}
})
複製程式碼

圖片載入相關

首先是實現圖片懶載入

<ul>
	<li><img src="./img/default.png" data="./img/1.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/2.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/3.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/4.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/5.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/6.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/7.png" alt=""></li>
	<li><img src="./img/default.png" data="./img/8.png" alt=""></li>
</ul>
let imgs =  document.querySelectorAll('img')
// 視窗可視區高度
let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
// img 距離視窗可視區頂部的距離 imgs[i].getBoundingClientRect().top
function lazyLoadImg () {
    for (let i = 0; i < imgs.length; i ++) {
        if((imgs[i].getBoundingClientRect().top + imgs[i].height)>=0&&imgs[i].getBoundingClientRect().top < clientHeight ){
            imgs[i].src = imgs[i].getAttribute('data')
        }
    }      
}
window.addEventListener('scroll', lazyLoadImg);
複製程式碼

但是這種方式會引起圖片下載過程中閃白一下,可以通過 JS 預先載入圖片解決。

同時上述的懶載入解決方案已經很老了,可以使用最新的 API Intersection_Observer 來做這件事,會更簡單而且可控一些。

無loop生成指定長度的陣列

const List1 = len => ''.padEnd(len, ',').split('.')

const List2 = len => [...new Array(len).keys()]
複製程式碼

非同步的 Promise的 then 方法的回撥是何時被新增到microtasks queue中的?

今天刷部落格的時候看到一個題:

const pro = new Promise((resolve, reject) => {
    const pro1 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(3);
        }, 0);
    });
    resolve(4);
    pro1.then((args) => {
        console.log(args);
    });
});
pro.then((args) => {
    console.log(args);
});
複製程式碼

很多人都知道這道題的輸出結果是4,3;但是我對題主的這個問題產生了很大的疑問,因為個人並沒有著手實現過符合promise A/A+規劃的promise,所以每次做這種題都是憑著平時的使用經驗,實際上內心虛得很,然後自己查閱了 spec:ECMAScript 2018 Language Specification 根據 spec,如果呼叫 then 時 promise 是 pending 狀態,回撥會進入 promise 的 [[PromiseFulfill/RejectReactions]] 列表裡;否則會進入 PromiseJobs。

PromiseJob 以及 Job Queue 是 ES 中的說法,而 macroTask 和 microTask 是瀏覽器中的概念,包括 setTimeout 也是宿主環境提供的。因此輸出 4 3 是 ECMAScript 和 瀏覽器兩種規範共同約束的結果。

PromiseJob 對應瀏覽器中的一個 microTask。對於呼叫 then 時 promise 處於 pending 狀態,回撥函式進入到對應的 reactions 佇列中。當該 promise 被 fulfill 或 reject 時,則 flush 對應的 reactions 佇列 ,其中的每個 reaction 對應一個 PromiseJob 被按序 enqueue 到 Job Queue如果呼叫 then 時 promise 處於其他兩個狀態,JS 引擎就直接 enqueue 一個對應的 PromiseJob 到 Job Queue示例中的程式碼。

在瀏覽器中如下順序:

0. current cycle of evevt loop start
1. Install Timer,Timer get enqueued
2. Resovle pro, because there is no fulfillReaction binding to pro, do nothing
3. call then() at pro1, because pro1 is pending, add fulfillReaction to pro1
4. call then() at pro, because pro is reolved,immediately enqueue a PromiseJob
5. current macroTask is finished
6. run all PromiseJobs(microTasks) in order, 
7. console.log(4)
8. current cycle of event loop is finishedanother cycle starts
9. Timer Fires, and pro1 is resolved
10. at this time, pro1 hasfulfillReactions,enqueue every fulfillReaction as a PromiseJob in order
11. current macro Job is finished 
12. run all PromiseJobs in order
13. console.log(3)
14. current cycle of event loop is finished
複製程式碼

移動端開啟指定App或者下載App

navToDownApp() {
      let u = navigator.userAgent
      if (/MicroMessenger/gi.test(u)) {
        // 如果是微信客戶端開啟,引導使用者在瀏覽器中開啟
        alert('請在瀏覽器中開啟')
      }
      if (u.indexOf('Android') > -1 || u.indexOf('Linux') > -1) {
        // Android
        if (this.openApp('en://startapp')) {
          this.openApp('en://startapp') // 通過Scheme協議開啟指定APP
        } else {
          //跳轉Android下載地址
        }
      } else if (u.indexOf('iPhone') > -1) {
        if (this.openApp('ios--scheme')) {
          this.openApp('ios--scheme') // 通過Scheme協議開啟指定APP
        } else {
          // 跳轉IOS下載地址
        }
      }
    },
    openApp(src) {
      // 通過iframe的方式試圖開啟APP,如果能正常開啟,會直接切換到APP,並自動阻止a標籤的預設行為
      // 否則開啟a標籤的href連結
      let ifr = document.createElement('iframe')
      ifr.src = src
      ifr.style.display = 'none'
      document.body.appendChild(ifr)
      window.setTimeout(function() {
        // 開啟App後移出這個iframe
        document.body.removeChild(ifr)
      }, 2000)
    }
複製程式碼

利用 a 標籤解析 URL

function parseURL(url) {
    var a =  document.createElement('a');
    a.href = url;
    return {
        host: a.hostname,
        port: a.port,
        query: a.search,
        params: (function(){
            var ret = {},
                seg = a.search.replace(/^\?/,'').split('&'),
                len = seg.length, i = 0, s;
            for (;i<len;i++) {
                if (!seg[i]) { continue; }
                s = seg[i].split('=');
                ret[s[0]] = s[1];
            }
            return ret;
        })(),
        hash: a.hash.replace('#','')
    };
}
複製程式碼

陣列去重

  var array = [1, 2, 1, 1, '1'];
  function unique(array) {
    var obj = {};
    return array.filter(function(item, index, array){
      return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
    })
  }
複製程式碼

利用一個空的 Object 物件,我們把陣列的值存成 Object 的 key 值,比如 Object[value1] = true,在判斷另一個值的時候,如果 Object[value2]存在的話,就說明該值是重複的。

因為 1 和 '1' 是不同的,但是這種方法會判斷為同一個值,這是因為物件的鍵值只能是字串,所以我們可以使用 typeof item + item 拼成字串作為 key 值來避免這個問題

JS 函式物件引數的陷阱

上週在實現某個彈層功能的時候,用到了rc-util裡的 contains 方法函式, 結果 code-review 的時候同事對該程式碼提出了疑問:

rc-util 原始碼倉庫

export default function contains(root, n) {
  let node = n;
  while (node) {
    if (node === root) {
      return true;
    }
    node = node.parentNode;
  }
  return false;
}
複製程式碼

上述程式碼是 antd 內部抽象的一個工具方法,用來判斷某個dom是否為另一個dom的祖先節點。

同事疑問的是 let node = n; 這段程式碼是不是多餘的?

首先一開始的理解是 函式引數 n 是一個物件,一個dom節點物件。
如果用 node 儲存 n 的值,防止 node = node.parentNode 這段程式碼執行的時候,會改變傳入的實參 n 對應的值。

畢竟以下的程式碼我們都很熟悉:

function contains(root, n) {
  if(n) {
    n.a = 3
  }
}

const A = {a:1};
const B = {a:2};
contains(A,B)
console.log(B)    // {a:3}
複製程式碼

即當實參為物件時,函式內部是可以改變該物件的值從而影響函式之外的實參。

但是測試另外一段程式碼,發現和理解的不一樣:

function contains(root, n) {
  if(n) {
    n = {a:3}
  }
}

const A = {a:1};
const B = {a:2}
contains(A,B)
console.log(B) // {a:2}
複製程式碼

n.a = 3n = {a:3} 這兩段程式碼是不一樣的。

網上也有相關資料,其實可以簡單的理解為: 當函式一開始執行時,n 是指向實參 B 的一個引用.

n.a = 3 是在引用上關聯了一個屬性,此時和 B 還是同一個引用,因此會改變實參B的值。

n = {a:3} 則使得 n 不再指向實參 B, 而是指向一個新物件{a:3},也就是 nB 徹底斷絕了關係,因此不會改變實參 B 的值。

是不是可以給螞蟻的團隊提個issue建議刪除該程式碼,不過有這句程式碼也不會有什麼bug~

相關資料:JavaScript深入之引數按值傳遞

其他

kill 指定埠

以下命令可以 kill 掉 8080 埠,當然你也可以選擇通過 npm 命令的方式指定需要 kill 的埠。

lsof -i tcp:8080 | grep LISTEN | awk '{print $2}'| awk -F"/" '{ print $1 }' | xargs kill -9
複製程式碼

另外以上命令在 windows 上是不可用的。如果有多平臺的需求的話,可以直接使用 Kill-port-process

Linux下通過命令列替換文字

# 將wxml檔案的i標籤替換為text
grep '<i ' -rl . --include='*.wxml' --exclude-dir=node_module --exclude-dir=dist | xargs sed -i -e 's/<i /<text /g'
grep '</i>' -rl . --include='*.wxml' --exclude-dir=node_module --exclude-dir=dist | xargs sed -i -e 's/<\/i>/<\/text>/g'
複製程式碼

如何判斷檔案中的換行符是 LF(\n) 還是 CRLF(\r\n)

文章連結,通過這篇文章可以瞭解到換行符到底是什麼。

另外這位大佬每天都將學習到的知識記錄了下來,感興趣的可以 閱讀一下

畢業半年感悟

  • 自身的實力最重要,要有一樣核心技能,其他方面也要有所涉獵。
  • 公司帶給個人的影響是很大的,如果一個公司不願意培養你,真的不值得去付出。
  • 溝通確實很重要,溝通不明確會導致接下來一系列的問題。
  • 說話是後天鍛煉出來的,多和人交流,話到嘴邊留三分。
  • 不用討厭加班,人與人拉開差距就在下班後的幾個小時,加班可以學習啊。雷軍還說過你拿3000塊錢換我一個月的青春,多不划算。

最後

這周的分享內容質量很高,我也從中汲取到了一些知識。

這是一個需要大家一起分享才能持續下去的事情,光靠我一人分享是做不下去的。歡迎大家參與到這件事情中來,地址在這裡

相關文章