前端基础内容(一)
前端基础内容一
基础核心
HTML&CSS
盒模型:标准盒模型 vs IE盒模型,如何用 box-sizing 控制?
标准盒模型与IE盒模型的区别:
- 标准盒模型:宽度和高度只包括内容的宽度和高度,不包括内边距(padding)和边框(border)。内边距和边框会在元素指定的宽度和高度之外增加额外的空间。
- IE盒模型(也称为怪异盒模型):宽度和高度包括内容、内边距和边框的总和。这意味着当你设置一个元素的宽度和高度时,内边距和边框的空间是从这个总宽度和高度中扣除的。
使用box-sizing控制盒模型:
box-sizing
属性可以用来控制元素的盒模型计算方式。box-sizing: content-box
:这是默认值,表示使用标准盒模型。box-sizing: border-box
:表示使用IE盒模型(或称为怪异盒模型),此时元素的宽度和高度将包括内容、内边距和边框。
通过设置box-sizing: border-box
,可以简化布局计算,因为元素的宽度和高度将直接包括所有可见部分,无需额外计算内边距和边框的空间。
布局方式:Flex/Grid的区别?
Grid布局
Flex布局:
- Flex布局是一维布局,主要适用于局部布局,如标头组件、导航组件等。
- 它使块级元素的布局排列变得灵活,适应性非常强,有强大的伸缩性,在响应式开发中可以发挥极大的作用。
- Flex布局的关键属性包括
flex-direction
(决定主轴的方向)、justify-content
(定义项目在主轴上的对齐方式)、align-items
(定义项目在交叉轴上的对齐方式)等。
Grid布局:
- Grid布局是二维布局,适用于页面整体规划。
- 它能够轻松地将页面划分为几个主要区域,并通过
grid-template-columns
、grid-template-rows
等属性定义行和列的尺寸。 - Grid布局还支持区域命名和子元素定位等高级功能。
实现垂直居中的N种方法
- 使用
display: inline-block
和vertical-align: middle
:需要一个参照空元素,如<span></span>
,并将其设置为与需居中元素同级的标签。然后设置参照元素的宽度为0,高度为父容器高度,居中方式为vertical-align: middle
。同时将需居中元素设置为vertical-align: middle
。 - 使用
height
和line-height
:当行高等于容器高度时,可实现单行文本在容器中垂直居中对齐。 - 使用
display: table-cell
:将元素类型转换为<td>
标签的元素类型,然后设置vertical-align: middle
即可。 - 使用定位(
position
):子元素设置position: absolute
脱离标准文档流;父元素设置position: relative
不脱离标准文档流。然后设置子元素的left
、top
、margin
等属性来实现垂直居中。 - 使用Flex布局:将父元素设置为弹性盒子
display: flex
;然后设置主轴对齐方式justify-content: center
和侧轴对齐方式align-items: center
。
BFC(块级格式化上下文):触发条件是什么?解决什么问题?
BFC
触发BFC的条件:
- 浮动(
float: left
、right
) - 定位(
position: absolute
、fixed
) - Flex/Grid布局(
display: flex
、grid
) overflow
(overflow: hidden
、auto
、scroll
)inline-block
(display: inline-block
)- 表格布局(
display: table
、inline-table
、table-row
、table-column
等) display: flow-root
(无副作用,但兼容性可能不够)- 根元素(
body
、html
)
BFC解决的问题:
- 垂直外边距重合问题:两个兄弟元素之间的垂直方向margin会合并。通过为各自元素添加BFC,可以解决这个问题,因为BFC具有独立性。
- 垂直外边距包含塌陷问题:父子嵌套元素在垂直方向的margin会结合在一起,并取其中最大值。通过为父元素添加BFC,可以解决这个问题。
- 浮动元素父标签高度塌陷问题:当父元素未设定高度,且子元素全部浮动时,父元素高度为0。通过为父元素添加BFC,可以将所有浮动元素包裹起来,从而解决高度塌陷问题。
响应式设计:媒体查询 (@media)、视口单位 (vw/vh) 的使用场景?
媒体查询(@media):
- 媒体查询允许你根据设备显示器的特性(如视窗宽度、屏幕宽度、设备方向等)来设定不同的CSS样式。
- 使用媒体查询可以实现响应式设计,使网页在不同设备和屏幕尺寸上都能良好地展示。
- 例如,可以使用媒体查询为不同屏幕尺寸的设备设置不同的样式规则,以确保网页在桌面、平板和手机等设备上都能正确显示。
视口单位(vw/vh):
- 视口单位是基于视口大小(浏览器窗口的大小)进行计算的单位。
vw
表示视口宽度的1%,vh
表示视口高度的1%。- 通过使用视口单位,可以创建更加灵活的布局和样式,使网页能够自适应不同屏幕尺寸的设备。
- 例如,可以使用
vw
和vh
来设置元素的宽度、高度、间距等属性,以确保网页在不同屏幕尺寸上都能保持一致的布局和样式。
Javascript
作用域与闭包
闭包的应用场景:
闭包在编程中有许多实际应用场景,包括但不限于以下几种:
- 封装:闭包可以用于创建私有变量和函数,从而实现信息隐藏和封装。这在JavaScript等语言中特别有用,因为它们本身并没有提供类似于Java或C++的私有成员的语法。
- 函数式编程:在函数式编程中,闭包是非常有用的工具。通过使用闭包,可以创建高阶函数、延迟执行函数、柯里化等功能。
- 定时器和事件处理:在处理定时器和事件时,闭包可以帮助保存局部状态,从而在稍后的时间点执行所需的逻辑。例如,防抖(debounce)和节流(throttle)函数就是闭包在定时器应用中的典型例子。防抖是指在一段时间内持续触发事件时,只在最后一次事件触发后执行一次回调,而节流是指每隔一段时间执行一次回调,不管在这段时间内触发了多少次事件。
- 模块模式:闭包可以用于创建模块,将相关的函数和数据封装在一起,提供一种更加模块化的编程方式。
- 回调函数:在异步编程中,闭包常常与回调函数一起使用,可以捕获周围作用域的状态,并在回调被触发时使用这些状态。
- 循环中的异步操作:在循环中进行异步操作时,使用闭包可以解决由于变量提升导致的问题,确保在异步操作完成时能够获取到正确的循环变量值。
- 缓存:利用闭包缓存计算结果,避免重复计算,提高程序的性能。
内存泄漏风险:
闭包可能导致内存泄漏,如果内部函数引用了外部函数的变量,并且外部函数又引用了内部函数,会导致内存泄漏。因此,在使用闭包时,要注意避免出现循环引用的情况。
事件循环
setTimeout、Promise、async/await的执行顺序:
在JavaScript中,setTimeout
、Promise、async/await
的执行顺序遵循以下规则:
- 同步代码执行:在任何异步操作开始之前,首先会执行所有的同步代码。
- setTimeout:
setTimeout
设置的回调函数会被放入JavaScript的事件队列中,等待当前同步代码执行完毕后才会执行。即使setTimeout
的延迟设置为0,它的回调函数也不会立即执行,而是会在同步代码执行完后,等待事件队列空闲时才会执行。setTimeout
的回调函数被放入宏任务队列中。 - Promise:Promise对象表示一个异步操作的最终完成(或失败)及其结果值。Promise的执行顺序依赖于其状态。如果Promise已经处于fulfilled或rejected状态,那么其
.then()
或.catch()
中的回调函数会立即执行(但实际上会被放入微任务队列中等待当前同步代码执行完毕后执行)。如果Promise还在pending状态,那么它的回调函数会被放入微任务队列中,等待当前同步代码和事件队列中的任务执行完毕后才会执行。 - async/await:
async
函数总是返回一个Promise对象。await
关键字用于等待一个Promise的解决或拒绝,并返回Promise的解决值。async/await
的执行顺序也依赖于Promise。async
函数内部的代码会同步执行,直到遇到await
关键字。在await
关键字处,async
函数的执行会暂停,并等待Promise的解决。一旦Promise解决,async
函数的执行会恢复,并继续执行后面的代码。由于async/await
的执行是基于Promise的,因此它们通常会被放入微任务队列中,与Promise的回调函数一起执行。
原型链
new操作符:
new
操作符在JavaScript中用于创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型的实例。具体来说,new
操作符执行了以下操作:
- 创建一个空对象。
- 将这个空对象的原型链接到构造函数的
prototype
属性。 - 绑定
this
到新创建的对象,并调用构造函数执行其代码(即给新对象添加属性)。 - 如果构造函数没有显式返回对象,则默认返回新创建的对象。
实现继承:
在JavaScript中,实现继承的方式主要有原型链继承、借用构造函数继承、组合继承(原型链+借用构造函数继承)、原型式继承、寄生式继承、寄生组合式继承等。其中,原型链继承是最基本也是最常见的一种方式。通过原型链继承,一个对象可以继承另一个对象的属性和方法。具体来说,就是将子类型的原型对象指向父类型的实例对象,从而实现继承。这样,子类型就可以访问父类型原型对象上的属性和方法了。
ES6+
箭头函数特性:
箭头函数是ES6中引入的一种新的函数写法,相比传统的函数写法,箭头函数具有以下特性:
- 更简洁的语法:如果函数体只有一个语句,可以省略大括号和
return
关键字。 - 没有
this
绑定:箭头函数不绑定自己的this
,它会捕获其所在上下文的this
值作为自己的this
值。这意味着在箭头函数内部使用this
时,它引用的是定义时所在上下文的this
值,而不是调用时的上下文。 - 不能用作构造函数:由于箭头函数没有
this
绑定,也没有prototype
属性,因此它们不能用作构造函数。 - 没有
arguments
对象:箭头函数不提供arguments
对象。如果需要访问函数的参数列表,可以使用剩余参数语法(...args
)。 - 不能改变
this
的指向:call()
、apply()
、bind()
等方法对于箭头函数来说只是调用函数,并不会改变this
的指向。
let/const vs var:
在ES6之前,JavaScript中声明变量的关键字只有var
。但是var
存在一些问题,比如变量提升(hoisting)和函数作用域(而不是块级作用域)。为了解决这个问题,ES6引入了let
和const
两个新的关键字来声明变量。
- 块级作用域:
let
和const
都具有块级作用域,这意味着它们只在声明它们的块或子块中可用。相比之下,var
声明的变量具有函数作用域或全局作用域。 - 重复声明:在同一作用域内,不能使用
let
或const
重复声明同一个变量。但是,可以使用var
重复声明变量(尽管这通常被认为是不好的做法)。 - 常量:
const
用于声明一个只读的常量。一旦const
变量被赋值,其值就不能再被改变(对于基本数据类型来说是这样;对于对象来说,const
保证的是引用地址不变,但对象本身的内容是可以修改的)。
解构赋值的实际应用:
解构赋值是ES6中引入的一种新的语法,允许从数组或对象中提取数据,并将其赋值给单独的变量。解构赋值在实际应用中非常有用,比如:
- 交换变量值:可以通过解构赋值方便地交换两个变量的值。
- 函数参数解构:在函数定义时,可以使用解构赋值从对象或数组中提取参数。
- 提取对象属性:可以从对象中提取属性并将其赋值给单独的变量,而无需使用点符号或方括号语法。
- 嵌套解构:可以嵌套地使用解构赋值来提取深层嵌套的对象或数组元素。
- 默认值:在解构赋值时,可以为变量指定默认值,以防提取的值是
undefined
或null
。
综上所述,作用域与闭包、事件循环、原型链以及ES6+的新特性都是JavaScript编程中的重要概念。理解这些概念有助于编写更高效、更可维护的代码。
浏览器原理
浏览器原理
框架
Vue&React
Diff算法是一种通过同层的树节点进行比较的高效算法,主要用于虚拟DOM树发生变化后,生成DOM树更新补丁的方式。以下是Diff算法的大致逻辑:
一、基本策略
- 同层比较:Diff算法的比较只会在同层级进行,不会跨层级比较。也就是说,它会比较两个节点的子节点,而不会比较父节点或兄弟节点。这种策略使得Diff算法能够以相对较小的计算量来找出节点之间的差异。
- 深度优先遍历:Diff算法使用深度优先遍历的方式来比较节点。它首先比较两个节点的类型,如果类型不同,则直接替换整个子树。
- 键值比较:在比较子节点时,Diff算法使用键值(key)来识别节点。如果两个节点的键值相同,则认为它们是同一个节点,并继续比较它们的子节点。如果键值不同,则删除旧的节点并插入新的节点。
二、比较流程
Diff算法的比较流程通常分为以下几个步骤:
- 初始化指针:设置新旧虚拟DOM节点的头尾指针,用于遍历和比较节点。
- 自前向后的对比(sync from start):从头部开始,依次比较新旧虚拟DOM节点的相同位置的节点。如果找到相同的节点,则进行打补丁操作(patch)。如果找到不同的节点,则跳出循环,进入下一步。
- 自后向前的对比(sync from end):从尾部开始,依次比较新旧虚拟DOM节点的相同位置的节点。这一步是为了处理那些在自前向后对比中未匹配的节点,看它们是否在尾部有相同的节点。
- 处理新节点多于旧节点的情况(common sequence + mount):如果新节点的数量多于旧节点的数量,则需要挂载新的节点。这一步会遍历剩余的新节点,并将它们插入到DOM中合适的位置。
- 处理旧节点多于新节点的情况(common sequence + unmount):如果旧节点的数量多于新节点的数量,则需要卸载旧的节点。这一步会遍历剩余的旧节点,并将它们从DOM中移除。
- 处理未知序列:如果以上步骤都没有找到匹配的节点,则需要遍历整个新旧虚拟DOM树,找到所有需要更新、创建或删除的节点,并进行相应的操作。
三、节点复用与创建
在Diff算法中,节点复用是一个重要的优化手段。当新旧虚拟DOM树中有相同的节点时,会尽量复用旧的节点,而不是创建新的节点。这可以减少DOM操作的次数,提高性能。如果在新虚拟DOM树中找到了一个与旧虚拟DOM树中某个节点相同的节点(通过键值比较),则会将这个旧节点打补丁后复用。如果没有找到相同的节点,则需要创建一个新的节点,并将其插入到DOM中合适的位置。
四、应用与优势
Diff算法广泛应用于前端框架中,如React、Vue等。通过使用Diff算法,这些框架能够在不重新渲染整个页面的情况下更新UI,从而提高了应用的性能和响应速度。Diff算法的优势在于它能够以较小的计算量找出节点之间的差异,并生成最小化的DOM变更补丁,从而减少了不必要的DOM操作。
综上所述,Diff算法是一种高效的比较和更新算法,它通过同层比较、深度优先遍历和键值比较等策略来找出新旧虚拟DOM树之间的差异,并生成最小化的DOM变更补丁。这些补丁随后可以用于更新真实的DOM树,从而提高应用的性能和响应速度。
HOC&自定义Hooks
设计可复用的高阶组件(HOC)或自定义 Hook 是 React 开发中的重要技能,它们能够帮助你提高代码的可重用性和可维护性。以下是一些设计原则和最佳实践,用于创建高质量的 HOC 或自定义 Hook:
高阶组件(HOC)设计原则
-
单一职责原则:
- 确保每个 HOC 只负责一个特定的功能。这有助于保持代码的清晰和可维护性。
-
无状态:
- 尽量避免在 HOC 中引入状态。如果确实需要状态,考虑使用自定义 Hook 而不是 HOC。
-
透传 props:
- 将未使用的 props 透传给被包裹的组件。这有助于保持组件的灵活性。
-
命名约定:
- 使用
with
前缀命名 HOC(例如withAuth
),以表明这是一个高阶组件。
- 使用
-
不要修改传入的组件:
- HOC 应该返回一个新的组件,而不是修改传入的组件。
-
纯函数:
- 确保 HOC 是一个纯函数,即给定相同的输入总是返回相同的输出。
自定义 Hook 设计原则
-
命名清晰:
- 使用动词或动词短语命名自定义 Hook(例如
useFetch
、useWindowSize
),以表明这是一个执行特定操作的 Hook。
- 使用动词或动词短语命名自定义 Hook(例如
-
封装逻辑:
- 将可复用的逻辑封装在自定义 Hook 中,以便在多个组件中共享。
-
避免副作用的滥用:
- 在自定义 Hook 中使用
useEffect
来处理副作用,但要确保它们是有必要的,并且被正确地管理。
- 在自定义 Hook 中使用
-
保持简单:
- 自定义 Hook 应该尽量保持简单和专注,避免包含过多的逻辑或状态。
-
提供默认值和参数:
- 为自定义 Hook 提供合理的默认值和可选参数,以增加其灵活性和可用性。
-
文档和示例:
- 为自定义 Hook 提供清晰的文档和示例代码,以帮助其他开发者理解和使用它们。
实践示例
高阶组件示例:
function withAuth(WrappedComponent) {
return function AuthComponent(props) {
const isAuthenticated = // ... 逻辑来检查用户是否已认证
if (!isAuthenticated) {
// 重定向到登录页面或显示错误消息
return <Redirect to="/login" />;
}
// 透传 props 给被包裹的组件
return <WrappedComponent {...props} />;
};
}
自定义 Hook 示例:
import { useState, useEffect } from 'react';
function useWindowSize() {
// 初始化窗口大小状态
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
// 处理窗口大小变化事件
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener('resize', handleResize);
// 清理函数,在组件卸载时移除事件监听器
return () => window.removeEventListener('resize', handleResize);
}, []); // 空依赖数组意味着这个 effect 只在组件挂载和卸载时运行一次
return windowSize;
}
通过遵循这些设计原则和最佳实践,你可以创建出强大、灵活且易于维护的 HOC 和自定义 Hook,从而提高你的 React 应用的代码质量和开发效率。