夯实JavaScript功底,前端要会的手写方法

6,736 阅读4分钟

call: 简单来说就是改变执行方法当前的this,可以传入不定参数

Function.prototype.myCall = function(context, ...args) {
  context.fn = this;
  const result = context.fn(...args);
  delete context.fn;
  return result;
}

getMessage.myCall(obj, 'name'); 

立即执行getMessage方法,不过是以obj.getMessage的方式,
所以这个时候getMessage内的this是obj,传入参数'name'。
(obj可能压根就没有getMessage方法)

apply: 和call作用一致,传递参数格式不同,需是数组

Function.prototype.myApply = function(context, args) {
  context.fn = this;
  const result = context.fn(args);
  delete context.fn;
  return result;
}

getMessage.myApply(obj, ['name']); 

bind: 改变指定方法的this后执行,以函数的形式返回执行结果

Function.prototype.myBind = function(context, ...args) {
  return () => {
    return this.myApply(context, args);
  }
}

const result = getMessage.myBind(obj) 

result是一个函数,再执行一次才是getMessage方法的执行结果

new:将构造函数实例化,将参数创建为对象以及赋值原型方法

function createNew(Ctor, ...args) {
  const obj = Object.create(Ctor.prototype);
  const ret = Ctur.apply(obj, args);
  return ret instanceof Object ? ret : obj;
}

1. 将构造函数的原型赋值给新建的obj的隐式原型__proto__。
2. 在obj下执行构造函数,并传入参数,
   这个时候构造函数内的this就是obj。
3. 如果这个'构造函数'没有return对象格式的结果,
   返回新创建的obj。

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.getName = function() {
  console.log(this.name);
}

const xm = createNew(Person, 'xiaoming', 22);

instanceof: 判断一个变量是否是某个类型

function myInstanceOf(left, right) {
  while(1) {
    if(left.__proto__ === null) {
      return false;
    }
    if(left.__proto__ === right.prototype) {
      return true;
    }
    left = left.__proto__;
  }
}

instanceof的原理就是通过原型链查找,
所以一直向上查找左侧的隐式原型__ptoto__是否等于右侧显式原型,
原型链的尽头是null,没找到就返回falsemyInstanceOf([1,2], Array);  // true

forEach: 遍历数组,这个大家经常用,想必不说都懂

Array.prototype.myForEach = function(fn) {
  const arr = this;
  for(let i = 0; i < arr.length; i++) {
    fn(arr[i], i, arr);
  }
}

接受一个fn回调函数,传递给回调函数三个参数:
每项的值,下标,自身。第二个参数有人用么?

const arr = ['a','b','c'];
arr.myForEach(item => {
  console.log(item);  // a   b   c
})

map: 返回经过处理的数组

Array.prototype.myMap = function(fn) {
  const arr = this;
  const ret = [];
  for(let i = 0; i < arr.length; i++) {
    ret.push(fn(arr[i], i, arr));
  }
  return ret;
}

和forEach类似也是接受一个fn回调,
不过会将回调处理的结果放入一个新的数组,
所以map回调内的每一项需要return,
因为要组成新的数组结果。

const arr = ['a', 'b', 'c'];
const newArr = arr.myMap(item => {  // a1   b1   c1
  return item + 1; // 要return结果
})

filter: 返回回调处理结果为true的新数组

Array.prototype.myFilter = function(fn) {
  const arr = this;
  const ret = [];
  for(let i = 0; i < arr.length; i++) {
    if(fn(arr[i], i, arr)) {
      ret.push(arr[i]);
    }
  }
  return ret;
}

大同小异,过滤出处理条件为true的值。

返回数组中不重复的值:
function repeat(arr) {
  return arr.myFilter((v, i, a) => {
	return a.indexOf(v) === a.lastIndexOf(v);
  })
}

const arr = [1,2,3,4,1,2,3,5,6,8,3];
repeat(arr); // [4,5,6,8]

find:返回处理条件第一个为true的数组项

Array.prototype.myFind = function(fn) {
  const arr =this;
  for(let i = 0; i < arr.length; i++) {
    if(fn(arr[i], i, arr)) {
      return arr[i];
    }
  }
}

否则返回undefined

findIndex: 返回处理条件第一个为true的数组下标

大家自己写下咯~

every:如果数组每一项都符合处理条件。返回true,否则返回false

