响应式原理

什么是响应式系统 在 Vue 中,数据驱动视图是一个非常重要的概念。当数据发生变化时,视图会自动更新,这是因为 Vue 实现了一个响应式系统,通过追踪依赖关系来自动更新视图。 简单来说,响应式系统就是 Vue 能够监听数据变化,并在变化时自动更新 DOM 的核心机制。在 Vue3 中,响应式系统的改进使得响应式数据可以更加高效地更新视图,同时也提升了开发体验和性能。 Vue2 的响应式原理 在 Vue2 中,响应式原理是通过 Object.defineProperty 来实现的。Vue 会在数据对象上定义 getter 和 setter,来实现数据变化时更新视图的功能。 举个例子,当一个数据对象被定义为响应式时,我们可以通过访问这个对象的属性来触发 get 操作。具体实现如下: Object.defineProperty(obj, 'foo', { get: function() { console.log('get foo') return obj._foo }, set: function(newVal) { console.log('set foo') obj._foo = newVal } }) 在上面的代码中,我们通过 Object.defineProperty 方法来监听对象上的属性 foo,当访问 foo 属性时,会触发 get 操作,并输出日志。同理,当给 foo 属性赋值时,会触发 set 操作,并输出日志。 在 Vue2 中,我们可以通过使用上面的方法来将数据对象中的所有属性转化成响应式的,以实现数据变化时更新视图的功能。 Vue3 的响应式原理 在 Vue3 中,响应式原理的实现方式和 Vue2 相比,有了相当大的变化。Vue3 直接使用了 ES6 Proxy 对象来拦截对对象的访问操作。Proxy 可以监听到对对象进行的访问、赋值等操作,并在这些操作发生时通知相关依赖以维护响应式系统的更新。 ...

October 5, 2024

Runtime

什么是 Runtime Runtime 是 Objective-C 的运行时系统,是一套底层的 C 语言 API。Objective-C 是一门动态语言,很多操作都是在运行时而非编译时决定的,这一切都依赖于 Runtime。 Runtime 提供的核心能力: 消息发送与转发 方法交换(Method Swizzling) 关联对象(Associated Objects) 动态创建类和对象 动态添加和修改方法、属性 关于对象、类、isa 指针等底层数据结构的详细介绍,请参考 Objective-C底层原理-NSObject。本文专注于 Runtime 的动态能力和实际应用。 消息发送机制 Objective-C 的消息发送机制是其动态性的核心体现。与 Swift 支持静态派发不同,Objective-C 的方法调用都是动态消息派发。更多关于两者的对比,请参考 Objective-C与Swift区别。 objc_msgSend Objective-C 的方法调用本质上是消息发送,编译器会将方法调用转换为 objc_msgSend 函数: // 源代码 [obj doSomething]; // 编译后 objc_msgSend(obj, @selector(doSomething)); 消息发送流程 1. 检查 receiver 是否为 nil,如果是则直接返回 2. 通过 isa 找到 receiver 的类对象 3. 在类对象的方法缓存(cache_t)中查找方法 4. 如果缓存命中,直接调用方法实现(IMP) 5. 如果缓存未命中,在类对象的方法列表中查找 6. 如果找到,缓存方法并调用 7. 如果未找到,沿着 superclass 链向上查找 8. 如果最终未找到,进入消息转发流程 方法缓存 为了提高消息发送效率,Runtime 在类对象中使用哈希表缓存最近调用的方法: ...

May 2, 2026

浏览器解析 HTML 文件

