精华内容
下载资源
问答
  • react虚拟dom原理This is part of my “React for beginners” series on introducing React, its core features and best practices to follow. More articles are coming! 这是我的“初学者React”系列的一部分,...

    react虚拟dom原理

    This is part of my “React for beginners” series on introducing React, its core features and best practices to follow. More articles are coming!
    这是我的“初学者React”系列的一部分,该系列介绍了React,其核心功能和可遵循的最佳实践。 更多文章来了!

    Next article >

    下一篇文章>

    Do you want to learn React without crawling the documentation (well written by the way)? You clicked on the right article.

    您是否想在不爬行文档的情况下学习React(顺便说一下)? 您单击了正确的文章。

    We will learn how to run React with a single HTML file and then expose ourselves to a first snippet.

    我们将学习如何使用单个HTML文件运行React,然后将自己暴露给第一个片段。

    By the end, you will be able to explain these concepts: props, functional component, JSX, and Virtual DOM.

    最后,您将能够解释以下概念:道具,功能组件,JSX和虚拟DOM。

    The goal is to make a watch which displays hours and minutes. React offers to architect our code with components. `Let’s create our watch component.

    目的是制作一块显示小时和分钟的手表。 React提供了使用组件构建代码的方法。 `让我们创建手表组件。

    <!-- Skipping all HTML5 boilerplate -->
    <script src="https://unpkg.com/react@16.2.0/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16.2.0/umd/react-dom.development.js"></script>
    
    <!-- For JSX support (with babel) -->
    <script src="https://unpkg.com/babel-standalone@6.24.2/babel.min.js" charset="utf-8"></script> 
    
    <div id="app"></div> <!-- React mounting point-->
    
    <script type="text/babel">
      class Watch extends React.Component {
        render() {
          return <div>{this.props.hours}:{this.props.minutes}</div>;
        }
      }
    
      ReactDOM.render(<Watch hours="9" minutes="15"/>, document.getElementById('app'));
    </script>

    Ignore HTML boilerplate and script imports for dependencies (with unpkg, see React example). The few remaining lines are actually React code.

    忽略依赖关系HTML样板文件和脚本导入(使用unpkg ,请参见React示例 )。 剩下的几行实际上是React代码。

    First, define the Watch component and its template. Then mount React into the DOM and ask to render a watch.

    首先,定义监视组件及其模板。 然后将React安装到DOM中并要求渲染手表。

    将数据注入组件 (Inject data into the component)

    Our watch is quite stupid, it displays the hours and minutes we provided to it.

    我们的手表很傻,它显示了我们提供给它的小时和分钟。

    You can try to play around and change the value for those properties (called props in React). It will always display what you asked for even if it’s not numbers.

    您可以尝试修改这些属性的值(在React中称为props )。 即使不是数字,它也会始终显示您要的内容。

    This kind of React component with only a render function are functional component. They have a more concise syntax compared to classes.

    这种只有渲染功能的React组件是功能组件。 与类相比,它们的语法更简洁。

    const Watch = (props) =>
      <div>{props.hours}:{props.minutes}</div>;
    
    ReactDOM.render(<Watch hours="Hello" minutes="World"/>, document.getElementById('app'));

    Props are only data passed to a component, generally by a surrounding component. The component uses props for business logic and rendering.

    道具只是通常由周围组件传递到组件的数据。 该组件将道具用于业务逻辑和渲染。

    But as soon as props do not belong to the component they are immutable. Thus, the component which provided the props is the only piece of code able to update props values.

    但是,一旦道具不属于组件,它们就不会改变 。 因此,提供道具的组件是唯一能够更新道具值的代码。

    Using props is pretty straightforward. Create a DOM node with your component name as the tag name. Then give it attributes named after props. Then the props will be available through this.props in the component.

    使用道具非常简单。 使用您的组件名称作为标记名称创建一个DOM节点。 然后赋予它以道具命名的属性。 然后,可通过this.props中的this.props获得道具。

    不带引号HTML呢? (What about unquoted HTML ?)

    I was sure you will notice the unquoted HTML returned by the render function. This code is using JSX language, it’s a shorthand syntax to define HTML template in React components.

    我确定您会注意到render函数返回的未引用HTML。 这段代码使用的是JSX 语言,这是在React组件中定义HTML模板的简写语法。

    // Equivalent to JSX: <Watch hours="9" minutes="15"/>
    React.createElement(Watch, {'hours': '9', 'minutes': '15'});

    Now you may want to avoid JSX to define the component’s template. Actually, JSX looks like syntactic sugar.

    现在,您可能要避免使用JSX定义组件的模板。 实际上,JSX看起来像语法糖

    Take a look at the following snippet which shows both JSX and React syntax to build your opinion.

    请看以下显示JSX和React语法的代码片段,以建立您的意见。

    // Using JS with React.createElement
    React.createElement('form', null, 
      React.createElement('div', {'className': 'form-group'},
        React.createElement('label', {'htmlFor': 'email'}, 'Email address'),
        React.createElement('input', {'type': 'email', 'id': 'email', 'className': 'form-control'}),
      ),
      React.createElement('button', {'type': 'submit', 'className': 'btn btn-primary'}, 'Submit')
    )
    
    // Using JSX
    <form>
      <div className="form-group">
        <label htmlFor="email">Email address</label>
        <input type="email" id="email" className="form-control"/>
      </div>
      <button type="submit" className="btn btn-primary">Submit</button>
    </form>

    虚拟DOM更进一步 (Going further with the Virtual DOM)

    This last part is more complicated but very interesting. It will help you to understand how React is working under the hood.

    最后一部分比较复杂,但非常有趣。 它将帮助您了解React在幕后的工作方式。

    Updating elements on a webpage (a node in the DOM tree) involves using the DOM API. It will repaint the page but it can be slow (see this article for why).

    更新网页(DOM树中的一个节点)上的元素需要使用DOM API。 它将重新绘制页面,但是速度可能很慢(有关原因,请参见本文 )。

    Many frameworks such as React and Vue.js get around this problem. They come up with a solution called the Virtual DOM.

    React和Vue.js等许多框架都可以解决此问题。 他们提出了一种称为虚拟DOM的解决方案。

    {
       "type":"div",
       "props":{ "className":"form-group" },
       "children":[
         {
           "type":"label",
           "props":{ "htmlFor":"email" },
           "children":[ "Email address"]
         },
         {
           "type":"input",
           "props":{ "type":"email", "id":"email", "className":"form-control"},
           "children":[]
         }
      ]
    }

    The idea is simple. Reading and updating the DOM tree is very expensive. So make as few changes as possible and update as few nodes as possible.

    这个想法很简单。 读取和更新DOM树非常昂贵。 因此,请进行尽可能少的更改并尽可能少地更新节点。

    Reducing calls to DOM API involves keeping DOM tree representation in memory. Since we are talking about JavaScript frameworks, choosing JSON sounds legitimate.

    减少对DOM API的调用涉及将DOM树表示形式保留在内存中。 由于我们在谈论JavaScript框架,因此选择JSON听起来很合理。

    This approach immediately reflects changes in the Virtual DOM.

    这种方法立即反映了虚拟DOM中的更改。

    Besides, it gathers a few updates to apply later on the Real DOM at once (to avoid performance issues).

    此外,它收集了一些更新,以便稍后立即应用于Real DOM(以避免性能问题)。

    Do you remember React.createElement ? Actually, this function (called directly or through JSX) creates a new node in the Virtual DOM.

    您还记得React.createElement吗? 实际上,此函数(直接调用或通过JSX调用)在虚拟DOM中创建一个新节点。

    // React.createElement naive implementation (using ES6 features)
    function createElement(type, props, ...children) {
      return { type, props, children };
    }

    To apply updates, the Virtual DOM core feature comes into play, the reconciliation algorithm.

    为了应用更新, 对帐算法是Virtual DOM核心功能。

    Its job is to come up with the most optimized solution to resolve the difference between previous and current Virtual DOM state.

    它的工作是提出最优化的解决方案,以解决以前和当前虚拟DOM状态之间的差异。

    And then apply the new Virtual DOM to the real DOM.

    然后将新的虚拟DOM应用于真实DOM。

    进一步阅读 (Further readings)

    This article goes far on React internal and Virtual DOM explanations. Still, it’s important to know a bit about how a framework works when using it.

    本文对React内部和虚拟DOM的解释进行了深入介绍。 尽管如此,了解框架在使用时的工作原理还是很重要的。

    If you want to learn how the Virtual DOM works in details, follow my reading recommendations. You can write your own Virtual DOM and learn about DOM rendering.

    如果您想详细了解Virtual DOM的工作原理,请遵循我的阅读建议。 您可以编写自己的虚拟DOM并了解DOM渲染

    How to write your own Virtual DOM‌‌

    如何编写自己的虚拟DOM

    There are two things you need to know to build your own Virtual DOM. You do not even need to dive into React’s source…

    要构建自己的虚拟DOM,需要了解两件事。 您甚至不需要深入研究React的源代码……

    Thank you for reading. Sorry if this is too technical for your first step in React. But I hope now you are aware of what props, functional component, JSX, and Virtual DOM are.

    感谢您的阅读。 抱歉,这对于您在React的第一步来说太技术性了。 但是,我希望您现在知道什么是道具,功能组件,JSX和虚拟DOM。

    If you found this article useful, please click on the ? button a few times to make others find the article and to show your support! ?

    如果您发现本文有用,请单击“ ?”。 几次单击以使其他人找到该文章并表示您的支持!

    Don’t forget to follow me to get notified of my upcoming articles ?

    别忘了跟随我以获取有关我即将发表的文章的通知吗?

    This is part of my “React for beginners” series on introducing React, its core features and best practices to follow.
    这是我的“初学者React”系列的一部分,该系列介绍了React,其核心功能和可遵循的最佳实践。

    Next article >

    下一篇文章>

    ➥JavaScript (➥ JavaScript)

    ➥提示与技巧 (➥ Tips & tricks)

    Originally published at www.linkedin.com on February 6, 2018.

    最初于2018年2月6日发布在www.linkedin.com上。

    翻译自: https://www.freecodecamp.org/news/a-quick-guide-to-learn-react-and-how-its-virtual-dom-works-c869d788cd44/

    react虚拟dom原理

    展开全文
  • vue中的虚拟DOM原理

    千次阅读 2020-06-06 14:02:51
    1、定义: 虚拟DOM其实就是一棵以 JavaScript 对象( VNode 节点)作为基础的树,用对象属性来...2、虚拟dom原理流程 模板 ==> 渲染函数 ==> 虚拟DOM树 ==> 真实DOM vuejs通过编译将模板(template)转成渲

    1、定义:

    虚拟DOM其实就是用一个原生的JS对象去描述一个DOM节点,实际上它只是对真实 DOM 的一层抽象。最终可以通过一系列操作使这棵树映射到真实环境上。

    相当于在js与DOM之间做了一个缓存,利用patch(diff算法)对比新旧虚拟DOM记录到一个对象中按需更新, 最后创建真实的DOM

    2、虚拟dom原理流程

    模板 ==> 渲染函数 ==> 虚拟DOM树 ==> 真实DOM

    • vuejs通过编译将模板(template)转成渲染函数(render),执行渲染函数可以得到一个虚拟节点树

    • 在对 Model 进行操作的时候,会触发对应 Dep 中的 Watcher 对象。Watcher 对象会调用对应的 update 来修改视图。

    虚拟 DOM 的实现原理主要包括以下 3 部分:

    1. 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
    2. diff 算法 — 比较两棵虚拟 DOM 树的差异;
    3. pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。
    图解:在这里插入图片描述
    • 渲染函数: 渲染函数是用来生成Virtual DOM的。
    • VNode虚拟节点: 它可以代表一个真实的dom节点。通过 createElement 方法能将 VNode 渲染成 dom 节点。简单地说,vnode可以理解成节点描述对象,它描述了应该怎样去创建真实的DOM节点
    • patch(也叫做patching算法): 虚拟DOM最核心的部分,它可以将vNode渲染成真实的DOM,这个过程是对比新旧虚拟节点之间有哪些不同,然后根据对比结果找出需要更新的的节点进行更新

    3、虚拟DOM好处

    • 具备跨平台的优势–由于 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说浏览器平台、Weex、Node 等。
    • 操作 DOM 慢,js运行效率高。我们可以将DOM对比操作放在JS层,提高效率。运用patching算法来计算出真正需要更新的节点,最大限度地减少DOM操作,从而显著提高性能。Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。
    • 提升渲染性能 Virtual DOM的优势不在于单次的操作,而是在大量、频繁的数据更新下,能够对视图进行合理、高效的更新
    • 虚拟DOM就是为了解决浏览器性能问题而被设计出来的
    展开全文
  • React虚拟DOM原理

    2019-11-30 14:25:54
    1.什么是虚拟DOM 我们在前端面试的时候,经常会被问到什么是虚拟DOM。这个概念,感觉很熟悉,但又说不出它到底是什么。现在我们来探索一下到底什么是虚拟DOM。 首先我们看下什么是DOM,对于DOM,我们应该都很熟悉了...

    1.什么是虚拟DOM

    我们在前端面试的时候,经常会被问到什么是虚拟DOM。这个概念,感觉很熟悉,但又说不出它到底是什么。现在我们来探索一下到底什么是虚拟DOM

    首先我们看下什么是DOM,对于DOM,我们应该都很熟悉了,下面是MDN对于DOM的定义

    文档对象模型 (DOM) 将 web 页面与到脚本或编程语言连接起来。通常是指 JavaScript,但将 HTML、SVG 或 XML 文档建模为对象并不是 JavaScript 语言的一部分。DOM模型用一个逻辑树来表示一个文档,树的每个分支的终点都是一个节点(node),每个节点都包含着对象(objects)。DOM的方法(methods)让你可以用特定方式操作这个树,用这些方法你可以改变文档的结构、样式或者内容。节点可以关联上事件处理器,一旦某一事件被触发了,那些事件处理器就会被执行。

    虚拟DOM自然就是跟DOM有很大关系的了。我们在使用原生JS开发或者使用Jquery开发,经常就会操作DOM,但是我们使用的时候发现,每次我们改变DOM的时候,页面再次渲染,会花费不短的一段时间,这样用户体验就不太好了。如果我们每次操作的不是DOM或者每次只操作更少的DOM呢,是不是会花费的时间更短呢,基于这个想法,就有了虚拟DOM

    在React中,会把DOM转换成JavaScript对象,然后再把JavaScript对象转化成DOM,这样我们对于DOM的操作,实际上是在操作这个JavaScript对象

    2.DOM是如何创建虚拟DOM的

    我们利用在线babel工具来看下。左边是JSX,右边是JSX经过babel转换后的效果,事实上JSX是右边这种写法的语法糖,我们在React项目中写的JSX的写法都会转换成右边这种写法。
    在这里插入图片描述
    在React项目中,使用以下这种写法,渲染出的效果也是一样的。

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    let element = React.createElement("h1", {
        id: "test",
        className: "testClass"
      }, "test");
    
    ReactDOM.render(element, document.getElementById('root'));
    

    现在我们来分析以下上面的代码

    • React.createElement()方法传入了3个参数,第1个参数对应的是标签名称,第2个参数是属性,第三个参数是内容,然后返回某个值
    • ReactDOM.render()方法接收了两个参数,第一个参数是刚刚提到的某个值,第二个参数是获取到的root元素,对应的是index.html中的<div id="root"></div>

    在上面的代码中加入console.log(element),打印出element的值,然后看到,原来某个值是这样的:
    在这里插入图片描述
    由此说明:React.createElement()方法创建了虚拟DOM

    3.模拟实现React.createElement()

    有上图可以这个对象有多个属性,目前来说对我们比较重要的是propstype属性,所以先实现对于这两个属性的操作。
    React.createElement()接收3个参数,现在要把这3个参数合并到typeprops中。
    React.createElement()接收3个以上参数,说明该元素里面有多个子元素(这些子元素仍然是React.createElement()),那么把第二个参数后面的所有参数转换成数组放入children

    function ReactElement(type, props) {
        const element = { type, props };
        return element;
    }
    
    function createElement(type, config = {}, children) {
        let propName;
        const props = {};  // 定义props
        for(propName in config) {
            props[propName] = config[propName];  // 复制config的属性到props中
        }
    
        // 处理children
        const childrenLength = arguments.length - 2;
        if(childrenLength === 1) {
            props.children = children;
        } else {
            // 有多个子元素的情况
            props.children = Array.from(arguments).slice(2);
        }
    
        return ReactElement(type, props);
    }
    

    加入以下代码测试下效果

    const element = createElement("h1", {
        id: "test",
        className: "testClass"
      }, createElement("span", null, "span1"), createElement("span", null, "span2"));
    
    console.log(JSON.stringify(element))
    

    打印结果为:
    在这里插入图片描述
    可以看到,最终,DOM转换成了JavaScript对象

    展开全文
  • 背景 大家都知道,在网页中浏览器资源开销最大便是DOM节点了,DOM很慢并且非常庞大,网页性能问题大多数都是有JavaScript修改DOM所引起的。我们使用Javascript来操纵DOM,...但是,Javascript运行速度很快,虚拟DOM

    背景

    大家都知道,在网页中浏览器资源开销最大便是DOM节点了,DOM很慢并且非常庞大,网页性能问题大多数都是有JavaScript修改DOM所引起的。我们使用Javascript来操纵DOM,操作效率往往很低,由于DOM被表示为树结构,每次DOM中的某些内容都会发生变化,因此对DOM的更改非常快,但更改后的元素,并且它的子项必须经过Reflow / Layout阶段,然后浏览器必须重新绘制更改,这很慢的。因此,回流/重绘的次数越多,您的应用程序就越卡顿。但是,Javascript运行速度很快,虚拟DOM是放在JS 和 HTML中间的一个层。它可以通过新旧DOM的对比,来获取对比之后的差异对象,然后有针对性的把差异部分真正地渲染到页面上,从而减少实际DOM操作,最终达到性能优化的目的。

    虚拟dom原理流程

    简单概括有三点:

    1. 用JavaScript模拟DOM树,并渲染这个DOM树
    2. 比较新老DOM树,得到比较的差异对象
    3. 把差异对象应用到渲染的DOM树。

    下面是流程图:

     

    下面我们用代码一步步去实现一个流程图

    用JavaScript模拟DOM树并渲染到页面上

    其实虚拟DOM,就是用JS对象结构的一种映射,下面我们一步步实现这个过程。

    我们用JS很容易模拟一个DOM树的结构,例如用这样的一个函数createEl(tagName, props, children)来创建DOM结构。

    tagName标签名、 props是属性的对象、 children是子节点。

    然后渲染到页面上,代码如下:

    const createEl = (tagName, props, children) => new CreactEl(tagName, props, children)
    
    const vdom = createEl('div', { 'id': 'box' }, [
      createEl('h1', { style: 'color: pink' }, ['I am H1']),
      createEl('ul', {class: 'list'}, [createEl('li', ['#list1']), createEl('li', ['#list2'])]),
      createEl('p', ['I am p'])
    ])
    
    const rootnode = vdom.render()
    document.body.appendChild(rootnode)

    通过上面的函数,调用vdom.render()这样子我们就很好的构建了如下所示的一个DOM树,然后渲染到页面上

    
    <div id="box">
      <h1 style="color: pink;">I am H1</h1>
      <ul class="list">
        <li>#list1</li>
        <li>#list2</li>
      </ul>
      <p>I am p</p>
    </div>
    

    下面我们看看CreactEl.js代码流程:

    
    import { setAttr } from './utils'
    class CreateEl {
      constructor (tagName, props, children) {
        // 当只有两个参数的时候 例如 celement(el, [123])
        if (Array.isArray(props)) {
          children = props
          props = {}
        }
        // tagName, props, children数据保存到this对象上
        this.tagName = tagName
        this.props = props || {}
        this.children = children || []
        this.key = props ? props.key : undefined
    
        let count = 0
        this.children.forEach(child => {
          if (child instanceof CreateEl) {
            count += child.count
          } else {
            child = '' + child
          }
          count++
        })
        // 给每一个节点设置一个count
        this.count = count
      }
      // 构建一个 dom 树
      render () {
        // 创建dom
        const el = document.createElement(this.tagName)
        const props = this.props
        // 循环所有属性,然后设置属性
        for (let [key, val] of Object.entries(props)) {
          setAttr(el, key, val)
        }
        this.children.forEach(child => {
          // 递归循环 构建tree
          let childEl = (child instanceof CreateEl) ? child.render() : document.createTextNode(child)
          el.appendChild(childEl)
        })
        return el
      }
    }
    

    上面render函数的功能是把节点创建好,然后设置节点属性,最后递归创建。这样子我们就得到一个DOM树,然后插入(appendChild)到页面上。

    比较新老dom树,得到比较的差异对象

    上面,我们已经创建了一个DOM树,然后在创建一个不同的DOM树,然后做比较,得到比较的差异对象。

    比较两棵DOM树的差异,是虚拟DOM的最核心部分,这也是人们常说的虚拟DOM的diff算法,两颗完全的树差异比较一个时间复杂度为 O(n^3)。但是在我们的web中很少用到跨层级DOM树的比较,所以一个层级跟一个层级对比,这样算法复杂度就可以达到 O(n)。如下图

     

    其实在代码中,我们会从根节点开始标志遍历,遍历的时候把每个节点的差异(包括文本不同,属性不同,节点不同)记录保存起来。如下图:

     

    两个节点之间的差异有总结起来有下面4种

    0 直接替换原有节点
    1 调整子节点,包括移动、删除等
    2 修改节点属性
    3 修改节点文本内容

    如下面两棵树比较,把差异记录下来。

     

    主要是简历一个遍历index(看图3),然后从根节点开始比较,比较万之后记录差异对象,继续从左子树比较,记录差异,一直遍历下去。主要流程如下

    
    // 这是比较两个树找到最小移动量的算法是Levenshtein距离,即O(n * m)
    // 具体请看 https://www.npmjs.com/package/list-diff2
    import listDiff from 'list-diff2'
    // 比较两棵树
    function diff (oldTree, newTree) {
      // 节点的遍历顺序
      let index = 0
      // 在遍历过程中记录节点的差异
      let patches = {}
      // 深度优先遍历两棵树
      deepTraversal(oldTree, newTree, index, patches)
      // 得到的差异对象返回出去
      return patches
    }
    
    function deepTraversal(oldNode, newNode, index, patches) {
      let currentPatch = []
      // ...中间有很多对patches的处理
      // 递归比较子节点是否相同
      diffChildren(oldNode.children, newNode.children, index, patches, currentPatch)
      if (currentPatch.length) {
        // 那个index节点的差异记录下来
        patches[index] = currentPatch
      }
    }
    
    // 子数的diff
    function diffChildren (oldChildren, newChildren, index, patches, currentPatch) {
      const diffs = listDiff(oldChildren, newChildren)
      newChildren = diffs.children
      // ...省略记录差异对象
      let leftNode = null
      let currentNodeIndex = index
      oldChildren.forEach((child, i) => {
        const newChild = newChildren[i]
        // index相加
        currentNodeIndex = (leftNode && leftNode.count) ? currentNodeIndex + leftNode.count + 1 : currentNodeIndex + 1
        // 深度遍历,递归
        deepTraversal(child, newChild, currentNodeIndex, patches)
        // 从左树开始
        leftNode = child
      })
    }
    

    然后我们调用完diff(tree, newTree)等到最后的差异对象是这样子的。

    
    {
      "1": [
        {
          "type": 0,
          "node": {
            "tagName": "h3",
            "props": {
              "style": "color: green"
            },
            "children": [
              "I am H1"
            ],
            "count": 1
          }
        }
      ]
      ...
    }
    

    key是代表那个节点,这里我们是第二个,也就是h1会改变成h3,还有省略的两个差异对象代码没有贴出来~~

    然后看下diff.js的完整代码,如下

    
    import listDiff from 'list-diff2'
    // 每个节点有四种变动
    export const REPLACE = 0 // 替换原有节点
    export const REORDER = 1 // 调整子节点,包括移动、删除等
    export const PROPS = 2 // 修改节点属性
    export const TEXT = 3 // 修改节点文本内容
    
    export function diff (oldTree, newTree) {
      // 节点的遍历顺序
      let index = 0
      // 在遍历过程中记录节点的差异
      let patches = {}
      // 深度优先遍历两棵树
      deepTraversal(oldTree, newTree, index, patches)
      // 得到的差异对象返回出去
      return patches
    }
    
    function deepTraversal(oldNode, newNode, index, patches) {
      let currentPatch = []
      if (newNode === null) { // 如果新节点没有的话直接不用比较了
        return
      }
      if (typeof oldNode === 'string' && typeof newNode === 'string') {
        // 比较文本节点
        if (oldNode !== newNode) {
          currentPatch.push({
            type: TEXT,
            content: newNode
          })
        }
      } else if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
        // 节点类型相同
        // 比较节点的属性是否相同
        let propasPatches = diffProps(oldNode, newNode)
        if (propasPatches) {
          currentPatch.push({
            type: PROPS,
            props: propsPatches
          })
        }
        // 递归比较子节点是否相同
        diffChildren(oldNode.children, newNode.children, index, patches, currentPatch)
      } else {
        // 节点不一样,直接替换
        currentPatch.push({ type: REPLACE, node: newNode })
      }
    
      if (currentPatch.length) {
        // 那个index节点的差异记录下来
        patches[index] = currentPatch
      }
    
    }
    
    // 子数的diff
    function diffChildren (oldChildren, newChildren, index, patches, currentPatch) {
      var diffs = listDiff(oldChildren, newChildren)
      newChildren = diffs.children
      // 如果调整子节点,包括移动、删除等的话
      if (diffs.moves.length) {
        var reorderPatch = {
          type: REORDER,
          moves: diffs.moves
        }
        currentPatch.push(reorderPatch)
      }
    
      var leftNode = null
      var currentNodeIndex = index
      oldChildren.forEach((child, i) => {
        var newChild = newChildren[i]
        // index相加
        currentNodeIndex = (leftNode && leftNode.count) ? currentNodeIndex + leftNode.count + 1 : currentNodeIndex + 1
        // 深度遍历,从左树开始
        deepTraversal(child, newChild, currentNodeIndex, patches)
        // 从左树开始
        leftNode = child
      })
    }
    
    // 记录属性的差异
    function diffProps (oldNode, newNode) {
      let count = 0 // 声明一个有没没有属性变更的标志
      const oldProps = oldNode.props
      const newProps = newNode.props
      const propsPatches = {}
    
      // 找出不同的属性
      for (let [key, val] of Object.entries(oldProps)) {
        // 新的不等于旧的
        if (newProps[key] !== val) {
          count++
          propsPatches[key] = newProps[key]
        }
      }
      // 找出新增的属性
      for (let [key, val] of Object.entries(newProps)) {
        if (!oldProps.hasOwnProperty(key)) {
          count++
          propsPatches[key] = val
        }
      }
      // 没有新增 也没有不同的属性 直接返回null
      if (count === 0) {
        return null
      }
    
      return propsPatches
    }
    

    得到差异对象之后,剩下就是把差异对象应用到我们的dom节点上面了。

    把差异对象应用到渲染的dom树

    到了这里其实就简单多了。我们上面得到的差异对象之后,然后选择同样的深度遍历,如果那个节点有差异的话,判断是上面4种中的哪一种,根据差异对象直接修改这个节点就可以了。

    function patch (node, patches) {
      // 也是从0开始
      const step = {
        index: 0
      }
      // 深度遍历
      deepTraversal(node, step, patches)
    }
    
    // 深度优先遍历dom结构
    function deepTraversal(node, step, patches) {
      // 拿到当前差异对象
      const currentPatches = patches[step.index]
      const len = node.childNodes ? node.childNodes.length : 0
      for (let i = 0; i < len; i++) {
        const child = node.childNodes[i]
        step.index++
        deepTraversal(child, step, patches)
      }
      //如果当前节点存在差异
      if (currentPatches) {
        // 把差异对象应用到当前节点上
        applyPatches(node, currentPatches)
      }
    }

    这样子,调用patch(rootnode, patches)就直接有针对性的改变有差异的节点了。

    path.js完整代码如下:

    import {REPLACE, REORDER, PROPS, TEXT} from './diff'
    import { setAttr } from './utils'
    
    export function patch (node, patches) {
      // 也是从0开始
      const step = {
        index: 0
      }
      // 深度遍历
      deepTraversal(node, step, patches)
    }
    
    // 深度优先遍历dom结构
    function deepTraversal(node, step, patches) {
      // 拿到当前差异对象
      const currentPatches = patches[step.index]
      const len = node.childNodes ? node.childNodes.length : 0
      for (let i = 0; i < len; i++) {
        const child = node.childNodes[i]
        step.index++
        deepTraversal(child, step, patches)
      }
      //如果当前节点存在差异
      if (currentPatches) {
        // 把差异对象应用到当前节点上
        applyPatches(node, currentPatches)
      }
    }
    
    // 把差异对象应用到当前节点上
    function applyPatches(node, currentPatches) {
      currentPatches.forEach(currentPatch => {
        switch (currentPatch.type) {
          // 0: 替换原有节点
          case REPLACE:
            var newNode = (typeof currentPatch.node === 'string') ?  document.createTextNode(currentPatch.node) : currentPatch.node.render()
            node.parentNode.replaceChild(newNode, node)
            break
          // 1: 调整子节点,包括移动、删除等
          case REORDER: 
            moveChildren(node, currentPatch.moves)
            break
          // 2: 修改节点属性
          case PROPS:
            for (let [key, val] of Object.entries(currentPatch.props)) {
              if (val === undefined) {
                node.removeAttribute(key)
              } else {
                setAttr(node, key, val)
              }
            }
            break;
          // 3:修改节点文本内容
          case TEXT:
            if (node.textContent) {
              node.textContent = currentPatch.content
            } else {
              node.nodeValue = currentPatch.content
            }
            break;
          default: 
            throw new Error('Unknow patch type ' + currentPatch.type);
        }
      })
    }
    
    // 调整子节点,包括移动、删除等
    function moveChildren (node, moves) {
      let staticNodelist = Array.from(node.childNodes)
      const maps = {}
      staticNodelist.forEach(node => {
        if (node.nodeType === 1) {
          const key = node.getAttribute('key')
          if (key) {
            maps[key] = node
          }
        }
      })
      moves.forEach(move => {
        const index = move.index
        if (move.type === 0) { // 变动类型为删除的节点
          if (staticNodeList[index] === node.childNodes[index]) {
            node.removeChild(node.childNodes[index]);
          }
          staticNodeList.splice(index, 1);
        } else {
          let insertNode = maps[move.item.key] 
              ? maps[move.item.key] : (typeof move.item === 'object') 
              ? move.item.render() : document.createTextNode(move.item)
          staticNodelist.splice(index, 0, insertNode);
          node.insertBefore(insertNode, node.childNodes[index] || null)
        }
      })
    }

    到这里,最基本的虚拟DOM原理已经讲完了,也简单了实现了一个虚拟DOM,如果本文有什么不对的地方请指正。

     

    展开全文
  • Vue虚拟DOM原理及面试题(笔记)

    千次阅读 2021-03-27 14:06:02
    Vue虚拟DOM原理及面试题(笔记) 面试题:请你阐述一下对vue虚拟dom的理解 什么是虚拟dom? 虚拟dom本质上就是一个普通的JS对象,用于描述视图的界面结构 在vue中,每个组件都有一个render函数,每个render函数...
  • 虚拟dom原理

    2020-11-13 15:06:54
    前端的DOM是一棵树,对于一个element来说,我们需要关注的是这个element的 tagName、属性、以及子元素,而这完全可以用一个js对象来表示,比如,使用tagName属性 来说明标签名,将所有的属性和值作为一个对象表示为...
  • 虚拟DOM原理简述

    2019-07-11 22:06:28
    1.为什么需要虚拟DOM JS操作DOM速度很慢,但是内存的速度很快 如何把DOM结构用JS对象表示出来,再用JS对象一次性修改DOM,成为了开发者的目标。 2.什么是虚拟DOM 用javascript对象可以很容易地表示DOM节点 通过...
  • vue虚拟dom原理与实现

    2020-08-07 17:27:17
    真实dom 浏览器渲染引擎工作流程: 创建DOM树——创建StyleRules——创建Render树——布局Layout——绘制Painting 详细点就是: 第一步,用HTML分析器,分析HTML元素,构建一颗DOM树(标记化和树构建)。 第二步,用...
  • 什么是Virtual DOM Virtual DOM是对DOM的抽象,本质上是JavaScript对象,这个对象就是更加轻量级的对DOM的描述. 为什么需要Virtual DOM 既然我们已经有了DOM,为什么还需要额外加一层抽象? 首先,我们都知道在前端...
  • Vue-双向绑定以及虚拟dom原理

    千次阅读 2019-01-11 09:36:20
    虚拟Dom(vdom) 参考:Vue原理解析之Virtual Dom, ppt Dom操作是比较浪费时间和性能的,当数据量很大时,更新DOM是非常耗费性能的操作。 当我们使用Javascript来修改我们的页面,浏览器已经做了一些工作,以找到...
  • 创建自己的虚拟DOM以了解它 此仓库服务于本文中所写的工作- ( ) 最好按照该文章提供的步骤来执行回购。 为了便于理解,这些步骤分为多个分支。 这是分支名称: 1-hyperscript-renderer显示如何使用超脚本功能...
  • 虚拟DOM原理

    2021-05-13 17:18:25
    虚拟dom就是用操作对象取代操作真实dom,把真实的dom放在内存中,在内存中的对象里做模拟操作, 然后生成一颗dom树和真实dom树进行对比,结合diff算法,之渲染不同的地方 补充: diff算法核心: ①如何用vnode...
  • 虚拟 DOM 实现原理

    2019-11-29 10:29:48
    虚拟DOM的思路是:(前提关键是:在虚拟DOM树上的操作不会渲染到视图) 一、将DOM树转换成JS对象树,产生第一个虚拟DOM树(与真实DOM树一样) 二、数据发生变化时(当你有增删操作)产生第二个虚拟...
  • 虚拟DOM实现原理

    2021-06-08 08:11:01
    虚拟DOM实现原理 什么是虚拟DOM Virtual Dom(虚拟DOM)是由普通的js对象来描述DOM对象,因为不是真实的DOM对象,因此被称为虚拟DOM 为什么使用虚拟DOM DOM对象成员多,创建dom对象成本高 手动操作dom比较麻烦,要...
  • Vue的核心是双向绑定和虚拟DOM, vdom因为是纯粹的JS对象,所以操作它会很高效,但是vdom的变更最终会转换成DOM操作,为了实现高效的DOM操作,一套高效的虚拟DOM diff算法显得很有必要。
  • 经常看到讲解Vue2的虚拟Dom diff原理的,但很多都是在原代码的基础上添加些注释等等,这里从0行代码开始实现一个Vue2的虚拟DOM 实现VNode src/core/vdom/Vnode.js export class VNode{ constructor ( tag, //...
  • 二,虚拟DOM原理 虚拟dom是为了解决DOM操作过多而导致性能低的问题,直接操作dom,改一次渲染一次,而虚拟dom是用js模拟一棵dom树,每次修改dom,先把虚拟dom修改,最后再一次性把虚拟dom更新,提高性能 虚...
  • 一、什么是DOMDOM的作用? 文档对象模型(Document Object model,简称DOM)是针对HTML和XML文档的一个API(应用程序编程接口)。 也就是说:DOM是一个接口,一个API,我们可以通过DOM这个接口来对文档进行修改。...
  • vue虚拟dom原理剖析

    2018-12-28 00:24:09
    在vue2.0渲染层做了...了解 snabbdom的原理之后再回过头来看 vue的虚拟dom结构的实现。就难度不大了! 于是,这里将自己写的 snabbdom 源码解析的一系列文章做一个汇总。 snabbdom源码解析(一) 准备工作 snabb...
  • 虚拟dom原理

    千次阅读 2018-04-03 15:30:38
    用js对象构造一个虚拟dom树,插入到文档中 状态变更时,记录新树和旧树的差异 把上面的差异构建到真正的dom中 1.用js对象构建一个dom树 一个dom节点包含节点类型(tagName),所有属性(props,是一个对象),...
  • Virtual DOM 的实现原理 ...通过源码解析更好的了解虚拟DOM的工作原理 什么是Virtual DOM Virtual DOM(虚拟DOM),是由普通的 JS 对象来描述DOM对象,因为不是真实的DOM对象,所以叫 Virtual DOM。
  • vue虚拟dom原理

    2018-03-26 11:31:00
    Virual DOM是用JS对象记录一个dom节点的副本,当dom发生更改时候,先用虚拟dom进行diff,算出最小差异,然后再修改真实dom。 vue的virtual dom的diff算法是基于snabbdom算法改造而来,与react的diff算法一样仅在...
  • vue 虚拟dom的实现原理

    千次阅读 2020-03-23 20:23:50
    vue 虚拟dom实现原理前言一、真实DOM和其解析流程二、Virtual DOM 作用是什么?三、虚拟DOM实现 前言 Vue.js 2.0引入Virtual DOM,比Vue.js 1.0的初始渲染速度提升了2-4倍,并大大降低了内存消耗。那么,什么是...
  • vue 虚拟DOM原理

    2020-10-14 17:29:51
    主要介绍了vue 虚拟DOM原理,帮助大家更好的理解和学习vue,感兴趣的朋友可以了解下
  • React虚拟DOM机制 虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象 状态变更时,记录新树和旧树的差异 最后把差异更新到真正的dom中 React引入了虚拟DOM(Virtual DOM)的机制:在浏览器端用Javascript实现了...
  • vue 虚拟dom实现原理

    万次阅读 多人点赞 2017-12-14 10:03:33
    相比于频繁的手动去操作dom而带来性能问题,vdom很好的将dom做了一层映射关系,进而将在我们本需要直接进行dom的一系列操作,映射到了操作vdom,而vdom上定义了关于真实dom的一些关键的信息,vdom完全是
  • 虚拟DOM与DOM Diff 的原理 虚拟DOM DOM 操作慢是对比于JS原生API,如数组操作,任何基于DOM的库(VUE/React)都不可能在操作DOM时比DOM快。 他是什么? 一个能代表DOM树的对象,同窗含有标签名、标签上的属性、事件...
  • 1.虚拟DOM中key的作用: key是虚拟DOM对象的标识,key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。 当状态中的数据发生变化时,react会根据【新数据】...
  • 虚拟DOM JavaScript对象描述真实DOM 真实DOM的属性很多,创建DOM节点开销很大 虚拟DOM只是普通JavaScript对象,描述属性并不需要很多,创建开销很小 手动操作DOM很难跟踪以前的DOM状态,而虚拟DOM可以跟踪上一次...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 23,727
精华内容 9,490
关键字:

虚拟dom原理