雖然 2020 的今天浓体,各種前端框架、工具林立渐裸,而這些框架跟工具也幫我們提前解決了不少麻煩的問題,但是工具始終是工具装悲,扎實的基本功才是最核心的昏鹃,現(xiàn)在一起來通過幾個實際的代碼片段來提高我們原生 JS 的編碼水平。
在這里小編建了一個前端學習交流扣扣群:1093794329诀诊,我自己整理的最新的前端資料和高級開發(fā)教程洞渤,如果有想需要的,可以加群一起學習交流
判斷數(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()方法,當要將對象表示為文本值或以預期字符串的方式引用對象時页眯,會自動調(diào)用該方法梯捕。默認情況下,從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
當然我們可以用擴展運算符:
[...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,所以做了以上的兼容性處理热芹。所以當我們做拖拽功能的時候贱傀,就可以依賴上以上屬性。
使用方法如下:
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通過遞歸的方式進行修改报腔。
其實這里需要提一個概念株搔,就是時間分片。
這是一個非常重要的概念纯蛾,例如React的Fiber核心實現(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é)點下的坐標。
如果我們要操作原生 DOM糊探,那么是繞不開獲取節(jié)點在該父節(jié)點的下標的這個功能的钾埂,那么我們該如何實現(xiàn)呢?
當然就是利用我們的循環(huán)啦科平,對子元素集合進行遍歷褥紫,直到確定下標為止,代碼如下:
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重復的對象豌研,這時候我們可以利用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)注。