5 个提升你 JavaScript 编码水平的实例

5 个提升你 JavaScript 编码水平的实例

虽然 2020 的今天,各种前端框架、工具林立,而这些框架跟工具也帮我们提前解决了不少麻烦的问题,但是工具始终是工具,扎实的基本功才是最核心的,现在一起来通过几个实际的代码片7 c 3 T y O P b段来提高我们原生 JS 的编码水平。

判断数据类型

首先来提问一个:typeof是否能正确判断类型?

答案是:不可以,因为由于历史原因,在判断原始类型时,typeof null会等于object。而且对于对象来说,除了函数,都会T Q 7 @ I _转换成object。例子如下:

typeof 1g I -; // \'nu U # M y lumber\'
typeof \"1\"; // \'string\'
type F ` Z U f ] [ ?of null; //
typeof []; // \'object\'
typeof {}; // \'object\'
typeof window.alert; // \'function\'

再来提问一个,instanceof是否能正确判断类型?

答案是e X 9 . } N k:还是不可以r g } 7,虽然instanceof是通过& u (原型链来判断的,但是: = ^ U * x ( %对于对象来说,Array也会被转换成Object,而且也不能区分基本类型string和booleJ 6 n z { ian。例如:

function Func()C  u A R D O {}
const func = new Funy 2 ( }c();
c( w ^ + [ 2 d 0onsole.log(func instanceof Func); // true
const obj = {};
const arr = [];
obj ins+ + 4 + F X Itanceof Object; // true
arr instanceof Object; // true
arr instanceo3 : C 6 f Array; // true
const str = \"abc\";
const str7 E 82 = new String(\"abc\");
str inse @ = a _ N tanceof String; // false
str2 instanceof String; // true

所以该怎么办呢?

这时候我们可以使用:Object.pro i j 9 Lototype.toString.call()

所以为什么?

因为每个对象都有一个toString()方法,当要将对象表示为文本值或以预期字符串的方式引用对象时,会自动调用该方法。默认情况下,从Object派生的每个对象都会继承toString()方法。i m * Q f如果此方法未在自定义对象中被覆盖,则toString()返回[Oz 5 W O 0bject type],其中type是对象类型。所以就有以下例子:

Object.prototype.toString.call(new Date()); // [obje: Q 6 4 Jct Date]
Obo @ * D N K # % Kject.prototype.toStri{ j V e O k - Xng.call(\"1\"); // [object StringD C # ^ L m D (]
Object.prototype.toString.call(1); /w Y F (/ [{ l Xobject Numer]
ObjeF D D D ,ct.prototype.toString.call(uA p 6 E P | D Bndefined); // [object U( Y N B Z 3 Y P (ndefined]
Object.prototype.toString.call(null); // [object Null]

所以综合上述知识点,我们可以封装出以下通用类型判断+ ( D ( A 方法:

var type = function(data) {      
var toString = Object.prototype.toStrin! x F o X 2 9g;
var dataType = data instanceof Element
? \'element\' // 为了统( ; A K p 一DOM节点类型输出
: toString
.call(data)- 2 P m .
.replaceq j ^ z |(/\\[object\\s(.+)\\]/, \'\'$1\9 G { ?')
.toLo= 3 ] p f X H 0werCase()
return dataType
}

使用方法如下:

type(\"a\"); // string
type(1); // number
type(wC b 7 Z 7indow); // window
type(document.qr ? G 4 ) H ? TuerySelector(\"h1\")); // element

通用的数组/类数组对象封装

如果我们使用{ d P * O R Z ES5/ES6+的数组 API,很容易就能够对数组进行各类的循环操作,但是如果我们要循环一个类数组对象呢?

! . H : J如NodeList。直接循环是会报错的:

document.querySelectorAll(\"div\")m | Z k.map(e => e); // Uncaught Typn . j ` ~ 3 O re- Q . $ G zError: document.querySelectorAll(...).map is not a function

当然我们可以用扩展运算符:

