前端高频面试题(15K级别),如何准备字节跳动的面试

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

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

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

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

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

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

正文

26.如何优雅的调试需要发布的 Npm 包?


  • 在需要调试的npm包目录下结构下的控制台输入npm link 这个命令会把当前包映射到本地的一个全局的npm包里面;

  • 在引用的目录结构下的控制台输入 npm link 包名称 这个命令会把本地引用的这个npm包的路径定位到全局的npm包下;

  • 全局的npm包相当于一个中转站,在编辑区域与引用区域之间中转。

27.了解 Git (Submodule)子模块吗?简单介绍一下 Git 子模块的作用?


子模块是进行开发和需求进行对接将需求文档作为子模块项目,嵌入开发人员的项目中。子模块的使用既可以减少需求或设计人员的git操作,又可以及时的将doc文档发布到项目的目录文件下,而且不会对开发人员的项目产生任何影响。

28.Git 如何修改已经提交的 Commit 信息?


  • git rebase -i <commit id> 列出 commit 列表

  • 找到需要修改的 commit 记录,把 pick 修改为 edit 或 e,:wq 保存退出

  • 修改 commit 的具体信息git commit --amend,保存并继续下一条git rebase --continue,直到全部完成

  • 中间也可跳过或退出git rebase (–skip | --abort)

29.Git 如何撤销 Commit 并保存之前的修改?


  • 查看commit git log --pretty=oneline

  • 撤销到上一个commit,但是保存当前的修改。git reset --soft <commit>

  • 修改成功。重建分支,进行提交。

30.Git 如何 ignore 被 commit 过的文件?


  • 删除 track 的文件 (已经 commit 的文件)

  • 在 .gitignore 文件中添加忽略规则

在 .gitignore 文件中添加 ignore 条目, 如: .DS_Store 提交 .gitignore 文件: git commit -a -m “添加ignore规则”

  • 推送到远程仓库让 ignore 规则对于其他开发者也能生效

31.在使用 Git 的时候如何规范 Git 的提交说明(Commit 信息)?


用Commitizen,Commitizen 是一个撰写符合上面 Commit Message 标准的一款工具。在push操作时检查commit的信息,使用正则检查是否匹配(比如使用angular的git规范),不符合的不允许Push。

32.ESLint 和 Prettier 的区别是什么?两者在一起工作时会产生问题吗?


这俩解决的不是一个问题,ESLint 主要解决的是代码质量问题;Prettier主要解决的是代码风格问题。两者在一起会产生问题。

33.如何调试 Node.js 代码?如何调试 Node.js TypeScript 代码?在浏览器中如何调试 Node.js 代码?


从nodejs8开始,node去掉了_debugger , 内部集成了inspect , 以往使用node-inspect实现的在线调试不再可用.node8开始要用新方法了。

  • 在服务端用inspect模式运行nodejs

node --inspect-brk=0.0.0.0:8080 index.js

  • 打开chrome浏览器 地址栏输入chrome://inspect,在弹出的界面中输入ip:port即可调试。

34.CDN 服务如何实现网络加速?


CDN的工作原理就是将您源站的资源缓存到位于全球各地的CDN节点上,用户请求资源时,就近返回节点上缓存的资源,而不需要每个用户的请求都回您的源站获取,避免网络拥塞、缓解源站压力,保证用户访问资源的速度和体验。

35.WebSocket 使用的是 TCP 还是 UDP 协议?


是基于TCP的,websocket的协议是在TCP/IP协议簇的应用层,和http在同一层。

36.什么是单工、半双工和全双工通信?


  • 单工数据传输只支持数据在一个方向上传输;

  • 半双工数据传输允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;

  • 全双工数据通信允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。

37.简单描述 HTTP 协议发送一个带域名的 URL 请求的协议传输过程?(DNS、TCP、IP、链路)


38.输入地址回车后


  • 浏览器通过请求得到一个HTML文本

  • 渲染进程解析HTML文本,构建DOM树

  • 解析HTML的同时,如果遇到内联样式或者样式脚本,则下载并构建样式规则(stytle rules),若遇到JavaScript脚本,则会下载执行脚本。

  • DOM树和样式规则构建完成之后,渲染进程将两者合并成渲染树(render tree)

  • 渲染进程开始对渲染树进行布局,生成布局树(layout tree)

  • 渲染进程对布局树进行绘制,生成绘制记录

  • 渲染进程的对布局树进行分层,分别栅格化每一层,并得到合成帧

  • 渲染进程将合成帧信息发送给GPU进程显示到页面中

