面试题总结

103 阅读17分钟

Css:

1、盒子水平垂直居中的五大方案

      1)定位+margin(需要子元素宽度)

.father {
    width: 500px;
    height: 400px;
    border: 2px solid red;
    position: relative;
}
.son {
    width: 200px;
    height: 200px;
    background-color: aqua;
    position: absolute;
    top: 50%;
    left: 50%;
    margin-left: -100px;
    margin-top: -100px;
}

       2)定位+transform: translate(-50%, -50%) 

.father {
    width: 500px;
    height: 400px;
    border: 2px solid red;
    position: relative;
}
.son {
    width: 200px;
    height: 200px;
    background-color: aqua;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

       3) 定位+top、bottom、left、right:0,margin:auto

.father {
    position: relative;
    width: 500px;
    height: 400px;
    border: 10px solid darkorange;
}
.son {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}

       4)flex 

.father {
    width: 500px;
    height: 400px;
    border: 2px solid red;
    display: flex;
    justify-content: center;
    align-items: center;
   }
.son {
    width: 200px;
    height: 200px;
    background-color: aqua;
}

       5)父:display: table-cell;vertical-align: center;text-align: center;

.father {
    width: 500px;
    height: 400px;
    border: 2px solid red;
    display: table-cell;
    vertical-align: middle;
    text-align: center;
}
.son {
    display: inline-block;
    width: 200px;
    height: 200px;
    background-color: aqua;
}

2、css盒模型

标准盒模型: width = content + padding + border +margin box-sizing: content-box 

怪异盒模型: width = content + padding                               box-sizing: border-box

3、BFC详解

具有 BFC 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素, 并且 BFC 具有普通容器所没有的一些特性。 

 触发BFC: 

      body 根元素 

      浮动元素:float 除 none 以外的值 

      绝对定位元素:position (absolute、fixed) 

      display 为 inline-block、table-cells、flex 

      overflow 除了 visible 以外的值 (hidden、auto、scroll)

实践:

     1)解决双margin合并问题,给一个div外部加上一个

,并设置wrapper:overflow: hidden;

     2)左边固定宽度,右边不设宽,因此右边的宽度自适应,随浏览器窗口大小的变化而变化。

4、flex:1

flex:1 => flex: 1 1 0 

       flex - flex-shrink flex-grow flex-basic

flex-grow 定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大 

flex-shrink 定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小 

flex-basis给上面两个属性分配多余空间之前, 计算项目是否有多余空间, 默认值为 auto, 即项目本身的大小

5、两边固定,中间自适应布局

1)flex

.container {
    display: flex;
    flex-direction: row;
    width: 100%;
    height: 500px;
}
.container .left {
    width: 200px;
    background-color: red;
}
.container .right {
    width: 200px;
    background-color: bisque;
}
.container .center {
    flex: 1;
    background-color: aqua;
}
<div class="container">
    <div class="left"></div>
    <div class="center"></div>
    <div class="right"></div>
</div>

2)利用浮动实现,左边盒子左浮动,右边盒子右浮动,中间自适应

.container {
	width: 100%;
	height: 500px;
}
.container .left {
	width: 200px;
	height: 100%;
	float: left;
	background-color: red;
}
.container .right {
	width: 200px;
	height: 100%;
	float: right;
	background-color: bisque;
}
.container .center {
	height: 100%;
	background-color: aqua;
}
<div class="container">
	<div class="left"></div>
	<div class="right"></div>
	<div class="center"></div>
</div>

3)使用相对定位

.container {
	width: 100%;
	height: 500px;
	position: relative;
}
.container .left {
	width: 200px;
	height: 100%;
	background-color: red;
	position: absolute;
	top: 0;
	left: 0;
}
.container .right {
	width: 200px;
	height: 100%;
	background-color: bisque;
	position: absolute;
	top: 0;
	right: 0;
}
.container .center {
	height: 100%;
	background-color: aqua;
}
<div class="container">
	<div class="left"></div>
	<div class="right"></div>
	<div class="center"></div>
</div>

4)圣杯布局

