前端面试手撕代码——基础篇

哈喽大家好,我是Range。现在的前端面试,相信都离不开手撕代码的环节,一般除了LeetCode这类太干的题目,还会有很多是结合了我们前端特性和场景的编程题。今天带来 一篇【前端面试手撕代码——基础篇】,不管有没有准备面试,都值得读一读。

下面是正文部分。




金九银十过了大半,笔者最近也面了一些公司,现将一些自己遇到的和收集的基础题目整理出来,后续会整理分享一些其他的信息,希望对你能有所帮助

前端面试必须掌握的手写题:基础篇

前端面试必须掌握的手写题:场景篇

前端面试必须掌握的手写题:进阶篇

闲言少叙,看正文

实现Object.create

创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。--MDN


function create(obj) {
function Fun(){

}
Func.prototype = obj;
Func.prototype.constructor = Func;
return new Fun();
}

实现instanceof方法

instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。


function myInstanceof(obj, ctor) {
let proto = Object.getPrototypeOf(obj);
let prototype = ctor.prototype;
where(true) {
if(!proto) return false;
if(proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}

实现new关键字

在调用new之后会发生这几个步骤

  1. 创建一个空对象

  2. 设置原型:将空白对象的原型设置为函数的prototype对象

  3. 让函数的this指向这个对象,执行构造函数的代码(为空白对象添加属性)

  4. 判断函数的返回值

    4.1.  如果是引用类型,直接返回,比如构造函数主动返回了一个对象:function T(){return {x: 1}}

    4.2.  如果不是引用类型,返回空白对象; 比如构造函数返回一个数字:function T(){return 1}


// 调用方法:objectFactory(构造函数,构造函数的参数)
function objectFactory() {
let newObject = null;
let constructor = Array.prototype.shift.call(arguments);
let result = null;
if (typeof constructor !== 'function') {
console.error('type error');
return
}
newObject = Object.create(constructor.prototype);
result = constructor.apply(newObject, arguments);
let flag = result && (typeof result === 'function' || typeof result === 'object');
return flag ? result : newObject;
}

拦截构造函数调用


function Person(name) {
if (new.target !== undefined) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}

// 另一种写法
function Person(name) {
if (new.target === Person) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}

var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三'); // 报错

实现继承

组合式继承


//1. 父类 实例属性放在构造函数中
function Father(name, age) {
this.name = name
this.age = age
this.hobby = ['敲代码', '解Bug', '睡觉']
}
// 父类方法放在原型上实现复用
Father.prototype.sayName = function () {
console.log(this.name, 666)
}
Father.prototype.x = 1
//2. 子类
function Child(name, age) {
Father.call(this, name, age) // 调用父类的构造函数 (继承父类的属性)
this.a = 1
}
Child.prototype = Object.create(Father.prototype)

// 另一种写法
function Super(foo) {
this.foo = foo
}
Super.prototype.printFoo = function() {
console.log(this.foo)
}
function Sub(bar) {
this.bar = bar
Super.call(this)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub

ES6版本继承


class Super {
constructor(foo) {
this.foo = foo
}
printFoo() {
console.log(this.foo)
}
}
class Sub extends Super {
constructor(foo, bar) {
super(foo)
this.bar = bar
}
}

简单实现Promise

这里简单实现一下,可以参考一下其他的Promise A+规范的实现,主要包含then,all,race

  • then:


const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

function MyPromise(fn) {
const self = this;
this.state = PENDING;
this.value = null;
this.reason = null;
this.resolvedCallbacks = [];
this.rejectedCallbacks = [];

function resolve(value) {
if (value instanceof MyPromise) {
value.then(resolve, reject)
}
// 保证代码执行顺序为本轮事件循环的末尾
setTimeout(() => {
if (self.state === PENDING) {
self.state = RESOLVED;
self.value = value;
self.resolvedCallbacks.forEach(cb => cb(value));
}
}, 0)
}

function reject(reason) {
setTimeout(() => {
if (self.state === PENDING) {
self.state = REJECTED;
self.reason = reason;
self.rejectedCallbacks.forEach(cb => cb(reason));
}
}, 0)
}
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}

MyPromise.prototype.then = function (onFulfilled, onReject) {
const self = this;
return new MyPromise((resolve, reject) => {
let fulfilled = () => {
try {
const result = onFulfilled(self.value);
return result instanceof MyPromise ? result.then(result) : resolve(result);
} catch (e) {
reject(e);
}
};
let rejected = () => {
try {
const result = onReject(self.reason);
return result instanceof MyPromise ? result.then(resolve, reject) : reject(result);
} catch (e) {
reject(e);
}
}
switch (self.state) {
case PENDING:
case RESOLVED:
case RESOLVED:

}
})
}

MyPromise.all = (promises) => {
return new MyPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
throw new TypeError('arguments must be array');
}
let resolvedCounter = 0;
let promiseNum = promises.length;
let resolvedResult = [];

for (let i = 0; i < promises.length; i++) {
MyPromise.resolve(promises[i]).then(value => {
resolvedCounter++;
resolvedResult[i] = value;
if (resolvedCounter === promiseNum) {
return resolve(resolvedResult);
}
}, error => {
return reject(error);
})
}
})
}
MyPromise.race = function(args) {
return new Promise((resolve, reject) => {
for(let i = 0; len = args.length; i++) {
args[i].then(resolve, reject);
}
})
}

