JavaScript手写题

目录

一、防抖

二、节流

三、instanceOf

四、call

 五、apply

六、bind 

七、对象深拷贝

八、手写new

九、手写Ajax

promise封装Ajax

十、数组扁平化

方法一:ES6语法 ==》flat  

方法二:数组方法 ==》join + split   

方法三:数组方法 ==》toString() + split() 

方法四:数组方法 ==》reduce

方法五:ES6语法 ==》扩展运算符

方法六:递归

十一、手写promise(A+版)

Promise.all(arr)

Promise.race(arr)

Promise.resolve(value)

Promise.reject()

Promise.finally(callback)

Promise.catch()

十二、函数柯里化

十三、setTimeout实现setInterval

十四、JS实现拖拽

 十五、手写数组转树

十六、获取url所有参数

十七、手写一个发布-订阅模式

十八、Proxy 实现观察者模式

十九、实现箭头函数的this指向


一、防抖

function debounce(fn, delay=200) {
    let timeout = null;  // 定时器控制
    return function(...args) {
        if (timeout) {  // 定时器存在,表示某个动作之前触发过了
            clearTimeout(timeout);  // 清除定时器
            timeout = null;
        } else {
            // 对第一次输入立即执行
            timeout = setTimeout(()=>{
            fn.apply(this, args);  // this指向function
        }, delay)
        }
        
    }
}

二、节流

function throttle(fn, time) {
  let pre = 0;
  let timeout = null;
  return function (...args) {
    const now = Date.now();
    // 如果时间超过了时间间隔,立即执行函数
    if (now - pre > time) {
      pre = now;
      fn.apply(this, args);
    } else {
      // 如果时间没有超过时间间隔,取消后续的定时器任务
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      // 最后一次事件的触发
      timeout = setTimeout(() => {
        pre = now;
        fn.apply(this, args);
      }, time);
    }
  };
}

三、instanceOf

判断数据类型的方法:typeof、 instanceOf、 Object.prototype.tostring.call()

作用:运算符,可以判断一个对象的类型

原理:原型和原型链

function myInstanceOf(obj, constructor) {
// obj=实例对象,constructor=实例对象的构造函数
  let proto = obj.__proto__;
  while (true) {  // 遍历原型链
    if (proto === null) {  
      return false;
    }
    if (proto === constructor.prototype) {
      return true;
    }
    proto = proto.__proto__;
  }
}

四、call

思路:1、判断是否是函数调用,若非函数调用抛异常;2、通过新对象(context)来调用函数:给context创建一个fn设置为需要调用的函数,结束调用完之后删除fn

// 初级写法
Function.prototype.myCall = function (ctx, ...args) {
  // ctx=上下文
  // 将方法挂载到传入的上下文ctx
  ctx.fn = this;
  // 将挂载以后的方法调用
  ctx.fn(...args);
  // 将添加的属性删除
  delete ctx.fn;
};

// 完整写法
Function.prototype.myCall = function (obj) {
  // obj是this的指向,后面还可以有多个参数即函数本身的参数
  var obj = obj || window;
  obj.p = this; // 为形参定义一个方法p,并把this给这个函数
  var newArguments = []; // 函数参数本身是没有this的,需要把所有参数另外保存起来,并且不保留第一个this参数,使参数回归到正常的序号
  // 获取函数的参数需要用到arguments对象
  for (var i = 1; i < arguments.length; i++) {
    newArguments.push("arguments[" + i + "]"); // 用字符串拼接参数
  }
  var result = eval("obj.p(" + newArguments + ")"); // 用eval可以执行到引号里的参数
  delete obj.p; // 要将p方法删除掉,因为不能改写对象
  return result;
};

 五、apply

思路:思路:1、判断是否是函数调用,若非函数调用抛异常;2、通过新对象(context)来调用函数:给context创建一个fn设置为需要调用的函数,结束调用完之后删除fn

