最后
我可以将最近整理的前端面试题分享出来,其中包含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))
// 给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)
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])
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’))
~ 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
// 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));
我个人的理解是,如果一个对象的值等于父级(祖父级,曾祖父级…),则说明是循环引用了
定义一个空数组吗,且对于目标对象进行递归,每次都判断递归项是否为对象,是的话就放到数组里,而且每次判断属性值是否存在,在的话说明环引用了
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);
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对项目进行优化?
-
防抖、节流
-
浏览器的缓存机制
-
描述一下二叉树, 并说明二叉树的几种遍历方式?
-
项目类问题
-
笔试编程题:
最后
技术栈比较搭,基本用过的东西都是一模一样的。快手终面喜欢问智力题,校招也是终面问智力题,大家要准备一下一些经典智力题。如果排列组合、概率论这些基础忘了,建议回去补一下。