前端手写代码整理

883 阅读15分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 13 天,点击查看活动详情

1、call、aplly、bind 实现

call、aplly、bind 本质都是改变 this 的指向,不同点 call、aplly 是直接调用函数,bind 是返回一个新的函数,call和apply都是对函数的直接调用,而bind方法返回的仍然是一个函数,因此后面还需要()来进行调用才可以。call 跟 aplly 就只有参数上不同。

bind 实现

1、箭头函数的 this 永远指向它所在的作用域

2、函数作为构造函数用 new 关键字调用时,不应该改变其 this 指向,因为 new绑定 的优先级高于 显示绑定 和 硬绑定
Function.prototype.mybind = function(thisArg) {
    if (typeof this !== 'function') {
      throw new Error("Bind must be called on a function");
    }
    // 拿到参数,为了传给调用者, 强制转化arguments为数组格式
    const args = Array.prototype.slice.call(arguments, 1),
    // 保存 this
    self = this,
    // 构建一个干净的函数,用于保存原函数的原型
    nop = function() {},
    // 绑定的函数
    bound = function() {
        // this instanceof nop, 判断是否使用 new 来调用 bound
        // 如果是 new 来调用的话,this的指向就是其实例,
        // 如果不是 new 调用的话,就改变 this 指向到指定的对象 o
        return self.apply(
          this instanceof nop ? this : thisArg,
          args.concat(Array.prototype.slice.call(arguments))
        );
      };
    // 箭头函数没有 prototype,箭头函数this永远指向它所在的作用域
    if (this.prototype) {
      nop.prototype = this.prototype;
    }
    // 修改绑定函数的原型指向
    bound.prototype = new nop();
    return bound;
  }
}
Function.prototype.bind = function(context, ...args) {
  if (typeof this !== 'function') {
    throw new Error("Type Error");
  }
  // 保存this的值
  var self = this;

  return function F() {
    // 考虑new的情况
    if(this instanceof F) {
      return new self(...args, ...arguments)
    }
    return self.apply(context, [...args, ...arguments])
  }
}
Function.prototype.myBind = function(context) {
  // 保存当前函数引用
  var self = this;
  
  // 获取myBind第二个及以后的参数
  var args = Array.prototype.slice.call(arguments, 1);
  
  return function() {
      // 获取函数调用时(myBind返回的函数被调用时),myBind返回的函数中的参数
      var bindArgs = Array.prototype.slice.call(arguments);
      
      // 执行原函数,并改变this指向到context
      return self.apply(context, args.concat(bindArgs));
  }
}

call 实现

bind 是封装了 call 的方法改变了 this 的指向并返回一个新的函数,那么 call 是如何做到改变 this 的指向呢?原理很简单,在方法调用模式下,this 总是指向调用它所在方法的对象,this 的指向与所在方法的调用位置有关,而与方法的声明位置无关(箭头函数特殊)

Function.prototype.mycall = function(thisArg) {
  // this指向调用call的对象
  if (typeof this !== 'function') {
    // 调用call的若不是函数则报错
     throw new Error('bi xu shi hanshu')
  }
  const args = [...arguments].slice(1);
  thisArg = thisArg || window;
  // 将调用call函数的对象添加到thisArg的属性中
  thisArg.fn = this;
  // 执行该属性
  const result = thisArg.fn(...arg);
  // 删除该属性
  delete thisArg.fn;
  // 返回函数执行结果
  return result;
}
Function.prototype.call = function(context = window, ...args) {
  if (typeof this !== 'function') {
    throw new TypeError('Type Error');
  }
  const fn = Symbol('fn');
  context[fn] = this;

  const res = context[fn](...args);
  delete context[fn];
  return res;
}

aplly 实现

Function.prototype.myapply = function(thisArg) {
  if (typeof this !== 'function') {
    throw new Error('bi xu shi hanshu')
  }
  const args = arguments[1]
  thisArg.fn = this;
  const result = thisArg.fn(...args);
  delete thisArg.fn;
  return result;
}
Function.prototype.apply = function(context = window, args) {
  if (typeof this !== 'function') {
    throw new TypeError('Type Error');
  }
  const fn = Symbol('fn');
  context[fn] = this;

  const res = context[fn](...args);
  delete context[fn];
  return res;
}