.container {
	height: 500px;
	padding-left: 200px;
	padding-right: 200px;
	overflow: hidden;
}
.container .col {
	float: left;
	position: relative;
}
.container .left {
	width: 200px;
	height: 500px;
	background-color: red;
	margin-left: -100%;
	left: -200px;
}
.container .right {
	width: 200px;
	height: 100%;
	background-color: bisque;
	margin-left: -200px;
	right: -200px;
}
.container .center {
	width: 100%;
	height: 100%;
	background-color: aqua;
}

<div class="container">
	<div class="col center"></div>
	<div class="col left"></div>
	<div class="col right"></div>
</div>

JS

1、数据类型

     1) 基本数据类型:number, string, boolean, null, undefined,symbol存在栈中

         引用数据类型:object, array     存在堆中,对象的地址(指针)存在堆中

     2) 引用数据类型出了object和array之外,还有 Date, RepExp, function

     3) null和undefined有什么区别?

        相同点: ①、都是原始类型的值,都保存在栈中;②、进行条件判断时,都是false

        不同点: ①、null是js的关键字,表示空值;undefined不是关键字,是一个全局变量

                      ②、 null是object的一个特殊值,如果object为null,则表示这个对象不是一个有效对象,而undefined是未定义,是一个属性

2、如何判断基本数据类型和引用数据类型?如何判断对象、数组、Date、function、RepExp?

    1) 判断基本数据类型还是引用数据类型?

            typeof, 可以判断基本数据类型还是引用数据类型(typeof null === 'object',特殊)

    2)   instanceof 用来测试一个对象在原型链中是否存在构造函数的prototype属性(就是判断对象是否是某个数据类型的实例(如Array)),基本数据类型不能用instanceof判断

    3)Object.prototype.toString.call(),万能方法,但不能判断某个person 是 Person的实例,判断是否是某个对象的实例,用instanceof

3、闭包

定于: 

       1)函数执行完毕,并且内部创建的东西被外部引用了,形成了不销毁的栈内存

       2)在代码中引用了自由变量

自由变量:是指在函数中使用的变量即不是函数参数arguments也不是函数内部声明的变量,那这个变量就是自由变量

闭包有3个特性: 

          ①函数嵌套函数

          ②函数内部可以引用函数外部的参数和变量 

          ③参数和变量不会被垃圾回收机制回收

4、如何解决内存泄漏

在退出函数之前,将不使用的局部变量全部删除。可以使变量赋值为null

function foo () {
    var name = 'foo'
    var age = 20

    function bar () {
        console.log(name)
        console.log(age)
    }
    return bar
} // 第二行至第八行为闭包函数 name 和 age 上升为自有变量

var fn = foo()
fn()
// 解决
fn = null

4、js如何实现链式调用 

     1) 定义一个对象 

     2)对象中定义一些方法(this.run, this.sing, this.stopSing),方法中return this

    //创建一个bird类 

function Bird(name) {                  this.name = name;                  this.run = function () { 
                      document.write(name+" "+"start run;"); 
                      return this;// return this返回当前调用方法的对象。 
                 } 
                 this.stopRun = function () { 
                      document.write(name+" "+"start run;"); 
                      return this; 
                 } 
                 this.sing = function () { 
                      document.write(name+" "+"start sing;"); 
                      return this; 
                 } 
                 this.stopSing = function () { 
                      document.write(name+" "+"start stopSing;"); 
                      return this; 
                 } 
} 
var bird=new Bird("测试"); 
bird.run().sing().stopSing().stopRun();//结果为;测试 start run;测试 start sing;测试 start stopSing;测试 start run;

6、this指向

1)一般函数,在全局作用域中,指向window;严格模式下,this指向undefined

2)对象的方法里调用,指向调用该方法的对象

3)构造函数中的this指向该构造函数的实例

4)箭头函数的this,指向箭头函数定义时所处环境的this,而非箭头函数执行时的所在对象,默认使用父级的this指向

5)事件绑定中的this,this指向事件源;事件监听和事件绑定基本一致

事件源.onclik = function(){ } //this -> 事件源事件源.addEventListener(function(){ }) //this->事件源

6)行内绑定

<input type="button" value="按钮" onclick="clickFun()">
<script>
    function clickFun(){
        this // 此函数的运行环境在全局window对象下,因此this指向window;
    }
</script>

7)定时器中的this指向看具体场景

例如:

