来啊JS,撕裂你!

336 阅读9分钟

手撕call

Function.prototype.call = function(context = window, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Type Error');
}
const fn = Symbol('fn'); 
context[fn] = this; // 将调用call的函数作为传入的执行上下文的一个属性
const res = context[fn](...args); //调用这个函数并且传入call的第二个参数也就是args
delete context[fn];  //删除这个属性
return res; //如果有返回值就直接返回
}

手撕apply

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; 
}

手撕bind

1.改变函数的this指向但是不立即执行而是返回一个函数让程序员自己调用,

2.将第一次传递的参数与之后传递的参数合并作为函数的参数统一传递给函数

3.当将bind返回的函数作为构造函数时返回的函数中的this指向构造函数创建的实例,否则指向指定的执行上下文

4.当传入的this不是函数时直接报错。

Function.prototype.bind2 = function (context, ...args1) {
  if (typeof this !== "function") {
    throw new Error("error");
  }
  let self = this;

  let fBound = function (...args2) {
    self.apply(this instanceof fBound ? this : context, [...args1, ...args2]);
  };
  return fBound;
};

防抖

事件被触发的n秒后执行回调,如果n秒内事件再次被触发则重新计时

  • 应用场景

    • search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
    • window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
function debounce(fn, deplay) {
  let timer;
  return function (...args) {
    if (timer) clearTimeout(timer);
    let context = this;
    timer = setTimeout(function () {
      fn.apply(context,args);
    },deplay);
  };
}

节流

持续触发事件,每隔一段时间,只触发一次回调

  • 应用场景

    • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
    • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
function throttle(fn, deplay) {
  let previous = 0;
  return function (...args) {
    let now = Date.now();
    let context = this;
    if (now - previous > deplay) {
      fn.apply(context, deplay);
      previous = now;
    }
  };
}

new的内部原理

new的内部具体做了如下几件事

  1. fn.prototype为原型创建一个对象。

2.将传入的构造函数的this指向实例并向内传递参数

3.判断构造函数返回的是对象还是基本类型,如果是对象就返回对象,如果不是则返回实例

function _new(context, ...args) {
  if (typeof context !== 'function') { throw new TypeError('Type Error'); }
  const obj = Object.create(context.prototype);
  let res = context.apply(obj, args);
  return (typeof res === 'object' || typeof res === 'function') && res !== null ? res : obj;
}

instanceof内部原理

function instance(leftValue, rightValue) {
  let rightProto = rightProto.prototype; `/获取右边变量原型`
  while (true) {
    if (!leftValue) return false; `//如果左边变量原型为返回null,直接返回false`
    if (leftValue === rightProto) return true; ` //如果右边变量的原型在左边变量的原型链上,返回true`
    leftValue = Object.getPrototypeOf(leftValue);`如果不等于,继续循环`
  }
}

数组扁平化

数组的扁平化,就是将一个嵌套多层的数组 array (嵌套可以是任何层数)转换为只有一层的数组。

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

函数柯里化

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

function currying(fn, ...args) {
  if (fn.length > args.length) {
    return (...newArgs) => {
      currying(fn, ...args, ...newArgs);
    };
  } else {
    return fn(...args);
  }
}

基于Promise封装Ajax

function ajax(url, method) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open(url, method);
    xhr.onreadystatechange = () => {
      if (xhr.readyState !== 4) return
      if (xhr.status === 200 || xhr.status === 304) {
          resolve(xhr.responseText);
        }  else {
          reject(new Error(xhr.responseText));
        }
    };
    xhr.send();
  });
}

寄生组合继承

function Parent(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
Parent.prototype.getName = function () {
  return this.name;
};

function Child(name, age) {
  Parent.call(this, name); // 调用父类的构造函数,将父类构造函数内的this指向子类的实例
  this.age = age;
}

//寄生组合式继承
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child

浅拷贝

function shallowClone(obj) {
  if (typeof obj !== "object") return;
  let newObj = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = obj[key];
    }
  }
  return newObj;
}

深拷贝

1.JSON.parse(JSON.stringify());