2、reduce 实现原理

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

Array.prototype.myReduce = function(callback, initValue=0){
    if(typeof callback !== 'function'){
        throw new Error('bi xu shi hanshu')
    }
    let result = initValue
    for (let i = 0; i < this.length; i++) {
        result = callback(result, this[i], i, this)
    }
    return result
}
Array.prototype.reduce = function(callback, initialValue) {
  if (this == undefined) {
    throw new TypeError('this is null or not defined');
  }
  if (typeof callback !== 'function') {
    throw new TypeError(callbackfn + ' is not a function');
  }
  const O = Object(this);
  const len = this.length >>> 0;
  let accumulator = initialValue;
  let k = 0;
  // 如果第二个参数为undefined的情况下
  // 则数组的第一个有效值作为累加器的初始值
  if (accumulator === undefined) {
    while (k < len && !(k in O)) {
      k++;
    }
    // 如果超出数组界限还没有找到累加器的初始值,则TypeError
    if (k >= len) {
      throw new TypeError('Reduce of empty array with no initial value');
    }
    accumulator = O[k++];
  }
  while (k < len) {
    if (k in O) {
      accumulator = callback.call(undefined, accumulator, O[k], k, O);
    }
    k++;
  }
  return accumulator;
}

3、new 实现

1、创建一个新对象;
2、将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
3、执行构造函数中的代码(为这个新对象添加属性)
4、返回新对象。
function myNew() {
  // 创建一个实例对象
  var obj = new Object();
  // 取得外部传入的构造器
  var Constructor = Array.prototype.shift.call(arguments);
  // 实现继承,实例可以访问构造器的属性
  obj.__proto__ = Constructor.prototype;
  // 调用构造器,并改变其 this 指向到实例
  var ret = Constructor.apply(obj, arguments);
  // 如果构造函数返回值是对象则返回这个对象,如果不是对象则返回新的实例对象
  returntypeof ret === 'object' ? ret : obj;
}

4、class 实现继承

使用寄生组合继承的方式

1、原型链继承,使子类可以调用父类原型上的方法和属性
2、借用构造函数继承,可以实现向父类传参
3、寄生继承,创造干净的没有构造方法的函数,用来寄生父类的 prototype
// 实现继承,通过继承父类 prototype
function __extends(child, parent) {
  // 修改对象原型
  Object.setPrototypeOf(child, parent);
  // 寄生继承,创建一个干净的构造函数,用于继承父类的 prototype
  // 这样做的好处是,修改子类的 prototype 不会影响父类的 prototype
  function __() {
    // 修正 constructor 指向子类
    this.constructor = child;
  }
  // 原型继承,继承父类原型属性,但是无法向父类构造函数传参
  child.prototype =
    parent === null
      ? Object.create(parent)
      : ((__.prototype = parent.prototype), new __());
}

var B = (function() {
  function B(opt) {
    this.name = opt.name;
  }
  return B;
})();

var A = (function(_super) {
  __extends(A, _super);
  function A() {
    // 借用继承,可以实现向父类传参, 使用 super 可以向父类传参
    return (_super !== null && _super.apply(this, { name: 'B' })) || this;
  }
  return A;
})(B);

5、async/await 实现

原理就是利用 generator(生成器)分割代码片段。然后我们使用一个函数让其自迭代,每一个yield 用 promise 包裹起来。执行下一步的时机由 promise 来控制

function asyncToGenerator(fn){
    return function(){
        const self = this
        let args = arguments
        return new Promise(function(resolve,reject){
            let gen = fn.apply(self, args)
            function _next(value){
                asyncGeneratorStep(gen, resolve, reject, _next, _throw, '_next', value)
            }
            function _throw(err){
                asyncGeneratorStep(gen, resolve, reject, _next, _throw, '_throw', err)
            }
            _next(undefined)
        })
    }
}
function asyncGeneratorSteo(gen, reaolve, reject, _next, _throw, key, arg){
    try{
        var info = gen[key](arg)
        var value = info.value
    }catch(err){
        reject(err)
        return
    }
    if(info.done){
        resolve(value)
    }else{
        Promise.resolve(value).then(_next, _throw)
    }
}

