跳到主内容

前端面试手写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)));

原理是利用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数