为什么要实现API?
- 一直是面试要考察的点
- 加深对API的记忆和理解
- 更能体现编程思维和能力
jQery offset 方法实现
使然 jQery 已经是"上古"神器了,但很多API都值得学习,也还是很经典。
问:如何获取文档任意元素与文档顶部的距离? 思路:
- 通过递归实现
- 通过getBoundingClienetReact
1.通过递归实现方案
我们可以通过便利目标元素、目标元素父节点,父节点的父节点。。。依次溯源,并累加这些遍历过的节点相对于其最近祖先节点(其position属性是非static的)的偏移量,向上溯源直到document,即可得到累加结果
实现如下:
const offset = (ele) => {
let result = {
top: 0,
left: 0,
}
const getOffset = (node, init) => {
if (node.nodeType !== 1) {
return
}
position = window.getComputedStyle(node)['position']
if (typeof init === 'undefined' && position === 'static') {
getOffset(node.parentNode)
return
}
result.top = node.offsetTop + result.top - node.scrollTop
result.left = node.offsetLeft + result.left - node.scrollLeft
if (position === 'fixed') {
return
}
getOffset(node.parentNode)
}
if (window.getComputedStyle(ele)['display'] === 'none') {
return result
}
let position
getOffset(ele, true)
return result
}
上述代码不难理解,使用递归实现。
- 如果节点node.nodeType的类型不是Element(1),则跳出;
- 如果相关节点position属性为static,则不计入计算,进入下一个节点(其父节点)的递归;
- 如果相关属性的display属性为nonde,则应该直接返回0作为结果;
这里只是粗略的启发示例,对于边界情况没有一一处理。但是却考察了递归的初级应用。
2.通过getBoundingClientRect方法
- 对于某一节点getBoundingClientRect方法,返回值是一个DOMRect类型的对象。这个对象表示一个矩形盒子,其中包含由left,top,right和bottom等只读属性。
实现如下:
const offset = (ele) => {
let result = {
top: 0,
left: 0,
}
if (!ele.getClientRects().length) {
return result
}
if (window.getComputedStyle(ele)['display'] === 'none') {
return result
}
result = ele.getBoundingClientRect()
var docElement = ele.ownerDocument.documentElement
return {
top: result.top + window.pageYOffset - docElement.clientTop,
left: result.left + window.pageXOffset - docElement.clientLeft,
}
}
- ele.ownerDocument代表文档,包含,documentElement两个节点;
- docElement.clientTop中的clientTop表示元素顶部边框的宽度,不包含外边距和内边距
- getBoundingClientRect方法是用来进行简单的几何运算,边界CSS处理和兼容性处理的
数组reduce 方法的实现
reduce 这个方法很好的体现了"函数式"理念;
reduce方法通过给定一个执行函数,对数组的每一项取值从左往右进行累加,最终产生一个结果
粗略实现如下:
Array.prototype._reduce = function(fn, initVal) {
let arr = this
let base = typeof initVal === 'undefined' ? arr[0] : initVal
let starPoint = typeof initVal === 'undefined' ? 1 : 0
arr.slice(starPoint).forEach(function(val, index){
base = fn(base, val, index+starPoint, arr)
})
return base
}
应用场景一:通过reduce实现runPromiseSsquence
const runPromiseSsquence = (array,value) => array.reduce(
(promiseChain, currentFunction) => promiseChain.then(currentFunction),
Promise.resolve(value)
)
runPromiseSsquence将会被一个每一项都返回一个Promise的数组调用,并且依次执行数组中的每一个Promise,参考下示例:
const f1 = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p1 running')
resolve(1)
}, 2000)
})
const f2 = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p2 running')
resolve(2)
}, 1000)
})
const array = [f1, f2]
const runPromiseSsquence = (array, value) =>
array.reduce(
(promiseChain, currentFunction) => promiseChain.then(currentFunction),
Promise.resolve(value)
)
runPromiseSsquence(array, 'init')
应用场景二:通过reduce实现pipe(柯里化函数)
const pip = (...functions) => input => functions.reduce(
(acc, fn) => fn(acc),
input
)
应用场景三:通过Koa only 模块源码认识reduce
Koa only 模块示例:
var o = {
a: 'a',
b: 'b',
c: 'c'
}
only(o,['a','b']) // {a: 'a', b: 'b'}
only模块返回一个经过指定筛选属性的新对象,该模块的代码实现如下:
var only = function (obj, keys) {
obj = obj || {}
if ('string' == typeof keys) keys = keys.split(/ +/)
return keys.reduce(function (ret, key) {
if (null == obj[key]) return ret
ret[key] = obj[key]
return ret
}, {})
}
实现compose方法
compose方法和前面提到的pipe方法一样,主要用于执行一串不定长度的任务(方法)
let funcs = [func1, func2, func3, func4]
let composeFunc = compose(...funcs)
- 执行 composeFunc(args)
- 相当与执行 func1(func2(func3(func4(args))))
它跟pipe方法的差别在于调用顺序不体哦那个
// compose
func1(func2(func3(func4(args))))
// pipe
func4(func3(func2(func1(args))))
实现compose方法的最简单方案式面向过程的:
const compose = function (...args) {
let length = args.length
let count = length - 1
let result
return function f1(...args1) {
result = args[count].apply(this, arg)
if (count <= 0) {
count = length - 1
return result
}
count--
return f1.call(null, result)
}
}
利用reduce实现方案:
const compose = (...args) =>
args.reverse().reduce(reduceFunc, args.shift())
利用Promise实现方案:
const compose = (...args) => {
let init = args.pop()
return (...arg) => {
args.reverse().reduce((sequence, func) => {
sequence.then((result) => func.call(null, result)),
Promise.resolve(init.apply(null, arg))
})
}
}
其实还可以使用lodash和Redux来实现compose方法的方案。
函数式概念确实有些抽象,但是一旦顿悟,必然会感受到其中的优雅和简洁。