6、实现一个双向绑定

defineProperty 版本

Object.defineProperty(obj, prop, descriptor)

Object.defineProperty(myObj, keyName, {
    // value: "小明", // 属性的值
    // writable: false, // 能否重写
    // configurable: false, // 能否设置其他
    // enumerable: false, // 是否可以在 for...in 循环和 Object.keys() 中被枚举
    /* ---------------分隔符---------------- */
    //当重新设置myObj.username时,触发set函数
    set:function(setValue){
        console.log("有人在改变我的username值,改变的值会传递给我的参数(setValue)="+setValue);
    },
    //当获取myObj.username时,触发get函数
    get: function(){
        console.log("有人在获取我的username值,他想获取的值由我说了算,我返回(return)什么,myObj.username就等于什么")
        return "我名由我"//如果不返回值,获取的值=undefined
    },
})
// 数据
const data = {
  text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
Object.defineProperty(data, 'text', {
  // 数据变化 --> 修改视图
  set(newVal) {
    input.value = newVal;
    span.innerHTML = newVal;
  }
});
// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {
  data.text = e.target.value;
});

proxy 版本

// 数据
const data = {
  text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
const handler = {
  set(target, key, value) {
    target[key] = value;
    // 数据变化 --> 修改视图
    input.value = value;
    span.innerHTML = value;
    return value;
  }
};
const proxy = new Proxy(data);

// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {
  proxy.text = e.target.value;
});

7、Object.create 的基本实现原理

function create(obj) {
  function F() {}
  F.prototype = obj;
  return new F();
}

8、instanceof 实现

原理:L 的 proto 是不是等于 R.prototype,不等于再找 L.proto.proto 直到 proto 为 null

// L 表示左表达式,R 表示右表达式
function instance_of(L, R) {
  var O = R.prototype;
  L = L.__proto__;
  while (true) {
    if (L === null) return false;
    // 这里重点:当 O 严格等于 L 时,返回 true
    if (O === L) return true;
    L = L.__proto__;
  }
}

9、Array.isArray 实现

Array.myIsArray = function(o) {
  return Object.prototype.toString.call(Object(o)) === '[object Array]';
};

console.log(Array.myIsArray([])); // true

10、getOwnPropertyNames 实现

Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。

if (typeof Object.getOwnPropertyNames !== 'function') {
  Object.getOwnPropertyNames = function(o) {
    if (o !== Object(o)) {
      throw new Error('Object.getOwnPropertyNames called on non-object');
    }
    var props = [], p;
    for (p in o) {
      if (Object.prototype.hasOwnProperty.call(o, p)) {
        props.push(p);
      }
    }
    return props;
  };
}

11、Promise 实现

实现原理:其实就是一个发布订阅者模式

1、构造函数接收一个 executor 函数,并会在 new Promise() 时立即执行该函数
2、then 时收集依赖,将回调函数收集到 成功/失败队列
3、executor 函数中调用 resolve/reject 函数
4、resolve/reject 函数被调用时会通知触发队列中的回调
const isFunction = variable =>typeof variable === 'function';