var obj = {
    fun:function(){
        this ;
    }
}
​
setInterval(obj.fun,1000);      // this指向window对象
setInterval('obj.fun()',1000);  // this指向obj对象

zhuanlan.zhihu.com/p/42145138

7、原型、原型链

8、new的原理 

在调用new的过程中都干了什么?

    1)在内存中创建一个新对象

    2)新对象内部的prototype特性被赋值为构造函数的prototype属性

    3)构造函数内部的this被赋值为这个新对象(即this指向新对象)

    4)执行构造函数内部的代码(即给新对象添加属性)

    5)如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象

function myNew() {
    // 创建第一个对象
    var newObj = new Object()
    // 取出第一个参数,获得构造函数
    var constructor = Array.prototype.shift.call(arguments)
    // 连接原型,新对象可以访问原型中的属性
    newObj.__proto__ = constructor.prototype
    // 执行构造函数,绑定this,并为新对象添加属性
    var result = constructor.apply(newObj, arguments)
    return typeof result === 'object' ? result : newObj;
}

9、call、apply、bind原理 

function myCall(context) {
    context = context || window
    context.fn = this
    var args = arguments.slice(1)
    var result = context.fn(...args)
    delete context.fn
    return result
}

function myApply(context, arr) {
   context = context || window
   context.fn = this
   var result = arr ? context.fn(...arr) : context.fn()
   delete context.fn
   return result
}

function myBind(obj) {
    if (typeof this !== 'function') {
        throw new TypeError('错误')
    }
    const self = this // 拿到执行bind方法的函数
    const arr = Array.prototype.slice.call(arguments, 1)
    var o = function() {}   // 使用o做一个中间过渡
    newF = function() {   // bind返回一个函数,另外可以new
        // bind有个函数柯里化的功能,将参数收集起来,按顺序排列好
        var arr2 = Array.prototype.slice.call(arguments)
        arrSum = arr.concat(arr2)
        if (this.instanceof o) {  // 判断调用函数是否是o的原型链上的
            that.apply(this, arrSum)
        } else {
            that.apply(obj, arrSum)
        }
    }
    o.prototype = that.prototype   // o的原型指向调用函数的原型
    newF.prototype = new o
    return newF
}

10、var const let对比 

        - let 声明的变量具有块作用域的特征。 

        - 在同一个块级作用域,不能重复声明变量。

        - let 声明的变量不存在变量提升,换一种说法,就是 let 声明存在暂时性死区(TDZ)。          - const 定义的变量,一旦定义后,就不能修改,即 const 声明的为常量。 

11、事件循环 

    事件队列:

  • 一个线程中,事件循环是唯一的,但是任务队列可以有多个;
  • 任务队列又分macro-task(宏任务)和micro-task(微任务);
  • macro-task包括:script(整体代码)、setTimeout、setInterval、setImmediate、I/O、UI rendering;
  • micro-task包括:process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)
  • setTimeout/Promise等称为任务源,而进入任务队列的是他们制定的具体执行任务;来自不同任务源的任务会进入到不同的任务队列,其中setTimeout与setInterval是同源的;

    宏任务可以理解成每次执行栈执行的代码就是一个宏任务。

事件循环运行机制

    (1)执行一个宏任务(栈中没有就从事件队列中获取)

    (2)执行过程中如果遇到微任务,就将它添加到微任务的任务队列中;

    (3)宏任务执行完毕后,立即执行当前微任务队列的所有微任务; 

    (4)当前微任务执行完毕,开始检查渲染,然后GUI线程接管渲染;

    (5)渲染完毕后,JS线程继续接管,开始下一个宏任务。

    Node的事件循环

          后续补上。。。

12、继承的实现 

13、cookie和localStorage和sesstionStorage的区别 

     ①传递方式不同 

           cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。 

           sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。 

    ②数据大小不同 

           cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下。 

           存储大小限制也不同,cookie数据不能超过4k,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识。

          sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。 

    ③数据有效期不同

          sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持;

          localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;

          cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。 

    ④作用域不同

          sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;

          localStorage 在所有同源窗口中都是共享的; 

          cookie也是在所有同源窗口中都是共享的。

