真实DOM的操作十分耗费性能,操作过于频繁会带来非常大的性能损耗。
用js的对象结构表示DOM树结构,然后利用这个js对象构建 一个真正的DOM树,插入到文档当中。相当于在JS和真实DOM中间加了一个缓存。编码时基本上只需要操作虚拟DOM,React会将虚拟DOM变化转为真实DOM变化,然后更新界面,大大减少了DOM操作。
基本思路:
创建一个变量存储dom手动实现一个render方法,将虚拟dom对象转化为真实dom <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> // 不引入react要解析jsx,必须进行以下操作 // @jsx是babel的自执行指令,指定babel执行自定义的createElement /*@jsx createElement*/ /* 此方法会递归的将html转为对象的形式 nodename:节点名字 attr:节点属性 args:后代节点 */ function createElement(nodeName, attr, ...args) { return { nodeName, attr, children: [].concat(...args) } } let users = [ { name: "张三" }, { name: "李四" }, { name: "王五" } ]; let vDom = (<div id="app" name="app"> hello world! <ul> { users.map((item, index) => { return ( <li key={index}>{item.name}</li> ) }) } </ul> </div>); /* 自定义render方法 */ function render(vnode) { if (vnode.split) { // 如果是文本,创建文本节点 return document.createTextNode(vnode); } // 如果不是文本,创建根节点 let node = document.createElement(vnode.nodeName); // 添加属性 let attr = vnode.attr || {}; // 返回attr自身可枚举属性,并组成一个数组,然后遍历 Object.keys(attr).forEach((k) => { node.setAttribute(k, attr[k]); }); // 添加子节点 (vnode.children || []).forEach((n) => { node.appendChild(render(n)) }); return node; } let dom = render(vDom); document.body.appendChild(dom); </script>React利用虚拟DOM,可以避免频繁操作真实DOM。但是虚拟DOM最终都会被React转化为真实DOM,DOM操作耗费性能,如果每次更新状态都重新渲染整个应用或者整个组件,将会损耗非常大的性能。为了减少DOM更新,我们需要找到渲染前后真正变化的部分,然后更新这部分。而对比变化,找出需要更新部分的算法,我们称之为Diff算法。
概念:将新旧两颗虚拟DOM树,按照层级对应的关系,从头到尾遍历一遍。
只会对相同颜色框(同级)的DOM节点进行比较,即同一父节点下的所有子节点。 我们以为的操作: A.parent.remove(A); D.append(A);React中的操作 A.destroy(); A= new A(); A.append(new B()); A.append(new C()); D.append(A);当发现该节点已经不存在,则该节点及其子节点将会被完全删除掉,不会用于进一步比较(这也是算法的时间复杂度能够降低到o(n)的原因)将同一位置上的组件进行对比,这种对比方法其实比较的就是类型
如果类型相同,暂不更新如果类型不相同,就需要更新(删除旧的组件,再创建一个新的组件,插入到删除组件的那个位置)在类型相同的组件内,再继续对比组件内部的元素,查看其是否相同。找到不同的元素,进行针对性修改,这就是Element Diff 三种节点操作:
插入:新的Component类型不在旧集合内,执行插入操作移动:旧的集合包含新的Component,执行移动操作,复用之前的DOM节点(有key值才会进行移动,否则只是简单粗暴的插入和删除)删除:旧的Component不再新的集合里,或者旧的Component的类型相同但是Element不同,不能复用,执行删除操作。