2024年最全前端核心手写面试题(看你的马步扎实不扎实),2024年最新看上面试官

最后

我可以将最近整理的前端面试题分享出来,其中包含HTML、CSS、JavaScript、服务端与网络、Vue、浏览器、数据结构与算法等等,还在持续整理更新中,希望大家都能找到心仪的工作。

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

篇幅有限,仅展示部分截图:

手在快1秒执行一次

深拷贝


var arr = {

a: 2,

b: [33]

}

function cloneDeep(arr = {}) {

// 终止递归 判断如果传进来的数据不是 object 或者 传进来的是一个 null 直接返回

if (!arr || typeof arr != ‘object’ || arr == null) return arr

// 声明一个对象

let result

// 用 instanceof 判断原型链上是否有该类型的原型 是 Array => [] ! Arrays =>{}

arr instanceof Array ? result = [] : result = {}

// forin 循环对象的key值

for (const key in arr) {

// 对象 key 赋值 result

result[key] = cloneDeep(arr[key])

}

return result

}

let arr2 = cloneDeep(arr)

// let arr2 = […arr]

arr2.b[0] = 5

console.log(arr);// {a: 2, b: Array(1)} Array a: 2 b: [33]

console.log(arr2);// {a: 2, b: Array(1)} Array a: 2 b: [5]

// 修改新对象不影响原来的对象

类型判断


/*

实现一个类型判断

[] 返回 array

{} 返回object

/^$/ RegExp

1 number

*/

// 第一种方案

function type_of(obj) {

let res = Object.prototype.toString.call(obj).split(’ ')[1] // 输出 RegExp]

/*

res.substring(0, res.length - 1) 从第 0 项开始截取 截取到它的长度 -1

也就是最后一项的前一项 把最后的一项 ] 给干掉

*/

return res.substring(0, res.length - 1).toLowerCase()

}

// 第二种方案

function type_of(obj) {

// .slice(8, -1) 从 第 8项开始截取 -1 不要最后一项

return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase()

}

数字千位分割


const format = (n) => {

let num = n.toString() // 拿到传进来的 number 数字 进行 toString

let len = num.length // 在拿到字符串的长度

// 当传进来的结果小于 3 也就是 千位还把结果返回出去 小于3 不足以分割

if (len < 3) {

return num

} else {

let render = len % 3 //传入 number 的长度 是否能被 3 整除

console.log(render)

/*

match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。

该方法类似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。

var str = ‘123123000’

str.match(/\w{3}/g).join(‘,’) // 123,123,000

*/

if (render > 0) { // 说明不是3的整数倍

return num.slice(0, render) + ‘,’ + num.slice(render, len).match(/\d{3}/g).join(‘,’)

} else {

return num.slice(0, len).match(/\d{3}/g).join(‘,’)

}

}

}

let str = format(298000)

console.log(str)

生成随机数


MDN随机数 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/random

// 生成随机数

function random(min, max) {

// Math.floor() 向下进行取证

return Math.floor(Math.random() * (max - min + 1) + min)

}

console.log(random(3, 5))

手写call方法


// 给function的原型上面添加一个 _call 方法

Function.prototype._call = function (context) {

// 判断调用者是否是一个函数 this 就是调用者

if (typeof this != ‘function’) {

return

}

// 如果有 context 传参就是传参者 没有就是window

that = context || window

// 保存当前调用的函数

that.fn = this // 吧 this == fns 存到that.fn来

// 截取传过来的参数

/*

arguments

a: 1

fn: ƒ fns()

*/

// 通过 slice 来截取传过来的参数

const local = […arguments].slice(1)

// 传入参数调用函数

let result = that.fn(…local)

// 删出 fn 属性

delete that.fn

return result

}

let obj = { a: 1 }

function fns(a, b) {

console.log(a, b);

console.log(this)

}

fns._call(obj, 23, 555)

手写apply


Function.prototype.myApply = function (context) {

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

// 判断当前调用者是否为函数

return

}

// 保存传入的this指向

that = context || window

// 保存当前调用的函数

that.fn = this

let result

// 这里开始判断传入的参数是否存在,此时参数是一个数组形式[thisArg,[传参]]

// 那么如果arguments[1]即传参存在的时候,就是需要传参调用保存的函数

// 如果不存在就直接调用函数

if (arguments[1]) {

result = that.fn(…arguments[1])

} else {

result = that.fn()

}

return result

}