1. 什么是浏览器解析和渲染HTML 一句话概括:将html代码转换成页面上显示的内容。 表1:形象展示将HTML转换成页面上显示的内容 2. 浏览器解析和渲染HTML过程? 那么浏览器拿到HTML代码后是如何把其转换成页面上显示的内容的? 2.1 宏观描述执行过程 2.1.1 解析和渲染 首先我们来看两个概念,解析和渲染,确切来说是DOM解析和DOM渲染。 DOM解析: 生成DOM tree的过程。就是把你所写的各种html标签,生成一个DOM tree,可以认为就是生成了一个最原始的页面,一点样式都没有,毫无CSS修饰。 DOM渲染: 将DOM tree与css样式结合,生成Render tree,并将其呈现在浏览器页面上。浏览器会把本身默认的样式+用户自己写得样式整合到一起,形成一个CSS tree,而DOM渲染就是指DOM tree 和CSS tree 结合到一起,生成一个Render tree,呈现出一个带有样式的页面。 这里需要注意,DOM解析和DOM渲染由两个不同的线程来完成的,简单说就是二者可以并行进行。 DOM解析通常是由浏览器的主线程来完成的。DOM渲染是由浏览器的渲染线程来完成的。 分离解析和渲染的这种设计使得浏览器能够在解析 HTML 文档的同时将页面渲染到屏幕上,这样用户就能够更快地看到完整的页面了。 2.1.2 宏观描述执行过程 补充了解析和渲染的概念,我们来看看浏览器具体是如何把其转换成页面上显示的内容的。其过程大致分为下图的四步。 图1:浏览器解析和渲染HTML的过程 第一步:将载入的HTML文件解析成DOM树(DOM tree),并且将各个标记标识解析成DOM树的各个节点;在解析HTML的同时会将CSS样式解析成CSS规则(CSS rules)。 第二步:将解析成的DOM树和CSS规则进行关联生成渲染树(Render tree)。 第三步:进入布局阶段,为DOM树的每个节点分配在屏幕上出现的确切坐标(这一阶段还是渲染树) 第四步:进入绘制阶段,在这里渲染引擎的工作就结束了,接下来就给用户界面后端(UI Backend)对渲染树的每个节点进行绘制,呈现出页面效果。 2.2 细化执行过程 2.2.1 顺序执行 dom解析过程是从上向下,顺序执行的。顺序执行的意思是一行一行的执行,上一行执行完再执行下一行。 <h1>1<h1> <h1>2<h1> <h1>3<h1> <script></script> <style></style> <!-- 先1,再2,再3,按顺序解析。 --> 2.2.2 渐进式 浏览器是渐进式渲染,即边解析边渲染。通俗的说就是,这一行代码解析完,就可以在浏览器的页面渲染展示出来。因为浏览器的解析和渲染由两个不同线程来完成,所以可以实现边解析边渲染。 <h1>1</h1> <script>debugger</script> <h1>2</h1> <script>debugger</script> <h1>3</h1> <!-- 先解析渲染出1,再debugger;再解析渲染出2,再debugger;最后解析渲染出3 --> 2.3 问题:css、js会阻塞解析和渲染吗? 这部分内容可参考这篇掘金内容,内含测试代码:关于 JS 与 CSS 是否阻塞 DOM 的渲染和解析 - 掘金 ...

November 5, 2024

视觉格式化模型

