前言
手写其实在前端的面试过程中必不可少,因为手写是考验你了解某一原理的最可观的体现。下面我汇总了一些,我在面试复习过程中遇到的手写题。我将实现思路写出来与大家共享,而实现只是一个参考,有兴趣的可以点击参考答案,有问题请指正。
原理实现
实现一个new操作符
首先我们需要了解new操作符干了哪些事情
new操作符返回的是一个对象。
对象的原型,指向的是构造函数的原型
如果构造函数有return的话,需要对return的进行判断,如果是对象,那么用函数return的,如果不是对象,那么直接返回新创建的对象
参考答案
function myNew(fn) {
let obj = Object.create(fn.prototype);
let res = fn.call(obj);
return res instanceof Object ? res : obj;
}
复制代码
实现一个instanceof操作符
首先我们需要知道instanceof是通过原型链来进行判断的
参考答案
instanceof操作符是判断原型链来生效的,所以只要你将左边的_proto_和右边的prototype做对比
复制代码function myInstance(left, right) {
// 当left是基础类型的时候直接返回false
if(typeof left !== 'object' || left === null) return false;
let proto = Object.getPrototypeOf(left);
while(true) {
if(proto === null) return false;
if(proto === right.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
复制代码
实现一个apply
实现apply主要是要注意以下几个方面:
1、参数不能取第一个,因为第一个是上下文。
2、赋值到对象上的方法需要进行删除,不能影响原对象。
3、对于上下文为undefined的情况需要把上下文指向window对象
参考答案
Function.prototype.myApply = function(context, [...args]) {
// 先判断context是否为空,如果为空则指向window对象
context = context || window;
context.fn = this;
context.fn(...args);
delete context.fn;
}
复制代码
实现一个call
call的实现和apply的实现类似,但是参数处理层面会略有不同。
参考答案
Function.prototype.myCall = function(context) {
// 先判断context是否为空,如果为空则指向window对象
context = context || window;
let args = [...arguments].slice(1);
context.fn = this;
context.fn(args);
delete context.fn;
}
复制代码
实现一个bind
bind的实现需要注意的是函数柯里化的情况。
参考答案
Function.prototype.myBind = function(context) {
const self = this;
let args = [...arguments].slice(1);
return function() {
// 考虑函数柯里化的情况
let newArgs = [...arguments];
this.apply(context, newArgs.concat(args))
}
}
复制代码
实现一个promise
promise的实现有必要着重看一下,我自己划分几个步骤来看:
1、先处理同步的逻辑,比如状态值变化之后,将不在发生改变,以及有哪些属性值等。
2、再处理异步逻辑,用回调的形式,以及存放的列表。
3、再处理链式调用的逻辑,链式调用比较复杂,多注意thenable的对象。
4、在处理promise的其他方法。
最后我在这里给大家推荐一篇文章,写的很不错!!promise文章
参考答案
class Promise(exector) {
constructor() {
this.value = undefined;
this.reason = '';
this.state = 'pending';
this.onResolveList = [];
this.onRejectList = [];
const resolve = (value) => {
if(this.state === 'fulfilled') {
this.value = value
this.state = 'fulfilled';
this.onResolveList.forEach((fn) => {
fn();
})
}
};
const reject = (reason) => {
if(this.state === 'rejected') {
this.reason = reason
this.state = 'rejected';
this.onRejectList.forEach((fn) => {
fn();
})
}
}
try {
exector(resolve, reject);
} catch(err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
const promise2 = new Promise((reslove, reject) => {
if(this.state === 'fulfilled') {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, reslove, reject);
}
if(this.state === 'rejected') {
onRejected(this.reason);
resolvePromise(promise2, x, reslove, reject);
}
if(this.state === 'pending') {
onResolveList.push(() => {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, reslove, reject);
});
onRejectList.push(() => {
let x = onRejected(this.reason);
resolvePromise(promise2, x, reslove, reject);
});
}
});
return promise2;
}
race(promises) {
return new Promise((resolve, reject) => {
for(let i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
})
}
all(promises) {
let arr = [];
let i = 0;
function processData(index, data) {
arr[index] = data;
i++;
if(i === promises.length) {
resolve(data);
}
}
return new Promise((resolve, reject) => {
for(let i = 0; i < promises.length; i++) {
promises[i].then((val) => {
processData(i, val);
}, reject)
}
})
}
resolve(val) {
return new Promise((resovle, reject) => {
resovle(val);
})
}
reject(val) {
return new Promise((resovle, reject) => {
reject(val);
})
}
}
// 这边要处理的情况有以下几种:1、循环引用, 2、thenable对象,3、promise对象
resolvePromise(promise2, x, reslove, reject) {
if(promise2 === x) {
reject('循环引用');
return;
}
// 防止多次调用
let called;
// 检测x的值的类型,如果不是对象或者函数,直接返回resolve
if(x !== null && (typeof x === 'object' || typeof x === 'function')) {
// 规范中then逻辑报错也会进入catch
try {
if(called) return;
let then = x.then;
if(typeof then === 'function') {
then.call(x, (y) => {
if(called) return;
called = true;
resolvePromise(promise2, y ,reslove, reject)
}, (err) => {
if(called) return;
reject(err);
called = true;
})
} else {
resolve(x);
}
} catch(err) {
if(called) return;
reject(err);
called = true;
}
} else {
resolve(x);
}
}
复制代码
实现一个寄生组合继承
寄生组合继承其实需要注意的是子构造函数constructor的指向问题。以及继承的弊病:超集会调用两次。
参考答案
function Super() {}
function Sub() {
Super.call(this)
}
Sub.prototype = new Super();
Sub.constructor = Sub;
复制代码
业务题实现
如何实现一个防抖函数
对于防抖的理解,最好结合业务场景记忆:防抖一般用于输入框场景。所以在实现层面会有以下两个方面:
1、当一定时间内事件再次触发时,定时器应该重置。
2、执行完毕后定时器重置。
参考答案
function debounce(cb, delay, ...args) {
let time = null;
return function() {
if(time) {
clearTimeout(time);
}
time = setTimeout(() => {
cb.apply(this, ...args);
clearTimeout(time);
}, delay);
}
}
复制代码
如何实现一个节流函数
对于节流函数,也需要结合场景来记忆。一般用于滚动事件中,一定时间内只会触发一次。实现层面也会有两个需要注意的点:
1、用一个锁变量来保证一定时间内只会触发一次。
2、执行完毕之后,解开锁即可
参考答案
function tr(fn, time, ...args) {
let lock = false;
return function() {
if(lock) return;
lock = true;
setTimeout(() => {
fn.apply(this, ...args);
lock = false;
}, time)
}
}
复制代码
实现一个中划线与驼峰的互相转换
这个其实主要考的是正则和replace方法。
参考答案
function camelize(str) {
return (str + '').replace(/-\D/g, function(match) {
return match.charAt(1).toUpperCase()
})
}
复制代码function hyphenate(str) {
return (str + '').replace(/[A-Z]/g, function(match) {
return '-' + match.toLowerCase();
})
}
复制代码
实现一个sleep函数
sleep函数实现的途径有很多,promise,async/await等等。我在这里就将一些最普通的。
参考答案
function sleep(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(true);
}, time)
})
}
复制代码
实现一个柯里化函数
实现柯里化其实就是把多个参数长度很分开来调用的意思,好处在于可以观测你参数调用的一个中间的过程,或者中间的变量。面试中常考的add(1, 2, 3)和add(1)(2)(3)就是这个问题
参考答案
function curry(fn) {
const finalLen = fn.length
let args = [].slice.call(this,1)
return function currying () {
args = args.concat(Array.from(arguments))
const len = args.length
return len >= fn.length ? fn.apply(this, args) : currying
}
}
复制代码
实现一个ajax
实现一个ajax其实主要是一个XMLHttpRequest对象以及其API方法的一个使用的问题。而在这里我建议尽量封装成promise的形式,方便使用。
参考答案
function ajax({url, methods, body, headers}) {
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest();
request.open(url, methods);
for(let key in headers) {
let value = headers[key]
request.setRequestHeader(key, value);
}
request.onreadystatechange = () => {
if(request.readyState === 4) {
if(request.status >= '200' && request.status < 300) {
resolve(request.responeText);
} else {
reject(request)
}
}
}
request.send(body)
})
}
复制代码
实现一个深拷贝
深拷贝也是面试中的一个高频考点,一般的方法,JSON的序列化和反序列化,但是这种方法的弊病有两个:
1、undefined、null和symbol类型的值会被删除
2、碰见循环引用的时候会报错。
那么我们在实现深拷贝的时候,也要时刻关注循环引用这个问题。
下列方法中,我主要是通过数组的形式去解决循环引用的问题。那为什么要有两个数组呢?
主要是一个数组维护的是原来对象的引用,一个数组维护的是新对象的引用。
参考答案
function deepClone(obj) {
const parents = [];
const children = [];
function helper(obj) {
if(obj === null) return null;
if(typeof obj !== 'object') return obj;
let child, proto;
if(Array.isArray(obj)) {
child = [];
} else {
proto = Object.getPrototypeOf(obj);
child = Object.create(proto);
}
// 处理循环引用
let index = parents.indexOf(obj)
if(index === -1) {
parents.push(obj);
children.push(child)
} else {
return children[index];
}
// for in迭代
for(let i in obj) {
child[i] = helper(obj[i]);
}
}
}
复制代码
关于找一找教程网
本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。
本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。
[前端面试手写题有备无患]http://www.zyiz.net/tech/detail-129907.html