为了账号安全,请及时绑定邮箱和手机立即绑定

通过5个实际的功能来提升你JS的编码水平

标签:
JavaScript

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

判断数据类型

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

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


typeof  1  // 'number'

typeof  '1'  // 'string'

typeof  null  //

  

typeof [] // 'object'

typeof {} // 'object'

typeof  window.alert  // 'function'

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

答案是:还是不可以,虽然instanceof是通过原型链来判断的,但是对于对象来说,Array也会被转换成Object,而且也不能区分基本类型stringboolean。例如:


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()方法,当要将对象表示为文本值或以预期字符串的方式引用对象时,会自动调用该方法。默认情况下,从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()去做位置判断。具体实现代码可以看: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()

}

上述是淡入淡出效果的具体实现,这里是利用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)

如果你喜欢探讨技术,或者对本文有任何的意见或建议,非常欢迎加鱼头一起探讨,当然,鱼头也非常希望能跟你一起聊生活,聊爱好,谈天说地。

点击查看更多内容
43人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消