手写常见面试题
防抖
防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
有两种情况:
- 点击之后立即执行
- 点击之后非立即执行
// 非立即执行
const debounce1 = (fn, delay) => {
let timer = null;
return (...args) => {
if(timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay)
}
}
// 立即执行
const debounce2 = (fn, delay) => {
let timer = null;
let emitNow = true;
return (...args) => {
if(timer) clearTimeout(timer);
if(emitNow) {
fn.apply(this, args);
emitNow = false;
} else {
timer = setTimeout(() => {
fn.apply(this, args);
emitNow = true;
}, delay)
}
}
}
// 通过参数控制是否立即执行
const debounce3 = (fn, delay, isImmediate) => {
let timer = null;
let emitNow = true;
return (...args) => {
if(timer) clearTimeout(timer);
if(isImmediate) {
if(emitNow) {
fn.apply(this, args);
emitNow = false;
} else {
timer = setTimeout(() => {
fn.apply(this, args);
emitNow = true;
}, delay)
}
} else {
timer = setTimeout(() => {
fn.apply(this, args);
}, delay)
}
}
}
节流
防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
有两种情况:
- 点击之后立即执行
- 点击之后非立即执行
// 非立即执行
const throttle1 = (fn, delay) => {
let isEmit = false;
return (...args) => {
if(isEmit) return;
isEmit = true;
setTimeout(() => {
fn.apply(this, args);
isEmit = false;
}, delay);
}
}
// 立即执行
const throttle2 = (fn, delay) => {
let isEmit = false;
return (...args) => {
if(isEmit) return;
isEmit = true;
fn.apply(this,args);
setTimeout(() => {
isEmit = false;
},delay);
}
}
// 通过参数控制是否立即执行
const throttle3 = (fn, delay, isImmediate) => {
let isEmit = false;
return (...args) => {
if(isEmit) return;
isEmit = true;
if(isImmediate) {
fn.apply(this, args);
setTimeout(() => {
isEmit = false;
},delay);
} else {
setTimeout(() => {
fn.apply(this, args);
isEmit = false;
}, delay);
}
}
}
深克隆
我们最常用的是JSON.parse(JSON.stringify(obj))
这样的方式来实现克隆,但是这个方式其实存在一些局限。
比如:
- 无法实现对函数 、
RegExp
等特殊对象的克隆 - 会抛弃原始对象的构造函数, 并指向
Object
- 当对象循环引用时,会报错
instanceOf
根据原型链的知识,我们能很快能知道根据对象的__proto__
属性就能找到其构造函数。
const instanceOf = function(object, target) {
// 取目标的原型对象
const instance = target.prototype;
// 取待检验的对象的隐式原型
object = object.__proto__;
while(true) {
if(!object) return false;
if(object === instance) return true;
object = object.__proto__;
}
}
new
操作符
new
的作用:
- 创建一个新对象
- 将
this
执行创建的新对象 - 创建的新对象会被链接到该函数的
prototype
对象上(新对象的__proto__
属性指向函数的prototype
); - 利用函数的call方法,将原本指向window的绑定对象this指向了obj。(这样一来,当我们向函数中再传递实参时,对象的属性就会被挂载到obj上。)
function createObject() {
// 创建一个新对象
const obj = {};
// 获取构造函数,采用call方法使得arguments能够使用shift方法将第一个参数(构造函数)拿出来
const Constructor = [].shift.call(arguments);
// 将对象__proto__属性链接到构造函数的prototype属性中
obj.__proto__ = Constructor.prototype;
// 将构造函数中的this指向对象并传递参数
const result = Constructor.apply(obj, arguments);
// 确保返回值是一个对象
return typeof ret === "object" ? result : obj;
}
实现call
方法
我们都很清楚call
这个方法就是用于修改this
指向,但是有些同学可能不太懂其原理,我们来手写一个call
方法帮助深入了解其原理。
Function.prototype.mycall = function(context) {
// 默认上下文为window
context = context || window;
// 添加一个属性用于保存当前调用call的函数
context.fn = this;
// 将arguments转变成数组并移除第一个参数(上下文)
const args = [...arguments].slice(1);
// 这样调用函数时该函数内部的this就指向调用者(context);
const result = context.fn(...args);
delete context.fn;
return result;
}
实现apply
方法
apply
原理与call
很相似,唯一不同就是传参问题,apply
方法的第二个参数是所有参数组合成的数组,而call
方法除了第一个参数是context
外,其他都是传入的参数。
Function.prototype.myapply = function(context, arr) {
// 默认上下文为window
context = context || window;
// 添加一个属性用于保存当前调用call的函数
context.fn = this;
// 将arguments转变成数组并移除第一个参数(上下文)
let result;
if(!arr) {
result = context.fn();
} else {
result = context.fn(arr);
}
delete context.fn;
return result;
}
实现bind
方法
相对于call
和apply
而言,bind
方法的返回值是一个改变了this
的函数(即非立即调用)。当返回的函数被当作构造函数使用时,this
失效,但是传入的参数依旧有效。
Function.prototype.mybind = function(context) {
if(typeof this !== 'function') {
throw new Error('Uncaught TypeError: not a function')
}
const args = [...arguments].slice(1);
// 用于记录当前传入的函数的prototype;
let Transit = function() {};
const _ = this;
const FunctionToBind = function() {
const bindArgs = [...arguments];
return _.apply(this instanceof Transit ? this : context, args.concat(bindArgs));
}
// 记录当前传入的函数的prototype;
Transit.prototype = this.prototype;
FunctionToBind.prototype = new Transit();
return FunctionToBind;
}