Function.prototype.myApply = function (obj, arr) {
  var obj = obj || window;
  obj.p = this;
  if (!arr) {
    // 如果执行myApply的时候没有输入参数arr,那么就直接执行方法p,不用考虑参数问题
    obj.p();
  } else {
    // 有传入参数arr,执行就跟call一样了
    var newArguments = [];
    for (var i = 1; i < arguments.length; i++) {
      newArguments.push("arguments[" + i + "]");
    }
    var result = eval("obj.p(" + newArguments + ")");
  }
  delete obj.p;
  return result;
};

六、bind 

思路:

处理边界:如果常用参数上下文context不存在的话,需要将其指向window

创造唯一的key,作为构造context内部方法名

将调用函数挂载到this指向的对象(即context)的fn属性上:

Function.prototype.myBind = function (obj) {
  if (typeof this !== "function") {
    throw new TypeError("wrong");
  }
  var that = this;
  var arr = Array.prototype.slice.call(arguments, 1);
  var o = function () {};
  newf = function () {
    var arr2 = Array.prototype.slice.call(arguments);
    var arr = arr.concat(arr2);
    if (this instanceof o) {
      that.apply(this, arrSum);
    } else {
      that.apply(obj, arrSum);
    }
  };
  o.prototype = that.prototype;
  newf.prototype = new o();
  return newf;
};

七、对象深拷贝

    // 简易版  
    function deepClone(o) {
      let obj = {}
      for (var i in o) {
        // if(o.hasOwnProperty(i)){
        if (typeof o[i] === "object") {
          obj[i] = deepClone(o[i])
        } else {
          obj[i] = o[i]
        }
        // }
      }
      return obj
    }

八、手写new

new发生的事:

  • 创建一个新对象
  • 使新对象的__proto__指向原函数的prototype
  • 改变this指向(指向新的obj)并执行该函数,执行结果保存起来作为result
  • 判断执行函数的结果是不是null或Undefined,如果是则返回之前的新对象,如果不是则返回result
    // 手写一个new
    function myNew(fn, ...args) {
      // 创建一个空对象
      let obj = {}
      // 使空对象的隐式原型指向原函数的显式原型
      obj.__proto__ = fn.prototype
      // this指向obj
      let result = fn.apply(obj, args)
      // 返回
      return result instanceof Object ? result : obj
    }

九、手写Ajax

Ajax的作用:浏览器输入网页后向服务器获取信息,当浏览器渲染网页资源后,还想获得更多的信息,页面又要重新渲染,为了页面不重新渲染

var Ajax = {
      get: function (url, callback) {
        // js没有与网络沟通的能力,需要创建由浏览器提供的xhr
        let xhr = XMLHttpRequest(); 
        // 定义请求
        xhr.open("get", url, false)  // 请求类型,URL,是否异步
        // 监听readyState属性的变化
        xhr.onreadystatechange = function () {
          if (xhr.readyState == 4) { // 为4则表明接收到所有相应
            // 接收到所有相应并不代表成功接收了文件,文件不存在时也属于收到响应,所以需要通过状态码来判断
            if (xhr.status == 200 || xhr.status == 304) {
              // 如果成功接收文件,就将页面内容更改为接收到的文件的对应的内容
              console.log(xhr.responseText);  // 响应返回的文本
              callback(xhr.responseText)
            }
          }
        }
        xhr.send() // 发送请求
      },



      post: function (url, data, callback) {
        let xhr = new XMLHttpRequest()
        // 第三个参数为是否异步执行
        xhr.open('post', url, true)
        // 添加http头,设置编码类型
        xhr.setRequestHeader("Content-type","x-www-form-urlencoded")
        xhr.onreadystatechange = function () {
          if(xhr.readyState == 4) {
            if(xhr.status == 200 || xhr.status == 304) {
              console.log(xhr.responseText);
              callback(xhr.responseText)
            }
          }
        }
        xhr.setRequestHeader('Content-type', "application/x-www-urlencoded")
        xhr.send(data)
      }
    }

promise封装Ajax

function promiseAjax() {
  let promise = new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("get", url, true);
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if ((xhr.status === 200 && xhr.status < 300) || xhr.status === 304) {
          resolve(xhr.responseText);
        } else {
          reject(new Error(xhr.statusText));
        }
      }
    };
    xhr.send(null);
  });
  return promise;
}

