前端开发核心知识进阶 1.3我们不背诵API,只实现API


有时候面试官不强求开发者准确无误地背诵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,其值为目标函数,调用后,删除这个属性,返回结果。

你可能感兴趣的