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

一、概述

本章节我们主要对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

五、数组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

六、数组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

七、数组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

九、数组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

十一、数组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

十二、数组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

十三、数组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

十四、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封装及应用)