5個提高你JS編碼水平的實例

雖然 2020 的今天,各類前端框架、工具林立,而這些框架跟工具也幫咱們提早解決了很多麻煩的問題,可是工具始終是工具,紮實的基本功纔是最核心的,如今一塊兒來經過幾個實際的代碼片斷來提升咱們原生 JS 的編碼水平。javascript

判斷數據類型

首先來提問一個:typeof是否能正確判斷類型?前端

答案是:不能夠,由於因爲歷史緣由,在判斷原始類型時,typeof null會等於object。並且對於對象來講,除了函數,都會轉換成object。例子以下:java

typeof 1 // 'number'
typeof "1" // 'string'
typeof null //

typeof [] // 'object'
typeof {} // 'object'
typeof window.alert // 'function'
複製代碼

再來提問一個,instanceof是否能正確判斷類型?git

答案是:仍是不能夠,雖然instanceof是經過原型鏈來判斷的,可是對於對象來講,Array也會被轉換成Object,並且也不能區分基本類型stringboolean。例如:github

function Func() {}
const func = new Func()
console.log(func instanceof Func) // true

const obj = {}
const arr = []
obj instanceof Object // true
arr instanceof Object // true
arr instanceof Array // true

const str = "abc"
const str2 = new String("abc")
str instanceof String // false
str2 instanceof String // true
複製代碼

因此該怎麼辦呢?後端

這時候咱們可使用:Object.prototype.toString.call()api

因此爲何?數組

由於每一個對象都有一個toString()方法,當要將對象表示爲文本值或以預期字符串的方式引用對象時,會自動調用該方法。默認狀況下,從Object派生的每一個對象都會繼承toString()方法。若是此方法未在自定義對象中被覆蓋,則toString()返回[Object type],其中type是對象類型。因此就有如下例子:瀏覽器

Object.prototype.toString.call(new Date()) // [object Date]
Object.prototype.toString.call("1") // [object String]
Object.prototype.toString.call(1) // [object Numer]
Object.prototype.toString.call(undefined) // [object Undefined]
Object.prototype.toString.call(null) // [object Null]
複製代碼

因此綜合上述知識點,咱們能夠封裝出如下通用類型判斷方法:前端框架

var type = function(data) {
      var toString = Object.prototype.toString;
      var dataType =
        data instanceof Element
          ? "element" // 爲了統一DOM節點類型輸出
          : toString
              .call(data)
              .replace(/\[object\s(.+)\]/, "$1")
              .toLowerCase()
      return dataType
};
複製代碼

使用方法以下:

type("a") // string
type(1) // number
type(window) // window
type(document.querySelector("h1")) // element
複製代碼

通用的數組/類數組對象封裝

若是咱們使用 ES5/ES6+的數組 API,很容易就可以對數組進行各種的循環操做,可是若是咱們要循環一個類數組對象呢?

例如NodeList。直接循環是會報錯的:

document.querySelectorAll("div").map(e => e) // Uncaught TypeError: document.querySelectorAll(...).map is not a function
複製代碼

固然咱們能夠用擴展運算符:

[...document.querySelectorAll("div")].map(e => e)
複製代碼

那若是咱們不用擴展運算符呢?

那麼咱們就能夠利用call的特性,將NodeList裏的元素一個一個的插入到數組中,例子以下:

var listMap = function(array, type, fn) {
    return !fn ? array : Array.prototype[type]["call"](array, fn)
};
複製代碼

使用方法以下:

var divs = document.querySelectorAll("div");
listMap(divs, "forEach", function(e) {
    console.log(e)
});
複製代碼

獲取 dom 元素節點的偏移量

若是有用過jQuery的童鞋,就必定不會忘記$('').offset()這個 api 的強大功能,這個 api 能夠輕易獲取元素的偏移量,那麼若是咱們不用jQuery該怎麼實現呢?

咱們先來看看例子:

var getOffset = function(el) {
      var scrollTop =
        el.getBoundingClientRect().top +
        document.body.scrollTop +
        document.documentElement.scrollTop;
      var scrollLeft =
        el.getBoundingClientRect().left +
        document.body.scrollLeft +
        document.documentElement.scrollLeft;
      return {
            top: scrollTop,
            left: scrollLeft
      }
}
複製代碼

首先咱們先來看getBoundingClientRect()這個方法。

getBoundingClientRect()方法返回元素的大小及其相對於視口的位置。返回值是一個 DOMRect 對象,是與該元素相關的 CSS 邊框集合 。

而後就是document.body.scrollTopdocument.documentElement.scrollTop這兩個是一個功能,只不過在不一樣的瀏覽器下會有一個始終爲 0,因此作了以上的兼容性處理。因此當咱們作拖拽功能的時候,就能夠依賴上以上屬性。

使用方法以下:

var el = document.querySelector(".moveBox")
getOffset(el) // {top: xxx, left: xxx}
複製代碼

