传说中的手写代码(初级版)

435 阅读6分钟

手写防抖,节流

防抖(debounce)

任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行,直白的讲就是:你尽管触发,我总是在你最后一次触发的时间之后的多少秒之后执行一次

防抖是一个高阶函数,使用了闭包

function debounce(fn, delay){
    // fn是我们需要包装的事件回调, delay是每次推迟执行的等待时间
    // 定时器
    let timer = null;
    // 将debounce处理结果当作函数返回
    return function(){
        // 保留调用时的this上下文
        let context = this;
        // 保留调用时传入的参数
        let args = arguments;
        // 每次事件被触发时,都去清除之前的旧定时器
        if(timer){
            clearTimeout(timer)
        }
        // 设置新定时器
        timer = setTimeout(function(){
            fn.apply(context, args)
        }, delay)
    }
}

总的来说,防抖核心思想就是,传入你要执行的函数fn,以及延迟执行的时间delay,首先设置定时器timer,并返回一个新函数,而在新函数中需要保存调用时的上下文this和传入的参数,并在执行fn前将旧的定时器清除,然后重新设置新的定时器在delay之后执行fn并绑定this上下文。

节流(throttle)

当持续触发事件时,保证一定时间段内只调用一次事件处理函数。 节流是一个高阶函数,使用了闭包

 fucntion throttle(fn, interval) {
  // fn是我们需要包装的事件回调, interval是时间间隔的阈值
  // last 为上一次触发回调的时间
  let last = 0;

  // 将throttle处理结果当作函数返回
  return function () {
    // 保留调用时的this上下文
    let context = this
    // 保留调用时传入的参数
    let args = arguments
    // 记录本次触发回调的时间
    let now = +new Date()
    // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
    if (now - last >= interval) {
      // 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
          last = now;
          fn.apply(context, args);
      }
  }
 }

节流函数 是一个高阶函数,使用闭包保存上一次触发回调的时间last,执行函数fn,时间阀值inerval,在要执行fn时,当前时间与上一次触发事件进行比较,如果时间间隔大于interval(now - last >= interval)执行函数fn.apply(context, args)

深度拷贝

function deepCopy(obj) {
  if(typeof obj !== "object"){
    return obj
  }
  const newObj = obj instanceof Array ? [] : {};
  for(let key in obj){
    if(obj.hasOwnProperty(key)){
      newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]
    }
  }
  return newObj
}

可以实现一个promise吗?

按照步骤规则,手写一个promise

可以实现一个promise的ajax吗?

实现思路简单整理如下:

  1. 首先我们要知道发起一个网络请求应该是_ajax(url, params)这样子的,即必须有请求地址 请求参数(可选)
  2. 而promise是可以链式的调用then方法的,那么要想链式调用then方法_ajax的返回值必须是一个promise对象 此时我们大体的架子已经有了
function _ajax(url, params){
    const promise = new Promise();
    return promise;
}
  1. 接下来我们去丰富里边的内容:
  2. new 一个promise,接收一个函数作为参数
  3. 该函数接收两个参数,resolve和reject,它们是两个函数,由js引擎提供
function _ajax(url, params){
    function fn(resolve, reject){
        
    }
    const promise = new Promise(fn);
    return promise;
}
  1. 接着创建xhr对象 即new XMLHttpRequest()
  2. 然后绑定事件onreadystatechange 在xhr的readyState属性发生变化的时候做出处理
  3. 接着.open()打开连接,同时设置请求方式,请求地址,以及是异步还是同步请求
  4. 然后可以使用setRequestHeader(k, v)添加http请求头字段
  5. 然后.send(params)发送请求
