哈喽大家好,我是Range。现在的前端面试,相信都离不开手撕代码的环节,一般除了LeetCode这类太干的题目,还会有很多是结合了我们前端特性和场景的编程题。今天带来 一篇【前端面试手撕代码——基础篇】,不管有没有准备面试,都值得读一读。
下面是正文部分。
金九银十过了大半,笔者最近也面了一些公司,现将一些自己遇到的和收集的基础题目整理出来,后续会整理分享一些其他的信息,希望对你能有所帮助
前端面试必须掌握的手写题:基础篇
前端面试必须掌握的手写题:场景篇
前端面试必须掌握的手写题:进阶篇
闲言少叙,看正文
创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。--MDN
function create(obj) {
function Fun(){
}
Func.prototype = obj;
Func.prototype.constructor = Func;
return new Fun();
}
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之后会发生这几个步骤
创建一个空对象
设置原型:将空白对象的原型设置为函数的prototype对象
让函数的this指向这个对象,执行构造函数的代码(为空白对象添加属性)
判断函数的返回值
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 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调用者就是上下文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;
}
唯一的不同就是最后参数的获取方式
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函数的上下文
返回一个函数,利用闭包原理实现对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;
}
可能的问题:
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.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语法糖就是使用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.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]);// 递归,实现更深层次的冻结
}
}
}
}
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/)或者前端面试题宝典的同学,如果近期准备或者正在找工作,千万不要错过,我们的题库主打无广告和更新快哦~。
老规矩,也给我们团队的辅导服务打个广告。