侯策《前端开发核心知识进阶》读书笔记——API实现

jQuery offset 实现

offset() 方法返回或设置匹配元素相对于文档的偏移。

(一)递归实现:

        const offset = ele => {
                let result = {
                    top: 0,
                    left: 0
                }
                /*
                * nodeType 属性返回以数字值返回指定节点的节点类型。
                * 如果节点是元素节点,则 nodeType 属性将返回 1。
                * 如果节点是属性节点,则 nodeType 属性将返回 2。
                * 如果节点 node.nodeType 类型不是 Element(1),则跳出;
                * 如果相关节点的 position 属性为 static,则不计入计算,进入下一个节点(其父节点)的递归。
                * 如果相关属性的 display 属性为 none,则应该直接返回 0 作为结果。
                */
                const getOffset = (node) => {
                    if (node.nodeType !== 1) {
                        return
                    }

                    position = window.getComputedStyle(node)['position']

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

                // 当前 DOM 节点的 display === 'none' 时, 直接返回 {top: 0, left: 0}
                if (window.getComputedStyle(ele)['display'] === 'none') {
                    return result
                }

                let position

                getOffset(ele)

                return result

            }
            let box = document.getElementById('box2')
            let result = offset(box);
            console.log(result)    

(二)通过 getBoundingClientRect API 实现

const offset = ele => {
    let result = {
        top: 0,
        left: 0
    }
    // 当前为 IE11 以下,直接返回 {top: 0, left: 0}
    if (!ele.getClientRects().length) {
        return result
    }

    // 当前 DOM 节点的 display === 'none' 时,直接返回 {top: 0, left: 0}
    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
    }
}

数组 reduce 方法的相关实现

(一)概念

arr.reduce(callback,[initialValue])
callback (执行数组中每个值的函数,包含四个参数)

    1、previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
    2、currentValue (数组中当前被处理的元素)
    3、index (当前元素在数组中的索引)
    4、array (调用 reduce 的数组)

initialValue (作为第一次调用 callback 的第一个参数。)

如果没有提供initialValue,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。如果提供initialValue,从索引0开始。

(二)reduce的用法

简单用法:

var  arr = [1, 2, 3, 4];
var sum = arr.reduce((x,y)=>x+y)
var mul = arr.reduce((x,y)=>x*y)
console.log( sum ); //求和,10
console.log( mul ); //求乘积,24

计算数组中每个元素出现的次数:

let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];

let nameNum = names.reduce((pre,cur)=>{
  if(cur in pre){
    pre[cur]++
  }else{
    pre[cur] = 1 
  }
  return pre
},{})
console.log(nameNum); //{Alice: 2, Bob: 1, Tiff: 1, Bruce: 1}

数组去重:

let arr = [1,2,3,4,4,1]
let newArr = arr.reduce((pre,cur)=>{
    if(!pre.includes(cur)){
      return pre.concat(cur)
    }else{
      return pre
    }
},[])
console.log(newArr);// [1, 2, 3, 4]

将二维数组转化为一维:

let arr = [[0, 1], [2, 3], [4, 5]]
let newArr = arr.reduce((pre,cur)=>{
    return pre.concat(cur)
},[])
console.log(newArr); // [0, 1, 2, 3, 4, 5]

将多维数组转化为一维:

let arr = [[0, 1], [2, 3], [4,[5,6,7]]]
const newArr = function(arr){
   return arr.reduce((pre,cur)=>pre.concat(Array.isArray(cur)?newArr(cur):cur),[])
}
console.log(newArr(arr)); //[0, 1, 2, 3, 4, 5, 6, 7]

对象里的属性求和:

var result = [
    {
        subject: 'math',
        score: 10
    },
    {
        subject: 'chinese',
        score: 20
    },
    {
        subject: 'english',
        score: 30
    }
];

var sum = result.reduce(function(prev, cur) {
    return cur.score + prev;
}, 0);
console.log(sum) //60

(三)reduce 实现 runPromiseInSequence

const f1 = () => {
    console.log('p1 running')
}

const f2 = () => {
    console.log('p2 running')
}
const array = [f1, f2]

const runPromiseInSequence = (array, value) => array.reduce(
    (promiseChain, currentFunction) => promiseChain.then(currentFunction),
    Promise.resolve(value)
)

runPromiseInSequence(array, 'init')

(四)reduce 实现 pipe

function pipe(src, ...fns){
    return fns.reduce(function(fn1, fn2){
        return fn2(fn1)
    }, src);
}

(五)实现一个reduce

if (!Array.prototype.reduce) {
  Object.defineProperty(Array.prototype, 'reduce', {
    value: function(callback /*, initialValue*/) {
      if (this === null) {
        throw new TypeError( 'Array.prototype.reduce ' + 
          'called on null or undefined' )
      }
      if (typeof callback !== 'function') {
        throw new TypeError( callback +
          ' is not a function')
      }

      var o = Object(this)
var len = o.length >>> 0 var k = 0 var value if (arguments.length >= 2) { value = arguments[1] } else { while (k < len && !(k in o)) { k++ } if (k >= len) { throw new TypeError( 'Reduce of empty array ' + 'with no initial value' ) } value = o[k++] } while (k < len) { if (k in o) { value = callback(value, o[k], k, o) } k++ } return value } }) }
其中x>>>0,保证x有意义(为数字类型),且为正整数,在有效的数组范围内(0 ~ 0xFFFFFFFF),且在无意义的情况下缺省值为0

forEach实现:

Array.prototype.reduce = Array.prototype.reduce || function(func, initialValue) {
    var arr = this
    var base = typeof initialValue === 'undefined' ? arr[0] : initialValue
    var startPoint = typeof initialValue === 'undefined' ? 1 : 0
    arr.slice(startPoint)
        .forEach(function(val, index) {
            base = func(base, val, index + startPoint, arr)
        })
    return base
}

compose 实现的几种方案

(一)compose 概念

  • compose 的参数是函数数组,返回的也是一个函数
  • compose 的参数是任意长度的,所有的参数都是函数,执行方向是自右向左的,因此初始函数一定放到参数的最右面
  • compose 执行后返回的函数可以接收参数,这个参数将作为初始函数的参数,所以初始函数的参数是多元的,初始函数的返回结果将作为下一个函数的参数,以此类推。因此除了初始函数之外,其他函数的接收值是一元的

compose 其实和前面提到的 pipe 一样,就是执行一连串不定长度的任务(方法) ,实际上,compose 和 pipe 的差别只在于调用顺序的不同:

// compose
fn1(fn2(fn3(fn4(args))))

// pipe
fn4(fn3(fn2(fn1(args))))

面向过程的实现方式:

const compose = function(...args) {
    let length = args.length
    let count = length - 1
    let result
    return function f1 (...arg1) {
        result = args[count].apply(this, arg1)
        if (count <= 0) {
            count = length - 1
            return result
        }
        count--
        return f1.call(null, result)
    }
}

reduce的实现方式:

const reduceFunc = (f, g) => (...arg) => g.call(this, f.apply(this, arg))
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)))
}

参考资料:

https://blog.csdn.net/zwkkkk1/article/details/80229923

https://www.jianshu.com/p/e375ba1cfc47

原文地址:https://www.cnblogs.com/fmyao/p/12796277.html