14、事件捕获、事件冒泡 

        事件触发顺序是由内到外的,这就是事件冒泡。如果点击子元素不想触发父元素的事件,可使用event.stopPropagation(); 

        事件捕获过程与事件的冒泡阶段是一个相反的阶段,即事件由祖先元素向子元素传播,和一个石子儿从水面向水底下沉一样,要说明的是在 IE,opera浏览器中,是不存在这个阶段的

15、promise实现

function reslovePromise(promise2, x, resolve, reject) {
	// 判断x是不是promise
	// 规范里规定了一段代码,这个代码可以实现我们的promise
	// 和别人的promise进行交互
	if(promise2 === x) {
		return reject(new TypeError('循环引用'))
	}
	if (x !== null && (typeof x === 'function' || typeof x === 'object')) {
		try{
			let then = x.then
			if (typeof then === 'function') {	// 如果then是一个函数,就认为它是promise
				// call的第一个参数是this,后面的是成功的回调和失败的回调
				then.call(x, y => {  // 如果y是promise,继续递归解析promise
					reslovePromise(promise2, y, resolve, reject)
				}, err => {
					reject(err)
				})
			}
		}catch(e){
			//TODO handle the exception
		}
	} else {
		resolve(x)
	}
}
class Promise {
	constructor(executor) {
		this.status = 'pending'
		this.value = undefined
		this.reason = undefined
		this.onResolvedCallBacks = []
		this.onRejectedCallBacks = []
		let resolve = (data) => {
			if(this.status === 'pending') {
				this.status = 'fulfilld'
				this.value = data
				this.onResolvedCallBacks.forEach(fn => fn())
			}
		}
		let reject = (err) => {
			if (this.status === 'pending') {
				this.status = 'rejected'
				this.reason = err
				this.onRejectedCallBacks.forEach(fn => fn())
			}
		}
		// 执行时可能会发生异常
		try{
			executor(this.res)
		}catch(e){
			reject(e)	// 失败
		}
	}
	then(onFulfilled, onRejected) {
		let promise2;
		if(this.status === 'resolved') {
			promise2 = new Promise((resolve, reject) => {
				let x = onFulfilled(this.value)
				// 看x是不是promise,如果是promise 取他的结果
				// 如果返回的是一个普通值,作为promise2的结果返回
				
				// reslovePromise 可以解析promise2 和 x 之间的关系
				reslovePromise(promise2, x, resolve, reject)
			})
		}
		if(this.status === 'rejected') {
			promise2 = new Promise((resolve, reject) => {
				let x = onRejected(this.reason)
				reslovePromise(promise2, x, resolve, reject)
			})
		}
		if(this.status === 'pending') {
			promise2 = new Promise((resolve, reject) => {
				this.onResolvedCallBacks.push(() => {
					let x = onFulfilled(this.value)
					reslovePromise(promise2, x, resolve, reject)
				})
				this.onRejectedCallBacks.push(() => {
					let x = onRejected(this.value)
					reslovePromise(promise2, x, resolve, reject)
				})
			})
		}
		return promise2
	}
}

16、promise.all实现

myAll = function(promises) {
	let results = []
	let promiseCount = 0
	let length = promises.length
	return new Promise((resolve, reject) => {
		for(let i = 0 ; i < promises.length; i++) {
			Promise.resolve(promises[i]).then(res => {
				results[i] = res
				promiseCount++
				if(promiseCount === length) {
					resolve(results)
				}
			}, err => {
				reject(err)
			})
		}
	})
}

17、深拷贝和浅拷贝

浅拷贝:只复制对象的指针,不复制对象本身,新旧对象共享一块内存

深拷贝:复制并创建一个一模一样的对象,不共享内存,修改新对象,旧对象保持不变

浅拷贝实现:

      Object.assign()

  注意:

  • 当对象只有一级属性为深拷贝;
  • 当对象中有多级属性时,二级属性后就是浅拷贝;

深拷贝实现:

    1)使用递归方式,逐个属性进行拷贝

    2)利用JSON.parse(JSON.strigfy(obj)),但此方法对对象中的function无法拷贝

    3)jquery中的extend方法

let a = [0, 1, [2, 3], 4]
let b = $.extend(true, [], a)
a[0] = 1
a[2][0] = 1     // [1,1,[1,3],4]
console.log(b)  // [0,1,[2,3],4]