JSON.parse(JSON.stringify());

缺点:

  1. 会忽略 undefined
  2. 会忽略 symbol
  3. 不能序列化函数
  4. 不能解决循环引用的对象
  5. 不能正确处理new Date()
  6. 不能处理正则

2.手写深拷贝 循环引用报错的根本原因是函数执行栈爆栈了,直接原因是对象的属性间接或直接的引用了自身。

解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝。

function deepClone(target, map = new Map()) {
  if (typeof target !== "object" || target === null) return target
  if (target instanceof Date) return new Date(target);
  if (target instanceof RegExp) return new RegExp(target);
  if (map.has(target)) return map.get(target);
  let newTarget = Array.isArray(target) ? [] : {};
  map.set(target, newTarget);
  for (let key in target) {
    if (target.hasOwnProperty(key)) {
      newTarget[key] =
        typeof target[key] === "object" && target[key] !== null
          ? deepClone(target[key], map)
          : target[key];
    }
  }
  return newTarget
}

实现一个 JSON.parse

function jsonParse(opt) {
  return eval('(' + opt + ')');
}
或
function ParseJsonTwo(opt) { 
return new Function("return " + opt)(); 
}

模拟实现Object.create

function create(target) {
  if (typeof target !== "object" || target === null) {
    throw new Error("error");
  }
  let O = function () {};
  O.prototype = target.prototype;
  return new O();
}

实现一个trim方法

function trim(str) {
    return str.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
}

实现千位数分隔符

function numFormat(num){
    num=num.toString().split(".");  // 分隔小数点
    var arr=num[0].split("").reverse();  // 转换成字符数组并且倒序排列
    var res=[];
    for(var i=0,len=arr.length;i<len;i++){
      if(i%3===0&&i!==0){
         res.push(",");   // 添加分隔符
      }
      res.push(arr[i]);
    }
    res.reverse(); // 再次倒序成为正确的顺序
    if(num[1]){  // 如果有小数的话添加小数部分
      res=res.join("").concat("."+num[1]);
    }else{
      res=res.join("");
    }
    return res;
}

或者

var a = 1234567894532;
var b = 673439.4542;
console.log(a.toLocaleString()); // "1,234,567,894,532"
console.log(b.toLocaleString()); // "673,439.454" (小数部分四舍五入了)

图片懒加载

参考文章

class LazyImage {
  constructor(selector) {
    this.lazyimages = document.querySelectorAll(selector);
    this.init();
  }
  throttle(fn, deplay = 15) {
    let previous = 0;
    return function (...args) {
      let now = Date.now();
      let context = this;
      if (now - previous > deplay) {
        fn.apply(context, args);
        previous = now;
      }
    };
  }
  inViewShow() {
    let len = this.lazyimages.length;
    for (let i = 0; i < len; i++) {
      let lazyImage = this.lazyimages[i];
      const rect = lazyImage.getBoundingClientRect();
      if (rect.top < document.documentElement.clientHeight) {
        lazyImage.src = lazyImage.dataset.src;
        this.lazyimages.splice(i, 1);
        i--;
        len--;
      }
      if (this.lazyimages.length === 0) {
        document.removeEventListener("scroll", this.throttle(this.inViewShow));
      }
    }
  }
  init() {
    if ("IntersectionObserver" in window) {
      let lazyImageObersver = new IntersectionObserver((entries, observe) => {
        entries.forEach((entry, index) => {
          if (entry.isIntersection) {
            let entryDom = entry.target;
            entryDom.src = entryDom.dataset.src;
            lazyImageObersver.unobserve(entryDom);
            this.lazyimages.splice(index, 1);
          }
        });
      });
      this.lazyimages.forEach((item) => {
        lazyImageObersver.observe(item);
      });
    } else {
      this.inViewShow();
      document.addEventListener("scroll", this.throttle(this.inViewShow));
    }
  }
}

发布订阅

参考文章