39.Cookie 可以在服务端生成吗?Cookie 在服务端生成后的工作流程是什么样的?


可以。HTTP 协议中的 Cookie 包括 Web Cookie 和浏览器 Cookie,它是服务器发送到 Web 浏览器的一小块数据。服务器发送到浏览器的 Cookie,浏览器会进行存储,并与下一个请求一起发送到服务器。通常,它用于判断两个请求是否来自于同一个浏览器,例如用户保持登录状态。

40.Session、Cookie 的区别和关联?如何进行临时性和永久性的 Session 存储?


  • Session

客户端请求服务端,服务端会为这次请求开辟一块内存空间,这个对象便是 Session 对象,存储结构为 ConcurrentHashMap。Session 弥补了 HTTP 无状态特性,服务器可以利用 Session 存储客户端在同一个会话期间的一些操作记录。

  • Cookies

HTTP 协议中的 Cookie 包括 Web Cookie 和浏览器 Cookie,它是服务器发送到 Web 浏览器的一小块数据。服务器发送到浏览器的 Cookie,浏览器会进行存储,并与下一个请求一起发送到服务器。通常,它用于判断两个请求是否来自于同一个浏览器,例如用户保持登录状态。

服务器端session,如果你不指定session的存储时间,在你打开的浏览器中存储的值,是可以在新打开的框口内得到的,关闭后就自动消失(消失的其实是session_id,因为session的机制是依赖于cookie的(还可以依赖其他的)。

41.设置 Cookie 时候如何防止 XSS 攻击?


在服务器端设置cookie的时候设置 http-only, 这样就可以防止用户通过JS获取cookie。对cookie的读写或发送一般有如下字段进行设置:

  • http-only: 只允许http或https请求读取cookie、JS代码是无法读取cookie的(document.cookie会显示http-only的cookie项被自动过滤掉)。发送请求时自动发送cookie.

  • secure-only: 只允许https请求读取,发送请求时自动发送cookie。

  • host-only: 只允许主机域名与domain设置完成一致的网站才能访问该cookie。

设置Cookie,可以防止攻击者拿到正常用户的Cookie冒充身份非法调用网站接口。

42.简单描述一下用户免登陆的实现过程?可能会出现哪些安全性问题?一般如何对用户登录的密码进行加密?


在用户第一次登录成功的时候,后端会返回一个 Token,这个值Token 主要的作用就是用于识别用户的身份。相当于账号密码。正常情况下,前端给后端发送请求的时候,后端都需要先判断用户的身份,来返回相应的数据给用户。获取到Token后,你需要把 Token 存在 Cookie中。接着向服务器发送请求时,你从 Cookie 中取出 Token,在请求头中携带上 Token 。Token过期时间设置足够长,只要token没过期,这段时间用户都是免登录。

安全问题:其他人使用本机,实现免登录,无法在每次使用应用时验证用户的身份。提供了便捷,失去了安全校验。

对用户登录的密码进行加密,密码MD5化,不使用明文传输。

43.HTTP 中提升传输速率的方式有哪些?常用的内容编码方式有哪些?


  • 使用压缩技术把实体主体压小,在客户端再把数据解析。

  • 使用分块传输编码,将实体主体分块传输,当浏览器解析到实体主体就能够显示了。

常用的内容编码方式:

  • 非归零码

  • 曼彻斯特编码

  • 差分曼彻斯特编码

44.传输图片的过程中如果突然中断,如何在恢复后从之前的中断中恢复传输?


文件的断点续传,

前端工作

  • 为每一个文件切割块添加不同的标识

  • 当上传成功的之后,记录上传成功的标识

  • 当我们暂停或者发送失败后,可以重新发送没有上传成功的切割文件

后端工作

  • 接收每一个切割文件,并在接收成功后,存到指定位置,并告诉前端接收成功

  • 收到合并信号,将所有的切割文件排序,合并,生成最终的大文件,然后删除切割小文件,并告知前端大文件的地址

45.什么是代理?什么是网关?代理和网关的作用是什么?


代理是中间人,使用代理的主机发出的IP报文的目的IP是代理的,但是会在应用层里明确告诉代理,自己真实需求是什么。网关即Gateway,它是连接基于不同通信协议的网络的设备,使文件可以在这些网络之间传输。

46.HTTPS 相比 HTTP 为什么更加安全可靠?


因为 HTTPS 保证了传输安全,防止传输过程被监听、防止数据被窃取,可以确认网站的真实性(具体细节二面再说)。不过需要注意的是,即便使用 HTTPS 仍可能会被抓包,因为HTTPS 只防止用户在不知情的情况下通信被监听,如果用户主动授信,是可以构建“中间人”网络,代理软件可以对传输内容进行解密。

47.什么是对称密钥(共享密钥)加密?什么是非对称密钥(公开密钥)加密?哪个更加安全?


传统的对称式加密需要通讯双方都保存同一份密钥,通过这份密钥进行加密和解密。所以非对称加密也称为单密钥加密。在非对称加密中,加密和解密使用的是不同的密钥。非对称加密中的密钥分为公钥和私钥。公钥顾名思义就是公开的,任何人都可以通过公钥进行信息加密,但是只有用户私钥的人才能完成信息解密。非对称加密带来了一个好处,避免了对称式加密需要传输和保存同一份密钥的痛苦。

非对称加密一定比对称加密机密性更高吗? 不一定, 因为机密性高低是根据秘钥长度而变化的。而且非对称加密最大的问题,就是性能较差,无法应用于长期的通信。

48.你觉得 HTTP 协议目前存在哪些缺点?


HTTP不具备必要的安全功能,与最初的设计相比,现今的Web网站应用的HTTP协议的使用方式已发生了翻天覆地的变化。几乎现今所有的Web网站都会使用会话(session)管理、加密处理等安全性方面的功能,而HTTP协议内并不具备这些功能。

从整体上看,HTTP就是一个通用的单纯协议机制。因此它具备较多优势,但是在安全性方面则呈劣势。就拿远程登录时会用到的SSH协议来说,SSH具备协议级别的认证及会话管理等功能,HTTP协议则没有。另外在架设SSH服务方面,任何人都可以轻易地创建安全等级高的服务,而HTTP即使已架设好服务器,但若想提供服务器基础上的Web应用,很多情况下都需要重新开发。

因此,开发者需要自行设计并开发认证及会话管理功能来满足Web应用的安全。而自行设计就意味着会出现各种形形色色的实现。结果,安全等级并不完备,可仍在运作的Web应用背后却隐藏着各种容易被攻击者滥用的安全漏洞的Bug。

49.Webpack 中的插件机制是如何设计的?


Webpack 插件机制的目的是为了增强 Webpack 在项目自动化构建方面的能力。在webpack中 Loader 就是负责完成项目中各种各样资源模块的加载,从而实现整体项目的模块化,而 Plugin 则是用来解决项目中除了资源模块打包以外的其他自动化工作,对比 Loader 只是在模块的加载环节工作,而插件的作用范围几乎可以触及 Webpack 工作的每一个环节。

Webpack 的插件机制就是我们在软件开发中最常见的钩子机制。钩子机制也特别容易理解,它有点类似于 Web 中的事件。在 Webpack 整个工作过程会有很多环节,为了便于插件的扩展,Webpack 几乎在每一个环节都埋下了一个钩子。这样我们在开发插件的时候,通过往这些不同节点上挂载不同的任务,就可以轻松扩展 Webpack 的能力。

50.如何提升 Node.js 代码的运行稳定性?


  • 保障进程安全

由于一个用户的异常访问或者数据异常,加上没有做好异常处理和安全保护,直接导致了整个 Node.js 服务重启了,从而中断了所有人的请求,用户体验非常差。①由于 Node.js 使用的是 JavaScript,而JavaScript 是一个弱类型语言,因此在现网经常会引发一些由代码逻辑的异常导致的进程异常退出。②其次在 Node.js 中也经常会因为内存的使用不当,导致内存泄漏,当在 64 位系统中达到 1.4 G(32 位系统 0.7 G)时,Node.js 就会异常崩溃。③再而由于Node.js 的 I/O 较多也较为频繁,当启用较多 I/O 句柄,但是没有及时释放,同样会引发进程问题。

  • parameters error

关于 JSON.parse 很多时候我们都比较自然地将其他接口或者第三方的数据拿来解析,但是这里往往会忽略其非 JSON 字符串的问题,在这里需要进行try catch 异常判断。

  • other errors

当前 Node.js 的 Promise 应用越来越广泛了,因此对于 Promise 的 catch 也应该多进行重视,对于每个 Promise 都应该要处理其异常 catch 逻辑,不然系统会提示 warning 信息。还有一些常见的长连接的服务,比如 Socket、Redis、Memcache 等等,我们需要在连接异常时进行处理,如果没有处理同样会导致异常,比如 Socket 提供了 Socket.on(‘error’) 的监听。

  • 注意服务异常方面的内存泄露

设置最大临时缓存数,超出则不使用缓存;设置最大缓存句柄数,超出则不使用缓存;定时清理当前的临时缓存和句柄缓存。

  • 避免全局变量

一般情况下不建议使用全局变量,全局变量必须要有一定的上限和清理规则才能保证服务的安全。

  • 避免单例模块中的变量内存泄漏

要注意一个点,有些模块我们使用单例的模式,就是在每次 require 后都返回这个对象,这种情况也比较容易引发内存泄漏的问题。因为单例模式会引发每个用户访问的数据的叠加。

  • 主动关闭打开文件后未关闭的情况

一般打开文件句柄后,我们都应该主动关闭,如果未主动关闭,就会导致文件句柄越来越多,从而引发句柄泄漏问题。

51.Vue SSR 的工作原理?Vuex 的数据如何同构渲染?


在Vue SSR中,创建Vue实例、创建store和创建router都是套了一层工厂函数的,目的就是避免数据的交叉污染。在服务端只能执行生命周期中的created和beforeCreate,原因是在服务端是无法操纵dom。服务端渲染和客户端渲染不同,需要创建两个entry分别跑在服务端和客户端,并且需要webpack对其分别打包;SSR服务端请求不带cookie,需要手动拿到浏览器的cookie传给服务端的请求。SSR要求dom结构规范,因为浏览器会自动给HTML添加一些结构比如tbody,但是客户端进行混淆服务端放回的HTML时,不会添加这些标签,导致混淆后的HTML和浏览器渲染的HTML不匹配。

对于同构应用来说,我们必须实现客户端与服务端的路由、模型组件、数据模型的共享。Vuex是实现我们客户端和服务端的状态共享的关键,我们可以不使用vuex,但是我们得去实现一套数据预取的逻辑;可以尝试封装一个可以给组件们共享的EventBus,在main.js中export出我们的EventBus以便两个entry使用,接下来是我们的两个entry了。server用来匹配我们的组件并调用组件的asyncData方法去获取数据,client用来将预渲染的数据存储到我们eventBus中的data中。这样就相当于实现类Vuex的功能。

52.SSR 技术和 SPA 技术的各自的优缺点是什么?


SSR:

  • 更利于SEO。

  • 更利于首屏渲染

  • 服务端压力较大

SPA:

  • 页面之间的切换非常快

  • 一定程度上减少了后端服务器的压力(不用管页面逻辑和渲染)

  • 后端程序只需要提供API,完全不用管客户端到底是Web界面还是手机等

  • 不利于SEO

  • 首屏加载压力大

53.什么是单点登录?如何做单点登录?


单点登录是指在同一帐号平台下的多个应用系统中,用户只需登录一次,就可访问所有相互信任的应用系统。比如你在网页中登录了百度云盘,随后你再去贴吧发帖 是不需要二次登录的。

单点登录的本质就是在多个应用系统中共享登录状态。如果用户的登录状态是记录在 Session 中的,要实现共享登录状态,就要先共享 Session,比如可以将 Session 序列化到 Redis 中,让多个应用系统共享同一个 Redis,直接读取 Redis 来获取 Session。

因为不同的应用系统有着不同的域名,尽管 Session 共享了,但是一个企业不同应用的域名不同,依然可能出现跨站or跨域。

前端方面的实现方式:

  • 父域 Cookie

  • 认证中心

  • LocalStorage 跨域

手写

=================================================================

01.数组扁平化


const arr = [1, [2, [3, [4, 5]]], 6];

方法一:使用flat()

const res1 = arr.flat(Infinity);

方法二:使用reduce

const flatten = arr => {

return arr.reduce((pre, cur) => {

return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);

}, [])

}