Vue

1、v-if和v-for哪个优先级高,为什么一般不一起使用? 

          v-for优先级高,源码中的判断是先判断了for循环,再判断if。若一起用,会先执行for循环,在判断if

2、vue中data为什么是函数 

        因为如果是对象的话,会导致它们共用一个data对象,那么状态将会影响所有的实例; 采用函数形式定义,在initData时会将其作为工厂函数返回全新的data对象,规避了多实例之间状态污染问题 根实例只有一个,不需要担心这种情况 

3、vue中set的原理

     对于数组来说,调用重写的splice方法进行修改,可以更新视图

     对于对象来说,调用defineReactive方法,变成响应式,然后调用dep.notify(),主动更新视图

4、key的作用: 

        为了高效更新虚拟dom,原理是vue在patch过程中,可以通过key来判断两个节点是否是同一个,避免更新 不同元素,减少dom操作量 

5、diff 

     1) diff算法:通过新旧虚拟dom做对比,将变化的地方更新在真实dom上 

     2) vue2为了降低watcher粒度,每个组件只有一个watcher与之对应($mounte-》new watcher()),只有 引入diff算法才能精确找到发生变化的地方 

     3) vue中diff执行的时刻是组件实例执行其更新函数时 

     4) diff遵循深度优先、同级比较的策略 -》 比较方法有4种 ,如果没有找到相同节点 -》 遍历查找,最后处 理剩下的节点 

6、Vue组件的渲染流程

1)在渲染父组件时会创建父组件的虚拟节点,其中可能包括子组件的标签

2)在创建虚拟节点时,获取组件的定义使用Vue.extend生成组件的构造函数

3)将虚拟节点转化为真实节点时,会创建组件的实例,并调用组件的$mount方法。

4)所以组件的创建过程是先父后子

7、mvc、mvp、mvvm 

       1) 这三者都是为了解决model和view的耦合问题 

       2)mvc,优点:分层清晰;缺点:数据流混乱 

       3)mvp,presenter作为中间层负责mv通信,解决了两者的耦合问题,但p层过于臃肿会导致维护问题 

       4)mvvm,不仅解决了耦合问题,还解决了维护两次映射关系的大量繁杂代码和dom操作代码,提高 开发效率、可读性的同时,还保持了优越的性能表现

8、vue组件间的通信 

     1)props

     2)vuex

     3)bus.emitemit on

     4)  provide inject

9、vue query和params传参的区别 

     1) query传参,参数会拼接到页面路径(get),页面跳转后刷新也可正常取值,路由跳转路径需要用path; 

     2) params传参,参数不会拼接到页面路径(post),页面跳转后刷新值不存在,路由跳转路径需要用name;

10、watch和computed的区别 

     watch是监控无缓存,computed是计算且有缓存;不能使用箭头函数,箭头函数没有this,会继承它的 父级(window) 

11、vue生命周期 

     1)beforeCreate: 实例创建出来之前,会执行它 

     2)created: 可以访问data和methods 

     3)beforeMount:模板已经在内存中完成,但是尚未把模板渲染到页面中 

     4)mounted:模板已经挂在完成 

     5)beforeUpdate:数据已经更新了,但是未更新视图 

     6)updated:视图更新完毕 

     7)beforeActive 

     8)activited:这两个是配合keepAlive使用的,可以进行页面缓存 

     9)beforeDestory:销毁前 

     10)destoryed: 销毁后 

12、nextTick的原理: 

     1)vue用异步队列的方式来控制dom更新和nexttick回调先后执行 

     2)microtask因为其高优先级特性,能确保队列中的微任务在一次时间循环前被执行完毕         3)因为兼容性问题,vue不得不做了microtask想macrotask的降级方案(即用settimeout兜底) 

13、vue生命周期,父子组件生命周期,路由生命周期  

    加载渲染过程 

       父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted 

    子组件更新过程 

     父beforeUpdate->子beforeUpdate->子updated->父updated 

    父组件更新过程

     父beforeUpdate->父updated 

    销毁过程

     父beforeDestroy->子beforeDestroy->子destroyed->父destroyed 

14、v-if和v-show的区别

  • **v-if:**通过对Dom元素进行添加和删除,来完成显示和隐藏!

  • **v-show:**通过对display属性值进行改变来完成显示和隐藏;不会对Dom元素进行添加和删除!

