跳转到主要内容
Chinese, Simplified

这是前端面试中必然会问到的问题

前端开发多年来一直在不断改进。从简单的静态页面到现在复杂的单页面应用程序,我们的工具变得越来越强大。现在,三大前端框架统治着前端开发,那么你知道这三个框架的区别吗?为什么一直保持着三足鼎立的局面,而不是某种框架来统一其他人?让我们在本文中讨论这些问题。


前端开发的演变


PHP && JSP


早些年,网页的动态内容是在服务器端渲染的,主要使用PHP、JSP等技术。
此时,服务器通过模板引擎填充数据,然后生成HTML,并将HTML返回给浏览器进行渲染。

<!DOCTYPE html>
<html>
<body><h1>My first PHP page</h1><?php
echo "Hello World!";
?></body>
</html>

AJAX


后来有了 AJAX 技术,JavaScript 可以直接在浏览器中发送异步 HTTP 请求,动态地从服务器获取数据,而不是把所有的数据都放在 HTML 中。


<body>
  <h2>Hello world</h2>
  <div id="demo"></div>
  <script>
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function () {
      if (this.readyState == 4 && this.status == 200) {
        // Typical action to be performed when the document is ready:
        document.getElementById("demo").innerHTML = xhttp.responseText;
      }
    };
    xhttp.open("GET", "/api/data", true);
    xhttp.send();
  </script>
</body>


最早的 AJAX 是基于 XML 的,所以现在我们用 JavaScript 发送 HTTP 请求时,使用的函数叫做 XMLHttpRequest。但是XML中有很多不必要的标签,浪费了服务器带宽,所以JSON格式的数据占据了主流位置。


DOM API && jQuery


过去开发网页时,主要是通过浏览器提供的DOM API来操作DOM。
但是 DOM API 比较繁琐,在不同的浏览器中存在兼容性问题。为了简化dom操作和兼容不同的浏览器,jQuery开始流行起来。在那个时候,jQuery可以说是前端开发者必学的技术。


$( "button.continue" ).html( "Next Step..." )


前端框架


然后在开发网页的时候,我们发现一个网页需要做的就是先从服务器获取数据,然后根据数据更新DOM。
而且这个过程是固定的。为了简化 DOM 操作,人们发明了 MVVM 框架来自动将数据更改映射到 DOM 更新,而无需手动操作 DOM。这就是前端框架 Angular、React、Vue 所做的。

import { useState } from 'react'function Counter() {
  
  const [count, setCount] = useState(0)  return (
    <div>
      <p>you clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>increase</button>
      <button onClick={() => setCount(count - 1)}>decrease</button>
  </div>)
}
export default Counter


同时前端框架也支持DOM的逻辑划分。我们可以将 DOM 的一部分封装成组件,将组件相互组合,形成整个应用程序。
这种思路让我们可以将一个复杂的页面拆分成不同的组件,方便我们开发项目。

 


React、Vue、Angular 之间的区别


这些前端框架主要使用以下思想:


UI = f(state)


我们只需要声明一个组件的状态、视图和组件之间的依赖关系,就会自动生成组件的UI。
尽管它们的核心思想相同,但这些框架的实现细节却有所不同。下面我们来分析一下。


React JSX 与 Vue 模板


React 和 Vue 在描述视图层时给出了不同的解决方案。 React 选择扩展 JavaScript 并引入 JSX。而 Vue 创建了一个独立的模板语法。


React JSX:


export default function TodoList() {
  let list = ['JavaScript', 'React', 'Vue']
  return <div>
    {
      list.map(item => <li>{ item }</li>)
    }
  </div>
}


Vue模板:


<ul id="array-rendering">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>


JSX 的优点:

  • 在编写 JSX 时,开发人员可以使用他们现有的 JavaScript 知识,而无需太多额外的学习成本。
  • JSX 可以利用 JavaScript 本身的特性,因此更加强大。
  • JSX 代码和普通的 JavaScript 代码将在同一个执行上下文中执行,因此 JSX 可以很容易地与 TypeScript 结合。 Vue Template 和 JavaScript 执行上下文是分开的,所以在 Vuejs 中引入 TypeScript 比较困难。您需要分别声明 prop、method 和 data 的类型。

当然,Vue Template 也有自己的优势。由于 Vue Template 只能使用有限的语法,因此 Vue 可以更轻松地对其进行静态分析和性能优化,这在 JSX 中是很难做到的。


检查数据更新的不同方法


这三个前端框架都需要观察数据变化来决定是否需要更新 UI,但是他们选择了完全不同的方式来做这件事。
Angular 采用的方式是脏检查。每条可能修改数据的语句执行完毕后,Angular 都会对比前后的数据,判断是否有数据变化。