let obj = {

a: 1

}

function fn(…val) {

console.log(this)

console.log(…val)

}

fn.apply(obj, [1, 4, 5, 6, 7, 9])

手写 bind


Function.prototype._bind = function (context) {

// 当调用用了_bind 函数 this 指向调用 _bind 的那个函数

// this 不是 function 不让使用 抛出类型错误

if (typeof this != ‘function’) {

throw new TypeError(‘Error’)

}

// 获取参数 除了传过来的第一项我全都要

const args = […arguments].slice(1)

// 吧 function 进行一个缓存

fn = this

// 返回一个函数 因为 bind 不是立即调用的

return function Fn() {

// 判断 this 原型链上是否又该类型的原型 Fn

return fn.apply(this instanceof Fn ? new fn(…arguments) : context, args.concat(…arguments))

}

}

let obj = {

a: 1

}

function fn(…val) {

console.log(this) // obj

console.log(…val) // 231, 31242344, 432

}

fn._bind(obj, 231, 31242344, 432)()

数组去重


/*

数组的去重

*/

let ylbsz = [1, 35, 6, 78, 66, 6, 35]

function _set(arr) {

// 放一个新数组

let newArr = []

for (let i = 0; i < arr.length; i++) {

if (newArr.indexOf(arr[i]) == -1) {

newArr.push(arr[i])

}

}

return newArr

}

console.log(_set(ylbsz))

console.log([…new Set([11, 11, 222, 222])])

// 字符串去重

let str = ‘123321你好你好’

console.log([…new Set(str.split(‘’))].join(‘’))

数组排序(冒泡)


function sort(arr) {

// 外层循环控制的是比较的轮数,你要循环几轮

// arr.length (5 ) - 1 = 4

for (let i = 0; i < arr.length - 1; i++) {

// 内层循环控制的每一轮交换的次数

// 第一轮要比较4次 下标为 0 arr.length - 1(4) - i(0) = 4

// 第二轮要比较3次 下标为 1 arr.length - 1(4) - i(1) = 3

// 第三轮要比较2次 下标为 2 arr.length - 1(4) - i(2) = 2

// 第四轮要比较1次 下标为 3 arr.length - 1(4) - i(3) = 1

// 内层循环控制的每一轮交换的次数

for (let j = 0; j < arr.length - 1 - i; j++) {

// arr[j] 第一项 arr[j + 1] 第二项

// arr[j] 第二项 arr[j + 1] 第三项

// …

if (arr[j] > arr[j + 1]) {

// 大于后一项 做一个交换变量 es6 结构赋值 的变量交换

[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]

}

}

}

return arr

}

console.log(sort([200, 100, 3, 9, 4]));

数组的排序(快排)


let arr = [1, 4, 6, 7, 7, 9]

// 1. 取出我们数组的中间项 用splice 拿到中间项

// 2. 在声明两个空数组,一个空数组用来放置 中间一项 大于 循环的每一项的话 就把他放到左边的数组里,下

// 余的话就放到右边的数组

// 3.接下来就是循环便利了

// 最后再次利用函数自己调用自己一直执行 递归

// 4. 如果传进来的数组 小于等于 1 的话就终止递归循环

// 做一个快排

function fn(arr) {

// 如果传进来的数组 小于等于 1 的话就终止递归循环

if (arr.length <= 1) return arr

// 总长度除以2的话就可以拿到中间一项的下标

let n = Math.floor(arr.length / 2)

// splice 返回被删除的元素 下标为 0 就是只拿第一项 因为它只返回一个数据 就是被删除的一项

let cen = arr.splice(n, 1)[0]

// 2. 在声明两个空数组,一个空数组用来放置 中间一项 大于 循环的每一项的话 就把他放到左边的数组里,下

// 余的话就放到右边的数组

let leftArr = []

let rightArr = []

// 接下来就是循环便利了 判断

for (let j = 0; j < arr.length; j++) {

let item = arr[j]

// > 如果是大于就是 从大到小 如果是小于 < 从小到大

cen < item ? rightArr.push(item) : leftArr.push(item)

}

return fn(leftArr).concat(cen, fn(rightArr))

}

