深入理解Vue.js轻量高效的前端组件化方案


Posted in Javascript onDecember 10, 2018

Vue.js通过简洁的API提供高效的数据绑定和灵活的组件系统。在前端纷繁复杂的生态中,Vue.js有幸受到一定程度的关注,目前在GitHub上已经有5000+的star。本文将从各方面对Vue.js做一个深入的介绍。

Vue.js 是我在2014年2月开源的一个前端开发库,通过简洁的 API 提供高效的数据绑定和灵活的组件系统。在前端纷繁复杂的生态中,Vue.js有幸受到一定程度的关注,目前在 GitHub上已经有5000+的star。本文将从各方面对Vue.js做一个深入的介绍。

开发初衷

2013年末,我还在Google Creative Lab工作。当时在项目中使用了一段时间的Angular,在感叹数据绑定带来生产力提升的同时,我也感到Angular的API设计过于繁琐,使得学习曲线颇为陡峭。出于对Angular数据绑定原理的好奇,我开始 “造轮子”,自己实现了一个非常粗糙的、基于依赖收集的数据绑定库。这就是Vue.js的前身。同时在实际开发中,我发现用户界面完全可以用嵌套的组件树来描述,而一个组件恰恰可以对应MVVM中的ViewModel。于是我决定将我的数据绑定实验改进成一个真正的开源项目,其核心思想便是 “数据驱动的组件系统”。

MVVM 数据绑定

MVVM的本质是通过数据绑定链接View和Model,让数据的变化自动映射为视图的更新。Vue.js在数据绑定的API设计上借鉴了Angular的指令机制:用户可以通过具有特殊前缀的HTML 属性来实现数据绑定,也可以使用常见的花括号模板插值,或是在表单元素上使用双向绑定:

<!-- 指令 -->
<span v-text="msg"></span>
<!-- 插值 -->
<span>{{msg}}</span>
<!-- 双向绑定 -->
<input v-model="msg">

插值本质上也是指令,只是为了方便模板的书写。在模板的编译过程中,Vue.js会为每一处需要动态更新的DOM节点创建一个指令对象。每当一个指令对象观测的数据变化时,它便会对所绑定的目标节点执行相应的DOM操作。基于指令的数据绑定使得具体的DOM操作都被合理地封装在指令定义中,业务代码只需要涉及模板和对数据状态的操作即可,这使得应用的开发效率和可维护性都大大提升。

深入理解Vue.js轻量高效的前端组件化方案

与Angular不同的是,Vue.js的API里并没有繁杂的module、controller、scope、factory、service等概念,一切都是以“ViewModel 实例”为基本单位:

<!-- 模板 -->
<div id="app">
 {{msg}}
</div>
// 原生对象即数据
var data = {
 msg: 'hello!'
}
// 创建一个 ViewModel 实例
var vm = new Vue({
 // 选择目标元素
 el: '#app',
 // 提供初始数据
 data: data
})

渲染结果:

<div id="app">
 hello!
</div>

在渲染的同时,Vue.js也已经完成了数据的动态绑定:此时如果改动data.msg的值,DOM将自动更新。是不是非常简单易懂呢?除此之外,Vue.js对自定义指令、过滤器的API也做了大幅的简化,如果你有Angular的开发经验,上手会非常迅速。

数据观测的实现

Vue.js的数据观测实现原理和Angular有着本质的不同。了解Angular的读者可能知道,Angular的数据观测采用的是脏检查(dirty checking)机制。每一个指令都会有一个对应的用来观测数据的对象,叫做watcher;一个作用域中会有很多个watcher。每当界面需要更新时,Angular会遍历当前作用域里的所有watcher,对它们一一求值,然后和之前保存的旧值进行比较。如果求值的结果变化了,就触发对应的更新,这个过程叫做digest cycle。脏检查有两个问题:
1.任何数据变动都意味着当前作用域的每一个watcher需要被重新求值,因此当watcher的数量庞大时,应用的性能就不可避免地受到影响,并且很难优化。
2.当数据变动时,框架并不能主动侦测到变化的发生,需要手动触发digest cycle才能触发相应的DOM 更新。Angular通过在DOM事件处理函数中自动触发digest cycle部分规避了这个问题,但还是有很多情况需要用户手动进行触发。