// 定义Promise的三种状态常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  // 构造函数,new 时触发
  constructor(handle: Function) {
    try {
      handle(this._resolve, this._reject);
    } catch (err) {
      this._reject(err);
    }
  }
  // 状态 pending fulfilled rejected
  private _status: string = PENDING;
  // 储存 value,用于 then 返回
  private _value: string | undefined = undefined;
  // 失败队列,在 then 时注入,resolve 时触发
  private _rejectedQueues: any = [];
  // 成功队列,在 then 时注入,resolve 时触发
  private _fulfilledQueues: any = [];
  // resovle 时执行的函数
  private _resolve = val => {
    const run = () => {
      if (this._status !== PENDING) return;
      this._status = FULFILLED;
      // 依次执行成功队列中的函数,并清空队列
      const runFulfilled = value => {
        let cb;
        while ((cb = this._fulfilledQueues.shift())) {
          cb(value);
        }
      };
      // 依次执行失败队列中的函数,并清空队列
      const runRejected = error => {
        let cb;
        while ((cb = this._rejectedQueues.shift())) {
          cb(error);
        }
      };
      /*
       * 如果resolve的参数为Promise对象,
       * 则必须等待该Promise对象状态改变后当前Promsie的状态才会改变
       * 且状态取决于参数Promsie对象的状态
       */
      if (val instanceof MyPromise) {
        val.then(
          value => {
            this._value = value;
            runFulfilled(value);
          },
          err => {
            this._value = err;
            runRejected(err);
          }
        );
      } else {
        this._value = val;
        runFulfilled(val);
      }
    };
    // 异步调用
    setTimeout(run);
  };
  // reject 时执行的函数
  private _reject = err => {
    if (this._status !== PENDING) return;
    // 依次执行失败队列中的函数,并清空队列
    const run = () => {
      this._status = REJECTED;
      this._value = err;
      let cb;
      while ((cb = this._rejectedQueues.shift())) {
        cb(err);
      }
    };
    // 为了支持同步的Promise,这里采用异步调用
    setTimeout(run);
  };
  // then 方法
  then(onFulfilled?, onRejected?) {
    const { _value, _status } = this;
    // 返回一个新的Promise对象
    returnnew MyPromise((onFulfilledNext, onRejectedNext) => {
      // 封装一个成功时执行的函数
      const fulfilled = value => {
        try {
          if (!isFunction(onFulfilled)) {
            onFulfilledNext(value);
          } else {
            const res = onFulfilled(value);
            if (res instanceof MyPromise) {
              // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
              res.then(onFulfilledNext, onRejectedNext);
            } else {
              //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
              onFulfilledNext(res);
            }
          }
        } catch (err) {
          // 如果函数执行出错,新的Promise对象的状态为失败
          onRejectedNext(err);
        }
      };

      // 封装一个失败时执行的函数
      const rejected = error => {
        try {
          if (!isFunction(onRejected)) {
            onRejectedNext(error);
          } else {
            const res = onRejected(error);
            if (res instanceof MyPromise) {
              // 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
              res.then(onFulfilledNext, onRejectedNext);
            } else {
              //否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
              onFulfilledNext(res);
            }
          }
        } catch (err) {
          // 如果函数执行出错,新的Promise对象的状态为失败
          onRejectedNext(err);
        }
      };

      switch (_status) {
        // 当状态为pending时,将then方法回调函数加入执行队列等待执行
        case PENDING:
          this._fulfilledQueues.push(fulfilled);
          this._rejectedQueues.push(rejected);
          break;
        // 当状态已经改变时,立即执行对应的回调函数
        case FULFILLED:
          fulfilled(_value);
          break;
        case REJECTED:
          rejected(_value);
          break;
      }
    });
  }
  // catch 方法
  catch(onRejected) {
    returnthis.then(undefined, onRejected);
  }
  // finally 方法
  finally(cb) {
    returnthis.then(
      value => MyPromise.resolve(cb()).then(() => value),
      reason =>
        MyPromise.resolve(cb()).then(() => {
          throw reason;
        })
    );
  }
  // 静态 resolve 方法
  static resolve(value) {
    // 如果参数是MyPromise实例,直接返回这个实例
    if (value instanceof MyPromise) return value;
    returnnew MyPromise(resolve => resolve(value));
  }
  // 静态 reject 方法
  static reject(value) {
    returnnew MyPromise((resolve, reject) => reject(value));
  }
  // 静态 all 方法
  static all(list) {
    returnnew MyPromise((resolve, reject) => {
      // 返回值的集合
      let values = [];
      let count = 0;
      for (let [i, p] of list.entries()) {
        // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
        this.resolve(p).then(
          res => {
            values[i] = res;
            count++;
            // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
            if (count === list.length) resolve(values);
          },
          err => {
            // 有一个被rejected时返回的MyPromise状态就变成rejected
            reject(err);
          }
        );
      }
    });
  }
  // 添加静态race方法
  static race(list) {
    returnnew MyPromise((resolve, reject) => {
      for (let p of list) {
        // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变
        this.resolve(p).then(
          res => {
            resolve(res);
          },
          err => {
            reject(err);
          }
        );
      }
    });
  }
}