console.log(fn(arr))

数组排序(插入排序)


function insert(arr) {

// 1、准备一个数组这个数组就是新抓的牌,开始先抓一张牌进来

let handle = []

handle.push(arr[0])

// 2、第二次开始按顺序抓牌一直把牌抓光了

for (let i = 1; i < arr.length; i++) {

// a 是新抓的牌

let a = arr[i]

// 和手里的牌进行比较(从后向前比)

// handle.length - 1 因为下标是从 0 开始数的

for (let j = handle.length - 1; j >= 0; j–) {

// 每一次要比较手里的牌

let b = handle[j]

// 如果当前新牌a比b 大了,可就要放到 b 的后面

// 如果 a > b 就是表示从小到大

// a < b 送大到小

if (a > b) {

// 如果想要放到 j 的后面就要加上一个 1,

/**

  • 比如说我要插队一般要插就是插到 a 的前面

  • 但是我想要插到 a 的后面怎么办

  • 那这个时候就要插到 a 下一个人的人前面就可以了

*/

handle.splice(j + 1, 0, a)

break

}

// 还有一种情况就是如果你比较到第一项的还比较吗,不比较了

// 我们吧新牌放到最前面即可

if (j == 0) {

handle.unshift(a)

}

}

}

return handle

}

console.log(insert([2, 8, 5, 92, 52, 4]))

数组的最大值最小值


const array = [5, 4, 7, 8, 9, 2];

let num = array.reduce((a, b) => a > b ? a : b);

console.log(num) // 9

let nums = array.reduce((a, b) => a < b ? a : b);

console.log(nums) // 2

对象去重


/@ 数据处理对象去重/

let arr = [

{

a: 1,

b: 2,

id: 6666

},

{

a: 1,

b: 2,

id: 6666

},

{

a: 6,

b: 8,

id: 77777

},

{

a: 8,

b: 2,

id: 88888

},

]

/*

@ 输出 :

0: { a: 1, b: 2, id: 6666 }

1: { a: 6, b: 8, id: 77777 }

2: { a: 8, b: 2, id: 88888 }

*/

function ssff(arr4, id) {

var obj = {};

function deWeightFour() {

arr4 = arr4.reduce(function (a, b) {

obj[b.id] ? ‘’ : obj[b.id] = true && a.push(b);

return a;

}, [])

return arr4;

}

var newArr4 = deWeightFour();

return newArr4

}

console.log(ssff(arr, ‘id’))

手写 indexOf


~ function () {

// 在函数前加上波浪号,其作用是把函数声明转换为表达式,

// 就可以直接将下面的代码放入某个函数里运行。

// 不用indexOf 和 includes

function myIndexOf(a) {

// 1、 这个也可以正则实现 下面代码

// let reg = new RegExp(a)

// res = reg.exec(this)

// return res === nu ll ? -1 : res.index

// 这个也可以正则实现

let lena = a.length

y = this.length

flag = -1

if (lena > y) return -1

// 如果输入的字符串大于要检测的字符串直接 -1

for (var i = 0; i <= y - lena; i++) {

if (this.substr(i, lena) === a) {

// substr() 方法可在字符串中抽取从 start 下标开始的指定数目的字符。

flag = i

break

}

}

return flag

}

String.prototype.myIndexOf = myIndexOf

}()

let demo = ‘dwanlghMappaw’

let str = ‘h’

console.log(demo.myIndexOf(str));

手写 instanceof


// instanceof

// 1、只要当前类出现在实例的原型上,结果都为 true

// 2、由于我们可以肆意的修改原型的指向,所以检测出来的结果是不准确的

// 3、不能检测基本数据类型

var arr = []

console.log(arr);