const res4 = flatten(arr);

方法五:函数递归

const res5 = [];

const fn = arr => {

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

if (Array.isArray(arr[i])) {

fn(arr[i]);

} else {

res5.push(arr[i]);

}

}

}

fn(arr);

02.数组去重


const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];

方法一:利用Set

const res1 = Array.from(**new** Set(arr));

方法二:两层for循环+splice

const unique1 = arr => {

let len = arr.length;

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

for (let j = i + 1; j < len; j++) {

if (arr[i] === arr[j]) {

arr.splice(j, 1);

// 每删除一个树,j–保证j的值经过自加后不变。同时,len–,减少循环次数提升性能

len–;

j–;

}

}

}

return arr;

}

方法三:利用indexOf

const unique2 = arr => {

const res = [];

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

if (res.indexOf(arr[i]) === -1) res.push(arr[i]);

}

return res;

}

复制代码

当然也可以用include、filter,思路大同小异。

方法四:利用include

const unique3 = arr => {

const res = [];

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

if (!res.includes(arr[i])) res.push(arr[i]);

}

return res;

}

复制代码

方法五:利用filter

const unique4 = arr => {

return arr.filter((item, index) => {

return arr.indexOf(item) === index;

});

}

复制代码

方法六:利用Map

const unique5 = arr => {

const map = new Map();

const res = [];

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

if (!map.has(arr[i])) {

map.set(arr[i], true)

res.push(arr[i]);

}

}

