前端面试手写JS代码
很多公司为了能够在面试中检验一个人的编码能力,通常会出一些简单的题目,让开发者手写代码或者真机编码,本文整理了常见的手写代码题,以供大家参考。
手写 call 方法
call 函数可以用来改变 this 指向,可以指定调用的对象,以及传递给函数的参数。
Function.prototype.myCall = function (context, ...args) {
context = (context ?? window) || new Object(context);
const key = Symbol();
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
};
手写 apply 方法
apply 函数功能同 call 一样,也可以改变 this 指向,唯一不同的是参数形式不同,apply 传递的一个数组,而 call 传递的是参数列表。
Function.prototype.myApply = function (context, args) {
context = (context ?? window) || new Object(context);
const key = Symbol();
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
};
手写 bind 方法
bind 函数也可以改变 this 指向,不过 bind 返回的是改变 this 后的一个函数,而不是调用。
Function.prototype.myBind = function (context, ...args) {
const fn = this;
const bindFn = function (...newFnArgs) {
return fn.call(this instanceof bindFn ? this : context, ...args, ...newFnArgs);
};
bindFn.prototype = Object.create(fn.prototype);
return bindFn;
};
实现 instanceof
递归遍历,判断左边的 __proto__
等于右边的 prototype
,如果找到则返回 true
,否则返回 false
function myInstanceof(a, b) {
let L = a;
while (true) {
if (L.__proto__ === null) {
return false;
}
if (L.__proto__ === b.prototype) {
return true;
}
L = L.__proto__;
}
}
实现深拷贝(简单版)
const deepClone = (target, cache = new WeakMap()) => {
if (target === null || typeof target !== "object") {
return target;
}
if (cache.get(target)) {
// 避免循环引用
return target;
}
const copy = Array.isArray(target) ? [] : {};
cache.set(target, copy);
Object.keys(target).forEach((key) => (copy[key] = deepClone(target[key], cache)));
return copy;
};
函数防抖
函数防抖是在事件被触发n
秒后再执行回调,如果在n
秒内又被触发,则重新计时。 函数防抖多用于input
输入框
- 箭头函数的
this
继承自父级上下文,这里指向触发事件的目标元素 - 事件被触发时,传入
event
对象 - 传入
leading
参数,判断是否可以立即执行回调函数,不必要等到事件停止触发后才开始执行 - 回调函数可以有返回值,需要返回执行结果
const debounce = (fn, wait = 300, leading = true) => {
let timerId, result;
return function (...args) {
timerId && clearTimeout(timerId);
if (leading) {
if (!timerId) result = fn.apply(this, args);
timerId = setTimeout(() => (timerId = null), wait);
} else {
timerId = setTimeout(() => (result = fn.apply(this, args)), wait);
}
return result;
};
};
函数节流(定时器)
函数节流是指连续触发事件,但是在 n
秒中只执行一次函数,适合应用于动画相关的场景
const throttle = (fn, wait = 300) => {
let timerId;
return function (...args) {
if (!timerId) {
timerId = setTimeout(() => {
timerId = null;
return (result = fn.apply(this, ...args));
}, wait);
}
};
};
函数节流(时间戳)
const throttle = (fn, wait = 300) => {
let prev = 0;
let result;
return function (...args) {
let now = +new Date();
if (now - prev > wait) {
prev = now;
return (result = fn.apply(this, ...args));
}
};
};
函数节流实现方法区别
方法 | 使用时间戳 | 使用定时器 |
---|---|---|
开始触发时 | 立刻执行 | n 秒后执行 |
停止触发后 | 不再执行事件 | 继续执行一次事件 |
数组扁平化(技巧版)
利用 toString
把数组变成以逗号分隔的字符串,遍历数组把每一项再变回原来的类型。缺点:数组中元素必须是 Number
类型,String
类型会被转化成Number
const str = [0, 1, [2, [3, 4]]].toString();
// '0, 1, 2, 3, 4'
const arr = str.split(",");
// ['0','1','2', '3', '4']
const newArr = arr.map((item) => +item);
// [0, 1, 2, 3, 4]
const flatten = (arr) =>
arr
.toString()
.split(",")
.map((item) => +item);
数组扁平化
reduce
+ 递归
const flatten = (arr, deep = 1) => {
return arr.reduce((cur, next) => {
return Array.isArray(next) && deep > 1 ? [...cur, ...flatten(next, deep - 1)] : [...cur, next];
}, []);
};
const arr = [1, [2], [3, [4]]];
flatten(arr, 1); // [1, [2], [3, [4]]]
flatten(arr, 2); // [1,2, [3, 4]]
flatten(arr, 3); // [1,2, 3, 4]
函数柯里化
const currying = (fn) =>
(_curry = (...args) => (args.length >= fn.length ? fn(...args) : (...newArgs) => _curry(...args, ...newArgs)));
原理是利用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数