class msgCenter {
  constructor() {
    this.message = {};
  }
  on(type, fn) {
    if (this.message[type]) {
      this.message[type].push(fn);
    } else {
      this.message[type] = [fn];
    }
  }
  emit(type, args) {
    if (!this.message[type]) {
      return false;
    }
    let event = {
      type,
      args: args || {}
    };
    for (let i = 0; i < this.message[type].length; i++) {
      this.message[type][i](event);
    }
  }
  cancel(type, fn) {
    if (!this.message[type]) {
      return false;
    }
    for (let i = 0; i < this.message.length; i++) {
      if (this.message[type][i] === fn) {
        this.message[type].splice(i, 1);
        break;
      }
    }
  }
}

测试数据

function Person() {
  this.alreadyRegister = {};
}

Person.prototype.on= function (type, fn) {
  if (this.alreadyRegister[type]) {
    console.log("已订阅过了");
  } else {
    msgCenter.on(type, fn);
    this.alreadyRegister[type] = fn;
  }
};

Person.prototype.cancel = function (type, fn) {
  msgCenter.cancel(type, this.alreadyRegister[type]);
  delete this.alreadyRegister[type];
};

var person1 = new Person();
var person2 = new Person();
var person3 = new Person();

person1.on("carInfo", function (e) {
  console.log("person1得到了" + e.type + "得消息,是:" + e.args.info);
});

person1.on("newsInfo", function (e) {
  console.log("person1得到了" + e.type + "得消息,是:" + e.args.info);
});

person2.on("carInfo", function (e) {
  console.log("person2得到了" + e.type + "得消息,是:" + e.args.info);
});

person3.on("carInfo", function (e) {
  console.log("person3得到了" + e.type + "得消息,是:" + e.args.info);
});

person3.on("newsInfo", function (e) {
  console.log("person3得到了" + e.type + "得消息,是:" + e.args.info);
});

msgCenter.emit("carInfo", { info: "新车上市" });
msgCenter.emit("newsInfo", { info: "日本地震" });

//   测试重复订阅
//   person1.on("carInfo", function (e) {
//     console.log("person1得到了" + e.type + "得消息,是:" + e.args.info);
//   });

person1.cancel("carInfo");
msgCenter.emit("carInfo", { info: "回收二手车" });

Promise.all

promise.all 的特点:

  • 入参是个由Promise实例组成的数组
  • 返回值是个promise,因为可以使用.then
  • 如果全部成功,状态变为resolved, 并且返回值组成一个数组传给回调
  • 但凡有一个失败,状态变为rejected, 并将error返回给回调
function dirPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      throw Error("promise1 must be array");
    }
    let result = [];
    let count = 0;
    promises.forEach((promise, index) => {
      promise.then(
        (res) => {
          result[count] = res;
          count++;
          count === promises.length && resolve(result);
        },
        (err) => {
          reject(err);
        }
      );
    });
  });
}

Promise.race

Promise.race 会返回一个由所有可迭代实例中第一个 fulfilled 或 rejected 的实例包装后的新实例。

function promiseRace(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return false;
    }
    promises.forEach((promise) => {
      promise.then((res) => resolve(res)).catch((err) => reject(err));
    });
  });
}

LRU缓存机制

由于当前考虑时间复杂度在O(1),且由于当进行缓存容量达到上限时,需删除最久未使用的数据值,因此我们需要一个有序的Hash结构,我们第一个反应便是通过Map来存储数据。这也是为什么要用Map而不是对象的原因(ps:对象key无序)。

参考文章

class LRU {
  constructor(capacity) {
    this.capacity = capacity;
    this.cache = new Map();
  }
  get(key) {
    const isHasKey = this.cache.has(key);
    if (!isHasKey) {
      return -1;
    } else {
      const val = this.cache.get(key);
      this.cache.delete(key);
      this.cache.set(key, val);
      return val;
    }
  }
  put(key, val) {
    if (this.cache.length >= this.capacity.length) {
      this.cache.delete(this.cache.keys().next().value());
    }
    const isHasKey = this.cache.has(key);
    if (isHasKey) {
      this.cache.delete(key);
    }

    this.cache.set(key, val);
  }
}

Promise红绿灯