return res;

}

03.类数组转化为数组


类数组是具有length属性,但不具有数组原型上的方法。常见的类数组有arguments、DOM操作方法返回的结果。

方法一:Array.from

Array.from(document.querySelectorAll(‘div’))

方法二:Array.prototype.slice.call()

Array.prototype.slice.call(document.querySelectorAll(‘div’))

方法三:扩展运算符

[…document.querySelectorAll(‘div’)]

方法四:利用concat

Array.prototype.concat.apply([], document.querySelectorAll(‘div’));

04.Array.prototype.filter()


img

Array.prototype.filter = function(callback, thisArg) {

if (this == undefined) {

throw new TypeError(‘this is null or not undefined’);

}

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

throw new TypeError(callback + ‘is not a function’);

}

const res = [];

// 让O成为回调函数的对象传递(强制转换对象)

const O = Object(this);

// >>>0 保证len为number,且为正整数

const len = O.length >>> 0;

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

// 检查i是否在O的属性(会检查原型链)

if (i in O) {

// 回调函数调用传参

if (callback.call(thisArg, O[i], i, O)) {

res.push(O[i]);

}

}

}

return res;

}

05.Array.prototype.map()


img

Array.prototype.map = function(callback, thisArg) {

if (this == undefined) {

throw new TypeError(‘this is null or not defined’);

}

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

throw new TypeError(callback + ’ is not a function’);

}

