JavaScript 核心面试题解析:instanceof、深拷贝、防抖与节流实现

作者:木棉时间:2026-10-30 19:08:09浏览:100

本文详细解析 JavaScript 面试中常见的核心考点,包括 instanceof 操作符的原型链查找机制、浅拷贝与深拷贝的区别及循环引用处理方案,以及函数防抖与节流的多种实现策略(时间戳、定时器及混合模式)。通过具体代码示例,帮助开发者深入理解底层原理并掌握实际应用场景。

一、instanceof 的实现

instanceof 运算符用于检测构造函数 prototype 属性是否存在于某个对象实例的原型链中。若存在,则返回 true;否则返回 false。

其核心原理是沿着左侧变量的原型链向上查找,判断右侧变量的 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));

四、函数防抖

  • 在持续触发事件的情况下,只有当指定时间内不再次触发事件时,事件处理函数才会执行一次。
  • 若在设定的时间间隔内再次触发事件,则取消上一次的定时器并重新开始计时。

常见应用场景包括 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();
    }
  }
}

定时器版本:

  • 触发事件时设置一个定时器。
  • 若再次触发事件且定时器存在,则不执行新操作,直到 wait 时间过后执行 handle 函数并清空定时器,从而允许下一个定时器被设置。
  • 首次触发时不立即执行,而是在 delay 秒后执行;此后无论多么频繁地触发,都仅在 wait 时间间隔内执行一次。
function throttle1(handle, wait) {
  let timer = null;
  return function() {
    if(!timer) {
      timer = setTimeout(() => {
        handle();
        timer = null;
      },wait);
    }
  }
}

定时器 + 时间戳版本:

  • 节流的两种单独实现方式均可使用,但更精确的做法是结合时间戳与定时器。这样既能保证首次触发时立即执行,也能确保最后一次触发后仍能执行一次。
  • 在节流函数内部,通过 pre(上次执行时间)、current(当前时间)与 wait(间隔时间)计算剩余时间 remaining。当 remaining <= 0 时,表示可以执行事件处理函数,这保证了首次触发立即执行以及每隔 wait 时间执行一次。
  • 若未到执行时间,则设定在 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);
    }
  }
}

节流和防抖总结:

  • 函数防抖:将多次操作合并为一次执行。原理是维护一个计时器,规定在 delay 时间后触发函数,若在 delay 时间内再次触发,则取消前一个计时器并重新设置。因此,只有最后一次操作会被触发。
  • 函数节流:确保在一定时间内只触发一次函数。原理是通过判断是否到达指定时间来触发函数。
  • 区别:函数节流无论事件触发多频繁,都会保证在规定时间内执行一次真正的事件处理函数;而函数防抖仅在最后一次事件触发后才执行一次。例如在页面无限加载场景中,用户滚动页面时每隔一段时间发送 Ajax 请求,适合使用节流技术。

以上就是JS面试必备之手写instanceof,深拷贝,节流和防抖的详细内容,更多关于JS面试的资料请关注本站其它相关文章!

作者最新文章