从输入 url 到渲染页面
- 浏览器的地址栏输入 URL 并按下回车。
- 浏览器查找当前 URL 是否存在缓存,并比较缓存是否过期。
- DNS 解析 URL 对应的 IP。
- 根据 IP 建立 TCP 连接(三次握手)。
- HTTP 发起请求。
- 服务器处理请求,浏览器接收 HTTP 响应。
- 渲染页面,构建 DOM 树。
- 关闭 TCP 连接(四次挥手)
- Js 中的单线程和事件循环
Js 是单线程,但是浏览器是多线程。
Js 中采用了事件循环(Event Loop)来执行异步任务。
所以,事件循环是一种异步编程模型,事件循环会不断地从任务队列(Task Queue)
中取出待处理的任务并执行,直到任务队列为空为止。任务可以分为两类:宏任务
(Macro Task)和微任务(Micro Task)。
微任务会优先于宏任务执行 Js 实现继承有哪几种方式?
- 原型链继承:将父类的实例作为子类的原型,通过 prototype 进行继承
- 构造继承:将父类的实例属性复制给子类,通过 call 进行继承
- 实例继承:为父类实例添加新特性,作为子类实例返回
- 拷贝继承:将父类实例通过循环拷贝给子类
- 组合继承:就是 原型链继承 和 构造继承,一起使用
- 寄生组合继承:通过寄生方式,砍掉父类的实例属性,避免了 组合继承中,在调用
两次父类的构造时,初始化两次实例方法/属性 的缺点
- Js 中 bind(),call()和 apply()的区别
JavaScript 中的 bind()、call() 和 apply() 方法都可以用来改变函数内部的 this
指向。
它们有一些重要的区别:
三种方法最大的区别在于参数传入方式不同:bind() 方法接受一系列参数列表,而
call() 和 apply() 方法则分别接受一组参数和一个参数列表。具体而言,bind() 将参
数作为一个个单独的值传入,而 call() 和 apply() 都允许传递一个数组作为参数。
执行时间不同:bind() 绑定后返回一个新函数,并不会立即执行,需要调用该函数
才会执行;而 call() 和 apply() 则会立即执行函数。
返回值不同:bind() 方法返回一个绑定后的新函数,而 call() 和 apply() 则直接执
行原始函数并返回执行结果。 它们的作用分别如下:
bind() 方法:bind() 可以指定函数内部的 this 指向,并将其绑定到一个新函数上进
行返回。该函数并不会立即执行,而是等待调用。bind() 也可以用来实现柯里化
(currying)
call() 方法:call() 可以在指定的 this 值和若干个参数(参数的列表)的前提下调用
某个函数或方法。注意,call() 方法需要将参数逐个传递进去,而不能像 apply() 方
法一样将所有参数打包成一个数组。
apply() 方法:apply() 和 call() 的作用非常类似,都是改变函数内部的 this 指向。
区别在于,apply() 方法需要将参数打包成一个数组传递进去,而 call() 则是将参数
逐个传递。 - Js 中的闭包及其使用场景
官方说法:闭包就是指有权访问另一个函数作用域中的变量的函数。
MDN 说法:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环
境。环境由闭包创建时在作用域中的任何局部变量组成。
深度回答:浏览器在加载页面会把代码放在栈内存( ECStack )中执行,函数进栈
执行会产生一个私有上下文( EC ),此上下文能保护里面的使用变量( AO )不
受外界干扰,并且如果当前执行上下文中的某些内容,被上下文以外的内容占用,
当前上下文不会出栈释放,这样可以保存里面的变量和变量值,所以我认为闭包是
一种保存和保护内部私有变量的机制。 - Vue2 与 Vue3 中的双向数据绑定
双向数据绑定就是:数据劫持 + 发布订阅模式(观察者模式)。
Vue2 中在实例初始化时遍历 data 中的所有属性,并使用 Object.defineProperty
把这些属性全部转为 getter/setter。并 劫持各个属性 getter 和 setter,在数据
变化时发布消息给订阅者,触发相应的监听回调,而这之间存在几个问题
初始化时需要遍历对象所有 key,如果对象层次较深,性能不好
通知更新过程需要维护大量 dep 实例和 watcher 实例,额外占用内存较多
Object.defineProperty 无法监听到数组元素的变化,只能通过劫持重写数方法
动态新增,删除对象属性无法拦截,只能用特定 set/delete API 代替
不支持 Map、Set 等数据结构
Vue3 中使用 Proxy 来监控数据的变化。Proxy 是 ES6 中提供的功能,其作用为:
用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。 Vue 中 template 模版的编译原理
- 解析(parse):将模板字符串解析成 AST(抽象语法树)。
- 静态分析(static analysis):对 AST 进行静态分析,标记出其中的静态节点(Static
Node)。 - 优化(optimize):遍历 AST,对静态节点进行优化,去掉不必要的操作。
- 代码生成(code generation):将 AST 转换成渲染函数(render function)的可执行代
码。 - 最终的渲染:将生成的渲染函数运用到数据上,最终生成视图。
Vue2 中的 diff 流程
Vue2 中的虚拟 DOM diff 算法,其核心是采用双端比较(Two-Ended Algorithm)。
具体来说,Vue2 中 diff 算法的步骤如下:- 首先比较新旧虚拟 DOM 树的根节点,如果它们是不同类型的节点,则直接替换整
个节点树; - 如果根节点相同,则比较它们的子节点,这个过程称之为“Diff Children”;
- 在“Diff Children”过程中,使用双端比较算法,即同时从新虚拟 DOM 树和旧虚拟 DOM
树的两端开始向中间遍历,找到相应的节点进行比较,找到更新的节点后就停止比
较; - 如果新旧虚拟 DOM 树的子节点数组长度不同,则根据差异进行添加或删除节点;
- 对于相同位置的节点,判断它们是否相同,如果不同则进行更新;
- 最后返回新的虚拟 DOM 树。
- 首先比较新旧虚拟 DOM 树的根节点,如果它们是不同类型的节点,则直接替换整
- Vue3 中的 diff 流程
在 Vue3 中,Diff 流程主要分为两个阶段:标记阶段和应用阶段。其中标记阶段
是用来比较新老 VNode 树的差异,并记录下来需要进行的具体操作;应用阶段
则是将这些具体操作应用在真实 DOM 上,完成页面的更新。 Vue 中的组件间通信有哪些?
- props:父组件通过 props 属性向子组件传递数据。子组件接收该数据后,即可在其
模板中直接使用。 - $emit() 和事件:子组件通过 $emit() 方法触发一个自定义事件,并把需要传递的数
据作为参数传入。父组件监听该自定义事件,并在回调中处理数据。通过事件可以
实现任意级别的组件间通信。 - $parent 和 $children:父组件可以通过 $children 找到所有的子组件,子组件可以
通过 $parent 找到其父组件。这种方式属于直接引用和修改组件对象,需要注意和
谨慎使用。 - provide 和 inject:祖先组件通过 provide 属性向子孙组件传递数据,子孙组件通过
inject 属性来获取这些数据。provide 和 inject 绑定并非响应式的,但是可以将一个
观察者实例注入到 provide 中,使得组件在 inject 期间发生变化时获得通知。 - Vuex 状态管理(Vue3 中的 pinia):Vuex 是一个专门为 Vue.js 应用程序开发的状
态管理库,提供了一种集中式存储管理应用程序中的所有组件的状态。组件通过调
用 mutation 方法来改变状态,其他组件通过监听 state 属性来获取最新的状态。 - 兄弟组件间的传值:Vue2 中使用 eventBus 中央事件总线 , Vue3 中使用的 mitt 库
- props:父组件通过 props 属性向子组件传递数据。子组件接收该数据后,即可在其
- Vue-router 中如何实现懒加载?
在路由配置文件中使用动态导入 import()语句,并用箭头函数返回实现路由懒加
载。 Vue3 相对于 Vue2 进行了哪些优化?
- 更灵活的响应式系统:Vue 2.x 中响应式系统的核心是 Object.defineProperty,劫持
整个对象,然后进行深度遍历所有属性,给每个属性添加 getter 和 setter,实现响应
式。Vue 3.x 中使用 Proxy 对象重写响应式系统。 - 更快的渲染速度:Vue3 的编译器生成的渲染函数比 Vue2 生成的更高效。
- 编译阶段:Vue 2.x 通过标记静态节点,优化 diff 的过程。Vue 3.x 中标记和提升所
有的静态节点,diff 的时候只需要对比动态节点内容。 - 更小的体积:Vue3 将源码拆分为多个独立的模块,这样就可以按需导入所需的模块,
从而减小了整个库的体积。 - 更好的 TypeScript 支持:Vue3 对 TypeScript 的支持更加友好,内部使用了更先进
的 TypeScript 特性,并为其提供了更好的声明文件。 - 更好的组件系统:比如,Vue3 中引入了一个新的 Fragment 组件,它可以替代原来
的 template 标签作为根节点 - 新增了 setup 组合式 API
- 更灵活的响应式系统:Vue 2.x 中响应式系统的核心是 Object.defineProperty,劫持
- 介绍一下 React Fiber
React Fiber 是 React 框架的一种重新实现,旨在改善渲染性能和用户体验。它通
过引入优先级调度、增量渲染和可中断的工作单元等机制,将渲染任务分解成小
的可中断的单元,从而使 React 能够更好地处理大型应用程序和高优先级任务,
提供流畅且响应迅速的用户界面。 React 中常用的高阶组件有哪些?
- withRouter:将路由信息注入到组件中,使它们能够访问到路由对象(如 location、
history 和 match 等)。 - connect:将 React 组件与 Redux Store 连接起来,并将 State 和 Dispatch 作为 Props
传递给组件。这使得组件能够直接从 Store 中读取和操作数据。 - memo:对于纯函数组件,使用 memo 可以缓存组件输出,以提高性能。
- withStyles:用于添加 CSS 样式到组件中。
- redux-thunk:使 Action Creator 返回一个函数而不是一个 Action 对象,从而可以执行
异步操作并 dispatch 新的 Action。 - recompose:提供了一组高阶功能,用于增强函数式 React 组件。例如,compose 函
数可以将多个 HOC 组合在一起。 - react-redux:提供了一组基于 Redux Store 的 React 组件,并简化了 React 与 Redux
之间的集成。
- withRouter:将路由信息注入到组件中,使它们能够访问到路由对象(如 location、
- React Hook 为什么不能放到条件语句中?
React Hook 不能放到条件语句中的原因是:React 需要使用 Hook 的规则是必
须确保每次渲染时,Hook 调用的顺序都是一致的。也就是说,在一个组件内部,
每一次渲染时,Hook 的调用顺序必须是相同的。
如果将 Hook 放到条件语句中,当条件发生变化时,Hook 的调用顺序就可能被
打乱,从而导致组件状态不一致、出现错误等问题。因此,React 在运行时会对
Hook 的调用顺序进行验证,来确保 Hook 的使用符合规范。 React 有哪些常用的 hooks?
- useState:该 Hook 用于在函数组件中添加一个状态管理器。通过 useState,可以创
建一个状态变量及其更新函数,并在组件内使用该变量来保存和更新组件的状态。 - useEffect:该 Hook 用于在组件渲染完成后执行一些副作用操作(例如订阅数据、
更新 DOM 等)。通过 useEffect,可以在组件加载、更新和卸载时设置和清理副作
用操作,并且可以在副作用操作之间共享状态。 - useContext:该 Hook 用于在组件之间共享一些全局的状态或函数,以避免通过多
层嵌套的 Props 传递进行数据传输。通过 useContext,可以让组件在全局状态或函
数的上下文中运行,并让它们能够方便地读取或更新全局状态或函数。 - useReducer:该 Hook 用于在组件中使用一种“状态容器”模式,以避免通过多层
Props 传递或 Context 共享进行状态管理。通过 useReducer,可以创建一个状态容
器及其更新函数,并在组件内使用该容器来保存和更新组件的状态。 - useMemo:该 Hook 用于在组件渲染完成后缓存一些计算结果,以避免因为重复计
算导致的性能问题。通过 useMemo,可以创建一个缓存变量,并在组件内使用该变
量来保存计算结果并缓存。 - useCallback:该 Hook 用于在组件渲染完成后,将一些函数进行缓存,以避免因函
数重复创建导致的性能问题。通过 useCallback,可以创建一个缓存函数,并在组件
内使用该函数来代替重复创建的函数。 - useRef:该 Hook 用于在组件渲染完成后创建一个引用,以便在组件多次渲染时能
够保留上一次渲染中的值。通过 useRef,可以创建一个引用变量,并在组件内使用
该变量来保存一些持久化的数据。 - useImperativeHandle:该 Hook 用于在组件中实现一些自定义的 Ref 对象,并且要
求将一些组件内部的方法或状态暴露给父组件使用。通过 useImperativeHandle,可
以创建一个自定义的 Ref 对象,并在组件内指定一些公开的方法或属性。 - useLayoutEffect:该 Hook 与 useEffect 类似,但它会在浏览器渲染更新之前同步执
行副作用操作,以确保 React 组件与浏览器同步更新。通常情况下,应该使用
useEffect,但在需要直接操作 DOM 元素或进行测量布局界面时,应当使用
useLayoutEffect。 - useDebugValue:该 Hook 可以帮助开发者在调试工具中显示额外的信息,以便更好
地理解 Hook 的使用和行为。通常情况下,这个 Hook 只用于调试过程中,而不是
实际的应用程序代码中。
- useState:该 Hook 用于在函数组件中添加一个状态管理器。通过 useState,可以创
- 介绍下 React 中的 useEffect
在 React 中,useEffect 是一个用于处理副作用的 Hook。
副作用是指在组件生命周期中的某些特定时刻需要执行的操作,例如数据获取、订
阅事件、手动操作 DOM 等。
useEffect 的作用就是在组件渲染完成后执行这些副作用操作。 介绍一下 Promise 的状态及其方法
在 JavaScript 中,Promise 对象包含三种状态:Pending(进行中)、Fulfilled(已
成功)和 Rejected(已失败)。
Promise 的方法如下:- Promise 对象可以通过 then() 方法添加成功(Fulfilled)和失败(Rejected)时的回
调函数。then() 方法可以链式调用,每次返回一个新的 Promise 对象,因此可以很
容易地实现异步任务的连续执行。 - Promise 对象还提供了 catch() 方法用于捕获错误和 finally() 方法用于在 Promise
被解析后运行代码块。 - Promise.all() 方法接收一个 Promise 数组作为参数,返回一个新的 Promise,只有
当所有 Promise 都解析成功时才会被解析,否则该 Promise 会被拒绝。 - Promise.race() 方法接收一个 Promise 数组作为参数,返回一个新的 Promise,只要
有一个 Promise 被解析或拒绝就会被解析或拒绝。 - Promise.resolve() 和 Promise.reject() 方法分别返回一个已解析和一个已拒绝的
Promise 对象,可以用于快速创建 Promise。
- Promise 对象可以通过 then() 方法添加成功(Fulfilled)和失败(Rejected)时的回
- 介绍一下 async/await 的实现原理
在 JavaScript 引擎中,async/await 函数的实现原理是基于 Promise 对象和生成
器函数(Generator Function)的协作。 - 介绍一下 let、const、var 的区别
箭头函数和普通函数有什么区别
- 写法不同:箭头函数使用箭头(=>)来定义,而普通函数使用 function 关键字定义。
- this 的处理方式不同:在箭头函数中,this 的值与外层作用域的 this 绑定。而在普
通函数中,this 的值由调用该函数的方式决定。 - 箭头函数没有 arguments 对象:箭头函数中没有自己的 arguments 对象,它的参
数只能通过参数列表来传递。 - 箭头函数不能用作构造函数:由于箭头函数中没有自己的 this 值,因此不能用作构
造函数来创建对象实例。
- Css 中常用的垂直居中解决方案有哪些?
vertical-align 属性用于控制元素内部的行内元素(如文本或图片)的垂直对齐
方式。通常与 display: table-cell 结合使用,实现单元格内部的元素垂直居中。
line-height属性
用于设置行内元素的行高,可以使单行文本元素垂直居中。
但是如果元素高度超过一行,则无法实现垂直居中。
flexbox 布局使用display: flex 和相关属性可以实现容器内元素的水平和垂直居中。
grid布局使用display: grid和相关属性可以实现网格布局中元素的水平和垂直居中。
绝对定位 + 负边距将需要垂直居中的元素绝对定位到容器中心,然后通过负边距调整元素位置。
- 什么是 BFC?
BFC(Block Formatting Context)是 CSS 中一个很重要的概念。它是指一个块级
容器,其中的元素按照特定规则布局和渲染,同时也影响着其内部和外部元素的
布局。 Css 中移动端适配有哪些方案?
- 首先,通过 meta 标签设置 viewport
- rem 单位搭配@media 媒体查询:可以通过使用 rem 单位,它以 HTML 元素的 font-size
为比例,也可以搭配 postcss-pxtorem 搭建项目 - vw/vh 布局:也可以通过使用 vw/vh 布局,vw/vh 方案与 rem 方案类似,都是将
页面分成一份一份的,只不过 vw/vh 是将页面分为 100 份,也可以搭配
postcss-px-to-viewport 搭建项目 - 百分比布局:也可以使用百分比来实现布局,但是需要特定宽度时,这个百分比的
计算对开发者来说并不友好,且元素百分比参考的对象为父元素,元素嵌套较深时
会有问题。
- 什么是 Css 中的回流(重排)与重绘?
回流(重排)(reflow)和重绘(repaint)是浏览器渲染页面时的两个核心概念。
回流(重排)指的是当页面中的元素发生布局或几何属性发生变化时,浏览器需要
重新计算这些元素的位置和大小,然后重新构建页面的渲染树,这个过程称为回流。
由于需要重新计算布局,回流的代价很大,会对页面的性能产生负面影响。
重绘指的是当页面中的元素样式发生改变时,浏览器会重新绘制这些元素的外观,
但不会改变它们在页面中的位置和大小。重绘的代价相对较小,但仍然会对页面性
能产生一定的影响。 常用的跨域解决方案有哪些?
- CORS:跨域资源共享(Cross-Origin Resource Sharing),是一种允许浏览器向跨域服
务 器 发 送 Ajax 请 求 的 机 制 , 支 持 现 代 浏 览 器 , 服 务 器 端 需 要 设 置
Access-Control-Allow-Origin 头信息,指定允许的源或通配符,从而实现跨域请求。 - 代理:在同源页面内部发送 AJAX 请求到同域服务器,由服务器代理转发请求到跨
域服务器,最后再将结果返回给同源页面。 - WebSocket:WebSocket 是一种 HTML5 协议,它使得浏览器和服务器之间可以建立
持久化的连接,可以直接使用 Socket 进行通信,避免了浏览器的跨域限制。
- CORS:跨域资源共享(Cross-Origin Resource Sharing),是一种允许浏览器向跨域服
Webpack 中有哪些核心概念?
- Entry(入口):Webpack 在打包时需要从哪个文件开始构建依赖关系图,就是入口。
可以设置多个入口文件,以生成多个输出文件。 - Output(输出):打包后的文件放在哪里,以及如何命名这些文件。可以指定输出
目录、文件名、公共路径等。 - Loader(模块加载器):Webpack 只能处理 JavaScript 文件,而其他类型的文件如 CSS、
图片等需要通过 Loader 转换才能被 Webpack 处理。Loader 用于对模块内容进行转换
处理。 - Plugin(插件):Plugin 可以用于执行各种任务,例如打包优化、错误处理和环境变
量注入等。Webpack 本身只提供了一些基本的 Plugin,但社区中有很多第三方 Plugin
可供使用。 - Mode(模式):Webpack 提供了三种模式:development、production 和 none。不
同的模式会启用不同的 Webpack 内置 Plugin 和 Loader,以便于开发和生产环境的优
化。 - Chunk(代码块):Webpack 在打包时会把所有相关联的模块组成一个 Chunk。可以
通过 Code Splitting 技术将代码拆分成多个 Chunk,以实现按需加载。 - Module(模块):Webpack 把每个文件都看作一个模块,它可以是 JavaScript、CSS、
图片等。这些模块通过依赖关系进行组合,构成整个应用程序。
- Entry(入口):Webpack 在打包时需要从哪个文件开始构建依赖关系图,就是入口。
- Vite 和 Webpack 的区别
优点:快速的冷启动: 采用 No Bundle 和 esbuild 预构建,速度远快于 Webpack 高效
的热更新:基于 ESM 实现,同时利用 HTTP 头来加速整个页面的重新加载,增加缓
存策略真正的按需加载: 基于浏览器 ESM 的支持,实现真正的按需加载
缺点生态:目前 Vite 的生态不如 Webapck,不过我觉得生态也只是时间上的问题。
生产环境由于 esbuild 对 css 和代码分割不友好使用 Rollup 进行打包 Webpack 常见的优化方案有哪些?
- 升级 webpack 版本,3 升 4,实测是提升了几十秒的打包速度
- 使用 Tree Shaking 和 Scope Hoisting 来减少代码体积和模块构建时间,其中 Tree
Shaking 可以去除未使用的代码,而 Scope Hoisting 可以将模块内的代码尽量合并到
一个函数(单一作用域)中,以减少函数声明和闭包的数量。 - 使用 splitChunksPlugin 插件来将公共代码抽离成单独的 chunk,以减少代码重复和提
高缓存命中率。 - 合理配置 resolve.alias 和 resolve.extensions 选项来减少 Webpack 查找文件的时间。
- 针对生产环境,可以开启代码压缩以及多进程并行处理等优化方式,以减少构建时
间和服务器负载。 - 使用 DLLPlugin 和 DllReferencePlugin 来预先编译一些稳定不变的代码,以减少每次构
建的时间。 - 使用 HappyPack 来启用多线程并发处理,以加速代码构建和增强开发体验。
- 对于图片、字体等资源文件,可以通过 url-loader 和 file-loader 等 loader 设置较小的
limit 值,将文件转换成 base64 编码的字符串内嵌在 js 文件中,以减少 http 请求次
数。
- 浏览器中强缓存与协商缓存的缓存机制
强缓存和协商缓存是两种不同的缓存策略。
强缓存通过设置响应头中的 Cache-Control 或 Expires 字段,告诉浏览器在一定时
间内直接使用本地缓存,不需要发送请求到服务器。只有当缓存过期或被清除时,
浏览器才会发送请求到服务器获取新的资源。
协商缓存通过设置响应头中的 ETag 和 Last-Modified 字段,浏览器在每次请求时
携带 If-None-Match 和 If-Modified-Since 字段,与服务器进行比较。如果资源未发
生变化,则服务器返回 304 Not Modified 状态码,并告知浏览器继续使用缓存;
如果资源发生变化,则服务器返回新的资源。
强缓存是基于时间的缓存控制,而协商缓存则是通过与服务器进行交互来判断是
否使用缓存,更加灵活。
来源:牛客网
链接:https://www.nowcoder.com/discuss/576490235048050688
恭喜
111
1111
欢迎加入 Typecho 大家族