十、数组扁平化

将多维数组转换成一维数组

方法一:ES6语法 ==》flat  

语法:数组.flat(需要拉平的层数 / Infinity)

如果原数组有空位,flat()方法会跳过空位。该方法返回一个新的数组,对原数据没有影响。

let arr = [1, 2, [3, [4, 5]]];

arr.flat()   // 默认拉平一层  [1, 2, 3, [4, 5]]
arr.flat(2)  // [1, 2, 3, 4, 5]

不知道具体有几层,但是就想让多维数组转换成一维,可以用Infinity关键字作为参数
arr..flat(Infinity)

【拓展】数组.flatMap(需要执行的函数)

  • 对原数组的每个成员执行一个函数,相当于执行Array.prototype.map(),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。
  • flatMap()只能展开一层数组
[2, 3, 4].flatMap((x) => [x, x * 2])
// 相当于 [[2, 4], [3, 6], [4, 8]].flat()
// [2, 4, 3, 6, 4, 8]

方法二:数组方法 ==》join + split   

将数组转换成字符串,再将字符串切割成数组

let arr = [1, 2, [3, [4, 5]]];

arr.join(',').split(',').map(Number)

方法三:数组方法 ==》toString() + split() 

return arr.toString().split(',').map(item => Number(item));

方法四:数组方法 ==》reduce

return arr.reduce((target, item) => {
    return target.concat(Array.isArray(item) ? flatten(item) : item);
    }, [])

方法五:ES6语法 ==》扩展运算符

while(arr.some(item => Array.isArray(item))){
     arr = [].concat(...arr);
}
return arr;

方法六:递归

let res = [];
arr.forEach(item => {
if (Array.isArray(item)) {
    res = res.concat(flatten(item))
    } else {
         res.push(item);
           }
    });
return res;

十一、手写promise(A+版)

思路:

  1. 初始化:包括3个状态、2个方法、1个then
  2. this指向:因为实例化问题,所以要用bind修改一下this指向,否则拿不到value
  3. 状态跳转:当状态为pending时可以跳转到resolve | reject,但是resolve | reject不能跳回pending,而且resolve和reject之间不能相互跳转
  4. then的功能:有2个参数,分别是2个方法,如果resolve就将value传给onFullfilled方法,如果reject就将reason传给onReject方法,如果是pending状态,
  5. 完善功能:①promise的异步:利用setTimeout;②promise的多重then回调:then直接return本promise,就可以链式拿到value;③promise传入的参数不是函数时:加入if判断传入的参数是否是函数,如果是函数就不用管,如果不是则将参数强行转换成空函数,这样控制台就不会报错了;④promise里参数是error:用try...catch捕获error给reject

无注释的代码:

class myPromise {
  static PENDING = "pending"; 
  static FULLFILLED = "fullfilled"; 
  static REJECTED = "rejected"; 
 
  constructor(executor) {
    this.status = myPromise.PENDING; 
    this.value = null; 
    this.resolveCallbacks = [];
    this.rejectCallbacks = [];
   
    try {
    
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(error);  
    }
  }
 
  resolve(value) {
    if (this.status === myPromise.PENDING) {
      this.status = myPromise.FULLFILLED;
      this.value = value;
      setTimeout(() => {
        this.resolveCallbacks.map((callback) => {
          callback.onFullfilled(this.value);
        });
      });
    }
  }

  reject(reason) {
    if (this.status === myPromise.PENDING) {
      this.status = myPromise.REJECTED;
      this.value = reason;
   
      setTimeout(() => {
        this.rejectCallbacks.map((callback) => {
          callback.onFullfilled(this.value);
        });
      });
    }
  }

