有时候面试官不强求开发者准确无误地背诵API,相反面试官喜欢告诉面试者API的使用方法,让面试者实现API。
实现一个API,可以考察面试者对API的理解,更能体现开发者的编程思维和能力。对于积极上进的前端工程师,模仿并实现一些经典方法,应该是“家常便饭”,这是比较基本的要求
第三节,我们探讨一下JQuery的offset( )、数组的reduce、compose、bind、apply的各种实现方法
一、JQuery的offset( )
首先得了解下JQuery的offset( ).top,dom元素的offsetTop、scrollTop
offset( ).top 返回元素到文档的纵向距离,就是相对于文档的纵向偏移量
offsetTop 返回元素的定位为relative、absolute、fixed的最近的祖先元素的纵向距离,如果元素本身定位为fixed,则返回相对于文档。没有定位的祖先元素,那也相对于文档。元素的父元素可滚动时,无论滚动到哪,此值都不会变化。
scrollTop 返回该元素滚动条已滚动的距离
了解完就可以尝试用offsetTop和scrollTop实现offset( ).top,遍历元素的祖先元素,找到第一个有定位的祖先元素
function offset(node){
var result = {
top:0,
left:0
}
var getOffset = function(node,init){
if(node.nodeType != 1){
return
}
position = window.getComputedStyle(node).position
if(position === 'static' && typeof init === 'undefined'){
getOffset(node.parentNode)
return
}
result.top = result.top + node.offsetTop - node.scrollTop
result.left = result.left + node.offsetLeft - node.scrollLeft
if(position === 'fixed'){
return
}
getOffset(node.parentNode)
}
if(window.getComputedStyle(node).display === 'none'){
return result
}
let position
getOffset(node)
return result
}
如果元素的display为none,返回空值,如果不是开始遍历。元素初始进行计算,拿到元素的
offsetTop,也就是到最近有定位的祖先元素的纵向距离,如果元素为fixed定位,其offsetTop的对
象就是文档,就直接返回值,不再遍历。直接遍历找到最近的有定位的祖先元素,找到便进行值的
计算,拿到祖先元素的offsetTop,因为越往下滚动,元素就会离文档顶部越近,所以要减去滚动高
度scrollTop。该祖先是fixed定位的话,其offsetTop的对象就是文档,所以就直接返回值,不再遍历,反之继续上述流程,到祖先节点遍历完毕。
二、数组的reduce
1.reduce的经典应用
1.1运行依次执行Promise
const fan = (r) => new Promise((res,rej)=>{
setTimeout(()=>{
console.log('p1 run');
res(r+1)
})
})
const fbn = (r) => new Promise((res,rej)=>{
setTimeout(()=>{
console.log('p2 run');
res(r+2)
})
})
var fnArr = [fan,fbn]
const runPromise = (arr,val) => arr.reduce(
(pre, cur) => pre.then(cur),
Promise.resolve(val)
)
runPromise(fnArr,3).then(res=>{
console.log(res); //p1 run p2 run 6
})
1.2函数式方法pipe,pipe(a,b,c)是柯里化函数,它返回一个函数,函数会完成
(...args)=> c(b(a(...args)))的调用
function f1(a){
a+=' f1'
return a
}
function f2(a){
a+=' f2'
return a
}
function f3(a){
a+=' f3'
return a
}
function f4(a){
a+=' f4'
return a
}
const pipe = (...funs) => (args) => funs.reduce(
(pre,cur) => cur(pre),
args
)
fn = pipe(f1,f2,f3,f4)
console.log(fn('f')); //f f1 f2 f3 f4
2.reduce的实现
reduce,如果没有传入第二个参数,则会拿数组第一项作为回调函数第一个参数,所以要判断有没有传入第二个参数,进行相应的操作
Array.prototype.newReduce = function(fn,pre){
var arr = this
var result = typeof pre === 'undefined' ? arr.shift() : pre
var startPoint = pre ? 0 : 1
for(var i=0; i
三、compose
compose与pipe的函数运行顺序相反
compose(a,b,c)(args)相对于(args)=> a(b(c(args)))的调用
1.用reduce和Promise实现
const promiseCompose = (...args) => {
let init = args.pop()
return (...arg) => {
return args.reverse().reduce((pre,cur) => pre.then(res => cur(res) )
,Promise.resolve(init(...arg)))
}
}
2.单纯使用reduce
function compose(...funcs){
if(funcs.length === 0){
return arg => arg
}
if(funcs.length === 1){
return funcs[0]
}
return funcs.reduce((a,b) => (...args) => a(b(...args)))
}
四、bind的进阶实现
第一节指出了bin返回的函数作为构造函数时,new对this的绑定优先级比较高,所以要实现bind考虑这一因素,为接收bind返回的函数传入的参数,还要进行参数传递
Function.prototype.newBind = function(context){
var a = this
var argsArr = Array.from(arguments)
var F = function(){}
F.prototype = this.prototype
var fn = function(){
finalArgs = argsArr.concat(Array.from(arguments))
return a.apply(this instanceof F ? this : context || this, finalArgs.slice(1))
}
fn.prototype = new F()
return fn
}
var aaa = 1
var obj = {
aaa : 2
}
function bindFn(...arg){
console.log(this.aaa);
console.log(arg);
}
var ffn = bindFn.newBind(obj,'a')
ffn('b') //2 [ 'a', 'b' ]
var fhn = new ffn('c')//undefined [ 'a', 'c' ]
在返回的函数的函数体中,用concat将其传入的参数存入参数数组中,进行传递,所以
bindFn函数就能接收到新函数传入的参数。这里声明了F函数,将其原型对象指向newBind函
数的原型对象,返回的fn函数的原型对象指向F的原型对象。如果fn作为构造函数,按照new
的流程,声明个空对象,该对象的原型指针指向构造函数fn的原型对象也就是函数F的原型对
象,然后把this指向该对象。所以进行this instanceof F判断this是否在F函数的原型链上,
是,就是调用了new,传入this即可,不是,则传入newBind第一个参数,参数不存在,返回当前的this。
五、apply的实现
既然bind的实现用了apply,那便看看apply怎么实现
c = 3
function fun(a,b){
return this.c + a + b
}
let sym = Symbol('a')
var obj = {
c : 4
}
Function.prototype.applyFn = function(obj,arr){
if(arr === null && typeof arr === 'undefined'){
arr = []
}
if(obj === null && typeof obj === 'undefined'){
obj = window
}
obj = new Object(obj)
var sym = Symbol('key')
obj[sym] = this
var res = obj[sym](...arr)
delete obj[sym]
return res
}
console.log(fun(1,2));
console.log( fun.applyFn(obj,[1,2]));
这里用了隐式绑定this,创建个独一无二的symbol作为对象的key,其值为目标函数,调用后,删除这个属性,返回结果。