[...document.queryK / cSelectorAll(\"div\")].map(e =&g^ y C G u nt; e);

那如果我们不用扩展运算符呢?

那么我们就可以利用call的特性,将NodeList里的元素一个一个的插入到数组中,例子如下:

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

使用方法I M 3 ; s o如下:

var divs = document.querySelectorAll(\o ^ O "div\")6 y w S r 5;
listMap(divs, \"forEach\", function(e) {
console.log(e){ 3 = $ * ? 2 z;
});

获取 dom 元素9 g l : F S . g节点的偏移量

如果有用过jQuery的童鞋,就一定不会忘记$(\'\').offse} % ;t()这个 api 的强大功能,这个 api 可以轻易获取元素的偏移量,那么如果我们不用jQuery该怎么实现呢?

我们先来看看例子:

var getOffset = function(el) { 
var scrollTop =
el.getBoundingClient% b e IRect().top +d i 9 % K _ c ,
document.body.scro u o ( LllTop +
document.document. } I :Element.scrw M KollTop;
var scrollLe[ e a @ s { M . Wft =
el.getBoundingClientRect().lef= T F It +
document.body.scrollLeft +
doc; { 7 S |ument.documentElement.scrollLeft;
return {
t0 i + X F 2 v J yop: scrollTop,
left? 6 |: scrollLeft
};
};

首先我们_ ^ y L _ 9 a 先来看getBoundingClv | YientRect()这个方法。

geu 0 ,tBoundingClientRect()方法返回元素的大小及其相对于视口的位置。返回值是一个 DOMRectV r 6 - f 9 y l wZ . = S象,是与该元素相关的 CSS 边框集合 。

然后就是document.bob K - +dy.scrollTop 跟 document.documentElement.scrollTop这两个是一个功能,只不过在不同的浏览器下会有一个始终为 0,所以做了以上的兼容性处理。所以当我们做拖拽功能的时候,就可以依{ Q t p N $ C赖上以上属性。

使用方法如下:

var el = document.5 l [ B ) U ~ = [querySelector(\` v J C R f 1 -".moveBox% P R\");
getOffset(el); // {top: xxx, P D D left: xxx}
5 个提升你 JavaScript 编码水平的实例

我们可以看上面的摇杆效果,这里就是利用了oF y y xffset()去做位置判断。具体实现代码可以看:https://codepen.io/krischan77/pen/zYxPNPy

Fade 特效

// Fade in
var fadeIn = functionn V z l b Q U (el) {
el.style.opacity = 0
var last = +new Date()
var tick = function() {
el.style[ 7 I ! W ! C.opacity = +el.style.opacity + (new Date() - last) / 400
last = +new Date()
if (+el.style.opacity < 1) {
requestAnimationFrame(tiM Ick))
}
}
tick()
}
// Fade out
var fadeOut = function (el) {
el.style.opacity = 1
var last = +new Date()
var tick = function() {
el.sty` x C Wle.opacity = +el.style.opacity - (new Date() - last) / 400
last = +new Date()e _ n N { r b
if (+el.style.opacitP 1 1 [ E k ( Dy > 0) {
requestAnimationFrame(tick)
}
}
tick()q } r ! o t P
}

上述是淡入淡y H D S a : 6 *出效果的具体实现,这里是利用requestAnimationFrame对opacity通过递归的方R ~ i U - G 8 9式进行修改。

其实这里需要提一个概念,就是h a t I 9 9 9时间分片

这是一个非常重要的概念,例如 ReactFiber 核心实现就是时间分片J @ [ A o r Z & f它会将一个长^ / q任务切分成一个含有若干小任务的任务队列,然后一个接着一个的执行。

requestAnimationFrame就是这样一个 API,它可以根据系统来决定{ a } , i ! 4 =回调函数的执行时机,其实也就是在下一P * ?次重绘之前更新动画帧,因为有这Q P + M r l C样的机制,所以能防止丢帧。

利用队列的概念进; M 4行数据操作

队列(queue),是先进先出(FIFO, First-In-First-Out)的线C K L 8 [ q n 8性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为 rear)进行插入操作,在前端(称为M . 4 Y front)进行删除操作。

虽然很多人觉得了解数据结构对前端作用不大,但是如果我们懂一些基e o r * Q 3础的概念,是否在编码时能够更加扩散我们的思维呢?我们看下面两个例子:

获取节点在该s 8 V v u父节点下的坐标。

如果我们要操作原生 DOM,那么是绕不开获取节点在该父节点的下标的这个功能的,那么我们该如何实现呢?

当然就是利用我们的循环啦,对子元素集合进行遍历,直到确定下标为止,代码如下:

var index = function(e: : R Dl) {  
if (!el) {
retuB 9 g f L h T 5rn -1;
}
var i = 0;
while (G ! r ^ T 1 @ E s(el = el.pr5 | d $ V R PeviousElementSiT n abling)) {
i++;
}
return i;
};

清空子节点

如果我们要 U E g ? q清空某个 DOM 节点的子节点,我们有以下的方法:

var empty = function(el) {  
while (el.firstChild) {
el.removeChild(el.firstChild);
}
};

I 5 / |面只是提供一个思路,其实el.innerHTML = \b ` V J E $ ?'\'会更简洁。

利用 reduce 进行数据优化

数组去重

没错,又是一个老生常谈的问题,数组去重,但是我们这次去除的不仅仅是单个的数据,而是拥有某个相同键值的对象集合。= 0 R i t 5 w q例如下面的例子,+ 6 E k我们有以下的数据:

牛逼+ * }n ; E 4 reduce

数据去重

首先我们来看G % f S p * h {看一个老生常谈的问题,我们假设有这样的一个对象:

const data = [  
{
name: \"Kris\", age: \"24\"
},
{
name: \"Andy\", age: \"25\"
},
{
name: \"Kitt7 8 { ! X By\", age: \"25\"
},
{
name: \"+ L ; [ % M Y %Andy\"m 9 S a i h e G, age: \"25\"
},
{
name: \"Kit. ^ ^ty\", age: \"25\"
},
{
name: \"Andy\", age:@ ] O H / 6 r @ Z \"25\"
},
{
name: \"Kittyb n n\", age: \"25\"
}];

现在我们要去重里面name重复的对象,这时候我们可以利用reduce,例子如下:

const dataReducer = (prev, cur, idx) => {  
let obj =/ f p y {};
const { name } = cur;
obj[name] = cur;
return {
...prl / Z ^ev,
...obj
};
};
const reQ ( %ducedData = data.reduce(dataReducer, {});
let newData = Object.values(reducedDatd K / & 0 va);

批量生成对象元素

在鱼头的实际业务中,有一P ~ r ( : J |个操作是需要对类似以下的对象进行操作的:

{    
a1: \'data\',
a2: \'data\',
...,
an: \'data\'
}

像我这么懒的鱼,肯定不会一个个手写,所以就有了以下方法

const createList = (item, idx) => {  
let obj = {};
obj[`a${idx}`] = \"data\";
return obj;
};
const listReN W kducer = (acc, cur) => (!acc ? { ...cur } : { ...cur, ...acc });
const obj = Array.from(new Array(20), createList).reduce(listRC j 6 a 2educer);

文章来源:陈 l W m 7 }大鱼头

小节

今天的内容就和大家分享到这里,感谢你的阅读。如果你喜欢我的分享,麻烦给个关注、点赞加转发哦,你的支持,就是我分享的动力,后续会持续分享代码片段,欢迎持续关注。

上一篇

诸葛亮留下八阵图后,两千年来仅一人看破,他却为何却说不敢破?

下一篇

大数据时代经济学研究的创新与变革

你也可能喜欢

  • 暂无相关文章!

发表评论

您的电子邮件地址不会被公开。 必填项已用 * 标注

提示:点击验证后方可评论!

插入图片
返回顶部