Vue.js采用的则是基于依赖收集的观测机制。从原理上来说,和老牌MVVM框架Knockout是一样的。依赖收集的基本原理是:
1.将原生的数据改造成 “可观察对象”。一个可观察对象可以被取值,也可以被赋值。
2.在watcher的求值过程中,每一个被取值的可观察对象都会将当前的watcher注册为自己的一个订阅者,并成为当前watcher的一个依赖。
3.当一个被依赖的可观察对象被赋值时,它会通知所有订阅自己的watcher重新求值,并触发相应的更新。
4.依赖收集的优点在于可以精确、主动地追踪数据的变化,不存在上述提到的脏检查的两个问题。但传统的依赖收集实现,比如Knockout,通常需要包裹原生数据来制造可观察对象,在取值和赋值时需要采用函数调用的形式,在进行数据操作时写法繁琐,不够直观;同时,对复杂嵌套结构的对象支持也不理想。

Vue.js利用了ES5的Object.defineProperty方法,直接将原生数据对象的属性改造为getter和setter,在这两个函数内部实现依赖的收集和触发,而且完美支持嵌套的对象结构。对于数组,则通过包裹数组的可变方法(比如push)来监听数组的变化。这使得操作Vue.js的数据和操作原生对象几乎没有差别[注:在添加/删除属性,或是修改数组特定位置元素时,需要调用特定的函数,如obj.$add(key, value)才能触发更新。这是受ES5的语言特性所限。],数据操作的逻辑更为清晰流畅,和第三方数据同步方案的整合也更为方便。

深入理解Vue.js轻量高效的前端组件化方案

组件系统

在大型的应用中,为了分工、复用和可维护性,我们不可避免地需要将应用抽象为多个相对独立的模块。在较为传统的开发模式中,我们只有在考虑复用时才会将某一部分做成组件;但实际上,应用类 UI 完全可以看作是全部由组件树构成的:

深入理解Vue.js轻量高效的前端组件化方案

因此,在Vue.js的设计中将组件作为一个核心概念。可以说,每一个Vue.js应用都是围绕着组件来开发的。

注册一个Vue.js组件十分简单:

Vue.component('my-component', {
 // 模板
 template: '<div>{{msg}} {{privateMsg}}</div>',
 // 接受参数
 props: {
  msg: String<br> 

 },
 // 私有数据,需要在函数中返回以避免多个实例共享一个对象
 data: function () {
  return {
   privateMsg: 'component!'
  }
 }
})

注册之后即可在父组件模板中以自定义元素的形式调用一个子组件:

<my-component msg="hello"></my-component>

渲染结果:

<div>hello component!</div>

Vue.js的组件可以理解为预先定义好了行为的ViewModel类。一个组件可以预定义很多选项,但最核心的是以下几个:

•模板(template):模板声明了数据和最终展现给用户的DOM之间的映射关系。
•初始数据(data):一个组件的初始数据状态。对于可复用的组件来说,这通常是私有的状态。
•接受的外部参数(props):组件之间通过参数来进行数据的传递和共享。参数默认是单向绑定(由上至下),但也可以显式地声明为双向绑定。
•方法(methods):对数据的改动操作一般都在组件的方法内进行。可以通过v-on指令将用户输入事件和组件方法进行绑定。
•生命周期钩子函数(lifecycle hooks):一个组件会触发多个生命周期钩子函数,比如created,attached,destroyed等等。在这些钩子函数中,我们可以封装一些自定义的逻辑。和传统的MVC相比,可以理解为 Controller的逻辑被分散到了这些钩子函数中。
•私有资源(assets):Vue.js当中将用户自定义的指令、过滤器、组件等统称为资源。由于全局注册资源容易导致命名冲突,一个组件可以声明自己的私有资源。私有资源只有该组件和它的子组件可以调用。

除此之外,同一颗组件树之内的组件之间还可以通过内建的事件API来进行通信。Vue.js提供了完善的定义、复用和嵌套组件的API,让开发者可以像搭积木一样用组件拼出整个应用的界面。这个思路的可行性在Facebook开源的React当中也得到了印证。

基于构建工具的单文件组件格式

Vue.js的核心库只提供基本的API,本身在如何组织应用的文件结构上并不做太多约束。但在构建大型应用时,推荐使用Webpack+vue-loader这个组合以使针对组件的开发更高效。