const res = [];

// 同理

const O = Object(this);

const len = O.length >>> 0;

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

if (i in O) {

// 调用回调函数并传入新数组

res[i] = callback.call(thisArg, O[i], i, this);

}

}

return res;

}

06.Array.prototype.forEach()


img

Array.prototype.forEach = function(callback, thisArg) {

if (this == null) {

throw new TypeError(‘this is null or not defined’);

}

if (typeof callback !== “function”) {

throw new TypeError(callback + ’ is not a function’);

}

const O = Object(this);

const len = O.length >>> 0;

let k = 0;

while (k < len) {

if (k in O) {

callback.call(thisArg, O[k], k, O);

}

k++;

}

}

07.Array.prototype.reduce()


img

Array.prototype.reduce = function(callback, initialValue) {

if (this == undefined) {

throw new TypeError(‘this is null or not defined’);

}

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

throw new TypeError(callbackfn + ’ is not a function’);

}

const O = Object(this);

const len = this.length >>> 0;

let accumulator = initialValue;

let k = 0;

// 如果第二个参数为undefined的情况下

// 则数组的第一个有效值作为累加器的初始值

if (accumulator === undefined) {

while (k < len && !(k in O)) {

k++;

}

// 如果超出数组界限还没有找到累加器的初始值,则TypeError

if (k >= len) {

throw new TypeError(‘Reduce of empty array with no initial value’);

}

accumulator = O[k++];

}

