5 個提升你 JavaScript 編碼水平的實例

雖然 2020 的今天,各種前端框架、工具林立,而這些框架跟工具也幫我們提前解決了不少麻煩的問題,但是工具始終是工具,扎實的基本功才是最核心的,現(xiàn)在一起來通過幾個實際的代碼片段來提高我們原生 JS 的編碼水平。

在這里小編建了一個前端學(xué)習(xí)交流扣扣群:1093794329,我自己整理的最新的前端資料和高級開發(fā)教程,如果有想需要的,可以加群一起學(xué)習(xí)交流

判斷數(shù)據(jù)類型

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

答案是:不可以,因為由于歷史原因,在判斷原始類型時,typeof null會等于object。而且對于對象來說,除了函數(shù),都會轉(zhuǎn)換成object。例子如下:

typeof 1; // 'number'

typeof "1"; // 'string'

typeof null; //

typeof []; // 'object'

typeof {}; // 'object'

typeof window.alert; // 'function'

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

答案是:還是不可以,雖然instanceof是通過原型鏈來判斷的,但是對于對象來說,Array也會被轉(zhuǎn)換成Object,而且也不能區(qū)分基本類型string和boolean。例如:

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()

所以為什么?

因為每個對象都有一個toString()方法,當(dāng)要將對象表示為文本值或以預(yù)期字符串的方式引用對象時,會自動調(diào)用該方法。默認(rèn)情況下,從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' // 為了統(tǒng)一DOM節(jié)點類型輸出

: toString

.call(data)

.replace(/\[object\s(.+)\]/, ''$1')

.toLowerCase()

return dataType

}

使用方法如下:

type("a"); // string

type(1); // number

type(window); // window

type(document.querySelector("h1")); // element

通用的數(shù)組/類數(shù)組對象封裝

如果我們使用 ES5/ES6+的數(shù)組 API,很容易就能夠?qū)?shù)組進行各類的循環(huán)操作,但是如果我們要循環(huán)一個類數(shù)組對象呢?

例如NodeList。直接循環(huán)是會報錯的:

document.querySelectorAll("div").map(e => e); // Uncaught TypeError: document.querySelectorAll(...).map is not a function

當(dāng)然我們可以用擴展運算符:

[...document.querySelectorAll("div")].map(e => e);

那如果我們不用擴展運算符呢?

那么我們就可以利用call的特性,將NodeList里的元素一個一個的插入到數(shù)組中,例子如下:

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 元素節(jié)點的偏移量

如果有用過jQuery的童鞋,就一定不會忘記$('').offset()這個 api 的強大功能,這個 api 可以輕易獲取元素的偏移量,那么如果我們不用jQuery該怎么實現(xiàn)呢?

我們先來看看例子:

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 對象,是與該元素相關(guān)的 CSS 邊框集合 。

然后就是document.body.scrollTop 跟 document.documentElement.scrollTop這兩個是一個功能,只不過在不同的瀏覽器下會有一個始終為 0,所以做了以上的兼容性處理。所以當(dāng)我們做拖拽功能的時候,就可以依賴上以上屬性。

使用方法如下:

var el = document.querySelector(".moveBox");

getOffset(el); // {top: xxx, left: xxx}

我們可以看上面的搖桿效果,這里就是利用了offset()去做位置判斷。具體實現(xiàn)代碼可以看:https://codepen.io/krischan77/pen/zYxPNPy

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()

}

上述是淡入淡出效果的具體實現(xiàn),這里是利用requestAnimationFrame對opacity通過遞歸的方式進行修改。

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

這是一個非常重要的概念,例如ReactFiber核心實現(xiàn)就是時間分片。它會將一個長任務(wù)切分成一個含有若干小任務(wù)的任務(wù)隊列,然后一個接著一個的執(zhí)行。

requestAnimationFrame就是這樣一個 API,它可以根據(jù)系統(tǒng)來決定回調(diào)函數(shù)的執(zhí)行時機,其實也就是在下一次重繪之前更新動畫幀,因為有這樣的機制,所以能防止丟幀。

利用隊列的概念進行數(shù)據(jù)操作

隊列(queue),是先進先出(FIFO, First-In-First-Out)的線性表。在具體應(yīng)用中通常用鏈表或者數(shù)組來實現(xiàn)。隊列只允許在后端(稱為 rear)進行插入操作,在前端(稱為 front)進行刪除操作。

雖然很多人覺得了解數(shù)據(jù)結(jié)構(gòu)對前端作用不大,但是如果我們懂一些基礎(chǔ)的概念,是否在編碼時能夠更加擴散我們的思維呢?我們看下面兩個例子:

獲取節(jié)點在該父節(jié)點下的坐標(biāo)。

如果我們要操作原生 DOM,那么是繞不開獲取節(jié)點在該父節(jié)點的下標(biāo)的這個功能的,那么我們該如何實現(xiàn)呢?

當(dāng)然就是利用我們的循環(huán)啦,對子元素集合進行遍歷,直到確定下標(biāo)為止,代碼如下:

var index = function(el) {

if (!el) {

return -1;

}

var i = 0;

while ((el = el.previousElementSibling)) {

i++;

}

return i;

};

清空子節(jié)點

如果我們要清空某個 DOM 節(jié)點的子節(jié)點,我們有以下的方法:

var empty = function(el) {

while (el.firstChild) {

el.removeChild(el.firstChild);

}

};

上面只是提供一個思路,其實el.innerHTML = ''會更簡潔。

利用 reduce 進行數(shù)據(jù)優(yōu)化

數(shù)組去重

沒錯,又是一個老生常談的問題,數(shù)組去重,但是我們這次去除的不僅僅是單個的數(shù)據(jù),而是擁有某個相同鍵值的對象集合。例如下面的例子,我們有以下的數(shù)據(jù):

牛逼的 reduce

數(shù)據(jù)去重

首先我們來看看一個老生常談的問題,我們假設(shè)有這樣的一個對象:

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"

}];

現(xiàn)在我們要去重里面name重復(fù)的對象,這時候我們可以利用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);

批量生成對象元素

在魚頭的實際業(yè)務(wù)中,有一個操作是需要對類似以下的對象進行操作的:

{

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);

小節(jié)

今天的內(nèi)容就和大家分享到這里,感謝你的閱讀。如果你喜歡我的分享,麻煩給個關(guān)注、點贊加轉(zhuǎn)發(fā)哦,你的支持,就是我分享的動力,后續(xù)會持續(xù)分享代碼片段,歡迎持續(xù)關(guān)注。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容