冒泡和捕获
大概遇到的情况就分为定长数据 与 不定长数据的处理吧。
定长数据
对于定长的数据包而言,发送端在发送数据的过程中,需要设置Content-Length
,来指明发送数据的长度。
当然了如果采用了Gzip压缩的话,Content-Length设置的就是压缩后的传输长度。
我们还需要知道的是👇
Content-Length
如果存在并且有效的话,则必须和消息内容的传输长度完全一致,也就是说,如果过短就会截断,过长的话,就会导致超时。Content-Length
,若是非Keep-alive,跟前面情况一样,Content-Length可有可无。那怎么来设置Content-Length
举个例子来看看👇
不定长数据
现在采用最多的就是HTTP/1.1版本,来完成传输数据,在保存Keep-alive状态下,当数据是不定长的时候,我们需要设置新的头部字段👇
通过chunked机制,可以完成对不定长数据的处理,当然了,你需要知道的是
Transfer-Encoding
,优先采用Transfer-Encoding里面的方法来找到对应的长度。那我们来模拟一下吧👇
上面使用的是nodejs中
http
模块,有兴趣的小伙伴可以去试一试,以上就是HTTP对定长数据和不定长数据传输过程中的处理手段。
一般两栏布局指的是左边一栏宽度固定,右边一栏宽度自适应,两栏布局的具体实现:
使用clear属性清除浮动,其语法如下:
如果单看字面意思,clear:left 是“清除左浮动”,clear:right 是“清除右浮动”,实际上,这种解释是有问题的,因为浮动一直还在,并没有清除。
官方对clear属性解释:“元素盒子的边不能和前面的浮动元素相邻”,对元素设置clear属性是为了避免浮动元素对该元素的影响,而不是清除掉浮动。
还需要注意 clear 属性指的是元素盒子的边不能和前面的浮动元素相邻,注意这里“前面的”3个字,也就是clear属性对“后面的”浮动元素是不闻不问的。考虑到float属性要么是left,要么是right,不可能同时存在,同时由于clear属性对“后面的”浮动元素不闻不问,因此,当clear:left有效的时候,clear:right必定无效,也就是此时clear:left等同于设置clear:both;同样地,clear:right如果有效也是等同于设置clear:both。由此可见,clear:left和clear:right这两个声明就没有任何使用的价值,至少在CSS世界中是如此,直接使用clear:both吧。
一般使用伪元素的方式清除浮动:
clear属性只有块级元素才有效的,而::after等伪元素默认都是内联水平,这就是借助伪元素清除浮动影响时需要设置display属性值的原因。
这样就能缩放到原来的0.5倍,如果是1px那么就会变成0.5px。viewport只针对于移动端,只在移动端上才能看到效果
方法 | 是否改变原数组 | 特点 |
---|---|---|
forEach() | 否 | 数组方法,不改变原数组,没有返回值 |
map() | 否 | 数组方法,不改变原数组,有返回值,可链式调用 |
filter() | 否 | 数组方法,过滤数组,返回包含符合条件的元素的数组,可链式调用 |
for...of | 否 | for...of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象,将异步循环变成同步循环 |
every() 和 some() | 否 | 数组方法,some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false. |
find() 和 findIndex() | 否 | 数组方法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值 |
reduce() 和 reduceRight() | 否 | 数组方法,reduce()对数组正序操作;reduceRight()对数组逆序操作 |
new操作符的执行过程:
(1)首先创建了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
具体实现:
现代计算机中数据都是以二进制的形式存储的,即0、1两种状态,计算机对二进制数据进行的运算加减乘除等都是叫位运算,即将符号位共同参与运算的运算。
常见的位运算有以下几种:
| 运算符 | 描述 | 运算规则 |
| --- | --- | --- | --- |
| &
| 与 | 两个位都为1时,结果才为1 |
| |
| 或 | 两个位都为0时,结果才为0 |
| ^
| 异或 | 两个位相同为0,相异为1 |
| ~
| 取反 | 0变1,1变0 |
| <<
| 左移 | 各二进制位全部左移若干位,高位丢弃,低位补0 |
| >>
| 右移 | 各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃 |
定义: 参加运算的两个数据按二进制位进行“与”运算。 运算规则:
总结:两位同时为1,结果才为1,否则结果为0。
例如:3&5 即:
因此 3&5 的值为1。
注意:负数按补码形式参加按位与运算。
用途:
(1)判断奇偶
只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((i & 1) == 0)
代替if (i % 2 == 0)
来判断a是不是偶数。
(2)清零
如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。
定义: 参加运算的两个对象按二进制位进行“或”运算。
运算规则:
总结:参加运算的两个对象只要有一个为1,其值为1。
例如:3|5即:
因此,3|5的值为7。
注意:负数按补码形式参加按位或运算。
定义: 参加运算的两个数据按二进制位进行“异或”运算。
运算规则:
总结:参加运算的两个对象,如果两个相应位相同为0,相异为1。
例如:3|5即:
因此,3^5的值为6。
异或运算的性质:
(a^b)^c == a^(b^c)
(a + b)^c == a^b + b^c
x^x=0,x^0=x
a^b^b=a^0=a
;定义: 参加运算的一个数据按二进制进行“取反”运算。
运算规则:
总结:对一个二进制数按位取反,即将0变1,1变0。
例如:~6 即:
在计算机中,正数用原码表示,负数使用补码存储,首先看最高位,最高位1表示负数,0表示正数。此计算机二进制码为负数,最高位为符号位。
当发现按位取反为负数时,就直接取其补码,变为十进制:
因此,~6的值为-7。
定义: 将一个运算对象的各二进制位全部左移若干位,左边的二进制位丢弃,右边补0。
设 a=1010 1110,a = a<< 2 将a的二进制位左移2位、右补0,即得a=1011 1000。
若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。
定义: 将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
例如:a=a>>2 将a的二进制位右移2位,左补0 或者 左补1得看被移数是正还是负。
操作数每右移一位,相当于该数除以2。
上面提到了补码、反码等知识,这里就补充一下。
计算机中的有符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。
(1)原码
原码就是一个数的二进制数。例如:10的原码为0000 1010
(2)反码
例如:-10
(3)补码
例如:-10
变量提升的表现是,无论在函数中何处位置声明的变量,好像都被提升到了函数的首部,可以在变量声明前访问到而不会报错。
造成变量声明提升的本质原因是 js 引擎在代码执行前有一个解析的过程,创建了执行上下文,初始化了一些代码执行时需要用到的对象。当访问一个变量时,会到当前执行上下文中的作用域链中去查找,而作用域链的首端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性,它包含了函数的形参、所有的函数和变量声明,这个对象的是在代码解析的时候创建的。
首先要知道,JS在拿到一个变量或者一个函数的时候,会有两步操作,即解析和执行。
那为什么会进行变量提升呢?主要有以下两个原因:
(1)提高性能 在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了提高性能,如果没有这一步,那么每次执行代码前都必须重新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会改变,解析一遍就够了。
在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计声明了哪些变量、创建了哪些函数,并对函数的代码进行压缩,去除注释、不必要的空白等。这样做的好处就是每次执行函数时都可以直接为该函数分配栈空间(不需要再解析一遍去获取代码中声明了哪些变量,创建了哪些函数),并且因为代码压缩的原因,代码执行也更快了。
(2)容错性更好
变量提升可以在一定程度上提高JS的容错性,看下面的代码:
如果没有变量提升,这两行代码就会报错,但是因为有了变量提升,这段代码就可以正常执行。
虽然,在可以开发过程中,可以完全避免这样写,但是有时代码很复杂的时候。可能因为疏忽而先使用后定义了,这样也不会影响正常使用。由于变量提升的存在,而会正常运行。
总结:
变量提升虽然有一些优点,但是他也会造成一定的问题,在ES6中提出了let、const来定义变量,它们就没有变量提升的机制。下面看一下变量提升可能会导致的问题:
在这个函数中,原本是要打印出外层的tmp变量,但是因为变量提升的问题,内层定义的tmp被提到函数内部的最顶部,相当于覆盖了外层的tmp,所以打印结果为undefined。
由于遍历时定义的i会变量提升成为一个全局变量,在函数结束之后不会被销毁,所以打印出来11。
(1)第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。
(2)第二种方式是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。
(3)第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。
(4)第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。
(5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是自定义类型时。缺点是没有办法实现函数的复用。
(6)第六种方式是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。
HTTP和HTTPS协议的主要区别如下:
(1)Promise.all Promise.all
可以将多个Promise
实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
Promise.all中传入的是数组,返回的也是是数组,并且会将进行映射,传入的promise对象返回的值是按照顺序在数组中排列的,但是注意的是他们执行的顺序并不是按照顺序的,除非可迭代对象为空。
需要注意,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,这样当遇到发送多个请求并根据请求顺序获取和使用数据的场景,就可以使用Promise.all来解决。
(2)Promise.race
顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race(p1, p2, p3)里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。当要做一件事,超过多长时间就不做了,可以用这个方法来解决:
Composition API
也叫组合式API,是Vue3.x的新特性。
通过创建 Vue 组件,我们可以将接口的可重复部分及其功能提取到可重用的代码段中。仅此一项就可以使我们的应用程序在可维护性和灵活性方面走得更远。然而,我们的经验已经证明,光靠这一点可能是不够的,尤其是当你的应用程序变得非常大的时候——想想几百个组件。在处理如此大的应用程序时,共享和重用代码变得尤为重要
watch,computed,methods
选项组织代码,而不是实际的业务逻辑。minxis
完成逻辑复用,但是当mixin
变多的时候,会使得难以找到对应的data、computed
或者method
来源于哪个mixin
,使得类型推断难以进行。Composition API
的出现,主要是也是为了解决Option API带来的问题,第一个是代码组织问题,Compostion API
可以让开发者根据业务逻辑组织自己的代码,让代码具备更好的可读性和可扩展性,也就是说当下一个开发者接触这一段不是他自己写的代码时,他可以更好的利用代码的组织反推出实际的业务逻辑,或者根据业务逻辑更好的理解代码。mixin
也可以实现逻辑提取与复用,但是像前面所说的,多个mixin
作用在同一个组件时,很难看出property
是来源于哪个mixin
,来源不清楚,另外,多个mixin
的property
存在变量命名冲突的风险。而Composition API
刚好解决了这两个问题。通俗的讲:
没有Composition API
之前vue相关业务的代码需要配置到option的特定的区域,中小型项目是没有问题的,但是在大型项目中会导致后期的维护性比较复杂,同时代码可复用性不高。Vue3.x中的composition-api就是为了解决这个问题而生的
compositon api提供了以下几个函数:
setup
ref
reactive
watchEffect
watch
computed
toRefs
hooks
都说Composition API与React Hook很像,说说区别
从React Hook的实现角度看,React Hook是根据useState调用的顺序来确定下一次重渲染时的state是来源于哪个useState,所以出现了以下限制
useEffect、useMemo
等函数必须手动确定依赖关系而Composition API是基于Vue的响应式系统实现的,与React Hook的相比
setup
函数内,一次组件实例化只调用一次setup
,而React Hook每次重渲染都需要调用Hook,使得React的GC比Vue更有压力,性能也相对于Vue来说也较慢Compositon API
的调用不需要顾虑调用顺序,也可以在循环、条件、嵌套函数中使用React Hook
需要手动传入依赖,而且必须必须保证依赖的顺序,让useEffect
、useMemo
等函数正确的捕获依赖变量,否则会由于依赖不正确使得组件性能下降。虽然
Compositon API
看起来比React Hook
好用,但是其设计思想也是借鉴React Hook
的。
watch
没有缓存性,更多的是观察的作用,可以监听某些数据执行回调。当我们需要深度监听对象中
的属性时,可以打开deep:true选项,这样便会对对象中的每一项进行监听。这样会带来性能问题,优化的话可以使用字符串形式监听
注意:Watcher : 观察者对象 , 实例分为
渲染 watcher
(render watcher),计算属性 watcher
(computed watcher),侦听器 watcher
(user watcher)三种
最主要的思想就是将任务拆分 。
window.requestIdleCallback
React 追求的是 “快速响应”,那么,“快速响应“的制约因素都有什么呢
CPU
的瓶颈:当项目变得庞大、组件数量繁多、遇到大计算量的操作或者设备性能不足使得页面掉帧,导致卡顿。IO
的瓶颈:发送网络请求后,由于需要等待数据返回才能进一步操作导致不能快速响应。
fiber
架构主要就是用来解决CPU
和网络的问题,这两个问题一直也是最影响前端开发体验的地方,一个会造成卡顿,一个会造成白屏。为此 react 为前端引入了两个新概念:Time Slicing时间分片
和Suspense
。
1. React 都做过哪些优化
Virtual DOM
,然后通过Diff
算法,快速找出需要更新的元素,放到更新队列中去,得到新的更新队列。在React15及以前,Reconciler采用递归的方式创建虚拟DOM,递归过程是不能中断的。如果组件树的层级很深,递归会占用线程很多时间,递归更新时间超过了16ms,用户交互就会卡顿。 为了解决这个问题,React16将递归的无法中断的更新重构为异步的可中断更新,由于曾经用于递归的虚拟DOM数据结构已经无法满足需要。于是,全新的Fiber架构应运而生。
React16架构可以分为三层
:2. 浏览器一帧都会干些什么以及requestIdleCallback的启示
我们都知道,页面的内容都是一帧一帧绘制出来的,浏览器刷新率代表浏览器一秒绘制多少帧。原则上说 1s 内绘制的帧数也多,画面表现就也细腻。目前浏览器大多是 60Hz(60帧/s),每一帧耗时也就是在 16.6ms 左右。那么在这一帧的(16.6ms) 过程中浏览器又干了些什么呢
通过上面这张图可以清楚的知道,浏览器一帧会经过下面这几个过程:
第七步的 RIC 事件不是每一帧结束都会执行,只有在一帧的 16.6ms 中做完了前面 6 件事儿且还有剩余时间,才会执行。如果一帧执行结束后还有时间执行 RIC 事件,那么下一帧需要在事件执行结束才能继续渲染,所以 RIC 执行不要超过 30ms,如果长时间不将控制权交还给浏览器,会影响下一帧的渲染,导致页面出现卡顿和事件响应不及时。
requestIdleCallback 的启示
:我们以浏览器是否有剩余时间作微任务中断的标准,那么我们需要一种机制,当浏览器有剩余时间时通知我们。
其实部分浏览器已经实现了这个API,这就是requestIdleCallback。但是由于以下因素,Facebook 抛弃了
requestIdleCallback
的原生 API:
requestIdleCallback
触发的频率会变得很低。基于以上原因,在React中实现了功能更完备的
requestIdleCallbackpolyfill
,这就是Scheduler
。除了在空闲时触发回调的功能外,Scheduler还提供了多种调度优先级供任务设置
3. React Fiber是什么
React Fiber
是对核心算法的一次重新实现。React Fiber
把更新过程碎片化,把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会
React Fiber
中,一次更新过程会分成多个分片完成,所以完全有可能一个更新任务还没有完成,就被另一个更高优先级的更新过程打断,这时候,优先级高的更新任务会优先处理完,而低优先级更新任务所做的工作则会完全作废,然后等待机会重头再来React Fiber
一个更新过程被分为两个阶段(Phase
):第一个阶段Reconciliation Phase
和第二阶段Commit Phase
Reconciliation Phase
,React Fiber
会找出需要更新哪些DOM
,这个阶段是可以被打断的;但是到了第二阶段Commit Phase
,那就一鼓作气把DOM
更新完,绝不会被打断React Fiber
做,和我们相关的也就是生命周期函数
React Fiber
改变了之前react
的组件渲染机制,新的架构使原来同步渲染的组件现在可以异步化,可中途中断渲染,执行更高优先级的任务。释放浏览器主线程
关键特性
增量渲染用来解决掉帧的问题,渲染任务拆分之后,每次只做一小段,做完一段就把时间控制权交还给主线程,而不像之前长时间占用
4. 组件的渲染顺序
假如有A,B,C,D组件,层级结构为:
我们知道组件的生命周期为:
挂载阶段 :
constructor()
componentWillMount()
render()
componentDidMount()
更新阶段为 :
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate
那么在挂载阶段,
A,B,C,D
的生命周期渲染顺序是如何的呢?
那么在挂载阶段,A,B,C,D的生命周期渲染顺序是如何的呢?
以
render()
函数为分界线。从顶层组件开始,一直往下,直至最底层子组件。然后再往上
组件update
阶段同理
前面是react16
以前的组建渲染方式。这就存在一个问题
如果这是一个很大,层级很深的组件,
react
渲染它需要几十甚至几百毫秒,在这期间,react
会一直占用浏览器主线程,任何其他的操作(包括用户的点击,鼠标移动等操作)都无法执行
Fiber架构就是为了解决这个问题
看一下fiber架构 组建的渲染顺序
加入
fiber
的react
将组件更新分为两个时期
这两个时期以render为分界
render
前的生命周期为phase1
,render
后的生命周期为phase2
phase1
的生命周期是可以被打断的,每隔一段时间它会跳出当前渲染进程,去确定是否有其他更重要的任务。此过程,React
在workingProgressTree
(并不是真实的virtualDomTree
)上复用current
上的Fiber
数据结构来一步地(通过requestIdleCallback
)来构建新的 tree,标记处需要更新的节点,放入队列中phase2
的生命周期是不可被打断的,React
将其所有的变更一次性更新到DOM
上
这里最重要的是phase1这是时期所做的事。因此我们需要具体了解phase1的机制
phase1
执行完会直接进入render
函数,构建真实的virtualDomTree
phase1
过程中被打断,即当前组件只渲染到一半(也许是在willMount
,也许是willUpdate
~反正是在render之前的生命周期),那么react
会怎么干呢? react
会放弃当前组件所有干到一半的事情,去做更高优先级更重要的任务(当然,也可能是用户鼠标移动,或者其他react监听之外的任务),当所有高优先级任务执行完之后,react
通过callback
回到之前渲染到一半的组件,从头开始渲染。(看起来放弃已经渲染完的生命周期,会有点不合理,反而会增加渲染时长,但是react
确实是这么干的)所有phase1的生命周期函数都可能被执行多次,因为可能会被打断重来
这样的话,就和
react16
版本之前有很大区别了,因为可能会被执行多次,那么我们最好就得保证phase1
的生命周期每一次执行的结果都是一样的,否则就会有问题,因此,最好都是纯函数
react16
增加fiber
结构,其实并不是为了减少组件的渲染时间,事实上也并不会减少,最重要的是现在可以使得一些更高优先级的任务,如用户的操作能够优先执行,提高用户的体验,至少用户不会感觉到卡顿5 React Fiber架构总结
React Fiber如何性能优化
requestIdleCallback
与 requestAnimationFrame
requestIdleCallback
处理,这是个浏览器提供的事件循环空闲期的回调函数,需要 pollyfill
,而且拥有 deadline
参数,限制执行事件,以继续切分任务;requestAnimationFrame
处理;React 的核心流程可以分为两个部分:
reconciliation
(调度算法,也可称为 render
)state
与 props
;virtual dom
Fiber Tree
更为符合;commit
dom
节点更新要了解 Fiber,我们首先来看为什么需要它
React V16
将调度算法进行了重构, 将之前的 stack reconciler
重构成新版的 fiber reconciler
,变成了具有链表和指针的 单链表树遍历算法。通过指针映射,每个单元都记录着遍历当下的上一步与下一步,从而使遍历变得可以被暂停和重启Fiber
这里可以具象为一个 数据结构sibling
遍历兄弟节点;requestIdleCallback
与 requestAnimationFrame
requestIdleCallback
处理,这是个浏览器提供的事件循环空闲期的回调函数,需要 pollyfill
,而且拥有 deadline
参数,限制执行事件,以继续切分任务;requestAnimationFrame
处理;Fiber 其实可以算是一种编程思想,在其它语言中也有许多应用(Ruby Fiber)。 核心思想是 任务拆分和协同,主动把执行权交给主线程,使主线程有时间空挡处理其他高优先级任务。 当遇到进程阻塞的问题时,任务分割、异步调用 和 缓存策略 是三个显著的解决思路。
ISO
为了更好的使网络应用更为普及,推出了OSI
参考模型。
OSI
参考模型中最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服务。我们常见应用层的网络服务协议有:HTTP
,HTTPS
,FTP
,POP3
、SMTP
等。
http(hyper text transfer protocol)(超文本传输协议)
或者https
.在后端设计数据接口时,我们常常使用到这个协议。FTP
是文件传输协议,在开发过程中,个人并没有涉及到,但是我想,在一些资源网站,比如百度网盘``迅雷
应该是基于此协议的。SMTP
是simple mail transfer protocol(简单邮件传输协议)
。在一个项目中,在用户邮箱验证码登录的功能时,使用到了这个协议。表示层提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别。如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据格式转换成通信中采用的标准表示形式。数据压缩和加密也是表示层可提供的转换功能之一。
在项目开发中,为了方便数据传输,可以使用base64
对数据进行编解码。如果按功能来划分,base64
应该是工作在表示层。
会话层就是负责建立、管理和终止表示层实体之间的通信会话。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。
传输层建立了主机端到端的链接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。该层向高层屏蔽了下层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户控制和设定的、可靠的数据通路。我们通常说的,TCP
UDP
就是在这一层。端口号既是这里的“端”。
本层通过IP
寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。就是通常说的IP
层。这一层就是我们经常说的IP
协议层。IP
协议是Internet
的基础。我们可以这样理解,网络层规定了数据包的传输路线,而传输层则规定了数据包的传输方式。
将比特组合成字节,再将字节组合成帧,使用链路层地址 (以太网使用MAC地址)来访问介质,并进行差错检测。
网络层与数据链路层的对比,通过上面的描述,我们或许可以这样理解,网络层是规划了数据包的传输路线,而数据链路层就是传输路线。不过,在数据链路层上还增加了差错控制的功能。
实际最终信号的传输是通过物理层实现的。通过物理介质传输比特流。规定了电平、速度和电缆针脚。常用设备有(各种物理设备)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。这些都是物理层的传输介质。
OSI七层模型通信特点:对等通信 对等通信,为了使数据分组从源传送到目的地,源端OSI模型的每一层都必须与目的端的对等层进行通信,这种通信方式称为对等层通信。在每一层通信过程中,使用本层自己协议进行通信。
JSX是语法糖,通过babel转成
React.createElement
函数,在babel官网上可以在线把JSX转成React的JS语法
createElement
函数vnode
script标签中不添加
text/babel
解析jsx语法的情况下
JSX的本质是React.createElement()函数
createElement
函数返回的对象是ReactEelement
对象。
createElement
的写法如下
实际开发中不会使用createElement
来创建ReactElement
的,一般都是使用JSX的形式开发。
ReactElement
在程序中打印一下
react通过babel把JSX转成
createElement
函数,生成ReactElement
对象,然后通过ReactDOM.render函
数把ReactElement
渲染成真实的DOM
元素
为什么 React 使用 JSX
JavaScript
的语法扩展,结构类似 XML。React
元素,但 React 中并不强制使用 JSX
。即使使用了 JSX
,也会在构建过程中,通过 Babel 插件编译为 React.createElement
。所以 JSX 更像是 React.createElement
的一种语法糖Babel 插件如何实现 JSX 到 JS 的编译? 在 React 面试中,这个问题很容易被追问,也经常被要求手写。
它的实现原理是这样的。Babel 读取代码并解析,生成 AST,再将 AST 传入插件层进行转换,在转换时就可以将 JSX 的结构转换为 React.createElement
的函数。如下代码所示:
React.createElement源码分析
入参解读:创造一个元素需要知道哪些信息
createElement 有 3 个入参,这 3 个入参囊括了 React 创建一个元素所需要知道的全部信息。
type
:用于标识节点的类型。它可以是类似“h1”“div”这样的标准 HTML 标签字符串,也可以是 React 组件类型或 React fragment
类型。config
:以对象形式传入,组件所有的属性都会以键值对的形式存储在 config 对象中。children
:以对象形式传入,它记录的是组件标签之间嵌套的内容,也就是所谓的“子节点”“子元素”这个调用对应的 DOM 结构如下:
createElement 函数体拆解
createElement 中并没有十分复杂的涉及算法或真实 DOM 的逻辑,它的每一个步骤几乎都是在格式化数据。
现在看来,
createElement
原来只是个“参数中介”。此时我们的注意力自然而然地就聚焦在了ReactElement
上
出参解读:初识虚拟 DOM
createElement
执行到最后会 return 一个针对 ReactElement 的调用。这里关于 ReactElement,我依然先给出源码 + 注释形式的解析
ReactElement
其实只做了一件事情,那就是“创建”,说得更精确一点,是“组装”:ReactElement
把传入的参数按照一定的规范,“组装”进了element
对象里,并把它返回给了eact.createElement
,最终React.createElement
又把它交回到了开发者手中
你会发现它确实是一个标准的 ReactElement
对象实例
这个 ReactElement
对象实例,本质上是以 JavaScript 对象形式存在的对 DOM
的描述,也就是老生常谈的“虚拟 DOM”(准确地说,是虚拟 DOM
中的一个节点)
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。