Vue的前世今生

本文介绍了Vue的发展历程,从Vue 1的响应式原理,包括walk函数和defineReactive的实现,到Vue 1存在的问题及Vue 2、3的优化,如VDOM、Composition API和AOT编译。Vue3通过Proxy实现响应式状态,降低了首屏渲染时间。还探讨了Vue与React的比较,强调了Composition API在代码组织上的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写作不易,未经作者允许禁止以任何形式转载!
如果觉得文章不错,欢迎关注、点赞和分享!
原文链接:Vue的前世今生

Vue的前世今生

  • 2013 尤雨溪个人项目
  • 2014.2 0.1版本发布
  • 2015.10 1.0版本发布
    • 模板语法改进
  • 2016.9 2.0版本发布
    • 跨端
    • 新的渲染机制
  • 2019.10 3.0 alpha发布
    • 性能
    • 架构
    • 按需引入
    • Composition API
    • Proxy observer
    • AOT优化

Vue 1 响应式原理

构建响应式对象流程

  • walk函数遍历data对象中的属性,调用defineReactive将其变成响应式对象
    • 对于对象属性进行递归调用walk,以保证data整个对象树中的属性都是响应式对象。
  • defineReactive中使用watchers数组储存watcher,使用Object.defineProperty的get函数收集watcher和返回值,set函数用来设置值和对watchers中的watcher进行视图更新。

Walk函数实现

function walk(data){

    Object.keys(data).foreach(key => {
        defineReactive(data, key, data[key])
        //对象递归调用walk
        walk(data[key])
    })
}

defineReactive函数实现

function defineReactive(obj, key, value){
    let oldValue = value;
    const watchers = []
    Object.defineProperty(obj, key, {
        get(){
            //收集watcher
            watchers.push(currentWatcher)
            return oldValue
        },

        set(){
            if(newValue === oldValue) return;
            oldValue = newValue;
            watchers.forEach(watcher => wathcer.update())//更新视图
        }
    })

}

看了这么久Watcher到底是什么?

  • Watcher用于获取数据和更新视图,并实现vue指令
    • watcher从data中get数据render视图,同时data中的响应式对象劫持当前watcher并“储存”起来
    • data更新数据会触发响应式对象的set函数,把get数据时“储存”的watchers取出遍历,“通知”其更新视图。
    • watcher“接到data中的数据更新通知”,重新render视图。
    • 视图发生变化会触发data的中响应式对象的set函数,循环形成数据流。
  • 例:
// vm指向当前组件,el指向当前dom节点,第三个参数为标签类型,第四个为回调函数
// currentWatcher为全局变量指针


// 普通渲染的watcher
Watcher(vm, el, 'text', () =>{
    // 将currentWatcher对象指向当前watcher(vdom节点)供响应式对象的get函数获取
    currentWatcher = this;
    // 读取显示的内容
    el.textContext = eval('vm.data.text')
    // 解绑currentWatcher,防止发生错误。
    currentWatcher = null
})


//带v-if指令的watcher
Watcher(vm, el, 'text', () =>{
    // 将currentWatcher对象指向当前watcher(vdom节点)供响应式对象的get函数获取
    currentWatcher = this;
    // 实现v-if指令,通过判断变量值决定是否显示该元素,v-show原理类似
    el.style.display = eval('Boolean(vm.data.text)') ? 'block' : 'none'
    // 解绑currentWatcher,防止发生错误。
    currentWatcher = null
})

Vue 1 中存在的几个明显问题

  1. 启动时拦截所有组件的状态,进行递归响应式代理影响首次渲染速度
  2. 内存占用率高,一个“指令”,“computed计算属性”,“handlebar表达式”等等均需要创建一个watcher,watcher数量过多导致内存占用率高。
  3. 模板经过编译后直接操作dom,无法跨端。

Vue中的优化

  1. 新的渲染引擎 - vdom
  2. Watcher依赖力度调整
  3. 其他
    1. API、语法糖重新设计与定义
    2. 生命周期调整
    3. 双向数据流 -> 单向数据流
    4. 支持了jsx语法
    5. 等等…

新的渲染引擎 - vdom

//template
<template>
    <div v-if="text">
        {{text}}
    </div>
</template>

// vue-loader 编译后的 compile render
// h函数用于生成Vdom节点,第一个参数为当前组件,第二个参数为属性,第三个属性为子节点
render(){
 return this.text
     ? h(
         'div',
         null,
         h(this.text, null,[])
     )
     : vm.createEmptyVNode()
}

Watcher依赖力度调整

watcher不再与单个dom节点、指令关联,一个component对应一个watcher,极大减少了vue 1 中watcher数量过多导致的内存问题。同时以来vdom diff在渲染时能以最小的代价来更新dom。

Watch(compoent, vm, 'text', () =>{
    const newVnode = component.render()
    const oldVnode = component.render()
    //通过diff算法返回新旧节点的差异
    const patches = vm.diff(newVnode, oldVnode)
    // 通过patch函数对该组件应用差异
    vm.patch(component, patches);
})

