Web前端最全前端面试js高频手写大全,2024年最新面试官常问的问题以及巧妙的回答

数据结构与算法

这一块在笔试、面试的代码题中考核较多,其中常考的数据结构主要有:数组、链表、队列、栈、Set、Map、哈希表等,不同数据结构有不同的方法以及储存原理,这些算是技术岗的必备知识。算法部分主要分为两大块,排序算法与一些其他算法题

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

排序算法根据考频高低主要有:快速排序、归并排序、堆排序、冒泡排序、插入排序、选择排序、希尔排序、桶排序、基数排序、Timsort这十种,这类考核点要么是算法的时间、空间复杂度、稳定度,要么是直接手写代码,故在理解算法原理的同时,对JS语言版的排序算法代码也要加强记忆。

  • 二叉树层序遍历
  • B 树的特性,B 树和 B+树的区别
  • 尾递归
  • 如何写一个大数阶乘?递归的方法会出现什么问题?
  • 把多维数组变成一维数组的方法
  • 知道的排序算法 说一下冒泡快排的原理
  • Heap 排序方法的原理?复杂度?
  • 几种常见的排序算法,手写
  • 数组的去重,尽可能写出多个方法
  • 如果有一个大的数组,都是整型,怎么找出最大的前 10 个数
  • 知道数据结构里面的常见的数据结构
  • 找出数组中第 k 大的数组出现多少次,比如数组【1,2, 4,4,3,5】第二大的数字是 4,出现两次,所以返回 2
  • 合并两个有序数组
  • 给一个数,去一个已经排好序的数组中寻找这个数的位 置(通过快速查找,二分查找)

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:

this._resolveQueue.push(resolveFn)

this._rejectQueue.push(rejectFn)

break;

case STATUS.FULFILLED:

resolveFn(this._value)

break;

case STATUS.REJECTED:

rejectFn(this._value)

break;

}

})

}

catch (rejectFn) {

return this.then(undefined, rejectFn)

}

// promise.finally方法

finally(callback) {

return this.then(value => MyPromise.resolve(callback()).then(() => value), error => {

MyPromise.resolve(callback()).then(() => error)

})

}

// 静态resolve方法

static resolve(value) {

return value instanceof MyPromise ? value : new MyPromise(resolve => resolve(value))

}

// 静态reject方法

static reject(error) {

return new MyPromise((resolve, reject) => reject(error))

}

// 静态all方法

static all(promiseArr) {

let count = 0

let result = []

return new MyPromise((resolve, reject) =>       {

if (!promiseArr.length) {

return resolve(result)

}

promiseArr.forEach((p, i) => {

MyPromise.resolve§.then(value => {

count++

result[i] = value

if (count === promiseArr.length) {

resolve(result)

}

}, error => {

reject(error)

})

})

})

}

// 静态race方法

static race(promiseArr) {

return new MyPromise((resolve, reject) => {

promiseArr.forEach(p => {

MyPromise.resolve§.then(value => {

resolve(value)

}, error => {

reject(error)

})

})

})

}

}

11. 手写原生AJAX

步骤

  1. 创建 XMLHttpRequest 实例

  2. 发出 HTTP 请求

  3. 服务器返回 XML 格式的字符串

  4. JS 解析 XML,并更新局部页面

不过随着历史进程的推进,XML 已经被淘汰,取而代之的是 JSON。

了解了属性和方法之后,根据 AJAX 的步骤,手写最简单的 GET 请求。

version 1.0:

myButton.addEventListener(‘click’, function () {

ajax()

})

function ajax() {

let xhr = new XMLHttpRequest() //实例化,以调用方法

xhr.open(‘get’, ‘https://www.google.com’)  //参数2,url。参数三:异步

xhr.onreadystatechange = () => {  //每当 readyState 属性改变时,就会调用该函数。

if (xhr.readyState === 4) {  //XMLHttpRequest 代理当前所处状态。

if (xhr.status >= 200 && xhr.status < 300) {  //200-300请求成功

let string = request.responseText

//JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象

let object = JSON.parse(string)

}

}

}

request.send() //用于实际发出 HTTP 请求。不带参数为GET请求

}

promise实现

function ajax(url) {

const p = new Promise((resolve, reject) => {

let xhr = new XMLHttpRequest()

xhr.open(‘get’, url)

xhr.onreadystatechange = () => {

if (xhr.readyState == 4) {

if (xhr.status >= 200 && xhr.status <= 300) {

resolve(JSON.parse(xhr.responseText))

} else {

reject(‘请求出错’)

}

}

}

xhr.send()  //发送hppt请求

})

return p

}

let url = ‘/data.json’

ajax(url).then(res => console.log(res))

.catch(reason => console.log(reason))

12. 手写节流防抖函数

防抖:

function debounce(fn, delay) {

if(typeof fn!==‘function’) {

throw new TypeError(‘fn不是函数’)

}

let timer; // 维护一个 timer

return function () {

var _this = this; // 取debounce执行作用域的this(原函数挂载到的对象)

var args = arguments;

if (timer) {

clearTimeout(timer);

}

timer = setTimeout(function () {

fn.apply(_this, args); // 用apply指向调用debounce的对象,相当于_this.fn(args);

}, delay);

};

}

input1.addEventListener(‘keyup’, debounce(() => {

console.log(input1.value)

}), 600)

节流:

function throttle(fn, delay) {

let timer;

return function () {

var _this = this;

var args = arguments;

if (timer) {

return;

最后

本人分享一下这次字节跳动、美团、头条等大厂的面试真题涉及到的知识点,以及我个人的学习方法、学习路线等,当然也整理了一些学习文档资料出来是给大家的。知识点涉及比较全面,包括但不限于前端基础,HTML,CSS,JavaScript,Vue,ES6,HTTP,浏览器,算法等等

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

前端视频资料:

return p

}

let url = ‘/data.json’

ajax(url).then(res => console.log(res))

.catch(reason => console.log(reason))

12. 手写节流防抖函数

防抖:

function debounce(fn, delay) {

if(typeof fn!==‘function’) {

throw new TypeError(‘fn不是函数’)

}

let timer; // 维护一个 timer

return function () {

var _this = this; // 取debounce执行作用域的this(原函数挂载到的对象)

var args = arguments;

if (timer) {

clearTimeout(timer);

}

timer = setTimeout(function () {

fn.apply(_this, args); // 用apply指向调用debounce的对象,相当于_this.fn(args);

}, delay);

};

}

input1.addEventListener(‘keyup’, debounce(() => {

console.log(input1.value)

}), 600)

节流:

function throttle(fn, delay) {

let timer;

return function () {

var _this = this;

var args = arguments;

if (timer) {

return;

最后

本人分享一下这次字节跳动、美团、头条等大厂的面试真题涉及到的知识点,以及我个人的学习方法、学习路线等,当然也整理了一些学习文档资料出来是给大家的。知识点涉及比较全面,包括但不限于前端基础,HTML,CSS,JavaScript,Vue,ES6,HTTP,浏览器,算法等等

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

[外链图片转存中…(img-dQTApsr9-1715885453675)]

前端视频资料:
[外链图片转存中…(img-ooIAC5dH-1715885453677)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值