new 操作符 JavaScript
中的 new 操作符是对一个构造函数创建实例的过程 如
1 2 3 4 5 function Person (name ) { this .name = name } const yueyun = new Person ('yueyun' )console .log (yueyun)
new 通过构造器创建的实例能访问构造函数中的属性 可以访问到构造属性原型链的属性
1 2 3 4 5 6 7 8 9 function myNew (Func ,...args) { const obj = {} obj.__proto__ = Func .prototype const result = Func .apply (obj,..args ) return result instanceof Object ? result : obj }
为什么要使用虚拟 DOM 虚拟 DOM 的性能一定比真实 DOM 差吗? 虚拟 DOM 比上真实 DOM 有如下几点原因
性能优化: 真实的 DOM 操作是昂贵的(性能消耗较大)。每次更改页面上的某个小部分时,浏览器可能需要重新计算布局并重新绘制整个页面。虚拟 DOM 允许框架在 JavaScript 中构建一个轻量级的 DOM 树副本 配合使用 Diff 算法 计算出渲染为真实 dom 的最小代价操作,再渲染为真实 DOM 利用 JS 运算成本来换取 DOM 执行成本的操作,而 JS 的运算速度快很多。DOM 引擎、JS 引擎 相互独立,但又工作在同一线程(主线程) JS 代码调用 DOM API 必须 挂起 JS 引擎、转换传入参数数据、激活 DOM 引擎,DOM 重绘后再转换可能有的返回值,最后激活 JS 引擎并继续执行若有频繁的 DOM API 调用,且浏览器厂商不做“批量处理”优化, 引擎间切换的单位代价将迅速积累若其中有强制重绘的 DOM API 调用,重新计算布局、重新绘制图像会引起更大的性能消耗。 虚拟 DOM 不会立马进行排版与重绘操作 虚拟 DOM 进行频繁修改,然后一次性比较并修改真实 DOM 中需要改的部分,最后在真实 DOM 中进行排版与重绘,减少过多 DOM 节点排版与重绘损耗 虚拟 DOM 有效降低大面积真实 DOM 的重绘与排版,因为最终与真实 DOM 比较差异,可以只渲染局部 声明式编程:使用虚拟 DOM 允许开发者通过声明式编程更加直观和简洁地描述界面应该呈现的样子。开发者只需关心数据的状态,界面应该如何根据这些状态变化而变化,而不是如何操作 DOM 来实现这些变化 跨平台: 虚拟 DOM 作为一个简单的 JavaScript 对象模型,可以使得同一个应用能够运行在不同的环境中,例如在浏览器、服务器(SSR)、甚至原生移动应用中(如 React Native)。这种跨平台的能力是因为虚拟 DOM 提供了一个与具体平台无关的中间层。 SPA 和 MPA SPA 即是(single page application) 意思是单页应用 网页应用模型 就是一张 web 页面(index.html)单页面跳转仅刷新局部资源、公共资源仅需要加载一次 即是使用 JS 去感知 URL 的变化去做动态改变
好处即是:性能更快 页面跳转时不用处理html
节约了请求花费 用户体验好 快 良好的前后端分离 传递数据方便 一般是高度交互的应用使用 SPA 比较多
缺点即使:SEO 不友好 内容通过 JavaScript 动态加载
MPA:多页面应用,MPA 在用户浏览不同的页面时,服务器将为每个请求返回新的 HTML 页面。这意味着每次用户请求新页面或刷新现有页面时,整个页面都会被重新加载 适合内容丰富的网站,如新闻网站、电商平台等,它们可以从每个页面的独立 URL 中获益。
路由的 history 和 hash 两种模式
Hash 模式主要是通过 URL 中的哈希值变化来控制页面的显示兼容性好,可以在不支持 HTML5 History API 的老旧浏览器中使用 ,而 History 模式则是利用 HTML5 的 History API 来实现 URL 的变化,URL 看起来更美观,没有#符号,用户体验更接近传统的多页面应用
hash 模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Router { constructor ( ) { this .routes = {} this .currentUrl = '' window .addEventListener ( 'load' , () => { }, false ) window .addEventListener ('hashchange' , () => {}, false ) } router (path, cb ) { this .routers [path] = cb } push (path ) { this .routes [path] && this .routes [path]() } render ( ) {} } window .miniRouter = new Router ()miniRouter.route ('/' , () => console .log ('page1' )) miniRouter.route ('/page2' , () => console .log ('page2' )) miniRouter.push ('/' ) miniRouter.push ('/page2' )
history 模式
history 模式借用HTML5 history api
api
提供了丰富的 router
相关属性先了解一个几个相关的 api
history.pushState
浏览器历史纪录添加记录history.replaceState
修改浏览器历史纪录中当前纪录history.popState
当 history
发生变化时触发1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Router { constructor ( ) { this .routes = {} this .listerPopState () } init (path ) { history.replaceState ({ path : path }, null , path) this .routes [path] && this .routes [path]() } route (path, cb ) { this .routes [path] = cb } push (path ) { history.pushState ({ path : path }, null , path) this .routes [path] && this .routes [path]() } listerPopState ( ) { window .addEventListener ('popstate' , (e ) => { const path = e.state && e.state .path this .routes [path] && this .routes [path]() }) } } window .miniRouter = new Router ()miniRouter.route ('/' , () => console .log ('page1' )) miniRouter.route ('/page2' , () => console .log ('page2' )) miniRouter.push ('/' ) miniRouter.push ('/page2' )
如何给 SPA 做 seo 优化?
服务端渲染 比如nuxt
next
等 静态化(静态站点生成 SSG)一种是通过程序将动态页面抓取并保存为静态页面 通过 WEB 服务器的 URL Rewrite
的方式,它的原理是通过 web 服务器内部模块按一定规则将外部的 URL 请求转化为内部的文件地址,一句话来说就是把外部请求的静态地址转化为实际的动态页面地址,而静态页面实际是不存在的 React Fiber 架构 Fiber 是什么 Fiber 是 react16 中新的协调引擎, 主要目的是使 Virtual Dom 可以进行增量式的渲染(FiberNode)
React 的 Reconciler 基于 Fiber 节点实现, 即是 Fiber Reconciler
作为静态的数据结构来说, 每个 Fiber 节点对应一个 React Element, 保存了该组件类型(函数组件/类组件/原生组件), 对应的 DOM 节点信息 作为动态的工作单位, 每个 fiber 节点保存了本次更新的组件变更状态, 要执行的操作状态(删除/插入/更新) 无论是作为数据结构还是工作单位, 都是使用 FiberNode 的身份存在, 即在 react 架构中命令为 Fiber, 协调器就是 Fiber Reconciler
Fiber 的作用 为了解决 React15 在组件中更新时产生的卡顿现象, 构建出了 Fiber 架构, 并在 React16 中发布, 将同步递归无法终中断的更新 重构为 异步的可中断更新
把可中断的任务拆分成小任务 对正在做的任务调整优先次序, 重做, 复用等 在父子任务间切换, 支持 React 执行过程中的布局刷新 支持 render 返回多个元素 error bounday React 中的架构
Scheduler(调度器): 一个渲染工作分解成多个小任务,并在多个帧之间分配和执行这些任务(requestldleCallback) Reconciler(协调器): Reconciler 则从递归变成了可以中断的循环过程。每次循环都会调用 shouldYield 判断当前是否有剩余时间 Render(渲染器): 将 Reconciler 打上标签的虚拟 DOM 对象(即 FiberNode)执行成 DOM(JS 变 视图) Fiber 理论实现 优先级分配 + 异步可中断
FiberNode 的结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 function FiberNode ( this : $FlowFixMe, tag: WorkTag, pendingProps: mixed, key: null | string , mode: TypeOfMode ) { this .tag = tag this .key = key this .elementType = null this .type = null this .stateNode = null this .return = null this .child = null this .sibling = null this .index = 0 this .ref = null this .refCleanup = null this .pendingProps = pendingProps this .memoizedProps = null this .updateQueue = null this .memoizedState = null this .dependencies = null this .mode = mode this .flags = NoFlags this .subtreeFlags = NoFlags this .deletions = null this .lanes = NoLanes this .childLanes = NoLanes this .alternate = null }
Fiber 是如何工作的 ReactDOM.render() (mount) 和 setState (update) 的时候开始创建更新 Schedule(调度器)设置优先级, 并将创建的更新加入任务队列, 等待调度 在 RequestldleCallback 空闲时执行任务 从根节点开始遍历 FiberNode,并且构建 WorkInProgress Tree Reconciler(协调器) 阶段生成 EffectList(对其打标签,进行 Diff 对比) Renderer(渲染器) 根据 EffectList 更新 DOM 设计模式 单例模式(Singleton) 确保一个类只有一个实例, 并提供全局访问点来获取该实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Singleton { private static instance : Singleton private constructor ( ) { } public static getInstance (): Singleton { if (!Singleton .instance ) { Singleton .instance = new Singleton () } return Singleton .instance } public someMethod ( ) { console .log ('This is a method of the Singleton class.' ) } } const singleton1 = Singleton .getInstance ()const singleton2 = Singleton .getInstance ()console .log (singleton1 === singleton2) singleton1.someMethod ()
工厂模式(Factory) 通过工厂方法创建对象,而不是直接使用new
操作符。这样可以隐藏具体实现,并根据需要创建所需类型的对象
假设我们现在有一个产品接口Product
和两个具体的产品类ConcreteProductA
|ConcreteProductB
我们将使用一个工厂类ProductFactory
来创建这些产品的实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 interface Product { operation (): string } class ConcreteProductA implements Product { public operation ():string { return 'Result of the ConcreteProductA' } } class ConcreteProductB implements Product { public operation ():string { return 'Result of the ConcreteProductB' } } class ProductFactory { public static createProduct (type :string ):Product { switch (type ) { case : 'A' : return new ConcreteProductA () case : 'B' : return new ConcreteProductB (); default : throw new Error ('Unknow product type.' ) } } } const productA : Product = ProductFactory .createProduct ('A' );console .log (productA.operation ()); const productB : Product = ProductFactory .createProduct ('B' );console .log (productB.operation ());
观察者模式(Observer) 定义了一种对一对多的依赖关系, 当一个对象状态发生改变时, 他的所有依赖者 (观察者) 都会收到通知并自动更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class SubJect { constructor ( ) { this .observes = [] } add (observe ) { this .observes .push (observe) } remove (observe ) { this .observes = this .observes .filter ((item ) => item !== observe) } notify ( ) { this .observes .forEach ((item ) => { item.update () }) } } class Observe { constructor (name ) { this .name = name } update ( ) { console .log ('update' , this .name ) } } const observe1 = new Observe ('observe1' )const observe2 = new Observe ('observe2' )const sub = new SubJect ()sub.add (observe1) sub.add (observe2) sub.remove (observe1) sub.notify ()
装饰器模式(Decorator) 动态地将责任附加到对象上, 通过将对象包装在装饰器对象中, 可以在运行时为对象添加新的行为,对已有的功能进行拓展, 这样不会更改原有的代码, 对其他的业务产生影响 这方便我们在较少的改动下对软件功能进行拓展
比如现在给上传数据加上一个 pv 操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Function .prototype .before = function (beforeFn ) { const that = this return function (...args ) { beforeFn.apply (this , ...args) return that.apply (this , ...args) } } const log = ( ) => { console .log ('打印上传前的日志' ) } const upload = ( ) => { console .log ('上传数据' ) } newUpload = upload.before (log) newUpload ()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import 'reflect-metadata' function Get (path: string ): MethodDecorator { return (target, propertyKey ) => { Reflect .defineMetadata ('path' , path, target, propertyKey) } } async function fetchRequest (path: string ) { const response = await fetch (path) const data = await response.json () return data } function handleRequest ( target: any , propertyKey: string , descriptor: PropertyDescriptor ) { const method = descriptor.value descriptor.value = async function (...args: any [] ) { const path = Reflect .getMetadata ('path' , target, propertyKey) if (path) { const data = await fetchRequest (path) return method.apply (this , [data, ...args]) } return method.apply (this , args) } } class MyController { @Get ('https://jsonplaceholder.typicode.com/todos/1' ) @handleRequest async fetchData (data: any ) { console .log ('Fetched Data:' , data) } } ;(async () => { const controller = new MyController () await controller.fetchData () })()
策略模式(Strategy) 定义了一系列算法,将每个算法封装起来并使它们可以相互替换。策略模式可以让算法独立于客户端而变化
比如创建一个简单的支付系统, 有不同的支付策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 interface PaymentStrategy { pay (amount : number ): void } class CreditCardPayment implements PaymentStrategy { pay (amount : number ): void { console .log (`Paid ${amount} using Credit Card.` ) } } class PaypalPayment implements PaymentStrategy { pay (amount : number ): void { console .log (`Paid ${amount} using PayPal.` ) } } class PaymentContext { private strategy : PaymentStrategy constructor (strategy: PaymentStrategy ) { this .strategy = strategy } setStrategy (strategy: PaymentStrategy ) { this .strategy = strategy } executeStrategy (amount : number ): void { this .strategy .pay (amount) } } const creditCardPayment = new CreditCardPayment ()const paypalPayment = new PaypalPayment ()const paymentContext = new PaymentContext (creditCardPayment)paymentContext.executeStrategy (100 ) paymentContext.setStrategy (paypalPayment) paymentContext.executeStrategy (200 )
适配器模式(Adapter) 将一个类的接口转换成客户端所期望的另一个接口。适配器模式使得原本由于接口不匹配而无法一起工作的类可以协同工作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class TencentMap { show ( ) { console .log ('腾讯地图渲染' ) } } class BaiduMap { display ( ) { console .log ('百度地图渲染' ) } } class TencentMapAdapter extends TencentMap { constructor ( ) { super () } showMap ( ) { this .show () } } class BaiduMapAdapter extends BaiduMap { constructor ( ) { super () } showMap ( ) { this .display () } } function render (map ) { map.showMap () } render (new TencentMapAdapter ())render (new BaiduMapAdapter ())
渲染是十万条数据解决方案 虚拟列表(按需渲染 || 可视区域渲染)
虚拟列表是按需显示的一种实现, 即只对可见区域进行渲染, 对非可见区域的数据不渲染或部分渲染的技术, 通过监听 scroll 来判断是上滑还是下拉, 从而更新数据, 同理 IntersectionObserver 和 getBoundingClientReact 实现
延迟渲染(即懒渲染)
最开始不渲染所有数据,只渲染可视区域中的数据(同虚拟列表一致)。当滚动到页面底部时,添加数据(concat),视图渲染新增 DOM
时间分片
分批渲染 DOM,使用 requestAnimationFrame 来让动画更加流畅, 在浏览器渲染的每一帧的空闲时间去渲染数据
为什么 Hooks 不能存在条件判断中 在 React 中, Hooks 是基于顺序调用的, 意味着在 Hooks 的调用顺序必须保持稳定, 不能在渲染的过程中发生改变, 这样会导致顺序发生变化, 违反了 React Hooks 设计规则, 在重新 render 的时候, react 会根据 hooks 的调用顺序来确定每个 hook 的对应状态, 如果放入条件语句中会导致组件的状态混乱, 不可测
在 React Hooks 的源码中, Hooks 的状态是与组件实例相关联, 每次组件重新渲染时, React 会使用相同的顺序来调用 hooks, 这样才能确保正确的管理组件状态, Hooks 的实现是基于链表结构, 当组件函数被调用的时候, React 会根据 Hooks 的调用顺序创建一个链表, 并将这个链表与实例相关联, 而这个 hook 存在一个对象里面存在一些属性(fiber)
JavaScript 中的垃圾回收机制 从输入网址到页面显示发生了那些过程 HTTP 请求而发送过程
总体来说大致是以下几个过程
生产 HTTP 信息
解析 URL 生成发送给 Web 服务器的请求信息
DNS 解析
递归的根据域名查询 IP 地址 DNS 优化中有 DNS 缓存, DNS 负载均衡
协议栈
通过 DNS 获取到 IP 地址后, 可以把 HTTP 的传输工作交给操作系统中的协议栈 TCP/UDP IP
发起 TCP 请求
三次握手(保证双方都有发送和接受的能力)和四次挥手, 建立 TCP 链接, 生成 TCP 报文
发送 IP 请求定位
IP 模块将数据封装成网络包发给通信的对象 IP 源地址(客户端地址) 根据路由表规则去匹配判断那张网卡去发送 可以手动配置静态路由, 也可以通过路由协议(RIP OSPF)去配置动态路由表
Mac 地址两点传输
发送 HTTP 请求 (IP 层 – 链路层)
服务器处理请求返回 HTTP 报文
浏览器解析渲染页面
断开连接
为什么 0.1 + 0.2 !== 0.3
浮点数在内存中是怎么存储的