防抖函数

防抖是n秒内会重新计时


function debounce(fn, wait) {
let timer = null;
return function() {
if(timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
fn.apply(this, arguments);
}, wait);
}
}

节流函数

n秒内不重新计时


function throttle(fn, delay) {
let timer = null;
return function () {
if (timer) return;
timer = setTimeout(() => {
timer = null;
return fn.apply(this, arguments);
}, delay)
}
}

实现类型判断函数


function getType(value) {
if (value === null) {
return value + '';
}
if(typeof value === 'object') {
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
} else {
return typeof value;
}
}

实现call函数

执行步骤:

  • 判断call的调用者是否为函数,不是函数需要抛出错误,call调用者就是上下文this,也就是需要被调用的函数

  • 判断需要被调用的函数的的上下文对象是否传入,不存在就设置为window

  • 处理传入的参数,截取第一个参数后的所有参数,作为被调用函数

  • 将需要被调用的函数,绑在传入的上下文上,作为一个属性

  • 使用传入的上下文调用这个函数,并返回结果

  • 删除绑定的属性

  • 返回结果

js

复制代码


Function.prototype.myCall = function(context) {
if(typeof this !== 'function') {
throw new TypeError('need function');
}
let args = arguments.slice(1);
let result = null;

context = context || window;

context.fn = this;
result = context.fn(...args);

delete context.fn;
return result;
}

实现apply函数

唯一的不同就是最后参数的获取方式


Function.prototype.myApply = function(context) {
if(typeof this !== 'function') {
throw new TypeError('need function');
}
let args = arguments[1];
let result = null;

context = context || window;

context.fn = this;
result = context.fn(...args);

delete context.fn;
return result;
}

实现bind

  • 先判断调用者是否为函数

  • 缓存当前需要bind的函数,就是上面的调用者,也是是bind函数的上下文

  • 返回一个函数,利用闭包原理实现对this的保存

  • 函数内部用apply函数来处理函数调用


    • 需要判断函数作为构造函数的情况,这个时候的this就是当前调用这个闭包函数的this

    • 作为普通函数,直接使用传入的上下文就好了


Function.prototype.myBind = function(context) {
if(typeof this !== 'function') {
throw new TypeError('need function');
}
let args = [...arguments].slice(1);
let fn = this;

return function F() {
return fn.apply(
this instanceof F ? this : context,
args.concat(...arguments)
)
}
}

浅拷贝


// es6的Object.assign
Object.assign(target, source1, source2);

// 扩展运算符
{...obj1, ...obj2}

// 数组的浅拷贝
Array.prototype.slice
Array.prototype.concat