function instance_of(example, classFunc) { // 实例.proto === 类.prototype => instanceof

let classFuncPrototype = classFunc.prototype // 这个就代表类的原型

proto = Object.getPrototypeOf(example) // example.proto 实例的.proto

while (true) { // 我也不知道你要找多少次 while循环

if (proto === null) { // 找到最顶层为null就说明没找到 返回 false

return false

}

if (proto === classFuncPrototype) {

return true

}

proto = Object.getPrototypeOf(proto)

}

}

console.log(instance_of(arr, Array));

console.log(instance_of(arr, Object));

console.log(instance_of(arr, RegExp));

怎么判断两个对象是否相等?


/*

① 首先比较两个对象的长度,如果长度不相等使flag为false,为不相等;

② 如果长度相等那就遍历对象1(对象2也可以),利用hasOwnProperty() 方法查看对象1中是否包含对象2中的属性或者方法,如果不包含则使flag为false,为不想等。

hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。

③ 接下来判断两对象的内存地址是否相同,不同则为true

*/

function compreObj(obj1, obj2) {

var flag = true;

function compre(obj1, obj2) {

if (Object.keys(obj1).length != Object.keys(obj2).length) {

flag = false;

} else {

for (let x in obj1) {

if (obj2.hasOwnProperty(x)) {

if (obj1[x] !== obj2[x]) {

compre(obj1[x], obj2[x]);

}

} else {

flag = false;

}

}

}

if (flag === false) {

return false;

} else {

return true;

}

}

return compre(obj1, obj2)

}

console.log(compreObj(对象1, 对象2));

如何快速让字符串变成已千为精度的数字


var str = “10000000000”;

/*

第一种:把数字转换成字符串后,打散为数组,再从末尾开始,逐个把数组中的元素插入到新数组(result)的开头。 每插入一个元素,counter就计一次数(加1),

当counter为3的倍数时,就插入一个逗号,但是要注意开头(i为0时)不需要逗号。最后通过调用新数组的join方法得出结果。

*/

String.prototype.toThousands = function(){

var num = this;

var result = [ ], counter = 0;

num = (num || 0).toString().split(‘’);

for (var i = num.length - 1; i >= 0; i–) {

counter++;

result.unshift(num[i]);

if (!(counter % 3) && i != 0) { result.unshift(‘,’); }

}

return result.join(‘’);

}

console.log(str.toThousands());

/*

第二种:通过正则表达式循环匹配末尾的三个数字,每匹配一次,就把逗号和匹配到的内容插入到结果字符串的开头,

然后把匹配目标(num)赋值为还没匹配的内(RegExp.leftContext)。如果数字的位数是3的倍数时,最后一次匹配到的内容肯定是三个数字,

但是最前面的三个数字前不需要加逗号;如果数字的位数不是3的倍数,那num变量最后肯定会剩下1到2个数字,循环过后,要把剩余的数字插入到结果字符串的开头。

*/

function toThousands(num) {

var num = (num || 0).toString(), re = /\d{3}$/, result = ‘’;

while ( re.test(num) ) {

result = RegExp.lastMatch + result;

if (num !== RegExp.lastMatch) {

result = ‘,’ + result;

num = RegExp.leftContext;

} else {

num = ‘’;

break;

}

}

if (num) { result = num + result; }

return result;

}

console.log(toThousands(str));

第三种:第二种的改良版

function toThousands(num) {

var num = (num || 0).toString(), result = ‘’;

while (num.length > 3) {

result = ‘,’ + num.slice(-3) + result;

num = num.slice(0, num.length - 3);

}

if (num) { result = num + result; }

return result;

}

console.log(toThousands(str));

第四种:懒人版

function toThousands(num) {

return (num || 0).toString().replace(/(\d)(?=(?:\d{3})+$)/g, ‘$1,’);

}

console.log(toThousands(str));

如何判断JS对象中是否存在循环引用


我个人的理解是,如果一个对象的值等于父级(祖父级,曾祖父级…),则说明是循环引用了

定义一个空数组吗,且对于目标对象进行递归,每次都判断递归项是否为对象,是的话就放到数组里,而且每次判断属性值是否存在,在的话说明环引用了