Array.prototype.myEvery = function(fn) {
  const arr = this;
  for(let i = 0; i < arr.length; i++) {
    if(!fn(arr[i], i, arr)) {
      return false;
    }
  }
  return true;
}

some:只要数组有一项符合处理条件。返回true,都不满足返回false。

这个相信大家都知道怎么写了~

reduce: 一般为数组做累计结果使用。

Array.prototype.myReduce = function(fn, second) {
  const arr = this;
  let index = 0;
  if(typeof second === 'undefined') { // 没传第二个参数
    index = 1;
    second = arr[0];
  }
  for(let i = index; i < arr.length; i++) {
    const invoked = fn(second, arr[i], i, arr);
    second = invoked;
  }
  return second;
}

一般会传入第二个参数作为初始值,如果没有传入,
初始值就是数组的第一项,将处理的结果进行累计,
最后返回累计的结果。

返回数组中指定参数重复的次数:
function count(arr, value) {
  return arr.myReduce((f, s) => {
    return Object.is(s, value) ? f + 1 : f + 0;
  }, 0)
}

const arr = [1,2,3,4,1,2,3,2,1];
count(arr, 2); // 3

map:使用reduce模仿map

Array.prototype.myMap = function (fn) {
  return this.reduce((pre, cur, i) => {
    return [...pre, fn(cur, i, this)]
  }, [])
}

includes:传入某项检测

Array.prototype.myIncludes = function (item) {
  const arr = this;
  for (let i = 0; i < arr.length; i++) {
    if (Object.is(arr[i], item)) {
      return true;
    }
  }
  return false;
}

debounce: 函数防抖

function debounce(fn, delay = 1000) {
  let timer;
  return () => {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, delay)
  }
}

函数防抖指的是一定时间内没有再次触发函数,就执行该函数,否则重新计时。
wow为例:
2.5s施法的寒冰箭,再读条的过程中,
你身子抖动打断了施法,想再次触发技能时麻烦您重新读条。

throttle:函数节流

function throttle(fn, delay = 100) {
  let timer;
  return () => {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, arguments);
        timer = null;
      }, delay)
    }
  }
}

函数节流指的是规定某个时间内只能执行一次函数。
wow为例:
火冲为瞬发技能,不过你规定cd为8s,
所以即使8s内按了10次,也只能来1发,节省点体力吧。

deepClone:深拷贝

一般够用型
function deepClone(source) {
  if (typeof source !== 'object' || source == null) {
    return source;
  }
  const target = Array.isArray(source) ? [] : {};
  for (const key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      if (typeof source[key] === 'object' && source[key] !== null) {
        target[key] = deepClone(source[key]);
      } else {
        target[key] = source[key];
      }
    }
  }
  return target;
}

解决循环引用和symblo类型
function cloneDeep(source, hash = new WeakMap()) {
  if (typeof source !== 'object' || source === null) {
    return source;
  }
  if (hash.has(source)) {
    return hash.get(source);
  }
  const ret = Array.isArray(source) ? [] : {};
  Reflect.ownKeys(source).forEach(key => {
    const val = source[key];
    if (typeof val === 'object' && val != null) {
      ret[key] = cloneDeep(val, hash);
    } else {
      ret[key] = val;
    }
  })
  return ret;
}

Promise:手写简易版

class MyPromise {
  constructor(fn) {
    this.state = 'PENDING';
    this.value = null
    this.resolvedCallbacks = []
    this.rejectedCallbacks = []
    const resolve = value => {
      if (this.state === 'PENDING') {
        this.state = 'RESOLVED'
        this.value = value
        this.resolvedCallbacks.map(cb => cb())
      }
    }
    const reject = value => {
      if (this.state === 'PENDING') {
        this.state = 'REJECTED'
        this.value = value
        this.rejectedCallbacks.map(cb => cb())
      }
    }
    try {
      fn(resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r }
    if (this.state === 'PENDING') {
      this.resolvedCallbacks.push(() => {
        onFulfilled(this.value) 
      })
      this.rejectedCallbacks.push(() => {
        onRejected(this.value) 
      })
    }
    if (this.state === 'RESOLVED') {
      onFulfilled(this.value)
    }
    if (this.state === 'REJECTED') {
      onRejected(this.value)
    }
  }
  
  catch(fn) {
    return this.then(null, fn);
  }
  
  static resolve(val) {
    return new MyPromise(resolve => {
      resolve(val);
    })
  }
  
  static reject(err) {
    return new MyPromise((resolve, reject) => {
      reject(err);
    })
  }
  
  static race(promises) {
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(resolve, reject);
      }
    })
  }
  
  static all(promises) {
    const arr = [];
    let i = 0;
    function processData(index, data, resolve) {
      arr[index] = data;
      i++;
      if (i === promises.length) {
        resolve(arr);
      }
    }
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(data => {
          processData(i, data, resolve);
        }, reject);
      }
    })
  }
}

