前端核心手写面试题(看你的马步扎实不扎实),2024年最新如何系统全面性学习前端语言

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
img

正文

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”: []

// },

// ]

// },

// ]

// }

// ]

// }

// ]

function tree(items) {

// 1、声明一个数组和一个对象 用来存储数据

let arr = []

let obj = {}

// 2、for of 便利我么传进来的一个数据,给当前的数据添加children 属性为 array 把他放到我们的obj对象里面

for (let item of items) {

obj[item.id] = { …item, children: [] }

}

// 3、for of 再次便利然后逻辑处理

for (let item of items) {

// 4、把数据里面的id 取出来赋值 方便下一步的操作

let id = item.id

let pid = item.pid

// 5、根据 id 将 obj 里面的每一项数据取出来

let treeitem = obj[id]

// 6、如果是第一项的话 吧treeitem 放到 arr 数组当中

if (pid === 0) {

// 把数据放到 arr 数组里面

arr.push(treeitem)

} else {

// 如果没有 pid 找不到 就开一个 obj { }

if (!obj[pid]) {

obj = {

children: []

}

}

// 否则给它的 obj 根基 pid(自己定义的下标) 进行查找 它里面的children属性 然后push

obj[pid].children.push(treeitem)

}

}

// 返回处理好的数据

return arr

}

console.log(tree(arr))

proxy 实现数据绑定


Document
  • 思考题 LazyMan


    // LazyMan(‘Hank’);

    // 输出:

    // Hi! This is Hank!

    // LazyMan(‘Hank’).sleep(3).eat(‘dinner’)

    // 输出:

    // Hi! This is Hank!

    // //等待3秒…

    // Wake up after 3

    // Eat dinner~

    // LazyMan(‘Hank’).eat(‘dinner’).eat(‘supper’)

    // 输出:

    // Hi This is Hank!

    // Eat dinner~

    // Eat supper~

    // LazyMan(‘Hank’).sleepFirst(2).eat(‘dinner’).sleep(3).eat(‘supper’)

    // 输出:

    // 等待2秒…

    // Wake up after 2

    ES6

    • 列举常用的ES6特性:

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

    • let、const、var

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

    • Set数据结构

    • 拓展:数组去重的方法

    • 箭头函数this的指向。

    • 手写ES6 class继承。

    微信小程序

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

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

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

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

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

    • 简述微信小程序原理?

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

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

    网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

    需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
    img

    一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
    rop] = value

    observer()

    }

    })

    function observer() {

    li.innerHTML = obj.name

    search.value = obj.name

    }

    setTimeout(() => {

    obj.name = ‘孙志豪’

    }, 1000)

    search.oninput = function () {

    obj.name = this.value

    }

    思考题 LazyMan


    // LazyMan(‘Hank’);

    // 输出:

    // Hi! This is Hank!

    // LazyMan(‘Hank’).sleep(3).eat(‘dinner’)

    // 输出:

    // Hi! This is Hank!

    // //等待3秒…

    // Wake up after 3

    // Eat dinner~

    // LazyMan(‘Hank’).eat(‘dinner’).eat(‘supper’)

    // 输出:

    // Hi This is Hank!

    // Eat dinner~

    // Eat supper~

    // LazyMan(‘Hank’).sleepFirst(2).eat(‘dinner’).sleep(3).eat(‘supper’)

    // 输出:

    // 等待2秒…

    // Wake up after 2

    ES6

    • 列举常用的ES6特性:

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

    • let、const、var

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

    • Set数据结构

    • 拓展:数组去重的方法

    • 箭头函数this的指向。

    • 手写ES6 class继承。

    微信小程序

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

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

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

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

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

    • 简述微信小程序原理?

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

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

    网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

    需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
    [外链图片转存中…(img-0eXj4E6J-1713477643961)]

    一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值