美文网首页
JS专题系列之ES5数组Api封装及应用

JS专题系列之ES5数组Api封装及应用

作者: DY_alley | 来源:发表于2020-12-06 22:51 被阅读0次

    一、概述

    本章节我们主要对ES5常用的数组方法进行封装,以及改变this指向的方法bindcallapply进行封装

    除此之外也会涉及到一些常用的案例及使用场景

    二、bind封装

    bind方法会创建一个新的函数,当这个函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。

    总结:

    • bind会返回一个函数体,而不是立即执行返回的函数,这也是它与call、apply的区别
    // bind封装
    Function.prototype.bindAlley = function(context) {
        context = context || window;
    
        // 其实this就是调用bindAlley的那个函数
        var _this = this;
    
        // 获取bindAlley中传入的其他参数
        var args = Array.prototype.slice.call(arguments,1);
    
        // 返回函数体
        return function() {
            var bindArgs = Array.prototype.slice.call(arguments);
            return _this.apply(context,args.concat(bindArgs));
        }
    }
    

    这样封装是不是很完美?不其实我们还差很多,因为使用bind返回的函数体也是可以使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
    也就是说当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。举个例子:

    var foo = {
        value: 2
    };
    
    function bar(name, age) {
        this.habit = 'shopping';
        console.log(this.value);
        console.log(name);
        console.log(age);
    }
    
    bar.prototype.friend = 'kevin';
    
    var bindFoo = bar.bindAlley1(foo, 'daisy');
    
    var obj = new bindFoo('18');
    console.log(obj.habit); // undefined
    console.log(obj.friend);// undefined
    

    完整版实现

    // bind封装
    Function.prototype.bindAlley = function(context) {
        // 其实this就是调用bindAlley的那个函数
        var _this = this;
    
        // 获取bindAlley中传入的其他参数
        var args = Array.prototype.slice.call(arguments,1);
        var fNOP = function() {};
    
        var fBound  = function() {
            // 将函数体的参数与bindAlley中的参数进行组合
            var argsArr = args.concat(Array.prototype.slice.call(arguments));
            // isPrototypeOf:检测一个对象是否是另一个对象的原型
    
            return _this.apply(fNOP.prototype.isPrototypeOf(this) ? this : context, argsArr);
        };
    
         // 以下这几行代码其实就是一个构造函数继承
        if(this.prototype) {
            fNOP.prototype = this.prototype;
        }
    
        fBound.prototype = new fNOP();
        // 返回函数体
        return fBound
    }
    

    三、call封装

    相比较而言call的封装比较简单。我们先来说下call的基本用法,fn.call(context,arg1,arg2,...)

    • 参数一是this的指向
    • 剩余参数是需要传入到函数中的参数
    • 特点是call会立即执行,而不是像bind一样返回一个函数体
    // call 封装
    Function.prototype.callAlley = function(context){
        context = context || window;
        // 这里记住this指调用callAlley的函数
        context.fn = this;
        
        var args = [];
        for(var i = 1, len = arguments.length; i < len; i++) {
            args.push('arguments[' + i + ']');
        }
        /*
            在eval中,args 自动调用 args.toString()方法,最终的效果相当于:
            var result = context.fn(arguments[1], arguments[2], ...);
        */
        var result = eval('context.fn('+args+')');
    
        delete context.fn;
        return result;
    }
    

    四、apply封装

    相比较而言apply的封装比较简单。我们先来说下apply的基本用法,fn.call(context,[arg1,arg2,...])

    • 参数一是this的指向
    • 参数二是一个数组
    • 特点是apply会立即执行,而不是像bind一样返回一个函数体
    Function.prototype.applyAlley = function(context,args) {
        context = context || window;
        context.fn = this;
    
        var result;
    
        // 如果参数没有传递
        if(!args) {
            result = context.fn();
        } else {
            // 如果参数传递封装方法与call一样
            var arr = [];
            for(var i=0;i<args.length;i++){
                arr.push('args['+i+']');
            }
            result = eval('context.fn('+arr+')');
        }
    
        delete context.fn;
        return result;
    }
    

    五、数组map方法封装

    首先我们先分析map的参数Array.map(callback,context) 参数一是需要执行的回调函数,回调函数中参数有3个valueindex、调用map方法的这个数组,context为回调函数的this指向

    // map方法封装
    Array.prototype.mapAlley = function(callback,context) {
        context = context || window;
        if(typeof callback !== 'function') {
            throw new TypeError(callback+'is not a function');
        }
    
        var arr = new Array(this.length);
        for(var i=0;i<arr.length;i++) {
            if(this.hasOwnProperty(this[i])) {
                arr.push(callback.apply(context,this[i],i,this));
            }
            
        }
        return arr;
    }
    

    六、数组forEach方法封装

    原文:segmentfault.com/a/1190000030694113

    我们都知道,forEach() 方法对数组的每个元素执行一次给定的函数。它的语法也很简单:arr.forEach(callback(currentValue [, index [, array]])[, thisArg])

    因为很多时候,forEach 方法被用来代替 for 循环来完成数组的遍历,所以经常可以看见 forEach 的一些 js 实现,例如

    Array.prototype.forEachAlley = function (fn, context) {
      context = context || arguments[1];
      if (typeof fn !== 'function') {
        throw new TypeError(fn + 'is not a function');
      }
    
      for (let i = 0; i < this.length; i++) {
        fn.call(context, this[i], i, this);
      }
    };
    

    但是这个forEach真的对吗?我们和原生的forEach进行对比

    //  示例1
    const items = ['', 'item2', 'item3', , undefined, null, 0];
    items.forEach((item) => {
      console.log(item); //  依次打印:'',item2,item3,undefined,null,0
    });
    items.forEachAlley((item) => {
      console.log(item); // 依次打印:'',item2,item3,undefined,undefined,null,0
    });
    
    
    // 示例2
    let arr = new Array(8);
    arr.forEach((item) => {
      console.log(item); //  无打印输出
    });
    arr[1] = 9;
    arr[5] = 3;
    arr.forEach((item) => {
      console.log(item); //  打印输出:9 3
    });
    arr.forEachCustom((item) => {
      console.log(item); // 打印输出:undefined 9 undefined*3  3 undefined*2
    });
    

    这到底是怎么回事?我们可以通过下图分析到,真正执行遍历操作的是第 8 条,通过一个 while 循环来实现,循环的终止条件是前面获取到的数组的长度(也就是说后期改变数组长度不会影响遍历次数),while 循环里,会先把当前遍历项的下标转为字符串,通过 HasProperty 方法判断数组对象中是否有下标对应的已初始化的项,有的话,获取对应的值,执行回调,没有的话,不会执行回调函数,而是直接遍历下一项

    如此看来,forEach 不对未初始化的值进行任何操作

    图一.jpg

    改版后的实现

    Array.prototype.forEachAlley = function(callback,context) {
        context = context || arguments[1];
        if(typeof callback !== 'function') {
            throw new TypeError(callback+'is not a function');
        }
    
        let len = this.length;
        let n = 0;
    
        while(n<len){
            if(this.hasOwnProperty(n)){
                callback.call(context,this[n],n,this);
            }
            n++;
        }
    }
    

    七、数组isArray方法封装

    该方法检测当前对象是否是一个数组,返回一个布尔值

    Array.isArrayAlley = function(params) {
        return Object.prototype.toString.call(params).slice(8,-1).toLocaleLowerCase()
          === 
        'array';
    }
    

    八、数组indexOf方法封装

    该方法用于查找传入对象在数组中的位置,并返回该位置,若没有找到则返回-1,该方法不能用于寻找undefined。

    Array.prototype.indexOfAlley = function(params) {
        var index = -1;
        var len = this.length;
        for(var i=0; i<len; i++) {
            if(this[i] === params && this[i] !== 'undefined') {
                index = i;
                break;
            }
        }
        return index;
    }
    

    九、数组lastIndexOf方法封装

    lastIndexOfindexOf方法一样只不过它是倒着循环

    Array.prototype.lastIndexOfAlley = function(params) {
        var index = -1;
        var len = this.length;
        for(var i = len-1; i>=0; i--) {
            if(this[i] === params && this[i] !== 'undefined') {
                index = i;
                break;
            }
        }
        return index;
    }
    

    十、数组every方法封装

    every()是对数组中每一项运行给定函数,如果该函数对每一项返回true,则返回true

    Array.prototype.everyAlley = function(callback) {
        if(typeof callback !== 'function') {
            throw new TypeError(callback+'is not a function');
        }
    
        var len = this.length,
            bStop = true;
    
        for(var i=0; i<len; i++) {
            if(!(bStop = callback(this[i],i,this))) {
                break; 
            }
        }
        return bStop;
    }
    

    十一、数组some方法封装

    some()是对数组中每一项运行给定函数,如果该函数对任一项返回true,则返回true

    Array.prototype.someAlley = function(callback) {
        if(typeof callback !== 'function') {
            throw new TypeError(callback+'is not a function');
        }
    
        var len = this.length,
            bStop = false;
    
        for(var i=0; i<len; i++) {
            if(bStop = callback(this[i],i,this)) {
                break; 
            }
        }
        return bStop;
    }
    

    十二、数组filter方法封装

    filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

    注意: filter() 不会对空数组进行检测。

    注意: filter() 不会改变原始数组

    Array.prototype.filterAlley = function(callback) {
        if(typeof callback !== 'function') {
            throw new TypeError(callback+'is not a function');
        }
        if(this.length === 0) return;
    
        var len = this;
        var arr = [];
    
        for(var i=0;i<len;i++) {
            if(callback(this[i],i,this)) {
                arr.push(this[i]);
            }
        }
        return;
    }
    

    十三、数组reduce方法封装

    reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

    reduce() 可以作为一个高阶函数,用于函数的 compose。

    注意: reduce() 对于空数组是不会执行回调函数的。

    Array.prototype.reduceAlley = function(callback,initiaVale) {
        if(typeof callback !== 'function') {
            throw new TypeError(callback+'is not a function');
        }   
    
        var len = this.length;
        if(len === 0) return;
    
        var prev, isInitiaValue = false, arr = this;
    
        if (initiaVale) {
            prev = initiaVale;
            isInitiaValue = true;
        }
    
        for(var i=0; i<len; i++) {
            if (isInitiaValue) {
                prev = callback(prev,arr[i],i,this);
            } else {
                prev = arr[i]; 
                isInitiaValue = true;
            }
        }
        return prev;
    }
    

    十四、reduce常见的应用

    1、数组扁平化

    var arr1 = [[1],[[2],[3]],[[[4]]]];
    
    function flat(arr) {
        return arr.reduce((prev,curr)=>{
            return prev.concat(Array.isArray(curr) ? flat(curr) : curr);
        },[])
    }
    
    console.log(flat(arr1))
    

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

    var arr = [1,1,2,2,3,4];
    
    var count = arr.reduce((prev,curr)=>{
        if(curr in prev){
          prev[curr]++
        }else{
          prev[curr] = 1;
        }
        return prev;
    },{})
    

    3、获取url中传递的参数

    /**
     * @module getUrlParams
     * @description 获取url中的参数,可以
     * @param {String} url 一个url地址,可以不传,默认会取window.location.href
     * @returns {*} 返回一个对象
     */
    function getUrlParams(url) {
         const _url = url || window.location.href;
         const _urlParams = _url.match(/([?&])(.+?=[^&]+)/igm);
         return _urlParams ? _urlParams.reduce((prev, curr) => {
            const value = curr.slice(1).split('=');
            prev[value[0]] = value[1]
            return prev;
        }, {}) : {};
    }
    

    相关文章

      网友评论

          本文标题:JS专题系列之ES5数组Api封装及应用

          本文链接:https://www.haomeiwen.com/subject/rkwawktx.html