前端面试 - 常考的手撕代码题(2),字节跳动算法工程师面试总结

apply、call和 bind 方法详解(含手写)

3. 深拷贝


function deepClone(obj = {}) {

if (typeof obj !== ‘object’ || obj == null) {

return obj // obj 是 null ,或者不是对象和数组,直接返回

}

let result // 初始化返回结果

if (obj instanceof Array) {    // 判断是否为数组

result = []

} else {

result = {}

}

for (let key in obj) {

if (obj.hasOwnProperty(key)) { // 保证 key 不是原型的属性

result[key] = deepClone(obj[key]) // 递归调用!!!

}

}

return result    // 返回结果

}

let a = {

age: undefined,

sex: Symbol(‘male’),

jobs: function () {},

name: ‘cmk’

}

let b = JSON.parse(JSON.stringify(a))

console.log(b) // { name: ‘cmk’ }

let c = deepClone(a)

console.log© // { age: undefined, sex: Symbol(male), jobs: [Function: jobs], name: ‘cmk’ }

4. 防抖 节流


scroll 事件本身会触发页面的重新渲染,同时 scroll 事件的 handler 又会被高频度的触发, 因此事件的 handler 内部不应该有复杂操作,例如 DOM 操作就不应该放在事件处理中。 针对此类高频度触发事件问题(例如页面 scroll ,屏幕 resize,监听用户输入等),有两种常用的解决方法,防抖和节流。

Function.prototype.myThrottle = function(cb, wait) {

let flag = false;

return function(…rest) {

if (flag) {

return;

}

flag = true;// 在wait时间范围内。。通过flag不让执行cb;

setTimeout(() => {

cb.apply(this, rest);

flag = false;

}, wait)

}

}

Function.prototype.myDebounce = function(cb, wait) {

let timer = null;

return function (…rest) {

if (timer) {

// 如果上次的定时器还存在,则清除。重新等待时间

clearTimeour(timer);

}

timer = setTimeout(() => {// 使用箭头函数,确保this还能定义在当前执行环境

// 里边的闭包的this必须保持和callback保持一致。确保使用ok

cb.apply(this, rest);

}, wait)

}

}

6. 字符串转驼峰


// 字符串转驼峰,注意第一个字符不大写,如border-bottom-color -> borderBottomColor

function Change(str) {

let arr = str.split(‘-’) // 字符串分割为数组

// map返回由新的项组成的数组

arr = arr.map((item, index) => {

return index === 0 ? item : item.charAt(0).toUpperCase() + item.substring(1) // 第一个字符转大写与之后的字符合并

})

return arr.join(“”) // 数组合并为字符串

}

console.log(Change(‘border-bottom-color’))

7. 数组拍平


function flat(arr) {

// 验证 arr 中,还有没有深层数组 [1, 2, [3, 4]]

const isDeep = arr.some(item => item instanceof Array)

if (!isDeep) {

return arr // 已经是 flatern [1, 2, 3, 4]

}

// oncat只能解决单层[]

const res = Array.prototype.concat.apply([], arr)

return flat(res) // 递归

}

const res = flat( [1, 2, [3, 4, [10, 20, [100, 200]]], 5] )

console.log(res)

function flattern(arr) {

return arr.reduce((preValue, currValue, currIndex, array) => {

return preValue.concat(Array.isArray(currValue) ? flattern(currValue) : currValue)

}, []) // []作为第一个preValue

}

// toString & split

function flattern2(arr) {

return arr.toString().split(‘,’).map(item => {

return Number(item) // split分隔后数组元素为字符串形式, 需要转换为数值形式

})

}

// join & split

function flattern3(arr) {

return arr.join(‘,’).split(‘,’).map(item => { // join方法和toString方法效果一样?

return parseInt(item)

})

}

// 扩展运算符

function flattern5(arr) {

while (arr.some(item => Array.isArray(item))) { // 如果数组元素中有数组

arr = [].concat(…arr) // [].concat(…[1, 2, 3, [4, 5]]) 扩展运算符可以将二维数组变为一维的

}

return arr

}

8. 数组去重


// 传统方式

function unique(arr) {

const res = []

arr.forEach(item => {

if (res.indexOf(item) < 0) { // 没有当前元素

res.push(item)

}

})

return res

}

// 使用 Set (无序,不能重复)

function unique(arr) {

const set = new Set(arr)

return […set] // 解构

}

const res = unique([30, 10, 20, 30, 40, 10])

console.log(res)

9. 函数柯里化


在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。

