事件流传机制(事件流)

冒泡和捕捉

谈一谈HTTP数据传输

大略遇到的状况就分为定长数据不定长数据的解决吧。

定长数据

对于定长的数据包而言,发送端在发送数据的过程中,须要设置Content-Length,来指明发送数据的长度。

当然了如果采纳了Gzip压缩的话,Content-Length设置的就是压缩后的传输长度。

咱们还须要晓得的是

  • Content-Length如果存在并且无效的话,则必须和音讯内容的传输长度完全一致,也就是说,如果过短就会截断,过长的话,就会导致超时。
  • 如果采纳短链接的话,间接能够通过服务器敞开连贯来确定音讯的传输长度。
  • 那么在HTTP/1.0之前的版本中,Content-Length字段可有可无,因为一旦服务器敞开连贯,咱们就能够获取到传输数据的长度了。
  • 在HTTP/1.1版本中,如果是Keep-alive的话,chunked优先级高于Content-Length,若是非Keep-alive,跟后面状况一样,Content-Length可有可无。

那怎么来设置Content-Length

举个例子来看看

const server = require('http').createServer();server.on('request', (req, res) => {  if(req.url === '/index') {      // 设置数据类型    res.setHeader('Content-Type', 'text/plain');    res.setHeader('Content-Length', 10);    res.write("你好,应用的是Content-Length设置传输数据模式");  }})server.listen(3000, () => {  console.log("胜利启动--TinaTian");})

不定长数据

当初采纳最多的就是HTTP/1.1版本,来实现传输数据,在保留Keep-alive状态下,当数据是不定长的时候,咱们须要设置新的头部字段
Transfer-Encoding: chunked

通过chunked机制,能够实现对不定长数据的解决,当然了,你须要晓得的是

  • 如果头部信息中有Transfer-Encoding,优先采纳Transfer-Encoding外面的办法来找到对应的长度。
  • 如果设置了Transfer-Encoding,那么Content-Length将被忽视。
  • 应用长连贯的话,会继续的推送动静内容。

那咱们来模仿一下吧

const server = require('http').createServer();server.on('request', (req, res) => {  if(req.url === '/index') {      // 设置数据类型    res.setHeader('Content-Type', 'text/html; charset=utf8');    res.setHeader('Content-Length', 10);    res.setHeader('Transfer-Encoding', 'chunked');    res.write("你好,应用的是Transfer-Encoding设置传输数据模式");    setTimeout(() => {      res.write("第一次传输数据给您<br/>");    }, 1000);    res.write("骚等一下");    setTimeout(() => {      res.write("第一次传输数据给您");      res.end()    }, 3000);  }})server.listen(3000, () => {  console.log("胜利启动--TinaTian");})
下面应用的是nodejs中http模块,有趣味的小伙伴能够去试一试,以上就是HTTP对定长数据不定长数据传输过程中的解决伎俩。

transition和animation的区别

  • transition是适度属性,强调适度,它的实现须要触发一个事件(比方鼠标挪动下来,焦点,点击等)才执行动画。它相似于flash的补间动画,设置一个开始关键帧,一个完结关键帧。
  • animation是动画属性,它的实现不须要触发事件,设定好工夫之后能够本人执行,且能够循环一个动画。它也相似于flash的补间动画,然而它能够设置多个关键帧(用@keyframe定义)实现动画。

两栏布局的实现

个别两栏布局指的是右边一栏宽度固定,左边一栏宽度自适应,两栏布局的具体实现:

  • 利用浮动,将右边元素宽度设置为200px,并且设置向左浮动。将左边元素的margin-left设置为200px,宽度设置为auto(默认为auto,撑满整个父元素)。
.outer {  height: 100px;}.left {  float: left;  width: 200px;  background: tomato;}.right {  margin-left: 200px;  width: auto;  background: gold;}
  • 利用浮动,左侧元素设置固定大小,并左浮动,右侧元素设置overflow: hidden; 这样左边就触发了BFC,BFC的区域不会与浮动元素产生重叠,所以两侧就不会产生重叠。
.left{     width: 100px;     height: 200px;     background: red;     float: left; } .right{     height: 300px;     background: blue;     overflow: hidden; }
  • 利用flex布局,将右边元素设置为固定宽度200px,将左边的元素设置为flex:1。
.outer {  display: flex;  height: 100px;}.left {  width: 200px;  background: tomato;}.right {  flex: 1;  background: gold;}
  • 利用相对定位,将父级元素设置为绝对定位。右边元素设置为absolute定位,并且宽度设置为200px。将左边元素的margin-left的值设置为200px。
.outer {  position: relative;  height: 100px;}.left {  position: absolute;  width: 200px;  height: 100px;  background: tomato;}.right {  margin-left: 200px;  background: gold;}
  • 利用相对定位,将父级元素设置为绝对定位。右边元素宽度设置为200px,左边元素设置为相对定位,右边定位为200px,其余方向定位为0。
.outer {  position: relative;  height: 100px;}.left {  width: 200px;  background: tomato;}.right {  position: absolute;  top: 0;  right: 0;  bottom: 0;  left: 200px;  background: gold;}

应用 clear 属性革除浮动的原理?

应用clear属性革除浮动,其语法如下:

clear:none|left|right|both

如果单看字面意思,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{  content:'';  display: block;   clear:both;}

clear属性只有块级元素才无效的,而::after等伪元素默认都是内联程度,这就是借助伪元素革除浮动影响时须要设置display属性值的起因。

画一条0.5px的线

  • 采纳transform: scale()的形式,该办法用来定义元素的2D 缩放转换:
transform: scale(0.5,0.5);
  • 采纳meta viewport的形式
<meta name="viewport" content="width=device-width, initial-scale=0.5, minimum-scale=0.5, maximum-scale=0.5"/>

这样就能缩放到原来的0.5倍,如果是1px那么就会变成0.5px。viewport只针对于挪动端,只在挪动端上能力看到成果

参考 前端进阶面试题具体解答

数组的遍历办法有哪些

办法是否扭转原数组特点
forEach()数组办法,不扭转原数组,没有返回值
map()数组办法,不扭转原数组,有返回值,可链式调用
filter()数组办法,过滤数组,返回蕴含符合条件的元素的数组,可链式调用
for...offor...of遍历具备Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历一般的obj对象,将异步循环变成同步循环
every() 和 some()数组办法,some()只有有一个是true,便返回true;而every()只有有一个是false,便返回false.
find() 和 findIndex()数组办法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值
reduce() 和 reduceRight()数组办法,reduce()对数组正序操作;reduceRight()对数组逆序操作

new操作符的实现原理

new操作符的执行过程:

(1)首先创立了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)

(4)判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象。

具体实现:

function objectFactory() {  let newObject = null;  let constructor = Array.prototype.shift.call(arguments);  let result = null;  // 判断参数是否是一个函数  if (typeof constructor !== "function") {    console.error("type error");    return;  }  // 新建一个空对象,对象的原型为构造函数的 prototype 对象  newObject = Object.create(constructor.prototype);  // 将 this 指向新建对象,并执行函数  result = constructor.apply(newObject, arguments);  // 判断返回对象  let flag = result && (typeof result === "object" || typeof result === "function");  // 判断返回后果  return flag ? result : newObject;}// 应用办法objectFactory(构造函数, 初始化参数);

程度垂直居中的实现

  • 利用相对定位,先将元素的左上角通过top:50%和left:50%定位到页面的核心,而后再通过translate来调整元素的中心点到页面的核心。该办法须要思考浏览器兼容问题。
.parent {    position: relative;} .child {    position: absolute;    left: 50%;    top: 50%;    transform: translate(-50%,-50%);}
  • 利用相对定位,设置四个方向的值都为0,并将margin设置为auto,因为宽高固定,因而对应方向实现平分,能够实现程度和垂直方向上的居中。该办法实用于盒子有宽高的状况:
.parent {    position: relative;}.child {    position: absolute;    top: 0;    bottom: 0;    left: 0;    right: 0;    margin: auto;}
  • 利用相对定位,先将元素的左上角通过top:50%和left:50%定位到页面的核心,而后再通过margin负值来调整元素的中心点到页面的核心。该办法实用于盒子宽高已知的状况
.parent {    position: relative;}.child {    position: absolute;    top: 50%;    left: 50%;    margin-top: -50px;     /* 本身 height 的一半 */    margin-left: -50px;    /* 本身 width 的一半 */}
  • 应用flex布局,通过align-items:center和justify-content:center设置容器的垂直和程度方向上为居中对齐,而后它的子元素也能够实现垂直和程度的居中。该办法要思考兼容的问题,该办法在挪动端用的较多:
.parent {    display: flex;    justify-content:center;    align-items:center;}

常见的位运算符有哪些?其计算规定是什么?

古代计算机中数据都是以二进制的模式存储的,即0、1两种状态,计算机对二进制数据进行的运算加减乘除等都是叫位运算,行将符号位独特参加运算的运算。

常见的位运算有以下几种:

运算符形容运算规定
&两个位都为1时,后果才为1
``两个位都为0时,后果才为0
^异或两个位雷同为0,相异为1
~取反0变1,1变0
<<左移各二进制位全副左移若干位,高位抛弃,低位补0
>>右移各二进制位全副右移若干位,负数左补0,正数左补1,左边抛弃

1. 按位与运算符(&)

定义: 加入运算的两个数据按二进制位进行“与”运算。 运算规定:

0 & 0 = 0  0 & 1 = 0  1 & 0 = 0  1 & 1 = 1

总结:两位同时为1,后果才为1,否则后果为0。
例如:3&5 即:

0000 0011    0000 0101  = 0000 0001

因而 3&5 的值为1。
留神:正数按补码模式加入按位与运算。

用处:

(1)判断奇偶

只有依据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因而能够用if ((i & 1) == 0)代替if (i % 2 == 0)来判断a是不是偶数。

(2)清零

如果想将一个单元清零,即便其全副二进制位为0,只有与一个各位都为零的数值相与,后果为零。

2. 按位或运算符(|)

定义: 加入运算的两个对象按二进制位进行“或”运算。

运算规定:

0 | 0 = 00 | 1 = 1  1 | 0 = 1  1 | 1 = 1

总结:加入运算的两个对象只有有一个为1,其值为1。
例如:3|5即:

0000 0011  0000 0101 = 0000 0111

因而,3|5的值为7。
留神:正数按补码模式加入按位或运算。

3. 异或运算符(^)

定义: 加入运算的两个数据按二进制位进行“异或”运算。

运算规定:

0 ^ 0 = 0  0 ^ 1 = 1  1 ^ 0 = 1  1 ^ 1 = 0

总结:加入运算的两个对象,如果两个相应位雷同为0,相异为1。
例如:3|5即:

0000 0011  0000 0101 = 0000 0110

因而,3^5的值为6。
异或运算的性质:

  • 交换律:(a^b)^c == a^(b^c)
  • 结合律:(a + b)^c == a^b + b^c
  • 对于任何数x,都有 x^x=0,x^0=x
  • 自反性: a^b^b=a^0=a;

4. 取反运算符 (~)

定义: 加入运算的一个数据按二进制进行“取反”运算。

运算规定:

~ 1 = 0~ 0 = 1

总结:对一个二进制数按位取反,行将0变1,1变0。
例如:~6 即:

0000 0110= 1111 1001

在计算机中,负数用原码示意,正数应用补码存储,首先看最高位,最高位1示意正数,0示意负数。此计算机二进制码为正数,最高位为符号位。
当发现按位取反为正数时,就间接取其补码,变为十进制:

0000 0110   = 1111 1001反码:1000 0110补码:1000 0111

因而,~6的值为-7。

5. 左移运算符(<<)

定义: 将一个运算对象的各二进制位全副左移若干位,右边的二进制位抛弃,左边补0。
设 a=1010 1110,a = a<< 2 将a的二进制位左移2位、右补0,即得a=1011 1000。
若左移时舍弃的高位不蕴含1,则每左移一位,相当于该数乘以2。

6. 右移运算符(>>)

定义: 将一个数的各二进制位全副右移若干位,负数左补0,正数左补1,左边抛弃。
例如:a=a>>2 将a的二进制位右移2位,左补0 或者 左补1得看被移数是正还是负。
操作数每右移一位,相当于该数除以2。

7. 原码、补码、反码

下面提到了补码、反码等常识,这里就补充一下。
计算机中的有符号数有三种示意办法,即原码、反码和补码。三种示意办法均有符号位和数值位两局部,符号位都是用0示意“正”,用1示意“负”,而数值位,三种示意办法各不相同。

(1)原码

原码就是一个数的二进制数。例如:10的原码为0000 1010

(2)反码

  • 负数的反码与原码雷同,如:10 反码为 0000 1010
  • 正数的反码为除符号位,按位取反,即0变1,1变0。

例如:-10

原码:1000 1010反码:1111 0101

(3)补码

  • 负数的补码与原码雷同,如:10 补码为 0000 1010
  • 正数的补码是原码除符号位外的所有位取反即0变1,1变0,而后加1,也就是反码加1。

例如:-10

原码:1000 1010反码:1111 0101补码:1111 0110

JavaScript为什么要进行变量晋升,它导致了什么问题?

变量晋升的体现是,无论在函数中何处地位申明的变量,如同都被晋升到了函数的首部,能够在变量申明前拜访到而不会报错。

造成变量申明晋升的实质起因是 js 引擎在代码执行前有一个解析的过程,创立了执行上下文,初始化了一些代码执行时须要用到的对象。当拜访一个变量时,会到以后执行上下文中的作用域链中去查找,而作用域链的首端指向的是以后执行上下文的变量对象,这个变量对象是执行上下文的一个属性,它蕴含了函数的形参、所有的函数和变量申明,这个对象的是在代码解析的时候创立的。

首先要晓得,JS在拿到一个变量或者一个函数的时候,会有两步操作,即解析和执行。

  • 在解析阶段,JS会查看语法,并对函数进行预编译。解析的时候会先创立一个全局执行上下文环境,先把代码中行将执行的变量、函数申明都拿进去,变量先赋值为undefined,函数先申明好可应用。在一个函数执行之前,也会创立一个函数执行上下文环境,跟全局执行上下文相似,不过函数执行上下文会多出this、arguments和函数的参数。

    • 全局上下文:变量定义,函数申明
    • 函数上下文:变量定义,函数申明,this,arguments
  • 在执行阶段,就是依照代码的程序顺次执行。

那为什么会进行变量晋升呢?次要有以下两个起因:

  • 进步性能
  • 容错性更好

(1)进步性能 在JS代码执行之前,会进行语法检查和预编译,并且这一操作只进行一次。这么做就是为了进步性能,如果没有这一步,那么每次执行代码前都必须从新解析一遍该变量(函数),而这是没有必要的,因为变量(函数)的代码并不会扭转,解析一遍就够了。

在解析的过程中,还会为函数生成预编译代码。在预编译时,会统计申明了哪些变量、创立了哪些函数,并对函数的代码进行压缩,去除正文、不必要的空白等。这样做的益处就是每次执行函数时都能够间接为该函数调配栈空间(不须要再解析一遍去获取代码中申明了哪些变量,创立了哪些函数),并且因为代码压缩的起因,代码执行也更快了。

(2)容错性更好

变量晋升能够在肯定水平上进步JS的容错性,看上面的代码:

a = 1;var a;console.log(a);

如果没有变量晋升,这两行代码就会报错,然而因为有了变量晋升,这段代码就能够失常执行。

尽管,在能够开发过程中,能够完全避免这样写,然而有时代码很简单的时候。可能因为忽略而先应用后定义了,这样也不会影响失常应用。因为变量晋升的存在,而会失常运行。

总结:

  • 解析和预编译过程中的申明晋升能够进步性能,让函数能够在执行时事后为变量调配栈空间
  • 申明晋升还能够进步JS代码的容错性,使一些不标准的代码也能够失常执行

变量晋升尽管有一些长处,然而他也会造成肯定的问题,在ES6中提出了let、const来定义变量,它们就没有变量晋升的机制。上面看一下变量晋升可能会导致的问题:

var tmp = new Date();function fn(){    console.log(tmp);    if(false){        var tmp = 'hello world';    }}fn();  // undefined

在这个函数中,本来是要打印出外层的tmp变量,然而因为变量晋升的问题,内层定义的tmp被提到函数外部的最顶部,相当于笼罩了外层的tmp,所以打印后果为undefined。

var tmp = 'hello world';for (var i = 0; i < tmp.length; i++) {    console.log(tmp[i]);}console.log(i); // 11

因为遍历时定义的i会变量晋升成为一个全局变量,在函数完结之后不会被销毁,所以打印进去11。

对象继承的形式有哪些?

(1)第一种是以原型链的形式来实现继承,然而这种实现形式存在的毛病是,在蕴含有援用类型的数据时,会被所有的实例对象所共享,容易造成批改的凌乱。还有就是在创立子类型的时候不能向超类型传递参数。

(2)第二种形式是应用借用构造函数的形式,这种形式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种办法解决了不能向超类型传递参数的毛病,然而它存在的一个问题就是无奈实现函数办法的复用,并且超类型原型定义的办法子类型也没有方法拜访到。

(3)第三种形式是组合继承,组合继承是将原型链和借用构造函数组合起来应用的一种形式。通过借用构造函数的形式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现办法的继承。这种形式解决了下面的两种模式独自应用时的问题,然而因为咱们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。

(4)第四种形式是原型式继承,原型式继承的次要思路就是基于已有的对象来创立新的对象,实现的原理是,向函数中传入一个对象,而后返回一个以这个对象为原型的对象。这种继承的思路次要不是为了实现发明一种新的类型,只是对某个对象实现一种简略继承,ES5 中定义的 Object.create() 办法就是原型式继承的实现。毛病与原型链形式雷同。

(5)第五种形式是寄生式继承,寄生式继承的思路是创立一个用于封装继承过程的函数,通过传入一个对象,而后复制一个对象的正本,而后对象进行扩大,最初返回这个对象。这个扩大的过程就能够了解是一种继承。这种继承的长处就是对一个简略对象实现继承,如果这个对象不是自定义类型时。毛病是没有方法实现函数的复用。

(6)第六种形式是寄生式组合继承,组合继承的毛病就是应用超类型的实例做为子类型的原型,导致增加了不必要的原型属性。寄生式组合继承的形式是应用超类型的原型的副原本作为子类型的原型,这样就防止了创立不必要的属性。

HTTP和HTTPS协定的区别

HTTP和HTTPS协定的次要区别如下:

  • HTTPS协定须要CA证书,费用较高;而HTTP协定不须要;
  • HTTP协定是超文本传输协定,信息是明文传输的,HTTPS则是具备安全性的SSL加密传输协定;
  • 应用不同的连贯形式,端口也不同,HTTP协定端口是80,HTTPS协定端口是443;
  • HTTP协定连贯很简略,是无状态的;HTTPS协定是有SSL和HTTP协定构建的可进行加密传输、身份认证的网络协议,比HTTP更加平安。

Promise.all和Promise.race的区别的应用场景

(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])外面哪个后果取得的快,就返回那个后果,不论后果自身是胜利状态还是失败状态。当要做一件事,超过多长时间就不做了,能够用这个办法来解决:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

POST和PUT申请的区别

  • PUT申请是向服务器端发送数据,从而批改数据的内容,然而不会减少数据的品种等,也就是说无论进行多少次PUT操作,其后果并没有不同。(能够了解为时更新数据
  • POST申请是向服务器端发送数据,该申请会扭转数据的品种等资源,它会创立新的内容。(能够了解为是创立数据

Compositon api

Composition API也叫组合式API,是Vue3.x的新个性。

通过创立 Vue 组件,咱们能够将接口的可重复部分及其性能提取到可重用的代码段中。仅此一项就能够使咱们的应用程序在可维护性和灵活性方面走得更远。然而,咱们的教训曾经证实,光靠这一点可能是不够的,尤其是当你的应用程序变得十分大的时候——想想几百个组件。在解决如此大的应用程序时,共享和重用代码变得尤为重要
  • Vue2.0中,随着性能的减少,组件变得越来越简单,越来越难保护,而难以保护的根本原因是Vue的API设计迫使开发者应用watch,computed,methods选项组织代码,而不是理论的业务逻辑。
  • 另外Vue2.0短少一种较为简洁的低成本的机制来实现逻辑复用,尽管能够minxis实现逻辑复用,然而当mixin变多的时候,会使得难以找到对应的data、computed或者method来源于哪个mixin,使得类型推断难以进行。
  • 所以Composition API的呈现,次要是也是为了解决Option API带来的问题,第一个是代码组织问题,Compostion API能够让开发者依据业务逻辑组织本人的代码,让代码具备更好的可读性和可扩展性,也就是说当下一个开发者接触这一段不是他本人写的代码时,他能够更好的利用代码的组织反推出理论的业务逻辑,或者依据业务逻辑更好的了解代码。
  • 第二个是实现代码的逻辑提取与复用,当然mixin也能够实现逻辑提取与复用,然而像后面所说的,多个mixin作用在同一个组件时,很难看出property是来源于哪个mixin,起源不分明,另外,多个mixinproperty存在变量命名抵触的危险。而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,所以呈现了以下限度
  • 不能在循环、条件、嵌套函数中调用Hook
  • 必须确保总是在你的React函数的顶层调用Hook
  • useEffect、useMemo等函数必须手动确定依赖关系
而Composition API是基于Vue的响应式零碎实现的,与React Hook的相比
  • 申明在setup函数内,一次组件实例化只调用一次setup,而React Hook每次重渲染都须要调用Hook,使得React的GC比Vue更有压力,性能也绝对于Vue来说也较慢
  • Compositon API的调用不须要顾虑调用程序,也能够在循环、条件、嵌套函数中应用
  • 响应式零碎主动实现了依赖收集,进而组件的局部的性能优化由Vue外部本人实现,而React Hook须要手动传入依赖,而且必须必须保障依赖的程序,让useEffectuseMemo等函数正确的捕捉依赖变量,否则会因为依赖不正确使得组件性能降落。
尽管Compositon API看起来比React Hook好用,然而其设计思维也是借鉴React Hook的。

watch 的了解

watch没有缓存性,更多的是察看的作用,能够监听某些数据执行回调。当咱们须要深度监听对象中的属性时,能够关上deep:true选项,这样便会对对象中的每一项进行监听。这样会带来性能问题,优化的话能够应用字符串模式监听

留神:Watcher : 观察者对象 , 实例分为渲染 watcher (render watcher),计算属性 watcher (computed watcher),侦听器 watcher(user watcher)三种

React Fiber架构

最次要的思维就是将工作拆分

  • DOM须要渲染时暂停,闲暇时复原。
  • window.requestIdleCallback
  • React外部实现的机制
React 谋求的是 “疾速响应”,那么,“疾速响应“的制约因素都有什么呢
  • CPU的瓶颈:当我的项目变得宏大、组件数量繁多、遇到大计算量的操作或者设施性能有余使得页面掉帧,导致卡顿。
  • IO的瓶颈:发送网络申请后,因为须要期待数据返回能力进一步操作导致不能疾速响应。
fiber 架构次要就是用来解决 CPU 和网络的问题,这两个问题始终也是最影响前端开发体验的中央,一个会造成卡顿,一个会造成白屏。为此 react 为前端引入了两个新概念:Time Slicing 工夫分片Suspense

1. React 都做过哪些优化

  • React渲染页面的两个阶段

    • 调度阶段(reconciliation):在这个阶段 React 会更新数据生成新的 Virtual DOM,而后通过Diff算法,疾速找出须要更新的元素,放到更新队列中去,失去新的更新队列。
    • 渲染阶段(commit):这个阶段 React 会遍历更新队列,将其所有的变更一次性更新到DOM上
  • React 15 架构

    • React15架构能够分为两层

      • Reconciler(协调器)—— 负责找出变动的组件;
      • Renderer(渲染器)—— 负责将变动的组件渲染到页面上;
  • 在React15及以前,Reconciler采纳递归的形式创立虚构DOM,递归过程是不能中断的。如果组件树的层级很深,递归会占用线程很多工夫,递归更新工夫超过了16ms,用户交互就会卡顿。
  • 为了解决这个问题,React16将递归的无奈中断的更新重构为异步的可中断更新,因为已经用于递归的虚构DOM数据结构曾经无奈满足需要。于是,全新的Fiber架构应运而生。
  • React 16 架构

    • 为了解决同步更新长时间占用线程导致页面卡顿的问题,也为了摸索运行时优化的更多可能,React开始重构并始终继续至今。重构的指标是实现Concurrent Mode(并发模式)。
    • 从v15到v16,React团队花了两年工夫将源码架构中的Stack Reconciler重构为Fiber Reconciler
    • React16架构能够分为三层

      • Scheduler(调度器)—— 调度工作的优先级,高优工作优先进入Reconciler;
      • Reconciler(协调器)—— 负责找出变动的组件:更新工作从递归变成了能够中断的循环过程。Reconciler外部采纳了Fiber的架构;
      • Renderer(渲染器)—— 负责将变动的组件渲染到页面上。
  • React 17 优化

    • 应用Lane来治理工作的优先级。Lane用二进制位示意工作的优先级,不便优先级的计算(位运算),不同优先级占用不同地位的“赛道”,而且存在批的概念,优先级越低,“赛道”越多。高优先级打断低优先级,新建的工作须要赋予什么优先级等问题都是Lane所要解决的问题。
    • Concurrent Mode的目标是实现一套可中断/复原的更新机制。其由两局部组成:

      • 一套协程架构:Fiber Reconciler
      • 基于协程架构的启发式更新算法:管制协程架构工作形式的算法

2. 浏览器一帧都会干些什么以及requestIdleCallback的启发

咱们都晓得,页面的内容都是一帧一帧绘制进去的,浏览器刷新率代表浏览器一秒绘制多少帧。原则上说 1s 内绘制的帧数也多,画面体现就也细腻。目前浏览器大多是 60Hz(60帧/s),每一帧耗时也就是在 16.6ms 左右。那么在这一帧的(16.6ms) 过程中浏览器又干了些什么呢

通过下面这张图能够分明的晓得,浏览器一帧会通过上面这几个过程:

  1. 承受输出事件
  2. 执行事件回调
  3. 开始一帧
  4. 执行 RAF (RequestAnimationFrame)
  5. 页面布局,款式计算
  6. 绘制渲染
  7. 执行 RIC (RequestIdelCallback)

第七步的 RIC 事件不是每一帧完结都会执行,只有在一帧的 16.6ms 中做完了后面 6 件事儿且还有剩余时间,才会执行。如果一帧执行完结后还有工夫执行 RIC 事件,那么下一帧须要在事件执行完结能力持续渲染,所以 RIC 执行不要超过 30ms,如果长时间不将控制权交还给浏览器,会影响下一帧的渲染,导致页面呈现卡顿和事件响应不及时。

requestIdleCallback 的启发:咱们以浏览器是否有剩余时间作微工作中断的规范,那么咱们须要一种机制,当浏览器有剩余时间时告诉咱们。

requestIdleCallback((deadline) => {// deadline 有两个参数  // timeRemaining(): 以后帧还剩下多少工夫  // didTimeout: 是否超时// 另外 requestIdleCallback 后如果跟上第二个参数 {timeout: ...} 则会强制浏览器在以后帧执行完后执行。 if (deadline.timeRemaining() > 0) {   // TODO } else {  requestIdleCallback(otherTasks); }});
// 用法示例var tasksNum = 10000requestIdleCallback(unImportWork)function unImportWork(deadline) {  while (deadline.timeRemaining() && tasksNum > 0) {    console.log(`执行了${10000 - tasksNum + 1}个工作`)    tasksNum--  }  if (tasksNum > 0) { // 在将来的帧中继续执行    requestIdleCallback(unImportWork)  }}
其实局部浏览器曾经实现了这个API,这就是requestIdleCallback。然而因为以下因素,Facebook 摈弃了 requestIdleCallback的原生 API:
  • 浏览器兼容性;
  • 触发频率不稳固,受很多因素影响。比方当咱们的浏览器切换tab后,之前tab注册的requestIdleCallback触发的频率会变得很低。
基于以上起因,在React中实现了性能更齐备的requestIdleCallbackpolyfill,这就是Scheduler。除了在闲暇时触发回调的性能外,Scheduler还提供了多种调度优先级供工作设置

3. React Fiber是什么

React Fiber是对外围算法的一次从新实现。React Fiber把更新过程碎片化,把一个耗时长的工作分成很多小片,每一个小片的运行工夫很短,尽管总工夫仍然很长,然而在每个小片执行完之后,都给其余工作一个执行的机会,这样惟一的线程就不会被独占,其余工作仍然有运行的机会
  1. React Fiber中,一次更新过程会分成多个分片实现,所以齐全有可能一个更新工作还没有实现,就被另一个更高优先级的更新过程打断,这时候,优先级高的更新工作会优先解决完,而低优先级更新工作所做的工作则会齐全作废,而后期待机会重头再来
  2. 因为一个更新过程可能被打断,所以React Fiber一个更新过程被分为两个阶段(Phase):第一个阶段Reconciliation Phase和第二阶段Commit Phase
  3. 在第一阶段Reconciliation PhaseReact Fiber会找出须要更新哪些DOM,这个阶段是能够被打断的;然而到了第二阶段Commit Phase,那就一鼓作气把DOM更新完,绝不会被打断
  4. 这两个阶段大部分工作都是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架构 组建的渲染程序

退出fiberreact将组件更新分为两个期间

这两个期间以render为分界

  • render前的生命周期为phase1,
  • render后的生命周期为phase2
  • phase1的生命周期是能够被打断的,每隔一段时间它会跳出以后渲染过程,去确定是否有其余更重要的工作。此过程,ReactworkingProgressTree (并不是实在的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的生命周期每一次执行的后果都是一样的,否则就会有问题,因而,最好都是纯函数
  • 如果高优先级的工作始终存在,那么低优先级的工作则永远无奈进行,组件永远无奈持续渲染。这个问题facebook目前如同还没解决
  • 所以,facebook在react16减少fiber构造,其实并不是为了缩小组件的渲染工夫,事实上也并不会缩小,最重要的是当初能够使得一些更高优先级的工作,如用户的操作可能优先执行,进步用户的体验,至多用户不会感觉到卡顿

5 React Fiber架构总结

React Fiber如何性能优化

  • 更新的两个阶段

    • 调度算法阶段-执行diff算法,纯js计算
    • Commit阶段-将diff后果渲染dom
  • 可能会有性能问题

    • JS是单线程的,且和DOM渲染专用一个线程
    • 当组件足够简单,组件更新时计算和渲染压力都大
    • 同时再有DOM操作需要(动画、鼠标拖拽等),将卡顿
  • 解决方案fiber

    • 将调度算法阶段阶段工作拆分(Commit无奈拆分)
    • DOM须要渲染时暂停,闲暇时复原
    • 扩散执行: 工作宰割后,就能够把小工作单元扩散到浏览器的闲暇期间去排队执行,而实现的要害是两个新API: requestIdleCallbackrequestAnimationFrame

      • 低优先级的工作交给requestIdleCallback解决,这是个浏览器提供的事件循环闲暇期的回调函数,须要 pollyfill,而且领有 deadline 参数,限度执行事件,以持续切分工作;
      • 高优先级的工作交给requestAnimationFrame解决;

React 的外围流程能够分为两个局部:

  • reconciliation (调度算法,也可称为 render)

    • 更新 stateprops
    • 调用生命周期钩子;
    • 生成 virtual dom

      • 这里应该称为 Fiber Tree 更为合乎;
    • 通过新旧 vdom 进行 diff 算法,获取 vdom change
    • 确定是否须要从新渲染
  • commit

    • 如须要,则操作 dom 节点更新
要理解 Fiber,咱们首先来看为什么须要它
  • 问题 : 随着利用变得越来越宏大,整个更新渲染的过程开始变得吃力,大量的组件渲染会导致主过程长时间被占用,导致一些动画或高频操作呈现卡顿和掉帧的状况。而关键点,便是 同步阻塞。在之前的调度算法中,React 须要实例化每个类组件,生成一颗组件树,应用 同步递归 的形式进行遍历渲染,而这个过程最大的问题就是无奈 暂停和复原。
  • 解决方案: 解决同步阻塞的办法,通常有两种: 异步 与 工作宰割。而 React Fiber 便是为了实现工作宰割而诞生的
  • 简述

    • React V16 将调度算法进行了重构, 将之前的 stack reconciler 重形成新版的 fiber reconciler,变成了具备链表和指针的 单链表树遍历算法。通过指针映射,每个单元都记录着遍历当下的上一步与下一步,从而使遍历变得能够被暂停和重启
    • 这里我了解为是一种 工作宰割调度算法,次要是 将原先同步更新渲染的工作宰割成一个个独立的 小工作单位,依据不同的优先级,将小工作扩散到浏览器的闲暇工夫执行,充分利用主过程的事件循环机制
  • 外围

    • Fiber 这里能够具象为一个 数据结构
class Fiber {    constructor(instance) {        this.instance = instance        // 指向第一个 child 节点        this.child = child        // 指向父节点        this.return = parent        // 指向第一个兄弟节点        this.sibling = previous    }    }
  • 链表树遍历算法 : 通过 节点保留与映射,便可能随时地进行 进行和重启,这样便能达到实现工作宰割的基本前提

    • 首先通过一直遍历子节点,到树开端;
    • 开始通过 sibling 遍历兄弟节点;
    • return 返回父节点,继续执行2;
    • 直到 root 节点后,跳出遍历;
  • 工作宰割 ,React 中的渲染更新能够分成两个阶段

    • reconciliation 阶段 : vdom 的数据比照,是个适宜拆分的阶段,比方比照一部分树后,先暂停执行个动画调用,待实现后再回来持续比对
    • Commit 阶段 : 将 change list 更新到 dom 上,并不适宜拆分,能力保持数据与 UI 的同步。否则可能因为阻塞 UI 更新,而导致数据更新和 UI 不统一的状况
  • 扩散执行: 工作宰割后,就能够把小工作单元扩散到浏览器的闲暇期间去排队执行,而实现的要害是两个新API: requestIdleCallbackrequestAnimationFrame

    • 低优先级的工作交给requestIdleCallback解决,这是个浏览器提供的事件循环闲暇期的回调函数,须要 pollyfill,而且领有 deadline 参数,限度执行事件,以持续切分工作;
    • 高优先级的工作交给requestAnimationFrame解决;
// 相似于这样的形式requestIdleCallback((deadline) => {    // 当有闲暇工夫时,咱们执行一个组件渲染;    // 把工作塞到一个个碎片工夫中去;    while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && nextComponent) {        nextComponent = performWork(nextComponent);    }});
  • 优先级策略: 文本框输出 > 本次调度完结需实现的工作 > 动画过渡 > 交互反馈 > 数据更新 > 不会显示但以防未来会显示的工作
  • Fiber 其实能够算是一种编程思维,在其它语言中也有许多利用(Ruby Fiber)。
  • 核心思想是 工作拆分和协同,被动把执行权交给主线程,使主线程有工夫空挡解决其余高优先级工作。
  • 当遇到过程阻塞的问题时,工作宰割、异步调用 和 缓存策略 是三个显著的解决思路。

OSI七层模型

ISO为了更好的使网络应用更为遍及,推出了OSI参考模型。

(1)应用层

OSI参考模型中最靠近用户的一层,是为计算机用户提供利用接口,也为用户间接提供各种网络服务。咱们常见应用层的网络服务协定有:HTTPHTTPSFTPPOP3SMTP等。

  • 在客户端与服务器中常常会有数据的申请,这个时候就是会用到http(hyper text transfer protocol)(超文本传输协定)或者https.在后端设计数据接口时,咱们经常应用到这个协定。
  • FTP是文件传输协定,在开发过程中,集体并没有波及到,然而我想,在一些资源网站,比方百度网盘`迅雷`应该是基于此协定的。
  • SMTPsimple mail transfer protocol(简略邮件传输协定)。在一个我的项目中,在用户邮箱验证码登录的性能时,应用到了这个协定。

(2)表示层

表示层提供各种用于应用层数据的编码和转换性能,确保一个零碎的应用层发送的数据能被另一个零碎的应用层辨认。如果必要,该层可提供一种规范示意模式,用于将计算机外部的多种数据格式转换成通信中采纳的规范示意模式。数据压缩和加密也是表示层可提供的转换性能之一。

在我的项目开发中,为了不便数据传输,能够应用base64对数据进行编解码。如果按性能来划分,base64应该是工作在表示层。

(3)会话层

会话层就是负责建设、治理和终止表示层实体之间的通信会话。该层的通信由不同设施中的应用程序之间的服务申请和响应组成。

(4)传输层

传输层建设了主机端到端的链接,传输层的作用是为下层协定提供端到端的牢靠和通明的数据传输服务,包含解决差错控制和流量管制等问题。该层向高层屏蔽了上层数据通信的细节,使高层用户看到的只是在两个传输实体间的一条主机到主机的、可由用户管制和设定的、牢靠的数据通路。咱们通常说的,TCP UDP就是在这一层。端口号既是这里的“端”。

(5)网络层

本层通过IP寻址来建设两个节点之间的连贯,为源端的运输层送来的分组,抉择适合的路由和替换节点,正确无误地依照地址传送给目标端的运输层。就是通常说的IP层。这一层就是咱们常常说的IP协定层。IP协定是Internet的根底。咱们能够这样了解,网络层规定了数据包的传输路线,而传输层则规定了数据包的传输方式。

(6)数据链路层

将比特组合成字节,再将字节组合成帧,应用链路层地址 (以太网应用MAC地址)来拜访介质,并进行过错检测。
网络层与数据链路层的比照,通过下面的形容,咱们或者能够这样了解,网络层是布局了数据包的传输路线,而数据链路层就是传输路线。不过,在数据链路层上还减少了差错控制的性能。

(7)物理层

理论最终信号的传输是通过物理层实现的。通过物理介质传输比特流。规定了电平、速度和电缆针脚。罕用设施有(各种物理设施)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。这些都是物理层的传输介质。

OSI七层模型通信特点:对等通信 对等通信,为了使数据分组从源传送到目的地,源端OSI模型的每一层都必须与目标端的对等层进行通信,这种通信形式称为对等层通信。在每一层通信过程中,应用本层本人协定进行通信。

JSX语法糖实质

JSX是语法糖,通过babel转成React.createElement函数,在babel官网上能够在线把JSX转成React的JS语法
  • 首先解析进去的话,就是一个createElement函数
  • 而后这个函数执行完后,会返回一个vnode
  • 通过vdom的patch或者是其余的一个办法,最初渲染一个页面

script标签中不增加text/babel解析jsx语法的状况下
<script>  const ele = React.createElement("h2", null, "Hello React!");  ReactDOM.render(ele, document.getElementById("app"));</script>

JSX的实质是React.createElement()函数

createElement函数返回的对象是ReactEelement对象。

createElement的写法如下

class App extends React.Component {  constructor() {    super()    this.state = {}  }  render() {    return React.createElement("div", null,        /*第一个子元素,header*/        React.createElement("div", { className: "header" },                            React.createElement("h1", { title: "\u6807\u9898" }, "\u6211\u662F\u6807\u9898")                          ),        /*第二个子元素,content*/        React.createElement("div", { className: "content" },                            React.createElement("h2", null, "\u6211\u662F\u9875\u9762\u7684\u5185\u5BB9"),                            React.createElement("button", null, "\u6309\u94AE"),                            React.createElement("button", null, "+1"),                            React.createElement("a", { href: "http://www.baidu.com" },                                                "\u767E\u5EA6\u4E00\u4E0B")                          ),        /*第三个子元素,footer*/        React.createElement("div", { className: "footer" },                            React.createElement("p", null, "\u6211\u662F\u5C3E\u90E8\u7684\u5185\u5BB9")                          )      );  }}ReactDOM.render(<App />, document.getElementById("app"));

理论开发中不会应用createElement来创立ReactElement的,个别都是应用JSX的模式开发。

ReactElement在程序中打印一下

render() {  let ele = (    <div>      <div className="header">        <h1 title="题目">我是题目</h1>      </div>      <div className="content">        <h2>我是页面的内容</h2>        <button>按钮</button>        <button>+1</button>        <a href="http://www.baidu.com">百度一下</a>      </div>      <div className="footer">        <p>我是尾部的内容</p>      </div>    </div>  )  console.log(ele);  return ele;}

react通过babel把JSX转成createElement函数,生成ReactElement对象,而后通过ReactDOM.render函数把ReactElement渲染成实在的DOM元素

为什么 React 应用 JSX

  • 在答复问题之前,我首先解释下什么是 JSX 吧。JSX 是一个 JavaScript 的语法扩大,构造相似 XML。
  • JSX 次要用于申明 React 元素,但 React 中并不强制应用 JSX。即便应用了 JSX,也会在构建过程中,通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖
  • 接下来与 JSX 以外的三种技术计划进行比照

    • 首先是模板,React 团队认为模板不应该是开发过程中的关注点,因为引入了模板语法、模板指令等概念,是一种不佳的实现计划
    • 其次是模板字符串,模板字符串编写的构造会造成屡次外部嵌套,使整个构造变得复杂,并且优化代码提醒也会变得困难重重
    • 所以 React 最初选用了 JSX,因为 JSX 与其设计思维贴合,不须要引入过多新的概念,对编辑器的代码提醒也极为敌对。

Babel 插件如何实现 JSX 到 JS 的编译? 在 React 面试中,这个问题很容易被诘问,也常常被要求手写。

它的实现原理是这样的。Babel 读取代码并解析,生成 AST,再将 AST 传入插件层进行转换,在转换时就能够将 JSX 的构造转换为 React.createElement 的函数。如下代码所示:

module.exports = function (babel) {  var t = babel.types;  return {    name: "custom-jsx-plugin",    visitor: {      JSXElement(path) {        var openingElement = path.node.openingElement;        var tagName = openingElement.name.name;        var args = [];         args.push(t.stringLiteral(tagName));         var attribs = t.nullLiteral();         args.push(attribs);         var reactIdentifier = t.identifier("React"); //object        var createElementIdentifier = t.identifier("createElement");         var callee = t.memberExpression(reactIdentifier, createElementIdentifier)        var callExpression = t.callExpression(callee, args);        callExpression.arguments = callExpression.arguments.concat(path.node.children);        path.replaceWith(callExpression, path.node);       },    },  };};

React.createElement源码剖析

/** 101. React的创立元素办法 */export function createElement(type, config, children) {  // propName 变量用于贮存前面须要用到的元素属性  let propName;   // props 变量用于贮存元素属性的键值对汇合  const props = {};   // key、ref、self、source 均为 React 元素的属性,此处不用深究  let key = null;  let ref = null;   let self = null;   let source = null;   // config 对象中存储的是元素的属性  if (config != null) {     // 进来之后做的第一件事,是顺次对 ref、key、self 和 source 属性赋值    if (hasValidRef(config)) {      ref = config.ref;    }    // 此处将 key 值字符串化    if (hasValidKey(config)) {      key = '' + config.key;     }    self = config.__self === undefined ? null : config.__self;    source = config.__source === undefined ? null : config.__source;    // 接着就是要把 config 外面的属性都一个一个挪到 props 这个之前申明好的对象外面    for (propName in config) {      if (        // 筛选出能够提进 props 对象里的属性        hasOwnProperty.call(config, propName) &&        !RESERVED_PROPS.hasOwnProperty(propName)       ) {        props[propName] = config[propName];       }    }  }  // childrenLength 指的是以后元素的子元素的个数,减去的 2 是 type 和 config 两个参数占用的长度  const childrenLength = arguments.length - 2;   // 如果抛去type和config,就只剩下一个参数,个别意味着文本节点呈现了  if (childrenLength === 1) {     // 间接把这个参数的值赋给props.children    props.children = children;     // 解决嵌套多个子元素的状况  } else if (childrenLength > 1) {     // 申明一个子元素数组    const childArray = Array(childrenLength);     // 把子元素推动数组里    for (let i = 0; i < childrenLength; i++) {       childArray[i] = arguments[i + 2];    }    // 最初把这个数组赋值给props.children    props.children = childArray;   }   // 解决 defaultProps  if (type && type.defaultProps) {    const defaultProps = type.defaultProps;    for (propName in defaultProps) {       if (props[propName] === undefined) {        props[propName] = defaultProps[propName];      }    }  }  // 最初返回一个调用ReactElement执行办法,并传入方才解决过的参数  return ReactElement(    type,    key,    ref,    self,    source,    ReactCurrentOwner.current,    props,  );}

入参解读:发明一个元素须要晓得哪些信息

export function createElement(type, config, children)

createElement 有 3 个入参,这 3 个入参囊括了 React 创立一个元素所须要晓得的全副信息。

  • type:用于标识节点的类型。它能够是相似“h1”“div”这样的规范 HTML 标签字符串,也能够是 React 组件类型或 React fragment 类型。
  • config:以对象模式传入,组件所有的属性都会以键值对的模式存储在 config 对象中。
  • children:以对象模式传入,它记录的是组件标签之间嵌套的内容,也就是所谓的“子节点”“子元素”
React.createElement("ul", {  // 传入属性键值对  className: "list"   // 从第三个入参开始往后,传入的参数都是 children}, React.createElement("li", {  key: "1"}, "1"), React.createElement("li", {  key: "2"}, "2"));

这个调用对应的 DOM 构造如下:

<ul className="list">  <li key="1">1</li>  <li key="2">2</li></ul>

createElement 函数体拆解

createElement 中并没有十分复杂的波及算法或实在 DOM 的逻辑,它的每一个步骤简直都是在格式化数据。

当初看来,createElement 原来只是个“参数中介”。此时咱们的注意力自然而然地就聚焦在了 ReactElement

出参解读:初识虚构 DOM

createElement 执行到最初会 return 一个针对 ReactElement 的调用。这里对于 ReactElement,我仍然先给出源码 + 正文模式的解析
const ReactElement = function(type, key, ref, self, source, owner, props) {  const element = {    // REACT_ELEMENT_TYPE是一个常量,用来标识该对象是一个ReactElement    $$typeof: REACT_ELEMENT_TYPE,    // 内置属性赋值    type: type,    key: key,    ref: ref,    props: props,    // 记录发明该元素的组件    _owner: owner,  };  //   if (__DEV__) {    // 这里是一些针对 __DEV__ 环境下的解决,对于大家了解次要逻辑意义不大,此处我间接省略掉,免得混淆视听  }  return element;};
ReactElement 其实只做了一件事件,那就是“创立”,说得更准确一点,是“组装”:ReactElement 把传入的参数依照肯定的标准,“组装”进了 element 对象里,并把它返回给了 eact.createElement,最终 React.createElement 又把它交回到了开发者手中

const AppJSX = (<div className="App">  <h1 className="title">I am the title</h1>  <p className="content">I am the content</p></div>)console.log(AppJSX)

你会发现它的确是一个规范的 ReactElement 对象实例

这个 ReactElement 对象实例,实质上是以 JavaScript 对象模式存在的对 DOM 的形容,也就是陈词滥调的“虚构 DOM”(精确地说,是虚构 DOM 中的一个节点)