while (k < len) {

if (k in O) {

accumulator = callback.call(undefined, accumulator, O[k], k, O);

}

k++;

}

return accumulator;

}

08.Function.prototype.apply()


第一个参数是绑定的this,默认为window,第二个参数是数组或类数组

Function.prototype.apply = function(context = window, args) {

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

throw new TypeError(‘Type Error’);

}

const fn = Symbol(‘fn’);

context[fn] = this;

const res = contextfn;

delete context[fn];

return res;

}

09.Function.prototype.call


call唯一不同的是,call()方法接受的是一个参数列表

Function.prototype.call = function(context = window, …args) {

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

throw new TypeError(‘Type Error’);

}

const fn = Symbol(‘fn’);

context[fn] = this;

const res = contextfn;

delete context[fn];

return res;

}

10.Function.prototype.bind


Function.prototype.bind = function(context, …args) {

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

throw new Error(“Type Error”);

}

// 保存this的值

var self = this;

return function F() {

// 考虑new的情况

if(this instanceof F) {

return new self(…args, …arguments)

}

return self.apply(context, […args, …arguments])

}

}

11.debounce(防抖)


触发高频时间后n秒内函数只会执行一次,如果n秒内高频时间再次触发,则重新计算时间。

const debounce = (fn, time) => {

let timeout = null;

return function() {

clearTimeout(timeout)

timeout = setTimeout(() => {

fn.apply(this, arguments);

}, time);

}

};

防抖常应用于用户进行搜索输入节约请求资源,window触发resize事件时进行防抖只触发一次。

12.throttle(节流)


高频时间触发,但n秒内只会执行一次,所以节流会稀释函数的执行频率。

const throttle = (fn, time) => {

let flag = true;

return function() {

if (!flag) return;

flag = false;

setTimeout(() => {

fn.apply(this, arguments);

flag = true;

}, time);

}

}

复制代码

节流常应用于鼠标不断点击触发、监听滚动事件。

13.Object.assign


Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象(请注意这个操作是浅拷贝)

Object.defineProperty(Object, ‘assign’, {

value: function(target, …args) {

if (target == null) {

return new TypeError(‘Cannot convert undefined or null to object’);

}

// 目标对象需要统一是引用数据类型,若不是会自动转换

const to = Object(target);

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

// 每一个源对象

const nextSource = args[i];

if (nextSource !== null) {

// 使用for…in和hasOwnProperty双重判断,确保只拿到本身的属性、方法(不包含继承的)

for (const nextKey in nextSource) {

if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {

to[nextKey] = nextSource[nextKey];

}

}

}

}

return to;

},

// 不可枚举

enumerable: false,

writable: true,

configurable: true,

})

14.深拷贝


const cloneDeep1 = (target, hash = new WeakMap()) => {

// 对于传入参数处理

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

return target;

}

// 哈希表中存在直接返回

if (hash.has(target)) return hash.get(target);

const cloneTarget = Array.isArray(target) ? [] : {};

hash.set(target, cloneTarget);

// 针对Symbol属性

const symKeys = Object.getOwnPropertySymbols(target);

if (symKeys.length) {

symKeys.forEach(symKey => {

if (typeof target[symKey] === ‘object’ && target[symKey] !== null) {

cloneTarget[symKey] = cloneDeep1(target[symKey]);

} else {

cloneTarget[symKey] = target[symKey];

}

})

}

for (const i in target) {

if (Object.prototype.hasOwnProperty.call(target, i)) {

cloneTarget[i] =

typeof target[i] === ‘object’ && target[i] !== null

? cloneDeep1(target[i], hash)
target[i];

}

}

return cloneTarget;

}

15.Promise


// 模拟实现Promise

// Promise利用三大手段解决回调地狱:

// 1. 回调函数延迟绑定

// 2. 返回值穿透

// 3. 错误冒泡

// 定义三种状态

