React: Virtual DOM

Virtual DOM(虚拟DOM):
使用js对象表示真实DOM对象,精简真实DOM对象中的原生DOM属性和方法,保留必要属性和方法。
虚拟DOM保存在内存中,方便随时调用。
对虚拟DOM使用diff算法得出真实DOM对象最小必要操作,减少真实DOM对象实际操作,节省性能开销。

// 示例
const virtualDom = {
	key:'A1',
	tag:'div',
	children:[{
		key:'B1',
		tag:'div',
		children:[{
			key:'C1',
			tag:'div',
			children:[]
		},{
			key:'C2',
			tag:'div',
			children:[]
		}]
	},{
		key:'B2',
		tag:'div',
		children:[]
	}],
}

React中虚拟DOM可使用JS语法和JSX语法两种方式创建:
JS语法:

React.createElement("div", { id: "A1" }, 
	React.createElement("div", { id: "B1" }, 
		React.createElement("div", { id: "C1" }, "abc"),//C1子元素abc为文本节点
		React.createElement("div", { id: "C2" })
	), 
	React.createElement("div", { id: "B2" })
);

JSX语法:

<div id='A1'>
	<div id='B1'>
		<div id='C1'>abcdiv> 
		<div id='C2'>div>
	div>
	<div id='B2'>div>
div>

JSX语法在浏览器中运行时需先使用Babel转换为JS语法。

React.createElement实现原理:

const TEXT_ELEMENT= Symbol('TEXT_ELEMENT'); // 文本节点类型

function createElement(type, props, ...children ){
	// 删除冗余属性
	delete props.__self;
	delete props.__source;
	// 返回虚拟DOM对象
	return{
		type,
		// props 包含所有属性和children
		props: {
			...props,
			children: children.map(child => {
				if(typeof child === 'object'){ // 元素节点
					return child 
				}
				if(typeof child ==='string'){ // 文本节点
					// 封装并返回文本节点类型的虚拟DOM
					return {
						type: TEXT_ELEMENT,
						props:{
							text: child,
							children:[]
						}
					}	
				}
			})
		}
	}
}
const React = { createElement }

ReactDOM.render实现原理:

function render(element, container){
	// 创建真实DOM元素对象
	const dom = 
		// 判断是否为文本节点类型虚拟DOM
		element.type == TEXT_ELEMENT
      	? document.createTextNode("")
      	: document.createElement(element.type)// 判断属性名称是否为 "children"
  	const isProperty = key => key !== "children"
  	Object.keys(element.props) // 遍历虚拟DOM props
	    .filter(isProperty) // 过滤children属性
	    .forEach(name => { 
	      	dom[name] = element.props[name] // 将所有属性添加到真实DOM元素
	    })// 子元素递归执行render方法
  	element.props.children.forEach(child =>
    	render(child, dom)
  	)// 将真实DOM挂载到页面上
  	container.appendChild(dom)
}
const ReactDOM = { render }

调用示例:

const app = (<div id='A1'>
	<div id='B1'>
		<div id='C1'>abc</div>
		<div id='C2'></div>
	</div>
	<div id='B2'></div>
</div>)
ReactDOM.render(app, document.getElementById('app'));

注意:上面代码中递归调用子元素运行render的逻辑在dom树过于庞大时会造成进程阻塞页面卡顿,React在新的版本中通过fiber解决了该问题,实现原理如下:

let nextUnitOfWork = null
function workLoop(deadline){
	let shouldYield = false; // 超时标记
	// 当前帧有空余时间 且 仍有回调待执行
	while(nextNode && !shouldYield){
		nextUnitOfWork = performUnitOfWork(nextUnitOfWork) // 运行回调并返回下一节点
		shouldYield = deadline.timeRemaining() < 1; // 更新超时标记
	}
	if(nextNode){ // 回调没有执行完
		requestIdleCallback(workLoop,{timeout:167}) // 注册到下一次空闲回调中
	}
}
function performUnitOfWork(node){
	// 运行回调 ...
	// 返回下一节点
	return node.next;
}
requestIdleCallback(workLoop, { timeout: 167 });

注意:React已并不使用requestIdleCallback方法实现调度,而是通过MessageChannel实现调度包(scheduler package),概念上是类似的。

你可能感兴趣的:(JS)