vdom带来的优势

  1. 框架屏蔽具体渲染细节,抽象了渲染层,组建的抽象能力得以提升,不再依赖浏览器运行,进而可以跨段,如SSR、同构渲染一姐小程序、weex、uni-app等框架。
  2. 通过静态分析进行更多的AOT(Ahead Of Time)编译优化。
  3. 附加能力:大量组件更新时以最小的代价去更新dom。
  4. vdom对比直接操作dom要慢,大部分情况下效率比vue 1 差,虽然牺牲了一点性能,但是使得vue获得更多特性及优化空间。

AOT编译优化

Cache static element

  • 缓存静态节点、属性,避免重复创建
// 编译前
<div>
    <span class="foo">
        Static
    </span>
    <span>
        {{dynmic}}
    </span>
</div>

// 编译后
const __static1=h('span',{
    class:'foo'
}, 'static')

render(){
    return h('div', [
        __static1,
        h('span', this.dynamic)
    ])
}

Component fast path

  • 编译后 直接判断是组件、原生标签还是文本节点,避免不必要的分支判断,提升性能。
  • 提高vdom diff时的效率
Vue2优化前
  • 每次都要调用h函数去做分支判断
// 编译前
<Comp></Comp>
<div></div>
<span></span>


// Vue2
render(){
    return createFragment([
        h(Comp, null, null),
        h('div', null, [
            h('span', null, null)
        ])
    ])
}

function h(type, attrs, children){
    if(isComponent(type)){
        //创建component vnode
        return createComponentVNode(type, attrs, children)
    }
    if(isDomElement(type)){
        //创建原生dom vnode
        return createElementVNode(type, attrs, children)
    }
    //创建纯string节点
    return createStringVNode(type, attrs, children)
}
Vue3优化后
  • 编译后直接调用不同的createVNode方法
// Vue3
render(){
    return createFragment([
        createComponentVNode(Comp, null, null),
        createElmentVNode('div',null, [
           createElmentVNode('span', null, null)
        ])
    ])
} 

SSR optimize

  • SSR时采用字符串拼接,不创建vnode。
//编译前
<template>
    <div>
        <p class="foo">
            {{msg}}
        </p>
        <comp/
    </div>
</template>

//编译后
render(){
    return h('div', [
        this.ssrString(
            `<p class="foo">`
            + this.msg
            + '</p>'
        ),
        h(comp)
    ])
}

Inline handler

  • 缓存dom上的event handler,避免重复创建。
// 编译前
<div @click="count++"></div>



// 编译后
import {getBoundMethod} from 'vue'

function __fn1(){
    this.count++
}

render(){
    return h('div',{
        onClick:getBoundMethod(__fn1,this)
    })
}

Vue3变更

Proxy Reactive State

  • Vue3改用Proxy去生成响应式对象
  • Vue1/2中遍历和递归所有data中的属性去生成响应式对象
  • Vue3中改为仅在get获取这个属性的时候才去生成响应式对象,延迟了响应式对象生成,加快了首屏渲染速度。
// Vue1/2中的做法
function walk(data){
    Object.keys(data).foreach(key => {
        defineReactive(data, key, data[key])
        //对象递归调用walk
        walk(data[key])
    })
}



// Vue3中的做法
function reactive(target){
    let observerd = new Proxy(target, {
        get(target, key, receiver){
            let result = Reflect.get(target, key, receiver)
             //只有在取对象子属性的时候才递归
            reactive(result)
            return result
        },
        set(target, key, value, receiver) {
            let oldValue = target[key]
            if (value == oldValue)
                return
            let result = Reflect.set(target, key, value, receiver)
            return result;
        }
    })
    return observerd
}

Composition API

  • Vue2中,代码根据数据、方法、计算属性等进行分块,导致可能同一个业务功能的代码需要反复上下地跳着去看。
    • 虽然有Mixin,但业务和业务之间的关系,包括命名空间都会出现一定问题。
  • Vue3中引入Composition API使得开发者可以根据业务将代码分块,按需引入响应式对象、watch、生命周期钩子等各种属性,使用方法类似React Hooks,使得开发者更灵活地开发。

Vue2

export default {   
    data() {     
        return {       
            counter: 0     
        }   
   },   
   watch: {     
       counter(newValue, oldValue) {       
           console.log('The new counter value is: ' + this.counter)     
       }   
   } 
}

Vue3

import { ref, watch } from 'vue' 


const counter = ref(0) 
watch(counter, (newValue, oldValue) => {   
    console.log('The new counter value is: ' + counter.value) 
})

Why use Composition API

  • mixin、hoc、composition api都是为了解决代码复用的问题。但是mixin、hoc过于灵活没有规范,导致开发人员容易写出零散、难以维护的逻辑。
  • Compostion API规避了mixin、hoc存在的缺陷,提供固定的编程模式->函数组合,对各模块解耦使得更优雅、更容易地去组合复用。
  • 以组件状态为例,传统写法所有state都在一个component,杂糅在一起,语义化不强,compostion api使得state按照不同的逻辑分离出来,抽象出状态层组件。
