本文详细解析 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面试的资料请关注本站其它相关文章!