大部分 Web 开发者都知道 CSS 中有一个盒模型,但很少有人知道在 CSS 的世界中还有另一个模型,他就是 CSS 的视觉格式化模型。 CSS 视觉格式化模型(英文称之Visual Formatting Model)。该模型主要是用来处理在视觉媒体上显示文档时使用的计算规则。仅从这一句话来描述,估计很多同学都会感到困惑,视觉格式化模型到底是什么鬼?大家先不用急着知道视觉格式化模型是什么,只需要知道它是 CSS 中很重要的一部分,如果理解了该部分所涉及到的知识点,将有助于你更好的理解和学习 CSS,特别是 Web 布局 什么是视觉格式化模型 在盒模型中我们了解到,在 CSS 的世界中,任何一个元素都是一个盒子,它具有一个专业术语,即盒模型。而视觉格式化模型会根据 CSS 盒模型将文档中的元素转换为一个个盒子。看到这里,很多同学可能更会感到困惑,“任何元素都是一个盒子,视觉格式化模型将元素转换为盒子,那这盒子是不是都是一样的盒子呢”? 首先要声明一点:视觉格式化模型和CSS盒模型不是同一个东西! 简单点说呢。Web 页面(文档树)是由很一个个盒子组成(因为任何元素都可以被视为是一个盒子),而视觉格式化模型却是一套规则,用来计算元素转换为盒子的规则。而页面的布局都由这些盒子的所处的各处位置组合而成。那么理解了元素怎么转成盒子的规则,就理解了 Web 页面是怎么布局。而每个盒子的布局主要由以下几个因素决定: 盒子的尺寸:精确指定、由约束条件指定或没有指定 盒子的类型:行内盒子(inline)、行内级盒子(inline-level)、原子行内级盒子(atomic inline-level)和块盒子(block) 定位方案:普通流定位、浮动定位或绝对定位 文档树中的其它元素:即当前盒子的子元素或兄弟元素 视窗尺寸与位置 所包含的图片的尺寸 其他的某些外部因素 先来看一张图,这个图是由下面的 HTML 结构构造出来的: <body> <header> <nav></nav> </header> <article> <p></p> <figure></figure> <p></p> </article> <aside></aside> <footer></footer> </body> 每个元素都是一个盒子,盒子中可以包含另一个或多个盒子。也正如上图所示,CSS 的视觉格式化模型会根据盒子的包含块(Containing Block,即包含其他盒子的块)的边界来渲染盒子。通常,盒子会创建一个包含其后代元素的包含块,但是盒子并不会被包含块限制住,盒子的布局会撑破包含块(即跑出包含块的边界),对于这种现象,在 CSS 中常被称为溢出。 溢出是一个很有意思的东东,在 Web 布局中时常会碰到,但在这一章中不做方面的阐述。 ...

October 10, 2024

watch与watchEffect

Watch 基本用法 当我们需要在数据变化时执行一些“副作用”:如更改 DOM、执行异步操作,我们可以使用 watch 函数: <script setup> import { ref, watch } from 'vue' const question = ref('') const answer = ref('This is answer. ;-)') // 侦听一个 ref watch(question, async (newQuestion, oldQuestion) => { answer.value = 'Thinking...' const res = await fetch('https://...') answer.value = (await res.json()).answer }) </script> <template> <input v-model="question" /> <p>{{ answer }}</p> </template> watch() 一共可以接受三个参数,侦听数据源、回调函数和配置选项。 侦听数据源 watch 的第一个参数可以是不同形式的“数据源”,它可以是: 一个 ref 一个计算属性 一个 getter 函数(有返回值的函数) 一个响应式对象 以上类型的值组成的数组 const x = ref(1) const y = ref(1) const doubleX = computed(() => x.value * 2) const obj = reactive({ count: 0 }) // 单个 ref watch(x, (newValue) => { console.log(`x is ${newValue}`) }) // 计算属性 watch(doubleX, (newValue) => { console.log(`doubleX is ${newValue}`) }) // getter 函数 watch( () => x.value + y.value, (sum) => { console.log(`sum of x + y is: ${sum}`) } ) // 响应式对象 watch(obj, (newValue, oldValue) => { // 在嵌套的属性变更时触发 // 注意:`newValue` 此处和 `oldValue` 是相等的 // 因为它们是同一个对象! }) // 以上类型的值组成的数组 watch([x, () => y.value], ([newX, newY]) => { console.log(`x is ${newX} and y is ${newY}`) }) 注意,你不能直接侦听响应式对象的属性值,例如: ...

October 5, 2024

正则表达式