const PENDING = ‘PENDING’; // 进行中

const FULFILLED = ‘FULFILLED’; // 已成功

const REJECTED = ‘REJECTED’; // 已失败

class Promise {

constructor(exector) {

// 初始化状态

this.status = PENDING;

// 将成功、失败结果放在this上,便于then、catch访问

this.value = undefined;

this.reason = undefined;

// 成功态回调函数队列

this.onFulfilledCallbacks = [];

// 失败态回调函数队列

this.onRejectedCallbacks = [];

const resolve = value => {

// 只有进行中状态才能更改状态

if (this.status === PENDING) {

this.status = FULFILLED;

this.value = value;

// 成功态函数依次执行

this.onFulfilledCallbacks.forEach(fn => fn(this.value));

}

}

const reject = reason => {

// 只有进行中状态才能更改状态

if (this.status === PENDING) {

this.status = REJECTED;

this.reason = reason;

// 失败态函数依次执行

this.onRejectedCallbacks.forEach(fn => fn(this.reason))

}

}

try {

// 立即执行executor

// 把内部的resolve和reject传入executor,用户可调用resolve和reject

exector(resolve, reject);

} catch(e) {

// executor执行出错,将错误内容reject抛出去

reject(e);

}

}

then(onFulfilled, onRejected) {

onFulfilled = typeof onFulfilled === ‘function’ ? onFulfilled : value => value;

onRejected = typeof onRejected === ‘function’? onRejected :

reason => { throw new Error(reason instanceof Error ? reason.message : reason) }

// 保存this

const self = this;

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

if (self.status === PENDING) {

self.onFulfilledCallbacks.push(() => {

// try捕获错误

try {

// 模拟微任务

setTimeout(() => {

const result = onFulfilled(self.value);

// 分两种情况:

// 1. 回调函数返回值是Promise,执行then操作

// 2. 如果不是Promise,调用新Promise的resolve函数

result instanceof Promise ? result.then(resolve, reject) : resolve(result);

})

} catch(e) {

reject(e);

}

});

self.onRejectedCallbacks.push(() => {

// 以下同理

try {

setTimeout(() => {

const result = onRejected(self.reason);

// 不同点:此时是reject

result instanceof Promise ? result.then(resolve, reject) : resolve(result);

})

} catch(e) {

reject(e);

}

})

} else if (self.status === FULFILLED) {

try {

setTimeout(() => {

const result = onFulfilled(self.value);

result instanceof Promise ? result.then(resolve, reject) : resolve(result);

});

} catch(e) {

结束

一次完整的面试流程就是这样啦,小编综合了腾讯的面试题做了一份前端面试题PDF文档,里面有面试题的详细解析,分享给小伙伴们,有没有需要的小伙伴们都去领取!

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

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

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

onFulfilled = typeof onFulfilled === ‘function’ ? onFulfilled : value => value;

onRejected = typeof onRejected === ‘function’? onRejected :

reason => { throw new Error(reason instanceof Error ? reason.message : reason) }

// 保存this

const self = this;

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

if (self.status === PENDING) {

self.onFulfilledCallbacks.push(() => {

// try捕获错误

try {

// 模拟微任务

setTimeout(() => {

const result = onFulfilled(self.value);

// 分两种情况:

// 1. 回调函数返回值是Promise,执行then操作

// 2. 如果不是Promise,调用新Promise的resolve函数

result instanceof Promise ? result.then(resolve, reject) : resolve(result);

})

} catch(e) {

reject(e);

}

});

self.onRejectedCallbacks.push(() => {

// 以下同理

try {

setTimeout(() => {

const result = onRejected(self.reason);

// 不同点:此时是reject

result instanceof Promise ? result.then(resolve, reject) : resolve(result);

})

} catch(e) {

reject(e);

}

})

} else if (self.status === FULFILLED) {

try {

setTimeout(() => {

const result = onFulfilled(self.value);

result instanceof Promise ? result.then(resolve, reject) : resolve(result);

});

} catch(e) {

结束

一次完整的面试流程就是这样啦,小编综合了腾讯的面试题做了一份前端面试题PDF文档,里面有面试题的详细解析,分享给小伙伴们,有没有需要的小伙伴们都去领取!

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

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

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

  • 27
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值