  then(onFullfilled, onRejected) {
    if (typeof onFullfilled !== "function") {
      onFullfilled = () => this.value;
    }

    if (typeof onRejected !== "function") {
      onRejected = () => this.value;
    }
    
    return new myPromise((mResolve, mReject) => {
      if (this.status === myPromise.PENDING) {
        this.callbacks.push({
          onFullfilled: (value) => {
            let result = onFullfilled(this.value);
            mResolve(result);
          },
          onRejected: (reason) => {
            let result = onRejected(this.value);
            mReject(result);
          },
        });
      }

      if (this.status === myPromise.FULLFILLED) {
        setTimeout(() => {
          let result = onFullfilled(this.value);
          mResolve(result);
        });
      }

      if (this.status === myPromise.REJECTED) {
        setTimeout(() => {
          let result = onRejected(this.value);
          mReject(result);
        });
      }
    });
  }
}

带有完整注释的代码:

// 自定义promise库
// 因为原生promise对象是new Promise((resolve, reject)->{}),可以用class类来写
class myPromise {
  // promise有3个状态:pending,fullfilled,rejected
  // 将promise的3个状态定义成静态属性
  static PENDING = "pending"; // 开始状态
  static FULLFILLED = "fullfilled"; // 成功状态
  static REJECTED = "rejected"; // 失败状态
  // 参数:executor执行者
  constructor(executor) {
    // promise有2个属性,将状态属性定义成静态的
    this.status = myPromise.PENDING; // 刚开始promise的状态是开始状态pending
    this.value = null; //promise的返回值,设为空
    // 构造缓存队列,用于存储onFullfilled, onRejected
    this.resolveCallbacks = [];
    this.rejectCallbacks = [];
    // 如果promise里丢出一个error,就将error给reject,没有error就按计划执行resolve、reject
    try {
      // 用executor调用方法
      executor(this.resolve.bind(this), this.reject.bind(this)); // es6方法是严格模式,通过this是访问不到的,所以要用bind来控制this指向this。也就是让resolve、reject在class里实例化一下
    } catch (error) {
      this.reject(error);  // 这里不需要用bind,因为是直接执行,而不是创建实例后执行
    }
  }
  // promise有3个方法
  // 执行回调方法(resolve, reject) => {}
  // 成功的回调,作用:1.通知promise修改状态:pending->fullfilled;2.返回值value
  resolve(value) {
    // 参数:希望返回的值value
    // 状态从pending更改到fullfilled或rejected后,是不能再改动的
    // 所以需要判断,当状态为pending时才可以发生状态改变,否则不能改变状态
    if (this.status === myPromise.PENDING) {
      this.status = myPromise.FULLFILLED;
      this.value = value;
      // promise里有异步任务resolve和同步任务时,将他们区分开成为异步任务,需要用setTimeout,否则在promise里都是自上而下执行的同步任务
      setTimeout(() => {
        this.resolveCallbacks.map((callback) => {
          // 遍历所有callback,将resolve的返回值给onFullfilled
          callback.onFullfilled(this.value);
        });
      });
    }
  }
  // 失败的回调,作用:1.通知promise修改状态:fullfilled->rejected;2.返回当前失败的原因
  reject(reason) {
    // 参数:失败的原因reason
    // 状态从pending更改到fullfilled或rejected后,是不能再改动的
    // 所以需要判断,当状态为pending时才可以发生状态改变,否则不能改变状态
    if (this.status === myPromise.PENDING) {
      this.status = myPromise.REJECTED;
      this.value = reason;
      // promise里有异步任务resolve和同步任务时,将他们区分开成为异步任务,需要用setTimeout,否则在promise里都是自上而下执行的同步任务
      setTimeout(() => {
        this.rejectCallbacks.map((callback) => {
          // 遍历所有callback,将resolve的返回值给onFullfilled
          callback.onFullfilled(this.value);
        });
      });
    }
  }
  // promise.then()是微任务
  then(onFullfilled, onRejected) {
    // 固定参数,是2个方法。原生promise里就是2个方法,一个是成功,一个是失败
    // 当resolve方法调用时,会将返回值传给onFullfilled
    // 当reject方法调用时,会将返回值传给onRejected

    // 原生promise里规定,如果传入的2个参数不是函数,就要被忽略
    // then里可以传null或者为空 =》promise.then(null) 或者 promise.then()
    // 为了使我们的方法也能做到这一点,需要将传入的参数(传入的参数不是函数)强行转换为函数,这样就不会报错了
    if (typeof onFullfilled !== "function") {
      
      //   onFullfilled = () => {};
      onFullfilled = () => this.value;
    }
    if (typeof onRejected !== "function") {
      //   onRejected = () => {};
      onRejected = () => this.value;
    }
    // 当promise.then().then(...)时,中间的.then().是不会影响value的传递的,所以要解决then的穿透问题。所以then要重新返回一个新的promise去拿到value
    return new myPromise((mResolve, mReject) => {
      if (this.status === myPromise.PENDING) {
        // 缓存onFullfilled, onRejected,当setTimeout到时间时调用resolve时再执行缓存的方法
        this.callbacks.push({
          // 为了完成promise的链式调用
          onFullfilled: (value) => {
            let result = onFullfilled(this.value);
            mResolve(result);
          },
          onRejected: (reason) => {
            let result = onRejected(this.value);
            mReject(result);
          },
        });
      }
      // 当状态是fullfilled时,调用onFullfilled方法,将resolve的返回值传给onFullfilled方法
      if (this.status === myPromise.FULLFILLED) {
        // 由于promise.then()是微任务,如果只写onFullfilled(this.value)就不是微任务,程序没有异步,而是从上至下执行了,所以需要进行异步操作
        setTimeout(() => {
          // onFullfilled(this.value);
          let result = onFullfilled(this.value);
          mResolve(result);
        });
      }
      // 当状态是rejected时,调用onRejected方法,将reject的返回值传给onRejected方法
      if (this.status === myPromise.REJECTED) {
        // 由于promise.then()是微任务,如果只写onRejected(this.value)就不是微任务,程序没有异步,而是从上至下执行了,所以需要进行异步操作
        setTimeout(() => {
          // onRejected(this.value);
          let result = onRejected(this.value);
          mReject(result);
        });
      }
    });
  }
}