Vue 直接使用 JavaScript 的原生特性来监控数据变化。 Vue2 使用了 Object.defineProperty ,而 Vue3 使用了 Proxy。


另一方面,React 采取了不同的策略。 React 并不直接监控数据变化,而是在数据和 UI 之间添加了一个虚拟 DOM。每次组件应该更新后都会重新生成一个虚拟 DOM,React 会获取新虚拟 DOM 和旧虚拟 DOM 之间的差异。然后 React 决定是否以及如何更新真实的 DOM。


Vue 和 React 的优化


Vue 的数据监视是在组件级别。当组件内部有很多地方可以看数据变化时,一次更新可能需要大量的计算,这可能会导致丢帧,也就是渲染卡顿。所以Vue的优化方法是将大组件拆分成小组件,这样每个数据不会有太多的watcher。


React 不会观察数据变化,而是渲染整个虚拟 dom,然后进行 diff。所以 React 的优化方法是对于不需要重新渲染的组件,通过 shouldComponentUpdate 跳过渲染。


但是,当应用程序的组件树非常大的时候,仅仅使用 shouldComponentUpdate 来跳过一些组件的渲染,可能仍然是非常耗费计算量的。大量的计算也可能导致渲染冻结。那么我们应该怎么做呢?


树遍历有两种方法:深度优先和广度优先。组件树的渲染是深度优先的,一般通过递归来实现。递归调用不能暂停,可能会导致页面冻结。


但是如果我们用链表来记录访问路径,就可以把树的递归遍历变成数组的循环遍历。循环遍历数组时,可以根据时间片进行分段,这样虚拟dom的生成就不会再阻塞页面渲染了。这与操作系统对多个进程的分时调度非常相似。


将组件树变为链表,将virtual dom的生成由递归变为循环的机制有一个著名的名字:React Fiber。

与之前的组件节点相比,Fiber节点没有parent和children属性,但是有child、sibling和return属性。 React 通过 Fiber 链表树优化渲染性能。
在这里我们可以发现,Vue 的性能优化与 React 有很大的不同:

  • Vue 使用组件级的数据监视解决方案。当一个属性有太多的watcher时,可能会出现性能瓶颈,所以优化思路是把大组件拆分成小组件,保证每个属性不会有太多的watcher。
  • 但是,React 不会监视或检查数据更改。它每次渲染生成virtual dom,然后对比新旧virtual dom。优化思路是使用 shouldComponentUpdate 跳过部分组件的渲染。

重用代码的不同方法


组件之间会有一些共同的逻辑需要重用。 React 和 Vue 有不同的解决方案。


Vue的组件都是option对象的形式,所以很自然的想到通过对象属性来进行mixins进行逻辑复用。 Vue2组件的内部逻辑复用方案确实是mixin,但是mixin很难区分自己的属性和混合属性,也无法判断方法的来源。所以 mixin 的代码很乱,维护也很差。但是没有更好的解决方案。


React 在开始时也支持 mixins,但后来被弃用了。


React 组件有两种形式:类组件和函数式组件。对于类组件,像高阶函数这样的高阶组件(HOC)是重用代码的一种自然方式。具体来说,我们可以使用父组件包装子组件,在父组件中执行一些逻辑,然后渲染子组件。


除了使用 HOC,我们还可以直接将一些 JSX 作为 props 传递给另一个组件进行复用,也就是 render props。


HOC 和 render props 是 React 的类组件支持的两种逻辑复用方案。


原始功能组件是无状态的,仅作为类组件渲染的辅助而存在。


然而,HOC 的逻辑复用方式最终导致了组件的深度嵌套。而且,类的内部生命周期很多,把不同的逻辑放在一起会使组件更加复杂。


如何解决类组件的深度嵌套?并且解决方案不能进行重大更新。


所以 React 团队看了一下功能组件。他们希望在功能组件中扩展一些 API 以支持状态。


如果一个功能组件要支持状态,那么状态应该存储在哪里?


类组件本身是有状态的,成为纤节点后还是有状态的。功能组件一开始没有状态,成为光纤节点后也没有。
这样想,给功能组件的光纤节点添加状态还不够吗?


所以 React 将 memorizedState 属性添加到功能组件的一个 Fiber 节点中来存储数据,然后开发者可以通过 API 使用功能组件中的数据。这些 API 被称为 React Hooks。因为数据是在光纤节点上使用的,所以 API 被命名为 useXxx。


结论


三个前端框架各有优缺点。简单地比较谁更好是没有意义的。我们需要做的是为一个应用场景选择一个合适的解决方案。
技术在不断变化,但最终目的是提高工作效率,降低开发成本,保证质量。

原文:https://betterprogramming.pub/from-jquery-to-react-vue-angular-the-evol…

本文:https://jiagoushi.pro/node/2022

Tags
 
Article
知识星球
 
微信公众号
 
视频号