前端面试手写js系列(面试够用版)

122 阅读4分钟

手写防抖函数

// 你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行,
// 防抖函数
function debounce(fn, wait) {
  var timeout;
  return function () {
    var contxt = this
    var args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(function(){
      fn.apply(context, args)
    }, wait)
  }
}

手写节流函数

// 节流(Throttling)实现
// 可以理解为事件在一个管道中传输,
// 加上这个节流阀以后,事件的流速就会减慢。
// 实际上这个函数的作用就是如此,它可以将一个函数的调用频率限制在一定阈值内
function throttle(func, wait) {
        var timeout;
        var previous = 0;
        return function() {
            var context = this;
            var args = arguments;
            if (!timeout) {
                timeout = setTimeout(function(){
                    timeout = null;
                    func.apply(context, args)
                }, wait)
            }
        }
    }

手写new方法

new 做了哪些事?

  1. 它创建了一个全新的对象。
  2. 它会被执行[[Prototype]](也就是__proto__)链接。
  3. 它使this指向新创建的对象。
  4. 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。
  5. 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用。
function aNew (fn) {
    if (typeof fn !== 'function') {
        throw Error('xxxx')
    }
    let res = {}
    if (fn.prototype !== null) {
        res.__proto__ = fn.prototype
    }
    let ret = fn.apply(res, [...arguments].slice.call(1))
    if ((typeof ret === 'object' || typeof ret === 'function') && ret !== null) {
        return ret;
    }
    return res;
 }
  var obj = New(A, 1, 2);
  // equals to
  var obj = new A(1, 2);

手写call方法

call核心:

  1. 指定this到函数,不传入参数,默认指向为 window
  2. 传入给定参数执行函数
  3. 删除这个函数
  4. 返回执行结果
Function.prototype.call2 = function(context = window) {
   if(typeof this != "function") {
     throw Error("not a function")
   }
   context.fn = this;
   let args = [...arguments].slice(1);
   let res = context.fn(...args);
   delete context.fn;
   return res;
}
// 测试方法
let foo = {
   value: 1
}
function bar(name, age) {
 console.log(name)
 console.log(age)
 console.log(this.value);
}
bar.call2(foo, 'black', '18') // black 18 1

手写apply()

apply()的实现和call()类似,只是参数形式不同。apply是一个「数组」

  1. 指定this到函数,不传入参数,默认指向为 window
  2. 传入给定参数执行函数
  3. 删除这个函数
  4. 返回执行结果
Function.prototype.apply2 = function(context = window) {
   if(typeof this != "function") {
     throw Error("not a function")
   }
   context.fn = this;
   let res;
   if (arguments[1]) {
       res = context.fn(...arguments[1])
   } else {
       res = context.fn();
   }
   delete context.fn;
   return res;
}

手写bind方法

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

此外,bind实现需要考虑实例化后对原型链的影响。

需要考虑的点

  1. 因为返回新的函数,要考虑到使用new去调用,并且new的优先级比较高,所以需要判断new的调用
  2. 还有一个特点就是bind调用的时候可以传参,调用之后生成的新的函数也可以传参,效果是一样的
Function.prototype.bind2 = function (context) {
    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);
    var fNOP = function () {};
    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }
    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
  }

手写promise方法

我们来过一遍Promise/A+规范:

  • 三种状态pending| fulfilled(resolved) | rejected
  • 当处于pending状态的时候,可以转移到fulfilled(resolved)或者rejected状态
  • 当处于fulfilled(resolved)状态或者rejected状态的时候,就不可变。

必须有一个then异步执行方法,then接受两个参数且必须返回一个promise

// 面试够用版
function myPromise(constructor){
  let self=this;
  self.status="pending"  //定义状态改变前的初始状态
  self.value=undefined; //定义状态为resolved的时候的状态
  self.reason=undefined; //定义状态为rejected的时候的状态
  function resolve(value){
    //两个==="pending",保证了状态的改变是不可逆的
    if(self.status==="pending"){
      self.value=value;
      self.status="resolved";
    }
  }
  function reject(reason){
        //两个==="pending",保证了状态的改变是不可逆的
    if(self.status==="pending"){
        self.reason=reason;
        self.status="rejected";
    }
  }
  //捕获构造异常
  try{
    constructor(resolve,reject);
  }catch(e){
    reject(e);
  }
}

// 同时,需要在myPromise的原型上定义链式调用的then方法:
myPromise.prototype.then=function(onFullfilled,onRejected){
  let self=this;
  switch(self.status){
      case "resolved":
        onFullfilled(self.value);
        break;
      case "rejected":
        onRejected(self.reason);
        break;
      default:       
  }
}
// 测试一下:
var p=new myPromise(function(resolve,reject){resolve(1)});
p.then(function(x){console.log(x)})

手写科里化

 function curry(fn, args) {
    var length = fn.length;
    args = args || [];
    return function() {
        var _args = args.slice(0);
        var arg, i;
        for (i = 0; i < arguments.length; i++) {
            arg = arguments[i];
            _args.push(arg);
        }
        if (_args.length < length) {
            return curry.call(this, fn, _args);
        } else {
            return fn.apply(this, _args);
        }
    }
}