前言 正则表达式使用单个[字符串]来描述、匹配一系列匹配某个[句法规则]的字符串,通常被用来检索、替换那些符合某个模式(规则)的文本。 创建 1.构造函数创建 new RegExp(‘正则表达式’,‘修饰符’) var reg = new RegExp('hello','igm'); 2.直接直面量创建 /正则表达式/修饰符 var reg = /hello/igm; 字符分类 1.普通字符 字母、数字、下划线、汉字、没有特殊含义的符号(,;!@等) 实际上不是特殊字符的字符都是普通字符 2.特殊字符 \:将特殊字符转义成普通字符 3.模式修饰符 i:ignoreCase,匹配时忽视大小写 m:multiline,多行匹配 g:global,全局匹配 字面量创建正则时,模式修饰符写在一对反斜线后 实例方法 1.exec() 用来匹配字符串中符合正则表达式的字符串 如果匹配到,返回值是一个result数组: [匹配的内容,index: 在str中匹配的起始位置,input: 参数字符串,groups: undefined] 否则返回null var str = 'Hello world javascript hello'; var reg = new RegExp('hello','igm'); var res = reg.exec(str); console.log(res); 输出结果为: [ 'Hello', index: 0, input: 'hello world javascript hello', groups: undefined ] 2.test() 用来测试待检测的字符串中是否有可以匹配到正则表达式的字符串 如果有返回true,否则返回false var reg = /hello/; var str = 'hello world'; console.log(reg.test(str)); //true 3.toSting() toLocaleString() 把正则表达式的内容转化成字面量形式字符串/有本地特色的字符串 var reg = /hello/; var str = 'hello world'; console.log(reg.toString(),typeof reg.toString()); // /hello/ string console.log(reg.toLocaleString(),typeof reg.toLocaleString()); // /hello/ string 4.valueOf() 返回正则表达式本身 ...

October 1, 2024

static关键字详解

基础概念 static 是 C 语言定义的存储类说明符(Storage Class Specifier),Objective-C 和 Swift 中均有使用,但语义有所扩展。它的核心作用可以归纳为两点: 改变存储位置:将局部变量从栈区移到数据段,使其在函数返回后依然存活 限制链接可见性:将全局变量或函数的链接属性从 external 改为 internal,使其仅在当前编译单元(.m 文件)内可见 flowchart TD A["static 关键字"] --> B["修饰局部变量"] A --> C["修饰全局变量"] A --> D["修饰函数"] A --> E["Swift 中的 static"] B --> B1["存储位置:栈 → 数据段"] B --> B2["生命周期:函数作用域 → 整个进程"] B --> B3["作用域不变:仍限于函数内"] C --> C1["存储位置不变:仍在数据段"] C --> C2["链接属性:external → internal"] C --> C3["作用域缩小:全局 → 当前文件"] D --> D1["存储位置不变:仍在代码段"] D --> D2["链接属性:external → internal"] D --> D3["作用域缩小:全局 → 当前文件"] 一、静态局部变量 基本用法 在函数或方法内部用 static 修饰局部变量,使其只初始化一次,后续调用保留上次的值。 ...

May 3, 2026

原子化CSS

1. 什么是原子化CSS 1.1 基本概念 原子化CSS(Atomic CSS) 近年来热度逐渐增加,与原子化CSS相关的库在Github上也收获上万的Star。那么什么是原子化CSS呢,引用文章 Let’s Define Exactly What Atomic CSS is 中定义:“原子化CSS是一种CSS架构方式,其支持小型、单一用途的类,其名称基于视觉功能。” 更加通俗的来讲,原子化CSS是一种新的CSS编程思路,它倾向于创建小巧且单一用途的class,并且以视觉效果进行命名。举个简单的例子: <!-- 原子化类定义 --> <style> .text-white { color: white; } .bg-black { background-color: black; } .text-center { text-align: center; } </style> <!-- 原子化类使用 --> <div class="text-white bg-black text-center">hello Atomic CSS</div> 1.2 VS 行内样式 看到以上的示例,你可能很快就想到,直接使用行内样式不是更好吗,还省去了原子类的定义。这个问题可以从样式编写、一致性、功能、和缓存四个方面来回答。 在样式编写层面,CSS预处理和后处理器很大程度上依赖单独的样式表,原子化CSS可以充分利用Sass、Less等CSS预处理器功能进行样式的编写,同时可以借助PostCSS进一步增强CSS的功能。而对于行内样式,虽然在技术上支持使用预处理和后处理器对其进行处理,但很少有成熟的工具对此提供支持和维护。 在一致性层面,原子化CSS框架一般有预定义的设计系统,开发者仅能在设计系统中选择要设置的值。而对于行内样式或者传统CSS类定义来说,可设置的值是没有任何限制的。对于行内样式或者传统的CSS类设置来说,一个标签的字体大小可能是14px或0.875rem,当产品(or 客户)说需要调小一点时,开发者A可能调整为13px,开发者B可能调整为12px。但对于原子化CSS框架来说,调小一点意味着设置的类从text-sm变为text-xs。 以下为部分采用传统CSS类编写的网站样式统计数据(统计来源): 掘金官网:283种背景颜色 471种字体颜色 264种字体大小 GitLab:1199种背景颜色 1351种字体颜色 450种字体大小 CSDN:585种背景颜色 1190种字体颜色 504种字体大小 在功能方面,原子化CSS本质上还是CSS类,因此支持媒体查询功能,也支持对元素的悬停、聚焦等状态进行处理,而内联样式缺少这部分的能力。 在打包方面,内联样式包含在JS文件中,样式的修改会导致整个bundle的改变,原子化CSS样式定义和JS逻辑分离,修改元素的class属性可能并不影响(在没有新CSS类的情况下)最终打包输出样式文件。 2. 谁在使用原子化CSS 截至目前已经有部分网站借鉴或者使用原子化CSS的思想重构了自己的web网站,比较知名的网站有Facebook、Twitter、Github、swipperjs等。根据网上公开的信息,Facebook在使用原子化CSS思想重构之后,仅登录页面的413KB样式文件,减少为整个站点的74KB。 Facebook ...