// 手动实现
function shallowCopy(object) {
if(!object || typeof object !== 'object') return;
let newObj = Array.isArray(object);

for(let key in object) {
if(object.hasOwnProperty(key)) {
newObj[key] = object(key);
}
}
return newObj;
}

深拷贝deepclone

可能的问题:

  • json方法出现函数或symbol类型的值的时候,会失效

  • 处理循环引用问题

  • 处理可迭代类型的数据

  • 处理包装类型

  • 处理普通类型

简单版本参考vue版本:

  • 判断类型是否为原始类型,如果是,无需拷贝,直接返回

  • 为避免出现循环引用,拷贝对象时先判断存储空间中是否存在当前对象,如果有就直接返回

  • 开辟一个存储空间,来存储当前对象和拷贝对象的对应关系

  • 对引用类型递归拷贝直到属性为原始类型


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(obj[key], cache))
return copy
}

实现Object.assign

就是实现一个浅拷贝



Object.myAssign = function (target, ...source) {
if (target === null) {
throw new TypeError('can not be null');
}
let ret = Object(target);
source.forEach(obj => {
if (!obj !== null) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
ret[key] = obj[key];
}
}
}
});
return ret;
}

简单实现async/await中的async函数

async/await语法糖就是使用Generator函数+自动执行器来运作的,注意只要要实现async函数就是实现一个generate函数+执行器的语法糖


// 定义了一个promise,用来模拟异步请求,作用是传入参数++
function getNum(num){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num+1)
}, 1000)
})
}

//自动执行器,如果一个Generator函数没有执行完,则递归调用
function asyncFun(func){
var gen = func();

function next(data){
var result = gen.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}

next();
}

// 所需要执行的Generator函数,内部的数据在执行完成一步的promise之后,再调用下一步
var func = function* (){
var f1 = yield getNum(1);
var f2 = yield getNum(f1);
console.log(f2) ;
};
asyncFun(func);

实现一个Object.freeze

锁定对象的方法

  • Object.preventExtensions()

no new properties or methods can be added to the project 对象不可扩展, 即不可以新增属性或方法, 但可以修改/删除

  • Object.seal()

same as prevent extension, plus prevents existing properties and methods from being deleted 在上面的基础上,对象属性不可删除, 但可以修改

  • Object.freeze()

same as seal, plus prevent existing properties and methods from being modified 在上面的基础上,对象所有属性只读, 不可修改

以上三个方法分别可用Object.isExtensible(), Object.isSealed(), Object.isFrozen()来检测


var deepFreeze =function (obj) {
var allProps = Object.getOwnPropertyNames(obj);
// 同上:var allProps = Object.keys(obj);
allProps.forEach(item => {
if (typeof obj[item] === 'object') {
deepFreeze(obj[item]);
}
});
return Object.freeze(obj);
}

模拟实现一个Object.freeze,使用了Object.seal


function myFreeze(obj) {
if (obj instanceof Object) {
Object.seal(obj);
let p;
for (p in obj) {
if (obj.hasOwnProperty(p)) {
Object.defineProperty(obj, p, {
writable: false
});
myFreeze(obj[p]);// 递归,实现更深层次的冻结
}
}
}
}

用ES5实现一下map和reduce函数


Array.prototype.myMap = (fn, context) => {
var arr = Array.prototype.slice.call(this);
var mapArray = [];
for (let i = 0; i < arr.length; i++) {
mapArray.push(fn.call(context, arr[i], i, this));
}
return mapArray;
}

Array.prototype.myReduce = (fn, initialValue) => {
var arr = Array.prototype.slice.call(this);
var res, startIndex;
res = initialValue ? initialValue : arr[0];
startIndex = initialValue ? 0 : 1;
for(let i = startIndex; i< arr.length; i++) {
res = fn.call(null, res, arr[i], i, this);
}
return res;
}


原文链接:https://juejin.cn/post/7288340985230409747

最后

还没有使用过我们刷题网站(https://fe.ecool.fun/)或者前端面试题宝典的同学,如果近期准备或者正在找工作,千万不要错过,我们的题库主打无广告和更新快哦~。

老规矩,也给我们团队的辅导服务打个广告。