iterator:不使用Generator函数创建迭代器

function myIterator(items) {
  let i = 0;
  return {
    next() {
      const done = i >= items.length;
      const value = !done ? items[i++] : undefined;
      return {
        done,  // 是否全部迭代完成
        value  // 返回迭代的值
      }
    }
  }
}

const interator = myIterator([1, 2, 3]);
interator.next();

Events:事件中心管理

class Events {
  constructor() {
    this._evnets = Object.create(null);
  }
  
  on(event, fn) {  // 往事件中心添加事件
    if (Array.isArray(event)) {
      for (let i = 0; i < event.length; i++) {
        this.on(evnet[i], fn);
      }
    } else {
      (this._evnets[event] || (this._evnets[event] = [])).push(fn);
    }
  }
  
  emit(event, ...args) {  // 触发事件中心对应事件
    const cbs = this._evnets[event];
    if (cbs) {
      for (let i = 0; i < cbs.length; i++) {
        cbs[i].apply(this, args);
      }
    }
  }
  
  off(event, fn) {  // 移除事件
    if (!arguments) {
      this._evnets = Object.create(null);
      return this;
    }
    if (Array.isArray(event)) {
      for (let i = 0; i < event.length; i++) {
        this.off(event[i], fn);
      }
      return this;
    }
    if (!fn) {
      this._evnets[event] = null;
      return this;
    }
    const cbs = this._evnets[event];
    let i = cbs.length;
    while (i--) {
      const cb = cbs[i];
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1);
        break;
      }
    }
    return this;
  }
  
  once(evnet, fn) {  // 只执行一次
    function on() {
      this.off(evnet, on);
      fn.apply(this, arguments);
    }
    on.fn = fn;
    this.on(evnet, on);
    return this;
  }
}

const event = new Event();
event.on('test', (name, sex) => { // 添加事件
  console.log(`${name}:${sex}`);
})
event.emit('test', 'cc', 'man');  // 传参并触发事件
evnet.off();  // 清空所有事件

迭代器

const queue = []

const iterator = (hook, next) => {
  hook(route, currnet, to => {
    next(to)
  })
}

const runQueue(queue, fn, cb) {
  const step = index => {
    if (index >= queue.length) {
      cb()
    } else {
      if (queue[index]) {
        fn(queue[index], () => {
          step(index + 1)
        })
      } else {
       step(index + 1)
      }
    }
  }
  step(0)
}

runQueue(queue, iterator, () => {
  onComplete()
})

setInterval: 使用setTimeout模拟,并随时取消

function mySetInterval(fn, delay) {
  let timer;
  const loop = () => {
    timer = setTimeout(() => {
      loop();
      fn();
    }, delay);
  };
  loop();
  return () => {
    clearInterval(timer)
  }
}

const stop = mySetInterval(() => {
  console.log('test')
}, 200);

stop() // 停止

setInterval: 使用requestAnimationFrame模拟

function mySetInterval(fn, interval) {
  const now = Date.now;
  let startTime = now();
  const loop = () => {
    const timer = requestAnimationFrame(loop);
    if (now() - startTime >= interval) {
      startTime = now();
      fn.call(this, timer);
    }
  }
  loop();
}

一般来说是不建议使用setInterval的,
如内部函数复杂就不能保证一定在规定时间内自动执行。
一般是通过setTimeout模仿setInterval。
那为什么要实现setInterval?
因为它内部的实现是使用requestAnimationFrame实现的,
该方法自带函数节流。
如有持续的动画需要执行,
基本会保证在16.6毫秒内执行一次,
提高动画性能并延时也是精确的。

mySetInterval(timer => {
  console.log('a');
  // cancelAnimationFram(timer); 取消当前定时器
})

写到最后
  • 如有谬误,万望斧正。