咱們能夠看上面的搖桿效果,這裏就是利用了offset()去作位置判斷。具體實現代碼能夠看:codepen.io/krischan77/…

Fade 特效

// Fade in
var fadeIn = function (el) {
    el.style.opacity = 0
    var last = +new Date()
    var tick = function() {
        el.style.opacity = +el.style.opacity + (new Date() - last) / 400
        last = +new Date()
        if (+el.style.opacity < 1) {
            requestAnimationFrame(tick))
        }
    }
    tick()
}
// Fade out
var fadeOut = function (el) {
    el.style.opacity = 1
    var last = +new Date()
    var tick = function() {
        el.style.opacity = +el.style.opacity - (new Date() - last) / 400
        last = +new Date()
        if (+el.style.opacity > 0) {
            requestAnimationFrame(tick)
        }
    }
    tick()
}
複製代碼

上述是淡入淡出效果的具體實現,這裏是利用requestAnimationFrameopacity經過遞歸的方式進行修改。

其實這裏須要提一個概念,就是時間分片

這是一個很是重要的概念,例如 ReactFiber 核心實現就是時間分片。它會將一個長任務切分紅一個含有若干小任務的任務隊列,而後一個接着一個的執行。

requestAnimationFrame就是這樣一個 API,它能夠根據系統來決定回調函數的執行時機,其實也就是在下一次重繪以前更新動畫幀,由於有這樣的機制,因此能防止丟幀。

利用隊列的概念進行數據操做

隊列(queue),是先進先出(FIFO, First-In-First-Out)的線性表。在具體應用中一般用鏈表或者數組來實現。隊列只容許在後端(稱爲 rear)進行插入操做,在前端(稱爲 front)進行刪除操做。

雖然不少人以爲了解數據結構對前端做用不大,可是若是咱們懂一些基礎的概念,是否在編碼時可以更加擴散咱們的思惟呢?咱們看下面兩個例子:

獲取節點在該父節點下的座標。

若是咱們要操做原生 DOM,那麼是繞不開獲取節點在該父節點的下標的這個功能的,那麼咱們該如何實現呢?

固然就是利用咱們的循環啦,對子元素集合進行遍歷,直到肯定下標爲止,代碼以下:

var index = function(el) {
      if (!el) {
        return -1
      }
      var i = 0
      while ((el = el.previousElementSibling)) {
        i++
      }
      return i
}
複製代碼

清空子節點

若是咱們要清空某個 DOM 節點的子節點,咱們有如下的方法:

var empty = function(el) {
      while (el.firstChild) {
            el.removeChild(el.firstChild);
      }
}
複製代碼

利用 reduce 進行數據優化

數組去重

沒錯,又是一個老生常談的問題,數組去重,可是咱們此次去除的不只僅是單個的數據,而是擁有某個相同鍵值的對象集合。例以下面的例子,咱們有如下的數據:

牛逼的 reduce

數據去重

首先咱們來看看一個老生常談的問題,咱們假設有這樣的一個對象:

const data = [
      {
            name: "Kris",
            age: "24"
      },
      {
            name: "Andy",
            age: "25"
      },
      {
            name: "Kitty",
            age: "25"
      },
      {
            name: "Andy",
            age: "25"
      },
      {
            name: "Kitty",
            age: "25"
      },
      {
            name: "Andy",
            age: "25"
      },
      {
            name: "Kitty",
            age: "25"
      }
]
複製代碼

如今咱們要去重裏面name重複的對象,這時候咱們能夠利用reduce,例子以下:

const dataReducer = (prev, cur, idx) => {
      let obj = {}
      const { name } = cur
      obj[name] = cur
      return {
            ...prev,
            ...obj
      }
}
const reducedData = data.reduce(dataReducer, {});
let newData = Object.values(reducedData);
複製代碼

批量生成對象元素

在魚頭的實際業務中,有一個操做是須要對相似如下的對象進行操做的:

{
    a1: 'data',
    a2: 'data',
    ...,
    an: 'data'
}
複製代碼

像我這麼懶的魚,確定不會一個個手寫,因此就有了如下方法

const createList = (item, idx) => {
      let obj = {}
      obj[`a{idx}`] = "data"
      return obj
}
const listReducer = (acc, cur) => (!acc ? { ...cur } : { ...cur, ...acc })
const obj = Array.from(new Array(20), createList).reduce(listReducer)
複製代碼

若是你喜歡探討技術,或者對本文有任何的意見或建議,很是歡迎加魚頭微信好友一塊兒探討,固然,魚頭也很是但願能跟你一塊兒聊生活,聊愛好,談天說地。 魚頭的微信號是:krisChans95 也能夠掃碼關注公衆號,訂閱更多精彩內容。

https://user-gold-cdn.xitu.io/2020/1/9/16f88c173601bd8c?w=1000&h=480&f=png&s=311000
相關文章
相關標籤/搜索