function cycle(obj, parent) {

//表示调用的父级数组

var parentArr = parent || [obj];

for (var i in obj) {

if (typeof obj[i] === “object”) {

//判断是否有循环引用

parentArr.forEach((pObj) => {

if (pObj === obj[i]) {

obj[i] = “[cycle]”

}

});

cycle(obj[i], […parentArr, obj[i]])

}

}

return obj;

}

/**@2方法*/

function hasLoop(obj){

// 判断对象内部是否有和源相同的属性

function findLoop(target, src){

// 源数组,并将自身传入

const source = src.slice().concat([target])

for(const key in target){

// 如果是对象才需要判断

if(typeof target[key] === ‘object’){

// 如果在源数组中找到 || 递归查找内部属性找到相同

if(source.indexOf(target[key]) > -1 || findLoop(target[key], source)){

return true

}

}

}

return false

}

// 如果传入值是对象,则执行判断,否则返回false

return typeof obj === ‘object’ ? findLoop(obj, []) : false

}

简易版Promise.all

function PromiseAll(promiseArray) { //返回一个Promise对象

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

if (!Array.isArray(promiseArray)) { //传入的参数是否为数组

return reject(new Error(‘传入的参数不是数组!’))

}

const res = []

let counter = 0 //设置一个计数器

for (let i = 0; i < promiseArray.length; i++) {

Promise.resolve(promiseArray[i]).then(value => {

counter++ //使用计数器返回 必须使用counter

res[i] = value

if (counter === promiseArray.length) {

resolve(res)

}

}).catch(e => reject(e))

}

})

}

const s1 = new Promise((res, rej) => {

setTimeout(() => {

res(‘p1’)

}, 1000)

})

const s2 = new Promise((res, rej) => {

setTimeout(() => {

res(‘p2’)

}, 2000)

})

const s3 = new Promise((res, rej) => {

setTimeout(() => {

res(‘p3’)

}, 3000)

})

const test = PromiseAll([s1,s2, s3])

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

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

console.log(test);

一维数组转换 tree 结构


let arr = [

{ id: 1, name: ‘部门1’, pid: 0 },

{ id: 2, name: ‘部门2’, pid: 1 },

{ id: 3, name: ‘部门3’, pid: 1 },

{ id: 4, name: ‘部门4’, pid: 3 },

{ id: 5, name: ‘部门5’, pid: 4 },

]

// // 上面的数据转换为 下面的 tree 数据

// [

// {

// “id”: 1,

// “name”: “部门1”,

// “pid”: 0,

// “children”: [

// {

// “id”: 2,

// “name”: “部门2”,

// “pid”: 1,

// “children”: []

// },

// {

// “id”: 3,

// “name”: “部门3”,

// “pid”: 1,

// “children”: [

// {

// id: 4,

// name: ‘部门4’,

// pid: 3,

// “children”: [

// {

// id: 5,

// name: ‘部门5’,

// pid: 4,

// “children”: []
ES6

  • 列举常用的ES6特性:

  • 箭头函数需要注意哪些地方?

  • let、const、var

  • 拓展:var方式定义的变量有什么样的bug?

  • Set数据结构

  • 拓展:数组去重的方法

  • 箭头函数this的指向。

  • 手写ES6 class继承。

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

微信小程序

  • 简单描述一下微信小程序的相关文件类型?

  • 你是怎么封装微信小程序的数据请求?

  • 有哪些参数传值的方法?

  • 你使用过哪些方法,来提高微信小程序的应用速度?

  • 小程序和原生App哪个好?

  • 简述微信小程序原理?

  • 分析微信小程序的优劣势

  • 怎么解决小程序的异步请求问题?

其他知识点面试

  • webpack的原理

  • webpack的loader和plugin的区别?

  • 怎么使用webpack对项目进行优化?

  • 防抖、节流

  • 浏览器的缓存机制

  • 描述一下二叉树, 并说明二叉树的几种遍历方式?

  • 项目类问题

  • 笔试编程题:

最后

技术栈比较搭,基本用过的东西都是一模一样的。快手终面喜欢问智力题,校招也是终面问智力题,大家要准备一下一些经典智力题。如果排列组合、概率论这些基础忘了,建议回去补一下。

  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值