November 9, 2024

输入 URL 到页面渲染完成

一、网站加载概述 面试过程中,常常遇到这样一道面试题,输入URL到页面加载完毕,浏览器做了哪些工作? 首先输入一个URL,你会看到浏览器上面的标签页出现了一个loading图标,开始时是逆时针旋转,接着顺时针旋转,当前页面消失,显示我们常说的空白页面,接着出现显示我们请求的新页面。此时如果网络很差,你有可能看到短暂的DOM页面,然后再看到渲染后的正常页面,这是从表面看到的加载过程,实际浏览器做的要多得多。 为什么浏览器这么多戏呢?直接显示不好吗,当然不行,就像喝粥,能直接吃米、喝水吗? 在用户输入URL到页面展示,浏览器要先向服务器获取前端资源,然后再将服务器返回的字节流转化成对应的页面,每一阶段都需要浏览器对应的能力进行处理的。 作为前端开发,了解整个过程其实很重要,只有知道了浏览器加载页面的整个过程,才能在开发中避免可能跳的坑,才能在发现问题后迅速定位问题,才能在性能优化时提出更多的解决方案。 比如下面问题,看了文章相信你就知道了为什么了 为什么会存在空白页面?(为了解决这个问题,各大厂都在实践各种方案) 为什么js要在Dom后面引入 为什么有个页面崩溃会导致多个页面崩溃 浏览器的缓存策略是怎样的? 为什么我们常常修改host就可以访问对应的域名 等等 二、浏览器的多进程架构 为了看懂下面的内容,这里必须要了解下现代浏览器的多进程架构,进程和线程的关系等知识点。 浏览器也是从单个进程架构一步步迭代到现代的多进程架构。 如上图,我们可以看到线程和进程的关系 通常一个程序实例就是一个进程,浏览器会为他分配内存空间。 一个进程间的数据是共享的,单线程就是一个进程包含一个线程,一个线程处理所有的任务 多线程就是,一个进程中包含多个线程可以同时执行任务,共享数据和内存 线程不能单独存在,必须依附于进程,一个线程失败,会导致进程执行失败,进程销毁,内存会被立即回收。 1、进程 下图是我们打开一个掘金首页后,再打开任务管理器,观察发现,此时浏览器包括的进程有很多,浏览器主进程,GPU进程,网络进程,存储进程,音频进程,渲染进程,多个插件进程。 这些进程负责的功能如下: 进程 说明 浏览器进程 负责浏览器各个子进程的通信,处理浏览器界面,包括地址栏等 渲染进程 也就是我们看到的图中的标签页进程,也就是我们常说的浏览器内核,v8就在这个进程。主要负责解析html、js、css渲染页面等 网络进程 负责发起网络请求,解析返回头信息 GUI进程 负责将渲染进程生成的图块转化成位图 渲染进程是运行在沙箱之中的,可以执行js,但是不能获取系统权限,和浏览器进程通过IPC通讯。这是为了保证浏览器进程的安全,锁进沙箱,即使有恶意代码,也不能突破沙箱读取或写入系统信息。 2、线程 通过上图,我们已经知道线程是不能独立存在的,必须依附于进程。这里我们主要关注下渲染进程,因为他的主要工作就是:完成页面的渲染和展示。 如图,我们可以看到渲染进程包括很多线程,除了图上展示的,还有合成线程等,各有各的作用,具体内容,我们将会在后面一章节,浏览器渲染原理分析中在来讨论。 三、浏览器请求的具体流程 从用户输入域名到浏览器渲染页面完成的过程,可以分为以下几个部分: 1、输入信息处理 2、网络请求 3、服务器返回请求资源 4、浏览器渲染 这里每一步涉及到的知识都非常多,其中缓存也在每个阶段占了很大比重,接着展开描述前三个阶段: 1、地址栏输入信息处理 当输入一个URL,浏览器会判断输入信息是检索信息还是请求URL 如果是检索的信息,就构建请求搜索的URL,调用浏览器默认的搜索引擎进行检索。 如果符合URL格式,浏览器主进程就通过IPC通信机制将URL发送给网络进程。 2、网络进程发起网络请求 1)网络进程首先会查找浏览器缓存,判断缓存是否存在,是否过期 如果存在且不过期,就直接返回缓存信息。具体的缓存策略可以接着看下面的浏览器缓存策略 2)如果没有缓存或过期,就开始进行DNS解析 DNS解析的过程也很复杂,最终目的就是拿到目标主机的IP地址,具体的解析过程可以看下面的域名解析 3)建立http连接或https连接 http通过三次握手建立连接 https需要建立TLS连接,浏览器会验证网站的数字证书是否合法,是否到期,是否安全等,这里https有自己的一套认证逻辑,我们重点不在这一块。 这里两个问题常被问到,不了解可以学习一下 http和https的区别? 简述三次握手、四次挥手? 4)发送请求 网络进程会构建http请求头,向服务器发送实际请求 5)网络传输,服务器处理,返回对应的资源 请求从应用层发出,经过运输层、网络层、物理层、数据链路层找到服务器,服务器拿到请求信息,返回对应的资源 这里的服务器可能是代理服务器,也可能是CDN节点,会判断当前数据是否存在缓存,如果有缓存且有效,直接返回(具体看配置的缓存策略),否则才会从服务器获取。 网络传输数据也是很复杂的过程,具体可以看下面简单的介绍计算机网络体系模型 3、服务器返回对应资源 1)处理返回信息 ...