12、防抖/截流

防抖函数 onscroll 结束时触发一次,延迟执行

防抖的场景:

1、登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖
2、调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
3、文本编辑器实时保存,当无任何更改操作一秒后进行保存
function debounce(func, wait) {
  let timeout;
  return function() {
    let context = this;
    let args = arguments;
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(() => {
      func.apply(context, args);
    }, wait);
  };
}
// 使用
window.onscroll = debounce(function() {
  console.log('debounce');
}, 1000);

节流函数 onscroll 时,每隔一段时间触发一次,像水滴一样

1、scroll 事件,每隔一秒计算一次位置信息等
2、浏览器播放事件,每个一秒计算一次进度信息等
3input 框实时搜索并发送请求展示下拉列表,每隔一秒发送一次请求 (也可做防抖)
function throttle(fn, delay) {
  var prevTime = Date.now();
  return function() {
    var curTime = Date.now();
    if (curTime - prevTime > delay) {
      fn.apply(this, arguments);
      prevTime = curTime;
    }
  };
}
// 使用
var throtteScroll = throttle(function() {
  console.log('throtte');
}, 1000);
window.onscroll = throtteScroll;

13、函数柯里化实现

它的本质就是将一个参数很多的函数分解成单一参数的多个函数。

实际应用中:

1、延迟计算 (用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,开始执行函数)
2、动态创建函数 (参数不够时会返回接受剩下参数的函数)
3、参数复用(每个参数可以多次复用)
const curry = fn =>
  (judge = (...args) =>
    args.length === fn.length
      ? fn(...args)
      : (...arg) => judge(...args, ...arg));

const sum = (a, b, c, d) => a + b + c + d;
const currySum = curry(sum);

currySum(1)(2)(3)(4); // 10
currySum(1, 2)(3)(4); // 10
currySum(1)(2, 3)(4); // 10

14、手写一个深拷贝

浅拷贝只复制地址值,实际上还是指向同一堆内存中的数据,深拷贝则是重新创建了一个相同的数据,二者指向的堆内存的地址值是不同的。这个时候修改赋值前的变量数据不会影响赋值后的变量。

判断类型函数

function getType(obj) {
  const str = Object.prototype.toString.call(obj);
  const map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object'
  };
  if (obj instanceof Element) {
    // 判断是否是dom元素,如div等
    return 'element';
  }
  return map[str];
}

简单版深拷贝,列举三个例子 array object function,可以自行扩展。主要是引发大家的思考

function deepCopy(ori) {
  const type = getType(ori);
  let copy;
  switch (type) {
    case'array':
      return copyArray(ori, type, copy);
    case'object':
      return copyObject(ori, type, copy);
    case'function':
      return copyFunction(ori, type, copy);
    default:
      return ori;
  }
}

// entries() 方法返回一个数组的迭代对象,该对象包含数组的键值对 (key/value)。
function copyArray(ori, type, copy = []) {
  for (const [index, value] of ori.entries()) {
    copy[index] = deepCopy(value);
  }
  return copy;
}

// Object.entries() 可以把一个对象的键值以数组的形式遍历出来,结果和 for...in 一致,但不会遍历原型属性
function copyObject(ori, type, copy = {}) {
  for (const [key, value] of Object.entries(ori)) {
    copy[key] = deepCopy(value);
  }
  return copy;
}

// eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
function copyFunction(ori, type, copy = () => {}) {
  const fun = eval(ori.toString());
  fun.prototype = ori.prototype
  return fun
}

15、数组扁平化

数组扁平化是指将一个多维数组变为一个一维数组

方法一:使用flat()

const res1 = arr.flat(Infinity);

