优化js数组的forEach方法——封装一个万能each

251 阅读3分钟

forEach方法的缺点

  1. 只能依次迭代,不能中途结束
  2. 不支持返回值
  3. 不能迭代对象

封装一个each方法能达到的目的

    //obj是需要进行迭代的内容(可以是数字,字符串,类数组,数组,对象),callback是函数
    const each=function each(obj,callback){};
  1. 做了传递参数的类型检测
  2. 可以迭代数组和对象
  3. 可以中途结束循环(callback中返回false即可)
  4. 支持修改原数组或对象中的内容(返回一个新数组,原始数据不变)
  5. obj可以是数字(创建一个等长度的数组进行迭代)
  6. 支持类数组按照数组的方式进行处理
  7. 支持callback中的this迭代原始数据

封装万能each

    //检测是否是对象的方法
    const isObject = function isObject(obj){
        return obj !== null && /^(object|function)$/.test(typeof obj);
    }
    //检测是否是函数的方法
    const isFunction = function isFunction(obj){
        return typeof obj === 'function';
    }
    //检测是否是类数组的方法
    const isArrayLike = function isArrayLike(obj){
        //存在一个obj,且obj有length属性
        let length = !!obj && 'length' in obj && obj.length;
        //由于function和window里也都有length属性,所以要做一个判断
        if(isFunction(obj) || obj === window) return false;
        //如果是空的类数组,length=0;如果不是空的类数组,length是大于0的数字且要有最大索引
        return length === 0 || (typeof length === 'number' && length > 0 && (length - 1) in obj);
    }
    //封装each方法
    const each = function each(obj,callback){
        //如果obj是一个有效的数字类型,则创建一个长度和它相同的密集数组
        if(typeof obj === 'number' && !isNaN(obj)) obj = new Array(obj).fill(null);
        //如果obj不是对象类型,就把它转换成对象
        if(!isObject(obj)) obj = Object(obj);
        //还要确保callback是一个函数,如果不是就抛出异常错误,阻止下面代码运行
        if(!isFunction(callback)) throw new TypeError('callback is not a function');
        //声明一个变量来保存原始数据
        let originObj=obj;
        //把传递的数组/对象进行浅克隆,这样我们只需要修改克隆后的数据,对原始数据没有影响
        //如果传递的数据是数组或者类数组
        if(Array.isArray(obj) || isArrayLike(obj)){
            //浅克隆
            obj = [...obj];
            let i = 0 , len = obj.length , result;
            for(; i < len ; i++){
                /*给callback函数穿参并执行,将函数的this指向原始数据,
                如果callback是箭头函数则无效,因为箭头函数本身没有this,
                用result接收函数的返回值*/
                result = callback.call(originObj , obj[i] , i);
                //如果返回值是false就结束循环
                if(result === false) break;
                //用返回的值替换当前项
                obj[i] = result;
            }
            //将修改后的新数组返回,原始数组不变
            return obj;
        }
        //如果传递的数据是一个对象
        //浅克隆
        obj = {...obj};
        let keys = Reflect.ownKeys(obj) , i = 0 , len = keys.length , key , result;
        for(; i < len ; i++){
            //获取对象的每个属性
            key = keys[i];
            //和上面数据为数组时result的作用一样
            result = callback.call(originObj , obj[key] , key);
            //如果返回值时false就结束循环
            if(result === false) break;
            //用返回的值替换当前项
            obj[key] = result;
        }
        //将修改后的新对象返回,原始数据不变
        return obj;
    }

测试each方法

  1. 如果obj为数字类型
    let num = each(3,(item) => item + 1);
    console.log(num)//输出结果为长度为3,各项都是1的数组
  1. 如果obj是字符串(类数组)
    let str = each('我爱你中国', function(value , key){
        console.log(key , this);
        /*
        输出结果是:
        0 String{‘我爱你中国’}
        1 String{‘我爱你中国’}
        2 String{‘我爱你中国’}
        3 String{‘我爱你中国’}
        4 String{‘我爱你中国’}
        */
        return value;
    })
    console.log(str);
    //输出结果是:[‘我’,‘爱’,‘你’,‘中’,‘国’]
  1. 如果obj是数组
    let arr = [10 , 20 , 30 , 40 , 50 ,60];
    let arr2 = each(arr , (item , index) => {
        if(index > 2) return false;
        return item * index ;
    })
    console.log(arr , arr2);
    //输出结果是:[10 , 20 , 30 , 40 , 50 , 60]  [0 , 20 , 60 , 40 , 50 , 60]
  1. 如果obj是对象
    let obj = {
        x:10,
        y:20,
        [Symbol()]:30
    }
    let ob2 = each(obj , (value , key) => {
        if(key === 'y') return false;
        console.log(value , key);
        //输出结果是:10    ‘x’
        return value * 10;
    })
    console.log(obj,ob2);
    //输出结果是:{x : 10 , y : 20 , Symbol() : 30}     {x : 100 , y : 20 , Symbol() : 30}