function curry(fn, args) {

var length = fn.length;

var args = args || [];

return function(){

newArgs = args.concat(Array.prototype.slice.call(arguments));

if (newArgs.length < length) {

return curry.call(this,fn,newArgs);

}else{

return fn.apply(this,newArgs);

}

}

}

function multiFn(a, b, c) {

return a * b * c;

}

var multi = curry(multiFn);

multi(2)(3)(4);

multi(2,3,4);

multi(2)(3,4);

multi(2,3)(4);

10. 实现jQuery


class jQuery {

constructor(selector) {

const result = document.querySelectorAll(selector)

const length = result.length

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

this[i] = result[i]

}

this.length = length

this.selector = selector

}

get(index) {

return this[index]

}

each(fn) {

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

const elem = this[i]

fn(elem)

}

}

on(type, fn) {

return this.each(elem => {

elem.addEventListener(type, fn, false)

})

}

// 扩展很多 DOM API

}

// 插件

jQuery.prototype.dialog = function (info) {

alert(info)

}

// 扩展 “造轮子”

class myJQuery extends jQuery {

constructor(selector) {

super(selector)

}

// 扩展自己的方法

addClass(className) {

}

style(data) {

}

}

11. 手写promise


Promise内部then函数注册后续需要执行的函数,resolve函数执行。需要保证函数在执行前都已注册好,所以resolve内部执行函数的代码需要加入延时机制setTimeout(0)放在任务队列的末尾。加入状态机制,若为pending,则将函数注册,等待后续resolve调用。若为fulfilled,则立即执行。resolve函数,将状态设为fulfilled

function myPromise(constructor){

let self=this;

self.status=“pending” //定义状态改变前的初始状态

self.value=undefined;//定义状态为resolved的时候的状态

self.reason=undefined;//定义状态为rejected的时候的状态

function resolve(value){

//两个===“pending”,保证了状态的改变是不可逆的

if(self.status===“pending”){

self.value=value;

self.status=“resolved”;

}

}

function reject(reason){

//两个===“pending”,保证了状态的改变是不可逆的

if(self.status===“pending”){

self.reason=reason;

self.status=“rejected”;

}

}

//捕获构造异常

try{

constructor(resolve,reject);

}catch(e){

reject(e);

}

}

实现promise链   参考链接:https://blog.csdn.net/chenzeze0707/article/details/79717354

复杂版本 :https://juejin.im/post/5c9c3989e51d454e3a3902b6#heading-17

12. 模拟new


  • 创建一个新的空对象

  • this绑定到空对象

  • 使空对象的__proto__指向构造函数的原型(prototype)

  • 执行构造函数,为空对象添加属性

  • 判断构造函数的返回值是否为对象,如果是对象,就使用构造函数的返回值,否则返回创建的对象

function New(func) {

var res = {};

if (func.prototype !== null) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

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

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

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

总结:

  • 函数式编程其实是一种编程思想,它追求更细的粒度,将应用拆分成一组组极小的单元函数,组合调用操作数据流;

  • 它提倡着 纯函数 / 函数复合 / 数据不可变, 谨慎对待函数内的 状态共享 / 依赖外部 / 副作用;

Tips:

其实我们很难也不需要在面试过程中去完美地阐述出整套思想,这里也只是浅尝辄止,一些个人理解而已。博主也是初级小菜鸟,停留在表面而已,只求对大家能有所帮助,轻喷🤣;

我个人觉得: 这些编程范式之间,其实并不矛盾,各有各的 优劣势

理解和学习它们的理念与优势,合理地 设计融合,将优秀的软件编程思想用于提升我们应用;

所有设计思想,最终的目标一定是使我们的应用更加 解耦颗粒化、易拓展、易测试、高复用,开发更为高效和安全

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

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

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-MsYypzUf-1712013003674)]

总结:

  • 函数式编程其实是一种编程思想,它追求更细的粒度,将应用拆分成一组组极小的单元函数,组合调用操作数据流;

  • 它提倡着 纯函数 / 函数复合 / 数据不可变, 谨慎对待函数内的 状态共享 / 依赖外部 / 副作用;

Tips:

其实我们很难也不需要在面试过程中去完美地阐述出整套思想,这里也只是浅尝辄止,一些个人理解而已。博主也是初级小菜鸟,停留在表面而已,只求对大家能有所帮助,轻喷🤣;

我个人觉得: 这些编程范式之间,其实并不矛盾,各有各的 优劣势

理解和学习它们的理念与优势,合理地 设计融合,将优秀的软件编程思想用于提升我们应用;

所有设计思想,最终的目标一定是使我们的应用更加 解耦颗粒化、易拓展、易测试、高复用,开发更为高效和安全

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值