方法二:利用正则 字符串和数组互转(但数据类型都会变为字符串)

const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');

方法三:正则改良版本

const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');

方法四:使用reduce 递归

const flatten = arr => {
  return arr.reduce((pre, cur) => {
    return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
  }, [])
}
const res4 = flatten(arr);

方法五:函数递归

const res5 = [];
const fn = arr => {
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      fn(arr[i]);
    } else {
      res5.push(arr[i]);
    }
  }
}
fn(arr);

16、数组去重

方法一:利用Set

const res1 = Array.from(new Set(arr));

方法二:两层for循环+splice

const unique1 = arr => {
  let len = arr.length;
  for (let i = 0; i < len; i++) {
    for (let j = i + 1; j < len; j++) {
      if (arr[i] === arr[j]) {
        arr.splice(j, 1);
        // 每删除一个树,j--保证j的值经过自加后不变。同时,len--,减少循环次数提升性能
        len--;
        j--;
      }
    }
  }
  return arr;
}

方法三:利用indexOf

const unique2 = arr => {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (res.indexOf(arr[i]) === -1) res.push(arr[i]);
  }
  return res;
}

方法四:利用include

const unique3 = arr => {
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (!res.includes(arr[i])) res.push(arr[i]);
  }
  return res;
}

方法五:利用filter

const unique4 = arr => {
  return arr.filter((item, index) => {
    return arr.indexOf(item) === index;
  });
}

方法六:利用Map

const unique5 = arr => {
  const map = new Map();
  const res = [];
  for (let i = 0; i < arr.length; i++) {
    if (!map.has(arr[i])) {
      map.set(arr[i], true)
      res.push(arr[i]);
    }
  }
  return res;
}

17、类数组转化为数组

类数组是具有length属性,但不具有数组原型上的方法。常见的类数组有arguments、DOM操作方法返回的结果。

方法一:Array.from

Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

Array.from(document.querySelectorAll('div'))

方法二:Array.prototype.slice.call()

Array.prototype.slice.call(document.querySelectorAll('div'))

方法三:扩展运算符

[...document.querySelectorAll('div')]

方法四:利用concat

Array.prototype.concat.apply([], document.querySelectorAll('div'));

18、Array.prototype.filter()

filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])

Array.prototype.filter = function(callback, thisArg) {
  if (this == undefined) {
    throw new TypeError('this is null or not undefined');
  }
  if (typeof callback !== 'function') {
    throw new TypeError(callback + 'is not a function');
  }
  const res = [];
  // 让O成为回调函数的对象传递(强制转换对象)
  const O = Object(this);
  // >>>0 保证len为number,且为正整数
  const len = O.length >>> 0;
  for (let i = 0; i < len; i++) {
    // 检查i是否在O的属性(会检查原型链)
    if (i in O) {
      // 回调函数调用传参
      if (callback.call(thisArg, O[i], i, O)) {
        res.push(O[i]);
      }
    }
  }
  return res;
}

19、Array.prototype.map()

Array.prototype.map = function(callback, thisArg) {
  if (this == undefined) {
    throw new TypeError('this is null or not defined');
  }
  if (typeof callback !== 'function') {
    throw new TypeError(callback + ' is not a function');
  }
  const res = [];
  // 同理
  const O = Object(this);
  const len = O.length >>> 0;
  for (let i = 0; i < len; i++) {
    if (i in O) {
      // 调用回调函数并传入新数组
      res[i] = callback.call(thisArg, O[i], i, this);
    }
  }
  return res;
}

20、Array.prototype.forEach()

Array.prototype.forEach = function(callback, thisArg) {
  if (this == null) {
    throw new TypeError('this is null or not defined');
  }
  if (typeof callback !== "function") {
    throw new TypeError(callback + ' is not a function');
  }
  const O = Object(this);
  const len = O.length >>> 0;
  let k = 0;
  while (k < len) {
    if (k in O) {
      callback.call(thisArg, O[k], k, O);
    }
    k++;
  }
}

Array.prototype.reduce()

未完待续...随时更新,关注不迷路!