一些有趣的前端知识
一些有趣的前端知识
浏览器的渲染机制
1. 解析 HTML 结构
浏览器首先从顶部到下方解析 HTML 文档,按照顺序识别标签。解析过程中会生成 DOM(Document Object Model) 树,描述页面结构。
2. CSSOM 树的构建
当解析到 <link>
标签或 <style>
标签时,浏览器会下载并解析 CSS,生成 CSSOM(CSS Object Model) 树。DOM 树和CSSOM 树结合后生成渲染树,决定元素的视觉呈现。
3. 加载和渲染顺序
通常情况下,渲染顺序是自上而下的,遵循文档的结构和样式优先级。
4. 阻塞资源
在 HTML 渲染过程中,以下资源会暂时“阻塞”渲染,直到它们加载完成:
- CSS 文件:浏览器会等待 CSS 文件加载完成,确保正确应用样式后再进行页面渲染。
- 因为
@import
会导致 CSS 文件的串行加载(即一个文件加载完后才开始下一个),相比<link>
更慢。因此,通常推荐将 CSS 文件通过<link>
引入,特别是在网站性能要求较高的情况下。
- 因为
- JavaScript 文件:默认情况下,
<script>
标签会阻塞 HTML 渲染,直到脚本加载并执行完成。- script标签中defer和async的区别
- script:会阻碍 HTML 解析,只有下载好并执行完脚本才会继续解析 HTML。
- defer:浏览器指示脚本在文档被解析后执行,script 被异步加载后并不会立刻执行,而是等待文档被解析完毕后执行。
- async:同样是异步加载脚本,区别是脚本加载完毕后立即执行,这导致async 属性下的脚本是乱序的,对于 script 有先后依赖关系的情况,并不适用
- script标签中defer和async的区别
5. 渲染和绘制
当浏览器构建好 DOM 和 CSSOM 后,会开始生成渲染树,对内容进行布局和绘制。渲染树确定元素的尺寸、位置、样式属性等,之后会绘制在屏幕上。
详细版可以看这里
优化SEO
- 合理的
title
、description
、keywords
- 语义化的
HTML
代码,img
带上alt
- 重要内容不要用
js
输出
html语义化
建议直接移步这篇博客
CSS的BFC
直接移步移步这篇博客
一个计算其子元素将如何定位,以及和其他元素的关系和相互作用的渲染区域
其触发方式
- 根元素 (当前文档中 html 标签就是一个BFC);
- float 的值不为 none 的其他属性值;
- overflow 的值不为 visible的其他属性值(有 hidden,auto,scroll);
- display 的值为 inline-block / table-cell / table-caption / flex / inline-flex;
- position 的值为 absolute 或 fixed;
特性:
边距合并:BFC 内部的元素的边距不会与外部元素的边距合并。即使在同一块上下文中,边距也不会相互影响。
清除浮动:BFC 可以包含内部的浮动元素,从而确保父元素能够正确计算高度,避免高度塌陷的问题。
不与浮动区域重叠: BFC
区域不会与浮动盒子发生重叠。
JS的 Symbol类型
可以用来表示一个独一无二的变量防止命名冲突。
还可以利用
symbol
不会被常规的方法遍历到,所以可以用来模拟私有变量。主要用来提供遍历接口
1
2
3
4
5
6
7
8
9const range ={
form:1,
to:5,
*[Symbol.iterator](){ // 迭代器
for(let v = this.form;v<=to;v++){
yield v;
}
}
}Symbol.for()
可以在全局访问symbol
事件流
包括事件捕获阶段,目标阶段,冒泡阶段,一般利用的都是冒泡阶段,冒泡阶段是事件从触发的元素一直向父节点传播
添加事件的方式有
- 直接在html上绑定事件,
element.onclick = func()
addEventListener
注册事件,removeEnventListener
移除事件,其中的参数需要指向同一个函数
事件代理
事件代理,俗地来讲,就是把一个元素响应事件(click
、keydown
……)的函数委托到另一个元素
前面讲到,事件流的都会经过三个阶段: 捕获阶段 -> 目标阶段 -> 冒泡阶段,而事件委托就是在冒泡阶段完成
事件委托,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,而不是目标元素
原型继承
原型:是 JavaScript
中对象的一种特殊属性,它允许对象共享属性和方法,从而实现继承。在 JavaScript
中,每个对象都有一个原型对象,当访问对象的属性或方法时,如果当前对象中没有找到,JavaScript
引擎就会沿着原型链向上查找,直到找到该属性或方法或达到 null
为止。
原型链: 是 JavaScript 中实现继承和共享属性的机制,它依赖于对象的 __proto__
指向上一级对象的原型,从而形成一个链式的继承结构。
构造函数:一个空对象obj
被创建,F.prototype
的值被链接到obj
的[[prototype]]
中,之后运行构造函数,之后返回obj
原型链中的继承:如果是一个构造函数中,想要添加函数,那么应该将函数挂载到该构造函数的prototype
上,这样共享内存和方法,提高效率。
this
指针:函数中,有调用者就指向调用者,没有就指向window,箭头函数都指向window,事件里的指向元素本身
call,apply,bind:
call、apply、bind 的共同点都是为了解决改变 this 的指向。作用都是相同的,只是传参的方式不同。
call() 和 apply() 是立即执行的,而 bind() 是返回一个函数。
call() 可以传递多个参数,第一个参数和 apply() 一样,是用来替换的对象,后面是参数列表。
apply() 最多只能有两个参数 —— 新this 对象和一个参数数组
argArray
bind() 和其他两个方法的作用也是一致的,只是该方法会返回一个函数,并且可以通过bind() 实现 柯里化。
Promise
使用:
1 | const a = new Promise((reso,reje)=>{reso(res);reje(res);}).then(fA1,fB1).then(fA2,fB2) |
实现:
可以看这篇文章,里面有详细的说明,我这里总结一下
首先是一些零碎的点,设置了三种状态,pending
,fulfilled
,rejected
来实现状态转化
为了实现异步,reso(res)
会将then
的fA1
存储,reje(res)
会将then
的fB1
存储,如果promise
中的函数执行完毕,就调用其中的fA1(res)/fB1(res)
,这样完成了异步
为了实现then
返回一个新的promise
,promise
中会有一个处理函数,处理函数的作用是为了将then
中的函数fA
,fB
的结果res
使用promise
的reso/reje
处理
Promise中包含了两个主要函数,一个是初始化函数,将执行结果保存在回调数组中,并在执行完毕后调用then的处理函数,另一个是then函数,负责将then中的处理函数放入回调数组中
闭包
什么是闭包:闭包是指一个函数可以记住周围的变量,并访问他们。
每个函数都是闭包的,他们在创建的时候会记录周围的变量
- 闭包的特性
- 保存:闭包将局部变量的生命周期拉长,不在随着函数调用结束而回收。
- 保护:不会成为全局变量、也不会收到外部影响。
- 可以构建私有变量。
- 闭包的应用
- 模块
- 私有属性
- 高阶函数、有状态的函数
- 闭包的缺点
- 内存泄漏
缓存
第一次加载资源:直接请求
下一次加载资源:判断是否有缓存,查看缓存是否过期,没过期就使用缓存,过期就开始协商缓存,通过以下两个字段
Last-Modified是服务器响应请求时,返回该资源文件在服务器最后被修改的时间;If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间.服务器通过修改事件判断这个资源是否修改,如果没有修改,则返回304,修改则返回200
Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),;If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值.同上理
Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since,同时存在则只有Etag / If-None-Match生效。为每个服务器上 Etag 的值都不一样,因此在考虑负载平衡时,最好不要设置 Etag 属性。
刷新
点击刷新按钮或者按 F5或者地址栏回车: 使用缓存
用户按 Ctrl+F5(强制刷新): 不适用强缓存,协商缓存使用
F12强制刷新缓存:不使用缓存
instanceof typeof
后者判断值类型,函数,是否是引用类型
前者判断一个对象是否是某个构造函数的实例或某个类的实例,会顺着原型链寻找
箭头函数
箭头函数不会创建自身的this,只会从上下文中寻找并继承this,箭头函数的this在定义的时候就已经确认了,之后不会改变。同时箭头函数无法作为构造函数使用,没有自身的prototype,也没有arguments。
事件循环
详细的可以看这篇博客
js
是单线程的
JS引擎之所以是单线程,是由于JavaScript最初是作为浏览器脚本语言开发的,并且JavaScript需要操作DOM等浏览器的API,如果多个线程同时进行DOM更新等操作则可能会出现各种问题(如竞态条件、数据难以同步、复杂的锁逻辑等),因此将JS引擎设计成单线程的形式就可以避免这些问题。
柯里化
柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c)
转换为可调用的 f(a)(b)(c)
。
可以通过这种方式实现只使用部分函数参数
拓展运算符
用处多,可以用来拓展对象和数组,可以将类数组转化为数组x
weakmap
其key是弱引用,不会阻止垃圾回收
DOM&BOM
DOM 主要关注网页内容和结构的操作,允许开发者通过 JavaScript 动态修改网页的显示内容。
BOM 主要关注与浏览器相关的功能,允许开发者与浏览器窗口和环境进行交互。
变量提升
- 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
- 声明提升还可以提高JS代码的容错性,使一些不规范的代码也可以正常执行
迭代器
1 | [Symbol.iterator](){ |
call,apply,bind
call可以接收多个参数,apply接收两个参数,第二个参数是个数组或者类数组
1 | Function.prototype.myCall=function(context,...args){ |
VUE原理
响应式原理
proxy,getter,setter,effect
虚拟dom
vnode,diff
Vue渲染时机
当有数据发生变化的时候,Vue会开启一个队列,将对应的订阅者watcher放入一个队列中中,等待下一个事件tick,多次变化只会被推入一次
Vue生命周期
创建前后:将data,props等数据初始化
挂载前后:构建虚拟dom,并挂载到dom上
更新前后:更新视图,在OnbeforeUpdate中的多次修改视图都会仅仅更新一次
React 节流和防抖
React在重新渲染的时候会将所有函数重新挂载
1 | function useDebounce(fn, delay=500, dep = []) { |
本地存储
同源策略
存储大小不同 4k 5M
有效时间不同
React的响应式原理
可以看这篇文章更加详细的解释了react的架构
react有在更新时有三个阶段:
Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler
Reconciler(协调器)—— 负责找出变化的组件
Renderer(渲染器)—— 负责将变化的组件渲染到页面上
在协调器中,react使用了一种新的响应式diff基础:fiber
这个可以帮助react在执行diff操作的时候,可以被中断,防止阻塞渲染进程。这个的原理是改变了虚拟dom树的存储结构,原本是之后子节点,现在是包含了下一个子节点和兄弟节点和父节点
可以看这篇文章
Vue响应式原理
在分析”响应式”之前, 我们应首先明确: 不进行任何逻辑封装的前提下, JS 是指令式的, 即对 X 的操作就局限于 X, 并不会影响到 Y 或 Z. 原生 DOM 操作也沿袭了指令式风格. 因此在为网站添加动态性, 也就是维护数据 -渲染-> DOM这一关系时, 数据变动指令其后必须跟随渲染指令
出于代码的简洁性, 让数据变动摆脱掉渲染这条”小尾巴”, 前端开发引入了响应式的概念. 具体表现为通过使用 React 或 Vue 等来自动维护数据和DOM之间的渲染逻辑. 当数据发生改变, React 或 Vue 将响应变化并依据改变后的结果执行渲染
此时我们可以将数据分到依赖组(dependencies), DOM分到结果组(results), 渲染逻辑从依赖组中获取数据, 经过执行后得到结果组. 那么泛化来说, 依赖组中可以放入任意数据; 结果组中也可以不局限于 DOM; 依赖和结果之间的绑定逻辑也可以自由指定, 且当依赖发生改变时自动执行, 来对结果进行更新, 维护依赖和结果之间的对应关系. 这就实现了响应式
更进一步, 当依赖组内部对象被读取(get), 这就表明某一逻辑依赖了该对象, 此时应追踪(track)该逻辑; 当依赖组内的对象被赋值(set), 此时应根据被赋值对象的追踪情况, 触发(trigger)所有依赖该被赋值对象的逻辑. 这时我们就实现了一个响应式系统