Promise.all(arr)

、、、

  // all是静态方法,接收一个参数:数组arr,返回一个promise对象
  static all(arr) {
    let result = []; // 结果数组,用来存放输出结果
    let index = 0; // 目的是判断是否遍历完所有的arr元素,因为arr里有promise对象时,是会出现异步操作的,但是循环arr是瞬间完成的,当循环完成后调用resolve时,不一定循环所有的元素,比如有些promise对象因为异步就没被循环到

    return myPromise((resolve, reject) => {
      // 定义一个方法,作用:将arr循环的结果放入result数组中
      function addData(key, value) {
        result[key] = value;
        index++; // 每放一个结果进result,index自加1
        // 判断index是否等于arr的长度,等于则表明已经循环了所有元素并且循环完毕了
        if (index === arr.length) {
          // 循环结束后调用resolve方法,将结果数组result返回
          resolve(result); // 因为resolve,所以这个方法得放进myPromise里
        }
      }

      // 循环传入的数组arr,如果arr里的元素是普通值(如:字符串),就直接放进result中,如果是promise对象,则先执行该promise对象,再将promise对象的输出放入result数组里
      for (let i = 0; i < arr.length; i++) {
        // 判断当前元素是否myPromise下的一个实例,如果是,则该元素是一个promise对象,如果不是,则该元素是一个普通值
        if (arr[i] instanceof myPromise) {
          // promise对象
          // 拿到当前元素,执行其then方法,传递成功回调和失败回调,如果成功,则将value传给result,如果失败则直接调用reject()并传入失败原因reason
          arr[i].then(
            (value) => {
              addData(i, value);
            },
            (reason) => {
              reject(reason);
            }
          );
        } else {
          // 普通值
          addData(i, arr[i]); // 将普通值放入result数组中
        }
      }
      // 循环结束后调用resolve方法,将结果数组result返回
      //   resolve(result);
    });
  }
}

Promise.race(arr)