const Foo = {
    template:'#modal',
    mixins:[Mixin1, Mixin2],
    methods:{
        click(){
            this.sendLog()
        }
    },
    components:{
        appChild:Child
    }
}
  • 看完以上代码会发现以下问题
    • sendLog到底来自哪个Mixin
    • mixin1,mixin2之间有没有逻辑关系
    • mixin1,mixin2如果都注入了sendLog使用哪个
    • 如果使用hoc的方式,hoc增加了两个组件实例消耗,多了两次diff。
    • 再来多几个mixin,这个组件更难维护。
  • 明显地体现了Composition API的好处

Time Slicing

  • Vue3最开始实现了这个特性,不过后面移除了
  • 原因总结为以下两条
    • 基于响应式原理及AOT编译优化,相比react而言vue vdom diff具有很高的效率
    • Time Slicing只在一些极端情况下有明显作用,引入会降低vdom diff效率,阻塞UI渲染,收益不大。

按需引入、支持treeshaking

  • Vue各模块(响应式、SSR、runtime等)的解耦,可按需引入。

Vue vs React

相同点

  1. 基于MVVM思想:响应式数据驱动试图更新
  2. 提供组件化的解决方案
  3. 跨端:基于vdom的渲染引擎

核心差异

  1. 定位
    1. React是一个Library,只专注于state到view的映射,状态、路由、动画等解决方案均来自于社区。
    2. Vue是一个渐进式Framework,设计之初考虑开发者可能面临的问题,官方提供路由、状态管理、动画、插件等比较齐全的解决方案,不强制使用,譬如模块机制、依赖注入,可以通过插件机制很好和社区方案集成。
    3. Library,职责范围小,开发效率低,需借助外力,但是易于扩展。对维护团队而言,保持版本间兼容成本较低。更容易集中精力专注于核心变更。
    4. Framework,职责范围大,开发效率高,内置一套解决方案,扩展程度低。对维护团队而言,保持版本间兼容成本较高。
  2. 渲染引擎
    1. Vue进行数据拦截/代理,它对侦测数据的变化更准确,改变了多少数据,就触发多少更新多少。
    2. React setState触发局部整体刷新,没有追踪数据变更,做到精确更新,所以提供给开发者shouldComponentUpdate去除一些不必要的更新。
    3. 基于这个响应式设计,间接影响了核心架构的Composition API、React Hooks的实现。
  3. 模板DSL
    1. Vue template语法更接近html,静态表达能力很强,基于声明式的能力,更方便做AOT编译优化。
    2. JSX语法可以认为是JS基础上又增加了对html的支持,本质还是命令式变成。静态表达能力偏弱,导致优化信息不足,无法很好地做静态编译。

原文链接:Vue的前世今生

掘金:前端LeBron

知乎:前端LeBron

  • 持续分享技术博文,关注微信公众号👇🏻

img

### D3.js 的发展历程与演变 D3.js 是一种基于数据驱动文档(Data-Driven Documents)理念构建的强大 JavaScript 库,用于创建复杂的 Web 可视化效果。自首次发布以来,该库经历了多个重要阶段和发展里程碑。 #### 初创与发展初期 最初由 Mike Bostock 创建于 2011 年,D3.js 继承并改进了其前身 Protovis 的设计理念[^1]。Protovis 虽然开创性地引入了许多现代可视化概念,但在灵活性方面存在局限;相比之下,D3.js 更加注重性能优化和支持更广泛的图表类型。 #### 功能增强和技术升级 随着时间推移,D3.js 不断吸收社区反馈并融入新的技术和方法论: - **模块化设计**:为了提高开发效率和维护便利性,在后续版本中实现了更加精细的功能划分,允许开发者按需加载所需部分而非整个框架。 - **SVG 改进**:早期版本主要依赖 SVG 进行绘图操作,后来增加了对 Canvas 和 WebGL 渲染的支持,从而显著提升了大规模数据集下的渲染速度和交互响应能力。 - **动画与过渡效果**:通过内置的时间函数和插值算法,使得复杂的数据变化能够平滑展现给用户,增强了用户体验的真实感和直观度。 #### 社区贡献与生态建设 除了官方团队的努力外,活跃的开源社区也为 D3.js 的成长做出了巨大贡献。众多第三方插件、教程资源以及实际案例分享极大地促进了这项技术的应用普及程度。特别是与其他前端框架如 React 或 Vue 结合使用的实践方案层出不穷,进一步拓宽了适用场景。 ```javascript // 使用 D3.js 创建简单的柱状图示例 const svg = d3.select('svg'); const width = +svg.attr('width'); const height = +svg.attr('height'); const data = [1, 2, 4, 8]; svg.selectAll('rect') .data(data) .enter().append('rect') .attr('x', (d,i) => i * 50) .attr('y', d => height - d * 10) .attr('width', 40) .attr('height', d => d * 10); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值