Webpack是由Tobias Koppers开发的一个开源前端模块构建工具。它的基本功能是将以模块格式书写的多个JavaScript文件打包成一个文件,同时支持CommonJS和AMD格式。但让它与众不同的是,它提供了强大的loader API来定义对不同文件格式的预处理逻辑,从而让我们可以将CSS、模板,甚至是自定义的文件格式当做JavaScript模块来使用。Webpack 基于loader还可以实现大量高级功能,比如自动分块打包并按需加载、对图片资源引用的自动定位、根据图片大小决定是否用base64内联、开发时的模块热替换等等,可以说是目前前端构建领域最有竞争力的解决方案之一。

我在Webpack的loader API基础上开发了vue-loader插件,从而让我们可以用这样的单文件格式 (*.vue) 来书写Vue组件:

<style>
.my-component h2 {
 color: red;
}
</style>
<template>
 <div class="my-component">
 <h2>Hello from {{msg}}</h2>
 <other-component></other-component>
 </div>
</template>
<script>
// 遵循 CommonJS 模块格式
var otherComponent = require('./other-component')
// 导出组件定义
module.exports = {
 data: function () {
 return {
  msg: 'vue-loader'
 }
 },
 components: {
 'other-component': otherComponent
 }
}
</script>

 同时,还可以在*.vue文件中使用其他预处理器,只需要安装对应的Webpack loader即可:

<style lang="stylus">
.my-component h2
 color red
</style>
<template lang="jade">
div.my-component
 h2 Hello from {{msg}}
</template>
<script lang="babel">
// 利用 Babel 编译 ES2015
export default {
 data () {
 return {
  msg: 'Hello from Babel!'
 }
 }
}
</script>

这样的组件格式,把一个组件的模板、样式、逻辑三要素整合在同一个文件中,即方便开发,也方便复用和维护。另外,Vue.js本身支持对组件的异步加载,配合Webpack的分块打包功能,可以极其轻松地实现组件的异步按需加载。

其他特性

Vue.js还有几个值得一提的特性:

1.异步批量DOM更新:当大量数据变动时,所有受到影响的watcher会被推送到一个队列中,并且每个watcher只会推进队列一次。这个队列会在进程的下一个 “tick” 异步执行。这个机制可以避免同一个数据多次变动产生的多余DOM操作,也可以保证所有的DOM写操作在一起执行,避免DOM读写切换可能导致的layout。
2.动画系统:Vue.js提供了简单却强大的动画系统,当一个元素的可见性变化时,用户不仅可以很简单地定义对应的CSS Transition或Animation效果,还可以利用丰富的JavaScript钩子函数进行更底层的动画处理。
3.可扩展性:除了自定义指令、过滤器和组件,Vue.js还提供了灵活的mixin机制,让用户可以在多个组件中复用共同的特性。

与Web Components的异同

对Web Components有了解的读者看到这里可能会产生疑问:Vue.js的组件和Web Components的区别在哪里呢?这里简要地做一下分析。

Web Components是一套底层规范,本身并不带有数据绑定、动画系统等上层功能,因此更合适的比较对象可能是Polymer。Polymer在API和功能上和Vue.js比较相似,但它对Web Components的硬性依赖使得它在浏览器支持方面有一定的问题——在不支持Web Components规范的浏览器中,需要加载庞大的polyfill,不仅在性能上会有影响,并且有些功能,比如ShadowDOM,polyfill并没有办法完美支持。同时,Web Components规范本身尚未定稿,一些具体设计上仍存在不小的分歧。相比之下,Vue.js在支持的浏览器中(IE9+)没有任何依赖。

除此之外,在支持Web Components的环境中,我们也可以很简单地利用Web Components底层API将一个Vue.js组件封装在一个真正的自定义元素中,从而实现Vue.js组件和其他框架的无缝整合。

总结

在发布之初,Vue.js原本是着眼于轻量的嵌入式使用场景。在今天,Vue.js也依然适用于这样的场景。由于其轻量(22kb min+gzip)、高性能的特点,对于移动场景也有很好的契合度。更重要的是,设计完备的组件系统和配套的构建工具、插件,使得Vue.js在保留了其简洁API的同时,也已经完全有能力担当起复杂的大型应用的开发。

从诞生起到现在的一年半历程中,Vue.js经历了一次彻底的重构,多次API的设计改进,目前已经趋于稳定,测试覆盖率长期保持在100%,GitHub Bug数量长期保持在个位数,并在世界各地都已经有公司/项目将Vue.js应用到生产环境中。在2015年晚些时候,Vue.js将发布1.0版本,敬请期待。

【参考链接】

Vue.js官方网站:http://vuejs.org

Vue.js GitHub仓库:https://github.com/yyx990803/vue