静态方法,需要一个数组arr作为参数,返回值是一个promise。promise的状态和结果,由arr中最快得到结果决定。arr中的普通值被视为成功的promise

 static race(arr) {
    return new myPromise((resolve, reject) => {
      // 遍历,看看谁最快
      arr.forEach((item) => {
        if (item instanceof myPromise) {
          // 如果该元素是promise对象,返回结果就是该promise的结果
          item.then(resolve, reject);
        } else {
          // 如果该元素是普通值,返回的是成功的promise
          // resolve(item);  // 不能直接这么写,因为元素如果是promise对象的话,输出返回值还包裹了一层then,就是异步,所以元素为普通值时也需要用一个异步包裹一下
          queueMicrotask(() => {   // queueMicrotask执行微任务
            resolve(item);
          });
        }
      });
    });
  }

Promise.resolve(value)

作用:将给定的值转换成promise对象,resolve方法的返回值就是promise对象

在resolve方法内部,会创建一个promise对象,并将传给resolve方法的值包裹在这个promise对象里,并将创建出来的promise对象作为resolve返回值,正因为这样,才能在resolve方法后链式调用then方法,通过then方法的成功回调,拿到传给resolve方法的值

如果传给resolve方法的是一个promise对象,则将这个promise对象原封不动的作为链式调用then的成功回调,返回这个promise的值

、、、

  static resolve(value) {
    // 判断value是否是myPromise对象的一个实例,如果是则value是promise对象,如果不是则value是一个普通值
    if (value instanceof myPromise) {
      // 是promise对象,直接返回
      return value;
    } else {
      // 是普通值,创建promise对象,传递一个执行器,在执行器中拿到resolve方法,利用resolve方法将value返回
      return new myPromise((resolve) => {
        resolve(value);
      });
    }
  }

Promise.reject()

静态方法,返回一个promise对象,不管传入的值是什么,都会被包裹成为失败的promise对象。

  static reject(value) {
    return new Promise((resolve, reject) => {
      reject(value);  // 将传入的value作为失败的原因reason
    });
  }

Promise.finally(callback)

特点:

  • 无论promise对象最终的状态是成功还是失败,finally这个方法都会被调用一次
  • 在finally方法后面,可以链式调用then方法,拿到当前这个promise对象最终返回的结果

finally方法不是静态方法,需要被定义在myPromise这类的原型对象上

  static resolve(value) {
    // 判断value是否是myPromise对象的一个实例,如果是则value是promise对象,如果不是则value是一个普通值
    if (value instanceof myPromise) {
      // 是promise对象,直接返回
      return value;
    } else {
      // 是普通值,创建promise对象,传递一个执行器,在执行器中拿到resolve方法,利用resolve方法将value返回
      return new myPromise((resolve) => {
        resolve(value);
      });
    }
  }

  // finally方法不是静态方法,需要被定义在myPromise这类的原型对象上
  //1、 参数为回调函数callback,因为无论当前promise对象是成功或失败都要去调用这个回调函数
  finally(callback) {
    // 调用then方法去得知当前promise对象是成功还是失败
    // 在成功与失败回调里都调用callback
    // 2、在finally方法后面可以链式调用then方法,得到当前promise状态
    // 链式调用then方法,finally方法最终也是要返回一个promise对象,而在finally内部调用了then,then返回的就是promise对象,所以可以将内部调用的then返回return出去
    return this.then(
      (value) => {
        // finally方法中是可以return一个promise对象的
        // 如果return了一个promise对象,需要等promise对象执行完毕了再return value,这是一个异步操作,而不是马上return value
        return myPromise.resolve(callback()).then(() => value); // 不管传入的是普通值,还是promise对象,都将其转换成promise对象
        // callback();
        // return value; // return这个value,下一个then方法才能拿到这个value
      },
      (reason) => {
        return myPromise.resolve(callback()).then(() => {
          throw reason;
        });
        // callback();
        // throw reason; // 失败状态的原因reason用throw传递出去
      }
    );
  }

Promise.catch()

作用:处理当前这个promise对象最终状态为失败的情况,意思是当调用then方法时,可以不传递失败回调,如果不传递失败回调,这个失败回调就会被catch方法捕获,从而去执行catch方法中的回调函数

实现:在catch方法内部调用then方法,在成功的回调undefined,在失败的回调传递一个回调函数

