2024 前端JavaScript高频手写面试题大全,助你查漏补缺_es5面向对象面试题

总结

秋招即将开始,校招的朋友普遍是缺少项目经历的,所以底层逻辑,基础知识要掌握好!

而一般的社招,更是神仙打架。特别强调,项目经历不可忽视;几乎简历上提到的项目都会被刨根问底,所以项目应用的技术要熟练,底层原理必须清楚。

这里给大家提供一份汇集各大厂面试高频核心考点前端学习资料。涵盖 HTML,CSS,JavaScript,HTTP,TCP协议,浏览器,Vue框架,算法等高频考点238道(含答案)

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

资料截图 :

高级前端工程师必备资料包

 var res = [];
 thisValue = thisValue||[];
 this.reduce(function(pre,cur,index,arr){
     return res.push(fn.call(thisValue,cur,index,arr));
 },[]);
 return res;

}

var arr = [2,3,1,5];
arr.myMap(function(item,index,arr){
console.log(item,index,arr);
})


### **介绍一款面试题库小程序   手机刷题更方便       MST题宝库**


 ![](https://img-blog.csdnimg.cn/3b57ee3e1749492ba8908bfd28cb860d.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5YmN56uv5oqA5pyv56uZ,size_12,color_FFFFFF,t_70,g_se,x_16)


#### 4. 手写数组的reduce方法


reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终为一个值,是ES5中新增的又一个数组逐项处理方法


**参数:**


* callback(一个在数组中每一项上调用的函数,接受四个函数:)
	+ previousValue(上一次调用回调函数时的返回值,或者初始值)
	+ currentValue(当前正在处理的数组元素)
	+ currentIndex(当前正在处理的数组元素下标)
	+ array(调用reduce()方法的数组)



* initialValue(可选的初始值。作为第一次调用回调函数时传给previousValue的值)



function reduce(arr, cb, initialValue){
var num = initValue == undefined? num = arr[0]: initValue;
var i = initValue == undefined? 1: 0
for (i; i< arr.length; i++){
num = cb(num,arr[i],i)
}
return num
}

function fn(result, currentValue, index){
return result + currentValue
}

var arr = [2,3,4,5]
var b = reduce(arr, fn,10)
var c = reduce(arr, fn)
console.log(b) // 24


#### 5. 数组扁平化


数组扁平化就是把多维数组转化成一维数组


**1. es6提供的新方法 flat(depth)**



let a = [1,[2,3]];
a.flat(); // [1,2,3]
a.flat(1); //[1,2,3]


其实还有一种更简单的办法,无需知道数组的维度,直接将目标数组变成1维数组。 depth的值设置为Infinity。



let a = [1,[2,3,[4,[5]]]];
a.flat(Infinity); // [1,2,3,4,5] a是4维数组


**2. 利用cancat**



function flatten(arr) {
var res = [];
for (let i = 0, length = arr.length; i < length; i++) {
if (Array.isArray(arr[i])) {
res = res.concat(flatten(arr[i])); //concat 并不会改变原数组
//res.push(…flatten(arr[i])); //或者用扩展运算符
} else {
res.push(arr[i]);
}
}
return res;
}
let arr1 = [1, 2,[3,1],[2,3,4,[2,3,4]]]
flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]


#### 6. 函数柯里化


柯里化的定义:接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数。


当柯里化函数接收到足够参数后,就会执行原函数,如何去确定何时达到足够的参数呢?


有两种思路:


1. 通过函数的 length 属性,获取函数的形参个数,形参的个数就是所需的参数个数
2. 在调用柯里化工具函数时,手动指定所需的参数个数


将这两点结合一下,实现一个简单 curry 函数:



/**

  • 将函数柯里化
  • @param fn 待柯里化的原函数
  • @param len 所需的参数个数,默认为原函数的形参个数
    /
    function curry(fn,len = fn.length) {
    return _curry.call(this,fn,len)
    }

    /
    *
  • 中转函数
  • @param fn 待柯里化的原函数
  • @param len 所需的参数个数
  • @param args 已接收的参数列表
    */
    function _curry(fn,len,…args) {
    return function (…params) {
    let _args = […args,…params];
    if(_args.length >= len){
    return fn.apply(this,_args);
    }else{
    return _curry.call(this,fn,len,…_args)
    }
    }
    }