function red() {
  console.log("red");
}

function green() {
  console.log("green");
}

function yellow() {
  console.log("yellow");
}

function sleep(asyncFunction, timeout) {
  return new Promise((resolve, reject) => {
    asyncFunction();
    setTimeout(() => {
      resolve();
    }, timeout);
  });
}

(function restart() {
  sleep(red, 3000)
    .then(() => {
      return sleep(green, 2000);
    })
    .then(() => {
      return sleep(yellow, 1000);
    })
    .then(() => {
      restart();
    });
})();

封装一个可以设置有效事件的localStorage

var Storage = {
  //设置缓存
  setItem(params){
      let obj = {
          name:'',
          value:'',
          expires:"",
          startTime:new Date().getTime()//记录何时将值存入缓存,毫秒级
      }
      let options = {};
      //将obj和传进来的params合并
      Object.assign(options,obj,params);
      if(options.expires){
      //如果options.expires设置了的话
      //以options.name为key,options为值放进去
          localStorage.setItem(options.name,JSON.stringify(options));
      }else{
      //如果options.expires没有设置,就判断一下value的类型
             let type = Object.prototype.toString.call(options.value);
             //如果value是对象或者数组对象的类型,就先用JSON.stringify转一下,再存进去
          if(Object.prototype.toString.call(options.value) == '[object Object]'){
              options.value = JSON.stringify(options.value);
          }
          if(Object.prototype.toString.call(options.value) == '[object Array]'){
              options.value = JSON.stringify(options.value);
          }
          localStorage.setItem(options.name,options.value);
      }
  },
  //拿到缓存
  getItem(name){
      let item = localStorage.getItem(name);
      //先将拿到的试着进行json转为对象的形式
      try{
          item = JSON.parse(item);
      }catch(error){
      //如果不行就不是json的字符串,就直接返回
          item = item;
      }
      //如果有startTime的值,说明设置了失效时间
      if(item.startTime){
          let date = new Date().getTime();
          //何时将值取出减去刚存入的时间,与item.expires比较,如果大于就是过期了,如果小于或等于就还没过期
          if(date - item.startTime > item.expires){
          //缓存过期,清除缓存,返回false
              localStorage.removeItem(name);
              return false;
          }else{
          //缓存未过期,返回值
              return item.value;
          }
      }else{
      //如果没有设置失效时间,直接返回值
          return item;
      }
  },
  //移出缓存
  removeItem(name){
      localStorage.removeItem(name);
  },
  //移出全部缓存
  clear(){
      localStorage.clear();
  }
}

斐波那契数列以及优化

原始斐波那契数列

效率十分低,很多值会重复求值

let fibonacci = function (n) {
  if (n === 0 || n === 1) {
    return n;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
};

原始斐波那契数列优化一

当函数进行计算之前,先看缓存对象中是否有次计算结果,如果有,就直接从缓存对象中获取结果;如果没有,就进行计算,并将结果保存到缓存对象中。

let fibnoacci = (function () {
  let memory = [];
  return function (n) {
    if (memory[n] !== undefined) {
      return memory[n];
    }
    return memory[n] = (n === 1 || n === 2) ? n : fibnoacci(n - 1) + fibnoacci(n - 2)
  }
})();

原始斐波那契数列优化二

var fibonacci = (function () {
  var memory = {};
  return function (n) {
    if (n == 0 || n == 1) {
      return n;
    }
    if (memory[n - 2] === undefined) {
      memory[n - 2] = fibonacci(n - 2);
    }
    if (memory[n - 1] === undefined) {
      memory[n - 1] = fibonacci(n - 1);
    }
    return (memory[n] = memory[n - 1] + memory[n - 2]);
  };
})();

原始斐波那契数列优化三

var fib = function(n) {
  function f(n, a = 1, b = 1) {
      if(n <= 1) return n
      if(n === 2) return b
      return f(n - 1, b, (a + b) % 1000000007)
  }
  return f(n)
};

动态规划

var fib = function(n) {
    if(n <= 1) return n
    let a=b=1, c=0
    while(n > 0) {
        a = b
        b = c
        c = (a + b) % 1000000007
        n--
    }
    return c
};

变式(青蛙跳台阶)

var fib = function(n) {
    if(n < 2) return 1
    let a= 0, b=1, c=1
    for(let i = 2; i <=n; i++) {
    a = b
    b = c
    c = (a + b) % 1000000007
    }
    return c
};

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;
}

