Advanced core knowledge of front-end development 1.3 we don't recite API, we only implement API

Sometimes the interviewer doesn't force the developer to recite the API accurately. On the contrary, the interviewer likes to tell the interviewer how to use the API and let the interviewer implement the API.

Implementing an API can examine the interviewer's understanding of the API and better reflect the developer's programming thinking and ability. For active front-end engineers, imitating and implementing some classic methods should be "routine", which is a basic requirement

In Section 3, we discuss various implementation methods of JQuery offset(), array reduce, compose, bind and apply

1, Offset of JQuery ()

First, you have to understand the offset () of JQuery Top, offsetTop and scrollTop of dom element

offset( ).top returns the vertical distance from the element to the document, which is the vertical offset relative to the document

offsetTop returns the vertical distance of the nearest ancestor element positioned as relative, absolute and fixed. If the element itself is positioned as fixed, it returns relative to the document. There is no positioned ancestor element, which is also relative to the document. When the parent element of an element is scrollable, this value does not change no matter where it is scrolled.

scrollTop returns the distance that the element's scroll bar has been scrolled

After understanding, you can try to implement offset () with offsetTop and scrollTop Top, traverse the ancestor element of the element and find the first ancestor element with location

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
}

If the display of the element is none, it returns a null value. If not, it starts traversal. The element is initially calculated to get the offsetTop of the element, that is, the vertical distance to the nearest positioned ancestor element. If the element is fixed, the object of its offsetTop is the document, and the value is returned directly without traversal. Directly traverse to find the nearest positioned ancestor element, calculate the value, and get the offsetTop of the ancestor element. Because the more you scroll down, the closer the element will be to the top of the document, you need to subtract the scrollTop. If the ancestor is fixed, its offsetTop object is a document, so it will directly return the value and will not be traversed. On the contrary, continue the above process until the ancestor node is traversed.

2, reduce of array

1. Classic application of reduce

1.1 run Promise in turn

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 functional method pipe, pipe(a,b,c) is a Coriolis function, which returns a function and the function will be completed

(... args) = > Call of 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. Implementation of reduce

reduce, if the second parameter is not passed in, the first item of the array will be used as the first parameter of the callback function, so it is necessary to judge whether the second parameter is passed in and carry out corresponding operations

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<arr.length; i++){
        result = fn(result,arr[i],i + startPoint,arr)
    }
    return result
}

3, compose

The functions of compose and pipe run in reverse order

Call of compose(a,b,c)(args) relative to (args) = > A (b (C (args)))

1. Implemented with reduce and 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. Use reduce only

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)))
}   

4, Advanced implementation of bind

The first section points out that when the function returned by bin is used as the constructor, the binding priority of new to this is relatively high. Therefore, to realize bind, consider this factor. In order to receive the parameters passed in by the function returned by bind, we also need to pass the parameters

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' ]

In the function body of the returned function, concat is used to store the parameters passed in into the parameter array for transfer, so the bindFn function can receive the parameters passed in by the new function. Here, the F function is declared to point its prototype object to the prototype object of the newBind function, and the prototype object of the returned fn function points to the prototype object of F. If fn is used as a constructor, declare an empty object according to the process of new. The prototype pointer of the object points to the prototype object of constructor fn, that is, the prototype object of function f, and then point this to the object. Therefore, conduct this instanceof F to judge whether this is on the prototype chain of F function. Yes, it means that new is called and this can be passed in. If not, the first parameter of newBind will be passed in. If the parameter does not exist, the current this will be returned.

Keywords: Javascript Front-end JQuery

Added by smalband on Tue, 28 Dec 2021 14:10:27 +0200