function _ajax(url, params){
    function fn(resolve, reject){
        const xhr = new XMLHttpRequest();
        const handleChange = function (){
          
        }
        xhr.onreadystatechange = handleChange;
        xhr.responseType = "json";
        xhr.setRequestHeader("Accept", 'application/json');
        xhr.open('POST', url);
        xhr.send(params);
    }
    const promise = new Promise(fn);
    return promise;
}
  1. 总的基本完成了 那么再去详细的完善 事件处理函数onreadystatechange
  2. readyState状态为:
    • 0: 尚未开始初始化,也没有调用open
    • 1: 此时调用了open但没有调用send
    • 2: 此时调用了send,但是服务器没有给出响应
    • 3: 此时正在接收服务器响应,但还没有接收完毕,此处一般不做任何处理
    • 4: 此时已经接收完了服务器的响应,可以对数据进行处理
  3. status状态码为200的时候进行处理
function _ajax(url, params){
  params = JSON.stringify(params);
  function fn(resolve, reject){
    const xhr = new XMLHttpRequest();
    const handleChange = function (){
      if(this.status === 200 && this.readyState === 4){
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.onreadystatechange = handleChange;
    xhr.responseType = "json";
    xhr.setRequestHeader("Accept", 'application/json');
    xhr.open('POST', url);
    xhr.send(params);
  }
  const promise = new Promise(fn);

  return promise;
} 
  1. 至此,我们基于promise的ajax已经完成了
  2. 完美!

手写一个call???

首先得抓住几个点:即实现call的核心思想

  • 将函数设为对象的属性
  • 执行&删除这个函数
  • 指定this到函数并传入给定参数执行函数
  • 如果不传入参数,默认指向为 window

那么按照此套路,我们来实现如下:

//  示例:bar.say._call(foo, 'aaa');辅助理解下方的话
Function.prototype._call = function(context=window){
  // 传入的context即是foo,
  // 又因为this  总是指向调用它的那个对象,那么调用_call方法时的this就是bar.say,即有了下一行的代码
  content.fn = this;
  let args = [...arguments].slice(1);
  // 执行fn方法
  let result = content.fn(...args);
  // 删除 fn
  delete content.fn
  return result
}

手写一个bind ???

中心思想是,并不立即执行, 而是返回一个函数

Function.prototype.myBind = function(context) {
  //返回一个绑定this的函数,我们需要在此保存this
  let self = this;
  // 可以支持柯里化传参,保存参数
  let args = [...arguments].slice(1);
  return function(){
    // 同样因为支持柯里化形式传参我们需要再次获取存储参数
    let newArgs = [...arguments];
    // 返回函数绑定this,传入两次保存的参数
    //考虑返回函数有返回值做了return
    return self.apply(context, args.concat(newArgs));
  }
}

手写一个数组map方法

首先得知道map方法,接收一个回调函数,该回调函数有三个参数:currentValue(当前元素的值),index(当前元素下标),arr(当前元素所属的数组对象) 且会返回一个新数组。

Array.prototype.__map = function(callback) {
  var arr = []
  for (var i = 0; i < this.length; i++) {
    arr.push(callback.call(this, this[i], i, this))
  }
  return arr
}
var res = [1, 2, 3].__map(item => {
  return item * 2
})
console.log('res: ', res);// res:  [ 2, 4, 6 ]

实现一个new

首先得知道new做了三件事:

  1. 创建了一个新对象并返回
  2. 将新对象的__proto__属性指向了构造函数的原型对象
  3. 执行了构造函数中的代码(为新对象添加属性)
function _new(fn, ...arg) {
  // 创建新对象将其原型对象指向构造函数的原型对象
  const obj = Object.create(fn.prototype);
  // 将this指向新创建的对象并指向该构造函数
  const ret = fn.apply(obj, arg);
  // 如果构造函数的执行结果是Object类型则返回执行结果,否则返回新创建的对象
  return ret instanceof Object ? ret : obj;
}

只要你明白了这个new的实际底层执行过程,那么类似于下面的这种类型的面试题百分百不会出错:

栗子:
function Constructor() {
    this.a = "123";
    // 第一种情况
    return false
    //第二种情况
    return []  或者 return {} 或者 return () => {}
    // 第三种情况
    没有return
} 
var obj = new Constructor();