Webpack官方网站: http://webpack.github.io

vue-loader单页组件示例:https://github.com/vuejs/vue-loader-example

总结

以上所述是小编给大家介绍的Vue.js轻量高效的前端组件化方案,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
检测是否已安装 .NET Framework 3.5的js脚本
Feb 14 Javascript
jQuery 性能优化指南(2)
May 21 Javascript
javascript学习之闭包分析
Dec 02 Javascript
将两个div左右并列显示并实现点击标题切换内容
Oct 22 Javascript
jQuery中prevUntil()方法用法实例
Jan 08 Javascript
Javascript非构造函数的继承
Apr 27 Javascript
JavaScript面对国际化编程时的一些建议
Jun 24 Javascript
浅谈javascript中new操作符的原理
Jun 07 Javascript
利用JQuery实现datatables插件的增加和删除行功能
Jan 06 Javascript
Vue中的混入的使用(vue mixins)
Jun 01 Javascript
AngularJs的UI组件ui-Bootstrap之Tooltip和Popover
Jul 13 Javascript
在vue中嵌入外部网站的实现
Nov 13 Javascript
es6基础学习之解构赋值
Dec 10 #Javascript
vue中将html字符串转换成html后遇到的问题小结
Dec 10 #Javascript
vue的.vue文件是怎么run起来的(vue-loader)
Dec 10 #Javascript
Bootstrap 实现表格样式、表单布局的实例代码
Dec 09 #Javascript
Bootstrap 按钮样式与使用代码详解
Dec 09 #Javascript
Vue源码中要const _toStr = Object.prototype.toString的原因分析
Dec 09 #Javascript
vue form check 表单验证的实现代码
Dec 09 #Javascript
You might like
PHP 将逗号、空格、回车分隔的字符串转换为数组的函数
2012/06/07 PHP
PHP获取用户的浏览器与操作系统信息的代码
2012/09/04 PHP
总结一些PHP中好用但又容易忽略的小知识
2017/06/02 PHP
windows下的WAMP环境搭建图文教程(推荐)
2017/07/27 PHP
PHP7 echo和print语句实例用法
2019/02/15 PHP
Yii2.0框架模型多表关联查询示例
2019/07/18 PHP
Eval and new funciton not the same thing
2012/12/27 Javascript
ExtJS DOM元素操作经验分享
2013/08/28 Javascript
让网页跳转到指定位置的jquery代码非书签
2013/09/06 Javascript
jQuery中last()方法用法实例
2015/01/06 Javascript
JavaScript跨平台的开源框架NativeScript
2015/03/24 Javascript
实现前后端数据交互方法汇总
2015/04/07 Javascript
js实现选中页面文字将其分享到新浪微博
2015/11/05 Javascript
关于Bootstrap弹出框无法调用问题的解决办法
2016/03/10 Javascript
AngularJS控制器继承自另一控制器
2016/05/09 Javascript
纯JS单页面赛车游戏制作代码分享
2017/03/03 Javascript
react中使用css的7中方式(最全总结)
2019/02/11 Javascript
深入了解JavaScript词法作用域
2020/07/29 Javascript
Postman环境变量全局变量使用方法详解
2020/08/13 Javascript
微信小程序基于高德地图API实现天气组件(动态效果)
2020/10/22 Javascript
[42:24]完美世界DOTA2联赛循环赛 LBZS vs DM BO2第一场 11.01
2020/11/02 DOTA
在Python上基于Markov链生成伪随机文本的教程
2015/04/17 Python
Python中random模块用法实例分析
2015/05/19 Python
python微信跳一跳系列之棋子定位颜色识别
2018/02/26 Python
Python中矩阵创建和矩阵运算方法
2018/08/04 Python
Python从文件中读取指定的行以及在文件指定位置写入
2019/09/06 Python
pytorch点乘与叉乘示例讲解
2019/12/27 Python
Lookfantastic瑞典:英国知名美妆购物网站
2018/04/06 全球购物
伦敦香水公司:The London Perfume Company
2019/11/13 全球购物
人事部岗位职责范本
2014/03/05 职场文书
农村婚庆司仪主持词
2014/03/15 职场文书
奥巴马开学演讲稿
2014/05/15 职场文书
2016情人节宣传语
2015/07/14 职场文书
创业计划书详解
2019/07/19 职场文书
python实现求纯色彩图像的边框
2021/04/08 Python
MySQL日期时间函数知识汇总
2022/03/17 MySQL