15、vue双向绑定原理 

16、vue2和vue3的区别 

      proxy dep.notify( watch update() ) dep.addsub() watcher类 进行数据更新-》vnode的更新 

17、vue-router原理 

     1)hash 

            hash("#") 的作用是加载 URL 中指示网页中的位置。# 本身以及它后面的字符称之为 hash,可通过 window.location.hash 获取

           hashChange()方法,监听hash的改变,每次改变hash,都会在浏览器历史中增加一个记录

           HashHistory.push()   将新路由添加到浏览器访问历史的栈顶

           HashHistory.replace(),replace()方法与push()方法不同之处在于,它并不是将新路由添加到浏览器访问历史的栈顶,而是替换掉当前的路由

     2) history 

History interface 是浏览器历史记录栈提供的接口,通过back()、forward()、go()等方法,我们可以读取浏览器历史记录栈的信息,进行各种跳转操作。
window.history.pushState()   与hash模式类似,只是将window.hash改为history.pushState

    window.history.replaceState()   与hash模式类似,只是将window.replace改为history.replaceState

    popState()方法监听 

18、vue常用的修饰符 

     .lazy:输入框改变,这个数据就会改变,lazy这个修饰符会在光标离开input框才会更新数据:

     .trim:输入框过滤首尾的空格:

     .stop:等同于 JavaScript 中的 event.stopPropagation() ,防止事件冒泡;

     .prevent :阻止默认行为,相当于调用了event.preventDefault()方法,比如表单的提交、a标签的跳转就是默认事件;

     .capture :与事件冒泡的方向相反,事件捕获由外到内;

     .self :只会触发自己范围内的事件,不包含子元素;

     .once :只会触发一次。 

     .sync:对prop进行双向绑定

19、为什么说Vue是渐进式框架

       在声明式渲染(视图模板引擎)的基础上,我们可以通过添加组件系统、客户端路由、大规模状态管理来构建一个完整的框架。更重要的是,这些功能相互独立,你可以在核心功能的基础上任意选用其他的部件,不一定要全部整合在一起。可以看到,所说的“渐进式”,其实就是Vue的使用方式,同时也体现了Vue的设计的理念

20、Vue2和Vue3的区别

 1)生命周期:

      整体变化不大,除beforeCreate和created由setup替代之外,其他前面加上了‘on’

 2)多根节点

      vue2只能单根节点,vue3可以多根节点

 3)Composition Api

     Vue2 是选项API(Options API),一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期钩子等),导致代码的可读性变差。当需要修改某个逻辑时,需要上下来回跳转文件位置。

     Vue3 组合式API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。

 4)响应式原理:

     Vue2 响应式原理基础是 Object.defineProperty;Vue3 响应式原理基础是 Proxy。

 5)虚拟dom

     Vue3 相比于 Vue2,虚拟DOM上增加 patchFlag 字段。

// patchFlags 字段类型列举
export const enum PatchFlags { 
  TEXT = 1,   // 动态文本内容
  CLASS = 1 << 1,   // 动态类名
  STYLE = 1 << 2,   // 动态样式
  PROPS = 1 << 3,   // 动态属性,不包含类名和样式
  FULL_PROPS = 1 << 4,   // 具有动态 key 属性,当 key 改变,需要进行完整的 diff 比较
  HYDRATE_EVENTS = 1 << 5,   // 带有监听事件的节点
  STABLE_FRAGMENT = 1 << 6,   // 不会改变子节点顺序的 fragment
  KEYED_FRAGMENT = 1 << 7,   // 带有 key 属性的 fragment 或部分子节点
  UNKEYED_FRAGMENT = 1 << 8,   // 子节点没有 key 的fragment
  NEED_PATCH = 1 << 9,   // 只会进行非 props 的比较
  DYNAMIC_SLOTS = 1 << 10,   // 动态的插槽
  HOISTED = -1,   // 静态节点,diff阶段忽略其子节点
  BAIL = -2   // 代表 diff 应该结束
}

6)diff算法

     patchFlag 帮助 diff 时区分静态节点,以及不同类型的动态节点。一定程度地减少节点本身及其属性的比对。