November 5, 2024

跨域

一、什么是跨域? 在前端领域中,跨域是指浏览器允许向服务器发送跨域请求,从而克服Ajax只能同源使用的限制。 什么是同源策略? 同源策略是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。 同源策略限制以下几种行为: Cookie、LocalStorage 和 IndexDB 无法读取 DOM和JS对象无法获得 AJAX 请求不能发送 二、常见的跨域场景 URL 说明 是否允许通信 www.domain.com/a.js www.domain.com/b.js www.domain.com/lab/c.js 同一域名,不同文件或路径 允许 www.domain.com:8000/a.js www.domain.com/b.js 同一域名,不同端口 不允许 http://www.domain.com/a.js https://www.domain.com/b.js 同一域名,不同协议 不允许 http://www.domain.com/a.js http://192.168.4.12/b.js 域名和域名对应相同ip 不允许 http://www.domain.com/a.js http://x.domain.com/b.js http://domain.com/c.js 主域相同,子域不同 不允许 http://www.domain1.com/a.js http://www.domain2.com/b.js 不同域名 不允许 三、9种跨域解决方案 1、JSONP跨域 jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。 1)原生JS实现: <script> var script = document.createElement('script'); script.type = 'text/javascript'; // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数 script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback'; document.head.appendChild(script); // 回调执行函数 function handleCallback(res) { alert(JSON.stringify(res)); } </script> 服务端返回如下(返回时即执行全局函数): ...

October 1, 2024