原型方法

  catch(failCallback) {
    // 为了使catch方法后面可以链式调用then方法,需要将其return出去
    return this.then(undefined, failCallback);
  }

十二、函数柯里化

应用一:参数复用;

应用二:兼容性检测:有的函数和方法是不能被浏览器支持的,如事件监听,去判断这个方法是否被浏览器支持;

应用三:延迟执行

// 应用:延迟执行

function add() {
  // 因为传入的参数数量不确定,这里就不设置参数了
  let args = Array.prototype.slice.call(arguments); // 把保存参数的arguments赋给args,但是arguments是对象不是数组,不能用数组的方法,所以要将对象转化成数组
  let inner = function () {
    // 内部函数,实际是接收第二次传入的参数,即add(1)(2)(3)...的第2个括号
    args.push(...arguments); // 将第2个括号的参数加入到第1个括号的参数内
    return inner;
  }; // 递归,就可以多次调用自己
  inner.toString = function () {
    return args.reduce(function (prev, cur) {
      return prev + cur;
    });
  };

  return inner; // 返回内部函数
}

十三、setTimeout实现setInterval

function myInterval(func, time) {
  //func=函数,time=延迟时间
  function inside() {
    func();
    setTimeout(inside, time);
  }
  setTimeout(inside, time);
}

清除setTimeout实现的setInterval

// 定义一个全局变量timer
timer = null;
function mockSetInterval(fn, delay) {
  const inside = () => {
    fn();
    timer = setTimeout(inside, delay);
  };
  timer = setTimeout(inside, delay);
}

//清除定时器
timer && clearTimeout(timer);

十四、JS实现拖拽

并不是所有元素都可以拖拽,但是图片是可拖拽的

应用:验证码登录验证

方式一:html5新增的draggable属性打开(draggable = "true"),再用js进行操作(document.addEventListener("拖拽事件名", (e) => {事件的具体操作}, false))

方式二: 鼠标事件,将拖拽分为三步:鼠标按下mousedown、鼠标移动mousemove、鼠标松开mouseup

 十五、手写数组转树

function transTree(data) {
    let result = []
    let map = {}
    if (!Array.isArray(data)) {//验证data是不是数组类型
        return []
    }
    data.forEach(item => {//建立每个数组元素id和该对象的关系
        map[item.id] = item //这里可以理解为浅拷贝,共享引用
    })
    data.forEach(item => {
        let parent = map[item.parentId] //找到data中每一项item的爸爸
        if (parent) {//说明元素有爸爸,把元素放在爸爸的children下面
            (parent.children || (parent.children = [])).push(item)
        } else {//说明元素没有爸爸,是根节点,把节点push到最终结果中
            result.push(item) //item是对象的引用
        }
    })
    return result //数组里的对象和data是共享的
}
console.log(JSON.stringify(transTree(data)))

十六、获取url所有参数

思路:

  1. 获取url参数部分
  2. 声明一个保存参数的对象
  3. 将参数分割成数组保存
  4. 遍历每对参数,再获取键值对,存储到保存参数的对象里
  5. 注意!对中文解码
function getURLParams() {
  // 1、获取url参数部分
  // window.location.search 是 JavaScript 中的一个属性,它表示当前页面的 URL 中的查询字符串部分
  let params = location.search.length > 0 ? location.search.substring(1) : ""; // 看url是否存在,如果存在就切割?后面的部分,如果不存在就置空

  //   2、声明一个保存参数的对象
  let obj = {};
  // 3、将每对参数进行分割成数组保存
  let items = params.length ? params.split("&") : []; // 参数都是以&拼接的,所以分割的时候以&分割,如果没有参数items就是空数组
  let item = "",
    key = "",
    value = ""; // 定义每个参数、键值
  let len = items.length;

  // 4、遍历每对参数,取键值对,保存在obj里,注意对中文参数解码decodeURIComponent
  for (let i = 0; i < len; i++) {
    item = items[i].split("=");
    key = decodeURIComponent(item[0]);
    value = decodeURIComponent(item[1]);

    // 保存到obj,有键值对才需要保存
    if (key.length) {
      obj[key] = value;
    }
  }
  return obj;
}