根据ID返回路径

var getPathById = function (catalog, id, callback) {
 
  //定义变量保存当前结果路径
  var temppath = [];
  try {
      function getNodePath(node) {
          temppath.push(node.name);

          //找到符合条件的节点,通过throw终止掉递归
          if (node.id == parseInt(cataid)) {
              throw ("GOT IT!");
          }
          if (node.children && node.children.length > 0) {
              for (var i = 0; i < node.children.length; i++) {
                  getNodePath(node.children[i]);
              }

              //当前节点的子节点遍历完依旧没找到,则删除路径中的该节点
              temppath.pop();
          }
          else {

              //找到叶子节点时,删除路径当中的该叶子节点
              temppath.pop();
          }
      }
      getNodePath(catalog);
  }
  catch (e) {
      var result = temppath.join("->");
      callback(result);
  }

树的前中后序遍历

前序遍历

递归实现

function preOrder(root) {
  if (!root) return;
  console.log(root.val);
  preOrder(root.left);
  preOrder(root.right);
}

非递归实现

function preOrder(root) {
  if (!root) return;
  const stack = [root];
  while (stack.length) {
    const node = stack.pop();
    if (node.right) stack.push(node.right);
    if (node.left) stack.push(node.left);
    console.log(node.val);
  }
}

中序遍历

递归实现

function inOrder(root) {
  if (!root) return;
  inOrder(root.left);
  console.log(root.val);
  inOrder(root.right);
}

非递归实现

const inorder = root => {
  // 结点是空的,直接返回
  if (!root) return;

  // 定义一个指针指向root
  let p = root;
  // 定义一个栈
  const stack = [];

  // 这里地方要|| p是因为不加的话,最开始它就不跑进来了
  while(stack.length || p) {
      // 只要p不为空,就一直去找左节点,压栈
      while(p) {
          stack.push(p)
          p = p.left;
      }
      // 第一轮的这个时候已经找到最左边边的结点了,给它pop出来
      const node = stack.pop();
      console.log(node.val)
      // 这个时候左结点和根结点都已经pop出来了,要去遍历右节点
      p = node.right;
  }
}

后序遍历

递归实现

function postOrder(root) {
  if (!root) return;
  postOrder(root.left);
  postOrder(root.right);
  console.log(root.val);
}

非递归实现

function postOrder(root) {
  if (!root) return;
  const stack = [root];
  const outStack = [];
  while (stack.length) {
    const node = stack.pop();
    outStack.push(node);
    if (node.left) stack.push(node.right);
    if (node.right) stack.push(node.left);
    console.log(node.val);
  }
  while (outStack.length) {
    const n = outStack.pop();
    console.log(n.val);
  }
}

BFS 广度优先遍历

层序遍历需要使用一个队列来存储有用的节点。整体的思路如下:

  • 将 root 放入队列
  • 取出队首元素,将 val 放入返回的数组中
  • 检查队首元素的子节点,若不为空,则将子节点放入队列
  • 检查队列是否为空,为空,结束并返回数组;不为空,回到第二步
let bfs = function (root) {
  if (!root) return [];
  const queue = [root];
  const res = [];
  while (queue.length) {
    const node = queue.shift();
    res.push(node.val);
    node.left && queue.push(node.left);
    node.right && queue.push(node.right);
  }
  return res;
};

函数柯里化之加法add应用

function add() {
  var args = Array.prototype.slice.call(arguments);
  var fn = function () {
    var arg_fn = Array.prototype.slice.call(arguments);
    return add.apply(null, args.concat(arg_fn));
  };
  fn.toString = function () {
    return args.reduce(function (a, b) {
      return a + b;
    });
  };
  return fn;
}