我们来验证一下:



let _fn = curry(function(a,b,c,d,e){
console.log(a,b,c,d,e)
});

_fn(1,2,3,4,5); // print: 1,2,3,4,5
_fn(1)(2)(3,4,5); // print: 1,2,3,4,5
_fn(1,2)(3,4)(5); // print: 1,2,3,4,5
_fn(1)(2)(3)(4)(5); // print: 1,2,3,4,5


我们常用的工具库 lodash 也提供了 curry 方法,并且增加了非常好玩的 placeholder 功能,通过占位符的方式来改变传入参数的顺序。


比如说,我们传入一个占位符,本次调用传递的参数略过占位符, 占位符所在的位置由下次调用的参数来填充,比如这样:


直接看一下官网的例子:


![](https://img-blog.csdnimg.cn/img_convert/2c8f4d0df97b480b4b131ec677c897bc.png)


接下来我们来思考,如何实现占位符的功能。


对于 lodash 的 curry 函数来说,curry 函数挂载在 lodash 对象上,所以将 lodash 对象当做默认占位符来使用。


而我们的自己实现的 curry 函数,本身并没有挂载在任何对象上,所以将 curry 函数当做默认占位符


使用占位符,目的是改变参数传递的顺序,所以在 curry 函数实现中,每次需要记录是否使用了占位符,并且记录占位符所代表的参数位置。


直接上代码:



/**

  • @param fn 待柯里化的函数
  • @param length 需要的参数个数,默认为函数的形参个数
  • @param holder 占位符,默认当前柯里化函数
  • @return {Function} 柯里化后的函数
    /
    function curry(fn,length = fn.length,holder = curry){
    return _curry.call(this,fn,length,holder,[],[])
    }
    /
    *
  • 中转函数
  • @param fn 柯里化的原函数
  • @param length 原函数需要的参数个数
  • @param holder 接收的占位符
  • @param args 已接收的参数列表
  • @param holders 已接收的占位符位置列表
  • @return {Function} 继续柯里化的函数 或 最终结果
    */
    function _curry(fn,length,holder,args,holders){
    return function(…_args){
    //将参数复制一份,避免多次操作同一函数导致参数混乱
    let params = args.slice();
    //将占位符位置列表复制一份,新增加的占位符增加至此
    let _holders = holders.slice();
    //循环入参,追加参数 或 替换占位符
    _args.forEach((arg,i)=>{
    //真实参数 之前存在占位符 将占位符替换为真实参数
    if (arg !== holder && holders.length) {
    let index = holders.shift();
    _holders.splice(_holders.indexOf(index),1);
    params[index] = arg;
    }
    //真实参数 之前不存在占位符 将参数追加到参数列表中
    else if(arg !== holder && !holders.length){
    params.push(arg);
    }
    //传入的是占位符,之前不存在占位符 记录占位符的位置
    else if(arg === holder && !holders.length){
    params.push(arg);
    _holders.push(params.length - 1);
    }
    //传入的是占位符,之前存在占位符 删除原占位符位置
    else if(arg === holder && holders.length){
    holders.shift();
    }
    });
    // params 中前 length 条记录中不包含占位符,执行函数
    if(params.length >= length && params.slice(0,length).every(i=>i!==holder)){
    return fn.apply(this,params);
    }else{
    return _curry.call(this,fn,length,holder,params,_holders)
    }
    }
    }

验证一下:;



let fn = function(a, b, c, d, e) {
console.log([a, b, c, d, e]);
}

let _ = {}; // 定义占位符
let fn = curry(fn,5,); // 将函数柯里化,指定所需的参数个数,指定所需的占位符

_fn(1, 2, 3, 4, 5); // print: 1,2,3,4,5
fn(, 2, 3, 4, 5)(1); // print: 1,2,3,4,5
_fn(1, _, 3, 4, 5)(2); // print: 1,2,3,4,5
fn(1, , 3)(, 4,)(2)(5); // print: 1,2,3,4,5
_fn(1, , , 4)(, 3)(2)(5); // print: 1,2,3,4,5
fn(, 2)(
, _, 4)(1)(3)(5); // print: 1,2,3,4,5


至此,我们已经完整实现了一个 curry 函数~~


#### 7. 实现深拷贝


**浅拷贝和深拷贝的区别:**


浅拷贝:只拷贝一层,更深层的对象级别的只拷贝引用


深拷贝:拷贝多层,**每一级别的数据都会拷贝**。这样更改拷贝值就不影响另外的对象


ES6浅拷贝方法:Object.assign(target,...sources)



let obj={
id:1,
name:‘Tom’,
msg:{
age:18
}
}
let o={}
//实现深拷贝 递归 可以用于生命游戏那个题对二维数组的拷贝,
//但比较麻烦,因为已知元素都是值,直接复制就行,无需判断
function deepCopy(newObj,oldObj){
for(var k in oldObj){
let item=oldObj[k]
//判断是数组?对象?简单类型?
if(item instanceof Array){
newObj[k]=[]
deepCopy(newObj[k],item)
}else if(item instanceof Object){
newObj[k]={}
deepCopy(newObj[k],item)
}else{ //简单数据类型,直接赋值
newObj[k]=item
}
}
}


#### 8. 手写call, apply, bind


**手写call**



Function.prototype.myCall=function(context=window){ // 函数的方法,所以写在Fuction原型对象上
if(typeof this !==“function”){ // 这里if其实没必要,会自动抛出错误
throw new Error(“不是函数”)
}
const obj=context||window //这里可用ES6方法,为参数添加默认值,js严格模式全局作用域this为undefined
obj.fn=this //this为调用的上下文,this此处为函数,将这个函数作为obj的方法
const arg=[…arguments].slice(1) //第一个为obj所以删除,伪数组转为数组
res=obj.fn(…arg)
delete obj.fn // 不删除会导致context属性越来越多
return res
}
//用法:f.call(obj,arg1)
function f(a,b){
console.log(a+b)
console.log(this.name)
}
let obj={
name:1
}
f.myCall(obj,1,2) //否则this指向window

obj.greet.call({name: ‘Spike’}) //打出来的是 Spike


**手写apply**(arguments[this, [参数1,参数2.....] ])



Function.prototype.myApply=function(context){ // 箭头函数从不具有参数对象!!!!!这里不能写成箭头函数
let obj=context||window
obj.fn=this
const arg=arguments[1]||[] //若有参数,得到的是数组
let res=obj.fn(…arg)
delete obj.fn
return res
}
function f(a,b){
console.log(a,b)
console.log(this.name)
}
let obj={
name:‘张三’
}
f.myApply(obj,[1,2]) //arguments[1]


**手写bind**



this.value = 2
var foo = {
value: 1
};
var bar = function(name, age, school){
console.log(name) // ‘An’
console.log(age) // 22
console.log(school) // ‘家里蹲大学’
}
var result = bar.bind(foo, ‘An’) //预置了部分参数’An’
result(22, ‘家里蹲大学’) //这个参数会和预置的参数合并到一起放入bar中


简单版本



Function.prototype.bind = function(context, …outerArgs) {
var fn = this;
return function(…innerArgs) { //返回了一个函数,…rest为实际调用时传入的参数
return fn.apply(context,[…outerArgs, …innerArgs]); //返回改变了this的函数,
//参数合并
}
}


new失败的原因:


例:



// 声明一个上下文
let thovino = {
name: ‘thovino’
}

// 声明一个构造函数
let eat = function (food) {
this.food = food
console.log(${this.name} eat ${this.food})
}
eat.prototype.sayFuncName = function () {
console.log(‘func name : eat’)
}

// bind一下
let thovinoEat = eat.bind(thovino)
let instance = new thovinoEat(‘orange’) //实际上orange放到了thovino里面
console.log(‘instance:’, instance) // {}


生成的实例是个空对象


在`new`操作符执行时,我们的`thovinoEat`函数可以看作是这样:



function thovinoEat (…innerArgs) {
eat.call(thovino, …outerArgs, …innerArgs)
}


在new操作符进行到第三步的操作`thovinoEat.call(obj, ...args)`时,这里的`obj`是new操作符自己创建的那个简单空对象`{}`,但它其实并没有替换掉`thovinoEat`函数内部的那个上下文对象`thovino`。这已经超出了`call`的能力范围,因为这个时候要替换的已经不是`thovinoEat`函数内部的`this`指向,而应该是`thovino`对象。


**换句话说,我们希望的是`new`操作符将`eat`内的`this`指向操作符自己创建的那个空对象。但是实际上指向了`thovino`,`new`操作符的第三步动作并没有成功**!


可new可继承版本



Function.prototype.bind = function (context, …outerArgs) {
let that = this;

function res (…innerArgs) {
if (this instanceof res) {
// new操作符执行时
// 这里的this在new操作符第三步操作时,会指向new自身创建的那个简单空对象{}
that.call(this, …outerArgs, …innerArgs)
} else {
// 普通bind
that.call(context, …outerArgs, …innerArgs)
}
}
res.prototype = this.prototype //!!!
return res
}


#### 9. 手动实现new


new的过程文字描述:


1. 创建一个空对象 obj;
2. 将空对象的隐式原型(**proto**)指向构造函数的prototype。
3. 使用 call 改变 this 的指向
4. 如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。



function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.sayHi=function(){
console.log(‘Hi!我是’+this.name)
}
let p1=new Person(‘张三’,18)

手动实现new
function create(){
let obj={}
//获取构造函数
let fn=[].shift.call(arguments) //将arguments对象提出来转化为数组,arguments并不是数组而是对象 !!!这种方法删除了arguments数组的第一个元素,!!这里的空数组里面填不填元素都没关系,不影响arguments的结果 或者let arg = [].slice.call(arguments,1)
obj.proto=fn.prototype
let res=fn.apply(obj,arguments) //改变this指向,为实例添加方法和属性
//确保返回的是一个对象(万一fn不是构造函数)
return typeof res===‘object’?res:obj
}

let p2=create(Person,‘李四’,19)
p2.sayHi()


细节:



[].shift.call(arguments) 也可写成:
let arg=[…arguments]
let fn=arg.shift() //使得arguments能调用数组方法,第一个参数为构造函数
obj.proto=fn.prototype
//改变this指向,为实例添加方法和属性
let res=fn.apply(obj,arg)


#### 10. 手写promise(常见promise.all, promise.race)



// Promise/A+ 规范规定的三种状态
const STATUS = {
PENDING: ‘pending’,
FULFILLED: ‘fulfilled’,
REJECTED: ‘rejected’
}

class MyPromise {
// 构造函数接收一个执行回调
constructor(executor) {
this._status = STATUS.PENDING // Promise初始状态
this._value = undefined // then回调的值
this._resolveQueue = [] // resolve时触发的成功队列
this._rejectQueue = [] // reject时触发的失败队列

// 使用箭头函数固定this(resolve函数在executor中触发,不然找不到this)
const resolve = value => {
const run = () => {
// Promise/A+ 规范规定的Promise状态只能从pending触发,变成fulfilled
if (this._status === STATUS.PENDING) {
this._status = STATUS.FULFILLED // 更改状态
this._value = value // 储存当前值,用于then回调

// 执行resolve回调
while (this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(value)
}
}
}
//把resolve执行回调的操作封装成一个函数,放进setTimeout里,以实现promise异步调用的特性(规范上是微任务,这里是宏任务)
setTimeout(run)
}

// 同 resolve
const reject = value => {
const run = () => {
if (this._status === STATUS.PENDING) {
this._status = STATUS.REJECTED
this._value = value

while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(value)
}
}
}
setTimeout(run)
}

 // new Promise()时立即执行executor,并传入resolve和reject
 executor(resolve, reject)

}

// then方法,接收一个成功的回调和一个失败的回调
function then(onFulfilled, onRejected) {
// 根据规范,如果then的参数不是function,则忽略它, 让值继续往下传递,链式调用继续往下执行
typeof onFulfilled !== ‘function’ ? onFulfilled = value => value : null
typeof onRejected !== ‘function’ ? onRejected = error => error : null

// then 返回一个新的promise
return new MyPromise((resolve, reject) => {
const resolveFn = value => {
try {
const x = onFulfilled(value)
// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
}
}

const rejectFn = error => {
try {
const x = onRejected(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}

switch (this._status) {
  case STATUS.PENDING:

最后

技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。

}
}

}
}

const rejectFn = error => {
try {
const x = onRejected(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}

switch (this._status) {
  case STATUS.PENDING:

最后

技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。

[外链图片转存中…(img-qMuZKC5P-1715335750721)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值