测试:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./10-获取所有url参数.js"></script>
  </head>
  <body>
    <script>
      console.log(getURLParams());
    </script>
  </body>
</html>

十七、手写一个发布-订阅模式

发布-订阅模式是什么 =》

思路:

  1. 声明一个事件变量对象存储消息事件
  2. 定义添加方法,将事件添加到事件变量
  3. 定义删除方法,将事件从事件变量里删除
  4. 定义派发方法,调用时间变量中的事件

基本框架:

class EventSubscription {
  constructor() {
    // 1、定义事件变量对象
    this.eventObject = {};
  }
  // 2、定义事件添加方法
  addEventFun() {}
  // 3、定义事件删除方法
  deleteEventFun() {}
  // 4、定义事件派发方法
  emitEventFun() {}
}

方法的具体实现:

class EventSubscription {
  constructor() {
    // 1、定义事件变量对象
    this.eventObject = {};
  }

  // 2、定义事件添加方法
  // 添加消息事件是需要一个事件名的,以及消息事件的事件回调函数
  // 需要将消息事件添加到事件变量中,并且不止一个变量
  // 添加事件变量时需要判断是否存在当前事件,做不同的处理
  addEventFun(name, callback) {
    // 判断事件变量中是否有当前事件, 如果没有的话初始化一个空数组
    if (!this.eventVarObject[name]) {
      this.eventVarObject[name] = [];
    }
    // 如果存在,那就是说明该事件多个订阅者,继续往后push
    this.eventVarObject[name].push(callback);
  }

  // 3、定义事件删除方法
  // 删除消息事件是需要一个事件名的,以及消息事件的事件回调函数
  // 事件变量中,对应的事件回调函数不止一个,需要对符合条件的删除,仅删除这个callback
  // 如果事件回调函数不存在的话,直接删除事件
  deleteEventFun() {
    // 事件回调函数不存在,删除相应整个事件
    if (!callback) {
      delete this.eventVarObject[name];
      return;
    }

    // 需要对符合条件的删除
    const index = this.eventVarObject[name].findIndex(
      (item) => item == callback
    );
    this.eventVarObject[name].splice(index, 1);
  }

  // 4、定义事件派发方法
  // 事件派发方法只需要事件名,确定是哪个事件,也可以传递参数
  // 将事件变量中,对应的事件回调函数依次执行
  emitEventFun() {
    // 确认事件是否订阅
    if (!this.eventVarObject[name]) return;

    // 依次执行事件回调函数
    this.eventVarObject[name].forEach((callback) => {
      callback(...data);
    });
  }
}

十八、Proxy 实现观察者模式

观察者模式(Observer mode):指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行

思路:实现observableobserve这两个函数,observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数

// 观察者函数都放进Set里
const queuedObservers = new Set();  

// 观察者函数
const observe = fn => queuedObservers.add(fn);

// 返回原始对象的代理
const observable = obj => new Proxy(obj, {set});

// 拦截函数,自动执行所有的观察者
function set(target, key, value, receiver) {
  // 设置target对象的key属性等于value
  const result = Reflect.set(target, key, value, receiver);
  // 自动执行所有观察者函数
  queuedObservers.forEach(observer => observer());
  return result;
}

十九、实现箭头函数的this指向

function func1() {
  // this.val1的this指向func1
  () => {
    this.val1;
  };

  const that = this;  // 这里that和箭头函数同级,拿到的this都是func1的this
  function func2() {
    // 要想在func2内部拿到箭头函数里的val1,如何实现?
    that.val1;  // 就可以通过that拿到箭头函数里的val1
  }
}

二十、修改this指向

封装函数 f,使 f 的 this 指向指定的对象

function bindThis(f, oTarget) {
 return function() {
     return f.apply(oTarget, arguments)
 }
}


function bindThis(f, oTarget) {
 return function() {
     return f.call(oTarget, ...arguments)
 }
}



function bindThis(f, oTarget) {
 return f.bind(oTarget)
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值