likes
comments
collection
share

JavaScript 手写源码(instanceof、深拷贝、节流、防抖)

作者站长头像
站长
· 阅读数 28

一、instanceof 的实现

使用 instanceof 操作符,如果一个实例的原型链中出现过相应的构造函数的原型,则 instanceof 返回 true。

instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。 因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。

function instanceof1(L,R) {
  L = L.__proto__;

  while(L != null) {
    if(L === R.prototype) {
      return true;
    }
    L = L.__proto__;
  }
  return false;
}

二、浅拷贝

// 浅拷贝
function shallowClone(obj) {
  const result = {};
  for(let key in obj) {
    console.log('ddddd',key);
    if(obj.hasOwnProperty(key)) {
      result[key] = obj[key];
    }
  }
  return result;
}

测试代码:

let obj = {
  b: {
    c: [1, 5, 11, 23, 422]
  },
  d: function() {
    console.log('hello world');
  }
};
const result = shallowClone(obj);
console.log(result);
const newObj = Object.assign({},obj);
console.log(newObj);

三、深拷贝

// 深拷贝
function deepClone(obj) {
  if(typeof obj != 'object' || obj == null) {
    return obj;
  }

  const result = Array.isArray(obj) ? [] : {};
  for(let key in obj) {
    // 自有属性
    if(obj.hasOwnProperty(key)) {
      const type = typeof obj[key];
      if(type == 'object' && obj[key] != null) {
        result[key] = deepClone(obj[key]);
      } else {
        result[key] = obj[key];
      }
    }
  }
  return result;
}

测试代码:

var data = {
  age: 18,
  name: "liuruchao",
  education: ["小学", "初中", "高中", "大学", undefined, null],
  likesFood: new Set(["fish", "banana"]),
  friends: [
        { name: "summer",  sex: "woman"},
        { name: "daWen",   sex: "woman"},
        { name: "yang",    sex: "man" }  ],
  work: {
          time: "2019",
          project: { name: "test",obtain: ["css", "html", "js"]}
        },
  play: function() {    console.log("玩滑板");  }
}

console.log(deepClone(data));

解决循环引用问题:

// 解决循环引用问题
function deepClone2(obj, hash = new WeakMap()) {
  const type = typeof obj;
  if(type != 'object' || obj == null) {
    return obj;
  }
  if(hash.has(obj)) {
    return hash.get(obj);
  }
  const result = Array.isArray(obj) ? [] : {};
  for(let key in obj) {
    if(obj.hasOwnProperty(key)) {
      if(typeof obj[key] != 'object' || obj[key] == null) {
        result[key] = obj[key];
      } else {
        // 首次调用时,weakMap为空,不会走上面那个if(hash.has())语句,如果待拷贝对象中有属性也为对象时,
        // 则将该待拷贝对象存入weakMap中,此时的健值和健名都是对该待拷贝对象的引用
        hash.set(obj, obj)
        result[key] = deepClone2(obj[key], hash);
      }
    }
  }
  return result;
}

测试代码:

var data2 = {
  name: 'foo',
  child: null,
}
data2.child = data2;
console.log(deepClone2(data2));

四、函数防抖

  1. 当持续触发事件时,一定时间内没有再触发事件,事件处理函数才会执行一次;
  2. 如果在设定的时间来到之前,又一次触发了事件,则会取消上次事件,重新开始计时;

使用场景:resize、scroll、输入框内容校验等。

function debounce(handle,wait) {
  let timer = null;
  return function () {
    if(timer != null) {
      clearTimeout(timer);
    }
    timer = setTimeout(handle, wait);
  }
}

五、节流

当持续触发事件时,保证一定时间内只调用一次事件处理函数。通俗解释就比如:我们把水龙头打开,水哗哗的往外流,秉着节约的原则,我们要把水龙头关小,最好是如我们的心愿按照一定的规律,在某个时间内一滴一滴的往下滴。

时间戳版本:

function throttle (handle,wait) {
  let prev = Date.now();
  return function() {
    let current = Date.now();
    if(current - prev >= wait) {
      handle();
      prev = Date.now();
    }
  }
}

定时器版本:

  1. 当触发事件的时候,我们设置一个定时器;
  2. 当再次触发的时候,如果定时器存在,就不执行,直到 wait 时间后,定时器执行 handle 函数,并且清空定时器,这样就可以设置下一个定时器;
  3. 当第一次触发事件时,不会立即执行行数,而是在 delay 秒之后才执行,而后在怎么频繁触发也都是在 wait 时间才执行一次。
function throttle1(handle, wait) {
  let timer = null;
  return function() {
    if(!timer) {
      timer = setTimeout(() => {
        handle();
        timer = null;
      },wait);
    }
  }
}

定时器 + 时间戳版本:

  1. 节流中使用时间戳和定时器版本都是可以的,更精确的,可以使用时间戳和定时器相结合,当第一次事件触发时马上执行事件处理函数,最后一次触发时也还会执行一次事件处理函数。
  2. 在节流函数内部使用了 pre 和 current 与 wait 来计算剩余时间 remaining,当 remaining <= 0 时,表示执行该执行事件处理函数了(保证了第一次触发事件就能立即执行事件处理函数和每隔 wait 时间执行一次事件处理函数)。
  3. 如果还没到时间的话就设定在remaining时间后再触发 (保证了最后一次触发事件后还能再执行一次事件处理函数)。当然在 remaining 这段时间中如果又一次触发事件,那么会取消当前的计时器,并重新计算一个remaining来判断当前状态。
function throttle2 (handle, wait) {
  let pre = Date.now();
  let timer = null;
  return function() {
    let current = Date.now();
    let remaining = wait - (current - pre);
    clearTimeout(timer);
    if(remaining <= 0) {
      handle();
      pre = Date.now();
    } else {
      timer = setTimeout(handle, remaining);
    }
  }
}

节流和防抖总结:

  1. 函数防抖:将几次操作合并为一次操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay 时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
  2. 函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。
  3. 区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。

参考: