第一篇 Vue.js程序开发
第1章 Vue.js开发基础
在正式学习Vue.js之前,先来对Web开发做一个简单的介绍,使读者能够拥有宏观的认知。本章的思维导图如下所示。
本章导读
1.1 Web前端开发概述
知识点讲解
随着互联网的快速发展,Web开发及其相关的技术变得日益重要。具体来说,Web开发大体可以分为前端开发、后端开发和算法3类。本书主要聚焦于前端开发,因此在本书的第1章中,将对Web前端开发的整体背景做一个简介。
1.1.1 Web开发简史
从历史发展来看,Web开发技术大致经过了4个阶段。
(1)早期阶段。
1995年以前,可以称为互联网的早期阶段。早期阶段的Web开发,可以认为仅仅是“内容开发”。此时,HTML 已经产生,但页面内容需手动编写。后来逐渐发展出动态生成内容的机制,称为CGI(common gateway interface,通用网关接口),能够在服务器上配合数据库等机制,动态生成HTML页面,然后返回客户端。这个阶段的特点是,开发复杂,功能简单,没有前端与后端的划分。
(2)服务器端模板阶段。
1995—2005年的服务器端模板阶段,以JSP等技术为代表,其特征正如JSP的第3个字母“P”所代表的含义——Page。Web 开发的主要特征是针对每个页面进行开发。前端开发非常简单,将业务逻辑代码直接嵌入HTML中即可,没有明确的前后端的区分,所有工作都由程序开发人员完成,数据、逻辑和用户界面紧密耦合在一起。
在这个阶段,产生了一些相对简单的前端工作,例如设计师把页面设计图交给开发工程师后,需要做一些简单的切图和图像处理工作,但还谈不上“开发”。
(3)服务器端MVC阶段。
2006—2015年前后的服务器端MVC阶段,具有代表性的技术包括Java SSH、ASP.net MVC、Ruby on Rails等各种框架。
在服务器端MVC阶段,出现了各种基于MVC(model-view-controller,模型-视图-控制器)模式的后端框架,每种语言都有一种或多种 MVC 框架。这时正式产生了“前端开发”这个概念,前端开发的主流技术特征是以CSS+DIV进行页面布局,以及一定的交互性功能的开发。后端开发的技术特征是逻辑、模型、视图分离。在这个阶段,前后端都产生了巨大的变化。例如,前端的 CSS、jQuery,后端的Java SSH,以及连接前后端的AJAX等技术都得到了爆发式发展。
此阶段的特点是,业务逻辑分层,开始从服务器端向浏览器端转移,前端层越来越“厚”。
(4)前后端分离阶段。
2012年,开始出现前后端分离,2014—2015年是JavaScript(也称JS)技术“大爆发”的两年,此后全面进入前后端分离阶段。Vue.js最初诞生于2014年。与前端开发相关的技术发展时间线大致如图1.1所示。
图1.1 前端开发相关的技术发展时间线
2016年前后,前后端分离的开发模式逐渐成为主流。从服务器端MVC阶段到前后端分离阶段是一个巨大的变革。具体来说,在实际的开发项目中,前端开发的工作占比越来越大。前端的变革,主要表现为jQuery被Vue.js、React等新的前端框架代替。而后端的变革,则以“API化”为特征,后端聚焦于业务逻辑本身,不再或较少关心UI(user interface,用户界面)表现,关心的内容变为如何通过API(application program interface,应用程序接口)提供数据服务、提高性能、进行自动化的测试、持续部署、开发自运维(DevOp)等。
1.1.2 基于前后端分离模式的Web开发
传统互联网时代之后,“移动互联网”时代对 Web 开发的技术提出了新的要求,这些要求具体有以下一些特征。
1.从提供内容到提供服务转变
移动互联网时代的最本质特征是从内容到服务的转变。具体来说,传统互联网有以下3个特点。
● 使用场景固定且有局限。
● “内容”为主。
● “服务”局限于特定领域。
回顾传统互联网,我们能想到的服务仅有新闻播报、论坛交流、软件下载、即时通信等。而与之相对应的,移动互联网有以下3个特点。
● 使用场景触及社会的每个角落。
● 很多事物被连接到云端。
● 海量“服务”。
在移动互联网时代,用户能够使用的服务大大增加了,出现大量新的服务,如短视频、慕课教育、移动支付、流媒体、直播、社交网络、播客、共享单车等。因此,在技术上有如下3点新的要求。
● 客户端需求复杂化,大量应用流行,对用户体验的期望提高。
● 客户端渲染成为“刚需”。
● 客户端程序不得不具备完整的生命周期、分层架构和技术栈。
2.从“单一网站”到“多终端”
由于移动设备的普及,原来简单的“单一网站”架构逐渐演变为“多终端”形态,包括PC、手机、平板电脑等,从而产生以下几个特点。
● 服务器端通过API输出数据,剥离“视图”。
● Web客户端支持独立开发和部署程序,不再是服务器端Web程序中的“前端”层。
● 每个客户端都倾向于拥有专门为自己量身打造、可被自己掌控的API网站。
因此,在移动互联网时代,终端形态多样化,一个应用往往需要适配以下几种不同的终端形态。
● 桌面应用:传统的Windows应用、macOS应用。
● 移动应用:iOS应用、安卓应用。
● Web:通过浏览器访问的应用。
● 超级App:以微信小程序为代表的超级App,成为新的应用程序平台。
1.1.3 Vue.js与MVVM模式
知识点讲解
前面介绍了Web前端开发的基本背景和发展历程,下面介绍Vue.js的基本背景。
Vue.js诞生于2014年,是一套针对前后端分离开发模式的、用于构建用户界面的渐进式框架。它关注视图层逻辑,采用自底向上、增量开发的设计。Vue.js 的目标是通过尽可能简单的操作实现响应的数据绑定和组合的视图组件。它不仅容易上手,还非常容易与其他库或已有项目进行整合。
作为目前世界上最流行的3个框架之一的Vue.js,具有以下特点。
● 轻量级。相比AngularJS和ReactJS而言,Vue.js更轻量,不但文件体积非常小,而且没有其他的依赖。
● 数据绑定。Vue.js最主要的特点就是双向的数据绑定。在传统的Web开发项目中,将数据在视图中展示出来后,如果需要再次修改视图,需要通过获取DOM的值的方法实现,这样才能维持数据和视图一致。而Vue.js是一个响应式的数据绑定系统,在建立绑定后,DOM将和Vue实例中的数据保持同步,这样就无须手动获取DOM的值再同步到JS中。
● 使用指令。在视图模板中,可以使用“指令”方便地控制响应式数据与模板MOM元素的表现方式。
● 组件化管理。Vue.js提供了非常方便高效的组件管理方式。
● 插件化开发。Vue.js保持轻量级的内核,其核心库与路由、状态管理、AJAX等功能分离,通过加载对应的插件来实现相应的功能。
● 完整的工具链。Vue.js提供了完整的工具链,包括项目脚手架及集成的工程化工具,可以覆盖项目的创建、开发、调试、构建的全流程。
在学习Vue.js 之前,先了解一下MVVM(model-view-viewmodel,模型-视图-视图模型)模式。所有的图形化应用程序,无论是Windows应用程序、手机App,还是用浏览器呈现的Web应用,总体来说,都可以粗略地分为两个部分:内部逻辑和用户界面。
内部逻辑又可以称为“业务逻辑”,通常用于实现应用程序所需要的核心功能;用户界面又可以称为“UI逻辑”,主要用于实现和操作界面相关的逻辑,包括接受用户的输入和向用户展示结果。
例如,“计算器”是我们常见的一个应用程序,无论是手机还是台式计算机上都有这个应用程序。这个应用程序实际上可以分为两个部分,一部分是核心的计算逻辑,用于解决对输入的表达式进行计算的问题,另一部分用于实现接受用户的按键操作及向用户显示运算结果。
在软件开发领域,人们很早就意识到,应该将这两部分分离,这也符合软件工程中著名的“关注点分离”原则。通常图形化应用程序的用户界面称为“视图”(view),用来解决用户的输入输出问题;实现内部功能的核心业务逻辑则需要面对数据进行相应的操作,核心业务逻辑产生的数据对象称为“模型”(model),众多的开发框架所解决的问题则是如何将二者联系起来。
事实上存在多种不同的理念来解决视图与模型的连接问题。不同的理念产生了不同的“模式”。例如MVC模式、MVP(model-view-presenter模型-视图-表达)模式及MVVM模式等,它们都是很常见的模式,并不能简单地说哪个更好。Vue.js是比较典型的基于MVVM模式的前端框架,尽管它没有严格遵循MVVM模式的所有规则。
MVVM模式包括以下3个核心部分。
● model:模型,核心的业务逻辑产生的数据对象,例如从数据库取出,并做特定处理后得到的数据。
● view:视图,即用户界面。
● viewmodel:视图模型,用于连接匹配模型和视图之间的专用模型。
Vue.js的核心思想包括以下两点。
(1)数据的双向绑定,view和model之间不直接沟通,而通过viewmodel这个“桥梁”进行交互。
通过viewmodel这个“桥梁”,可以实现view和model之间的自动双向同步。当用户操作view时, viewmodel会感知到view变化,然后通知model发生同步改变;反之当model发生改变时,viewmodel也能感知到变化,从而使view进行相应更新。MVVM模式示意图如图1.2所示。
图1.2 MVVM模式示意图
(2)使用“声明式”编程的理念。
“声明式”(declarative)是一个程序设计领域的术语,与它相对的是“命令式”(imperative)。
● 命令式编程:倾向于明确地命令计算机去做一件事。
● 声明式编程:倾向于告诉计算机想要的是什么,让计算机自己决定如何去做。
理解声明式编程,可以思考一下Excel软件中的操作。如图1.3所示,在D1单元格中输入数字3,然后在旁边的E1单元格使用公式“=D1+2”,按Enter键后E1单元格的内容就变成了5。这时如把D1单元格中的数值改为6,E1单元格中的数值会随之自动变为8。
图1.3 在Excel表格中通过公式关联两个单元格
请读者思考一下,使用普通的程序设计语言,例如JavaScript、C或Java等,如何执行类似的赋值语句。
1 let D1=3; 2 let E1=D1+2; 3 console.log(E1); 4 let D1=6; 5 console.log(E1);
运行上面的代码,第一次输出的变量E1的值等于D1的值加2,即等于5,然后把D1的值改为6,此时第2次输出的变量E1的值仍然是5,它不会自动跟着D1的变化而变化。因为这里使用“=”运算符仅仅是对一个变量进行赋值,是一个一次性的动作,并没有再次将E1变量和“D1+2”这个式子进行关联。
因此可以看出,在Excel表格中,使用“=”将一个单元格与另一个单元格通过公式关联起来,它们之间就会产生“联动”的效果,这就是“声明式”。本质上这个等号的作用是声明这两个单元格之间的数量关系。
在JavaScript中的赋值操作则是“命令式”的。这个对比可以很好地帮助读者理解命令式编程与声明式编程的区别。
理解这些概念之后,我们来看看 Vue.js 框架和另一个常用的 jQuery 框架之间的区别。Vue.js 和jQuery 都是非常流行的前端框架,而它们遵循的基本理念是完全不同的。Vue.js 遵循“声明式”的理念,jQuery遵循典型的“命令式”的理念。
例如,在一个网页中有一个文本段落元素p:
<p id="demo">这是段落内容……</p>
在使用jQuery的时候,当我们需要改变段落内容时,会使用函数:
jQuery("p#demo").text(content);
jQuery()是一个函数,用于根据选择器获取DOM元素的对象,然后调用它的text()函数,将存放新内容的变量content作为参数传递给text()函数。在这里变量content和这个DOM元素本身没有联系。此后如果content变量的内容发生了变化,这个段落的内容也不会自动修改,必须再次调用函数去修改它。
而使用Vue.js时,需要“声明”该元素的内容与某个变量关联,不需要说明是如何让它们关联的:
<p id="demo">{{content}}</p>
上面代码中,双花括号是一个特殊符号,括起来的内容就是变量的名称,可通过这个语法把DOM元素的内容和content变量关联起来。不用写任何具体的函数来操作DOM元素,它们的值会自动保持同步变化。这样只需要一次声明,以后无论怎么修改变量的值,都不需要再去考虑修改界面元素的问题了。
这样,Vue.js程序的开发就会变得相当简便而有序。简单来说,使用Vue.js做开发,总共有3步:先把核心业务逻辑封装好,再把视图做好,最后通过viewmodel把二者绑定起来。这样开发任务就完成了。
说明
MVVM模式是一个很通行的模式,并不是Vue.js特有的,很多开发框架都是基于MVVM模式的。因此掌握Vue.js的核心原理,以后用任何其他的MVVM模式的框架都会很容易,这也是掌握一个通用框架的好处。
当一个变量被纳入Vue.js管理的模型中后,它就具有了“响应式”的特性,就可以通过声明的方式与视图上的UI元素进行关联,形成联动关系。
注意
在Web前端开发中,常常会在两个地方遇见“响应式”这个术语,但它们的含义是完全不同的。一个是在CSS中常常会制作“响应式页面”,这个“响应式”来自英文responsive,指的是这个页面可以自动地适应不同设备的屏幕。另一个就是这里提到的“响应式”,它的英文是 reactive,其实将其翻译为“交互式”更贴切一些,指的是模型中的数据和视图中的元素实现绑定后,一侧的改变会引发另一侧的改变。
1.1.4 安装Vue.js
使用Vue.js有两种方式:一种是简单地通过<script>标记引入的方式,适用于简单的页面开发;另一种是使用相关的命令行工具进行完整的安装,适用于组件化的项目开发。
注意
建议读者在学习过程中,先用简单的方式通过<script>标记引入Vue.js。掌握了Vue.js的基本使用方法之后,当需要进行组件化的开发时,再开始使用相关的命令行工具。本书也是按照这种方式来组织内容的。前面的内容使用非常简单的方式,读者可以进行无障碍的学习。到了第8章,需要学习多组件开发的时候,再讲解如何使用相关的命令行工具的方法。
安装Vue.js最简单的方法是使用CDN(content delivery network,内容分发服务)引入vue.js文件。目前国内外有不少提供各种前端框架文件的CDN的网站,国内读者可以直接访问国内的服务商提供的vue.js文件的CDN:
bootcdn.cn/vue/2.6.12/
进入这个页面以后可以看到很多链接,这些链接对应不同用途的文件,只需要找到合适的那个就可以了。如图1.4所示,请使用图中方框标记的两个地址中的一个,即可找到合适的vue.js文件。
图1.4 找到合适的vue.js文件
说明
vue.min.js和vue.js本质是一样的,前者是后者经过压缩以后的版本。
在一个HTML文件中,使用<script>标记引入vue.js或者vue.min.js文件后,就可以使用Vue.js提供的功能了。
1 <script> 2 src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"> 3 </script>
如果不希望使用CDN,例如为了防止出现CDN文件不可用的情况,也可以直接把上面的地址提供的文件下载下来,然后将其引入网页。在本书配套资源中也可以找到vue.js文件,读者可以直接使用。
除了上述方法外,还可以使用npm方法安装,其过程在本书后面用到该方法的时候再讲解。
1.1.5 上手实践:第一个Vue.js程序
案例讲解
作为本节的最后一小节,我们来实际动手使用Vue.js制作一个案例。这个案例实现的是一个简单的猜数游戏,在浏览器中打开页面,猜数游戏初始效果如图1.5所示。如果用户输入的数值不正确,会提示猜的数太大了或者太小了,如图1.6所示。当用户输入的数值正确时,会显示用户猜对了,如图1.7所示。
图1.5 猜数游戏初始效果
图1.6 用户输入的数值不正确时给出提示
图1.7 用户输入的数值正确时显示猜对了
先创建一个文件夹,放入下载的vue.js文件,并且在同一个文件夹中创建一个HTML文件,内容如下所示本案例的完整代码可以参考本书配套资源文件“第1章/basic-01.html”。
1 <html>
2 <head>
3 <title>猜数游戏</title>
4 <script src="./vue.js"></script>
5 <style>
6 div#app{
7 width: 250px;
8 margin: 30px auto;
9 border: 1px solid #666;
10 border-radius: 10px;
11 padding:10px;
12 }
13 p{
14 text-align: center;
15 }
16 </style>
17 </head>
18 <body>
19 <div id="app">
20 <p>
21 <input
22 type="text"
23 placeholder="猜数游戏"
24 />
25 </p>
26 <p>请猜一个1~100的整数</p>
27 </div>
28 </body>
29 </html>
可以看到,这是一个普通的HTML文件,如果用浏览器打开,看到的效果和图1.5所示的效果相同,但是它现在还没有和用户交互的能力。注意,我们已经通过<script>标记引入了同目录下的 vue.js文件,如果引入的是vue.min.js文件,则使其与vue.js文件一致即可。
接下来,我们需要修改这个文件的内容,修改后<body>标记部分的代码如下所示。本案例的完整代码可以参考本书配套资源文件“第1章/basic-02.html”。
1 <body> 2 <div id="app"> 3 <p> 4 <input 5 type="text" 6 placeholder="猜数游戏" 7 v-model="guessed"/> 8 </p> 9 <p>{{result}}</p> 10 </div> 11 <script> 12 let vm = new Vue({ 13 el:"#app", 14 data: { 15 guessed: '' 16 }, 17 computed: { 18 result(){ 19 const key = 87; 20 const value = parseInt(this.guessed); 21 22 //如果输入的文字不能转换成整数 23 if(isNaN(value)) 24 return "请猜一个1~100的整数"; 25 26 if(value === key) 27 return "祝贺你,你猜对了" ; 28 29 if(value > key) 30 return "太大了,往小一点猜"; 31 32 return "太小了,往大一点猜"; 33 } 34 } 35 }); 36 </script> 37 </body>
可以看到,文本框input元素设置了一个名为“v-model”的属性,属性值被设置为“guessed”。这个v-model是Vue.js提供的一个指令,其含义就是将文本框的内容与一个数据变量关联起来,也称为“绑定”。
然后在p元素中,把原来固定的内容改为用双花括号包含的另一个数据变量,用于显示提示信息和结果。
接着给<body>标记部分增加一个<script>标记,在里面加入与用户交互的逻辑。可以看到,data部分定义了数据模型,包括一个guessed变量,它与input元素已经绑定,用于记录用户输入的猜测的数值。每一次用户在改变了文本框中的数值时,会执行computed部分的result()函数,里面的逻辑是根据输入的数给出相应的提示。
读者不必深究每一行代码的细节,这里仅供读者体会一下Vue.js的基本原理,后面的章节会逐一详细讲解。
本案例非常简单,被猜的数固定为87。如果希望让这个游戏再有趣一些:由程序自动产生一个随机数作为被猜的数,并且猜中以后可以换一个新的数让用户继续玩这个游戏。可以稍做改进,这里不再讲解,在本书配套资源文件“第1章/basic-03.html”中给出演示,读者可以参考。
1.2 Vue.js开发入门
知识点讲解
从本节开始正式学习Vue.js,首先从最基础的知识开始,了解一下让Vue.js运转起来的基本结构,以及如何在一个简单的页面上实现数据模型和页面元素的绑定。
Web开发的基础是HTML、CSS和JavaScript这3门基本的语言,因此,希望读者正式开始学习之前,确认对这3门语言有基本的掌握,特别是JavaScript。由于历史原因,JavaScript经过了比较复杂的演进过程,目前主流的开发中使用ES6的语法,如果读者不是很熟悉,可以先自行学习一下。
1.2.1 Vue根实例
Vue.js遵循MVVM模式,因此使用Vue.js的核心工作就是创建一个视图模型对象,将它作为视图和业务模型之间的“桥梁”。Vue.js的做法是提供一个Vue类型,开发者通过创建一个Vue类型的实例(简称Vue实例),来实现视图模型的定义。在一个完整的项目中,可能会创建多个Vue实例,形成层次结构,但是通常一个应用中会有唯一的、最上层的Vue实例,这个实例称为“根实例”。具体的语法形式如下所示。
1 let vm = new Vue({ 2 // 选项对象 3 })
可以看到,从 JavaScript 的角度来说,Vue 是一个类型,Vue()是它的构造函数,通过使用 new运算符调用Vue()构造函数会创建一个“实例”,或者叫作“对象”。调用构造函数的时候,传入一个参数,参数是以JavaScript的对象形式传入的。在这个对象中,可以设定很多选项,这些选项指定了这个实例的行为,即指定它与页面如何配合工作。学习使用 Vue.js 的很大一部分内容是学习如何设置这个对象。
1.文本插值
先来看一个简单的例子。在这个例子中,显示一个用户的基本信息,并向他问好。本案例的完整代码可以参考本书配套资源文件“第1章/instance-01.html”。
1 <html> 2 <head> 3 <script src="vue.js"></script> 4 </head> 5 <body> 6 <div id="app"> 7 <p>用户您好!</p> 8 <ul> 9 <li>姓名 : {{name}}</li> 10 <li>城市 : {{city}}</li> 11 </ul> 12 </div> 13 <script> 14 let user = { 15 name : "Chance", 16 city : "Beijing", 17 }; 18 let vm = new Vue({ 19 el: '#app', 20 data: user 21 }) 22 </script> 23 </body> 24 </html>
尽管这个例子非常简单,但是已经充分体现了Vue.js的使用方式。首先需要引入vue.js文件,然后编写HTML部分——像普通网页一样,只是在某些需要动态生成的部分,用双花括号将一些变量括起来,这些变量正好和后面的JavaScript代码中声明的对象一致。
仔细观察<script>标记部分的代码,理解传入Vue()构造函数中的对象(注意要使用new运算才能创建对象),这是我们学习Vue.js的关键。
首先它有一个名为el的属性(el是element的前两个字母,表示元素),其值恰好是我们需要处理的HTML元素的根节点的id,即#app。这样,要执行的任何操作都只会影响这个div元素,而不会影响它之外的任何内容。
接下来,参数对象(也可以称为选项对象)中定义了data属性,它的值是一个名为user的对象,这个对象就是业务模型。它在代码中已经声明好了,有两个属性,分别是name和city,即姓名和城市。可以看到,在HTML结构中,有如下部分。
1 <div id="app"> 2 <p>用户您好!</p> 3 <ul> 4 <li>姓名 : {{name}}</li> 5 <li>城市 : {{city}}</li> 6 </ul> 7 </div>
用双花括号括起来的 name 和 city,正好对应于 data对象的两个属性。渲染页面的时候,Vue.js 会自动地将{{name}}和{{city}}替换为data.name和data.city这两个值。这个过程被称为“文本插值”,即将文本的内容插入页面中。
图1.8 文本插值效果
运行上面的程序,文本插值效果如图1.8所示。
结果正如我们预料的那样,根据 data 属性的值,姓名和城市信息正好替换掉了HTML中相应的用双花括号包含的部分。这个过程也被称为“绑定”,即通过双花括号语法,将页面元素中的文本内容与数据模型的变量进行绑定。
在这个简单的页面中,我们来分别找一找模型、视图与视图模型。
● 插入了一些特殊语法(比如{{}})的HTML页面就是视图。
● 在调用构造函数的参数对象中,有一个data属性,它定义的就是模型。
● 通过Vue()构造函数创建的名为vm的实例就是视图模型。
说明
在参数对象中,data对象包含的数据被称为“响应式”数据。也就是说,在data中定义的数据,会被Vue.js的机制所监控和管理,实现自动地跟踪和更新。在实际的Web开发项目中,data中的数据通常是通过AJAX从服务器端获取来的。例如上面例子的用户信息数据,通常是在用户登录一个网站后,程序从远程的数据库获取到登录用户的信息,然后将其显示到页面上。
在Vue.js中,data属性除了可以像上面那样直接设置为一个对象,也可以设置为一个函数的返回值,代码如下。本案例的完整代码可以参考本书配套资源文件“第1章/instance-02.html”。
1 <script> 2 let user = { 3 name : "Chance", 4 city : "Beijing", 5 }; 6 let vm = new Vue({ 7 el: '#app', 8 data() { 9 return user; 10 } 11 }) 12 </script>
上面代码符合ES6的语法规则。任何一个对象的成员要么是数据成员,要么是函数成员。无论是数据成员还是函数成员,本质上都是键值对,“键”是这个成员的名字,“值”是这个成员的内容。对于函数成员来说,“键”是函数的名字,“值”是函数的地址。因此,只要能够描述一个函数的名字和函数要执行的操作就可以。
在ES6中,一个对象中的函数成员可以有3种描述方法,我们举个简单例子。
1 { 2 functionEs5: function(){ 3 return Math.random(); 4 }, 5 functionEs6(){ 6 return Math.random(); 7 }, 8 functionArrow: () => Math.random() 9 }
上面代码中functionEs5、functionEs6和functionArrow都是语法正确的函数成员。
functionEs5是传统的写法,functionEs6是在ES6中合法的写法,functionArrow是ES6引入的箭头函数的写法。关于箭头函数,特别要注意箭头函数对this指针的特殊处理。
注意
functionEs5和functionEs6写法是完全等价的,可以互换,不会有任何问题。但是functionArrow与它们并不是完全等价的,主要差别在于对this指针的处理方式不同。在后面我们会频繁地遇到这个问题,随着学习的深入,读者会对这个知识点理解得越来越深刻。
请读者务必认真理解上述内容,这看起来很简单,但是遇到一些比较复杂的实际代码时,就容易感到混乱。另外,我们时常会到网上查找一些案例代码来参考,但网上的资源适用的年代不同,写法各异,正确的和错误的常混杂在一起,如果不熟悉各种写法的等价性,很容易感到混乱。
如果用ES5的语法来写,data后面的冒号和function不能省略,代码如下所示。
1 <script> 2 …… 3 let vm = new Vue({ 4 el: '#app', 5 data: function() { 6 return user; 7 } 8 }) 9 </script>
Vue.js的最大优点是能实现页面元素与数据模型的“双向绑定”。在这个例子中我们目前还只能看到单向绑定,即数据模型中的内容传递给了页面元素,反向的传递在后面会介绍。可以想象,如果页面元素是一个文本框,当用户在文本框里输入一些文字时,Vue.js 也会实时地把输入的内容同步传递给模型的对象,这就是所谓的“双向绑定”。
这种绑定机制是如何实现的呢?我们简单探究一下底层的实现方法。通过 Chrome 浏览器的开发者工具,我们可以观察Vue()构造函数产生的对象。在代码中加入一条在浏览器控制台的输出语句,注意输出的是vm.$data对象,这个对象是Vue()构造函数动态创建的,代码如下。本案例的完整代码可以参考本书配套资源文件“第1章/instance-03.html”。
1 <script> 2 let user = { 3 name : "Chance", 4 city : "Beijing", 5 }; 6 let vm = new Vue({ 7 el: '#app', 8 data: user 9 }); 10 console.log(vm.$data); 11 </script>
用Chrome浏览器打开页面,按Ctrl+Shift+I组合键打开开发者工具,选择Console(控制台)标签,可以观察到对应的输出,如图1.9所示。
图1.9 在控制台观察输出的变量
可以看到,vm.$data对象中,除了name和city这两个属性之外,Vue.js还自动地给这两个属性分别生成了相应的存取器(getter和setter)方法,从而拦截了对name和city属性的读写操作,Vue.js会借这个时机把得到的值写到页面中。在图1.9中可以看到存取器方法对应的是 reactiveGetter()和reactiveSetter(),即它们都是响应式的。
说明
类似于$data,以$开头的一些对象都是 Vue.js 动态创建的,在开发过程中可以直接使用。以后我们还会遇到其他的类似形式的对象。
2.方法属性
知识点讲解
传入Vue()构造函数的对象中,除了el和data属性外,还可以包括方法(methods)属性,其中可以定义多个方法(或者叫作函数)。例如我们希望在页面上根据用户的姓名和城市信息显示一句欢迎词,即“欢迎来自某地的某人”,效果如图1.10所示。添加一个sayHello()函数,其作用是将name和city属性插入一个模板字符串中,以得到一个完整的句子。本案例的完整代码可以参考本书配套资源文件“第1章/instance-04.html”。
图1.10 欢迎词显示的效果
1 <div id="app"> 2 <p>{{sayHello()}}</p> 3 <ul> 4 <li>姓名 : {{name}}</li> 5 <li>城市 : {{city}}</li> 6 </ul> 7 </div> 8 <script> 9 let user = { 10 name : "Chance", 11 city : "Beijing", 12 }; 13 let vm = new Vue({ 14 el: '#app', 15 data: user, 16 methods:{ 17 sayHello(){ 18 return ’您好,欢迎来自 ${this.city} 的 ${this.name} !' 19 } 20 } 21 }) 22 </script>
请注意这里构造欢迎词的字符串使用的是一个ES6新增的语法结构,称为“字符串模板”。它以“`”符号为开头和结尾,代替了以字符串为开头和结尾的单引号或者双引号。在这种字符串模板中,可以方便地插入变量,例如下面两条语句分别是ES6和ES5的写法,二者是等价的。但显然ES6的写法更方便且易于理解。
1 //ES6 2 let hello = `欢迎来自 ${this.city} 的 ${this.name} !`; 3 4 //ES5 5 let hello = "欢迎来自 " + this.city + " 的 " + this.name " !";
说明
模板字符串的一个优点是,它可以跨行,直接产生多行文本,而普通的字符串不能跨行,如果要定义多行字符串必须通过多个单行字符串拼接实现。
另外,注意上面在methods的属性对象中,定义sayHello()方法的语法形式也采用ES6的写法,它等价于下面的ES5的传统写法:
1 methods: { 2 sayHello: function() { 3 return ’欢迎来自${this.city}的${this.name}!'; 4 } 5 }
注意
在HTML部分,用双花括号语法将p元素的内容与sayHello()方法绑定,不要忘记写sayHello后面的圆括号,这样在绑定时才会先执行sayHello()方法,把得到的结果显示在p元素中。如果不加圆括号,在页面上显示的则会是这个函数本身的内容,效果如图1.11所示。
图1.11 绑定方法时不加圆括号的效果
下面我们深入理解一下Vue.js的“响应式系统”(reactivity system),它是Vue.js实现很多“魔法”的关键。
当一个Vue实例被创建时,data对象中的所有属性会被自动加入Vue.js的响应式系统中。当这些属性值发生改变时,视图将会产生“响应”,即匹配更新为新的值。用以下简单的代码说明,请仔细理解注释。
1 // 定义一个简单的数据模型 2 var model = { a: 1 } 3 4 // 将该对象加入一个Vue实例中 5 var vm = new Vue({ 6 data: model 7 }) 8 9 // Vue实例中的字段与data中的字段一致 10 vm.a == data.a // => true 11 12 // 修改Vue实例中相应的字段,也会影响到data中的原始数据 13 vm.a = 2 14 console.log(data.a) // => 2 15 16 // 反之,修改data中的字段,也会影响到Vue实例中的数据 17 data.a = 3 18 vm.a // => 3
由上面的说明可知,methods中定义的方法内部,可以使用this指针引用data对象中定义的属性,这个this指针指向的就是Vue()构造函数构建的对象,也就是上面代码中的vm对象,Vue.js的响应式系统会自动地将所有data对象中的属性加入vm对象的属性中。
这样会产生副作用,因为在methods中定义的方法内部,往往不能使用ES6引入箭头函数的方式来写方法。例如前面的sayHello()方法,从语法角度来看,如果用箭头函数的方式,可以写成下面的形式。本案例的完整代码可以参考本书配套资源文件“第1章/instance-05.html”。
1 methods: { 2 //箭头函数不绑定自己的this指针,因此不能这样写 3 sayHello: () => `欢迎来自${this.city}的${this.name}!` 4 }
从语法形式来看上面代码是正确的,但是实际效果却不正确,得到的效果如图1.12所示。
图1.12 使用箭头函数的效果
从图1.12中可以看出,city属性是undefined,而name属性为空字符串。这是因为箭头函数不绑定自己的this指针,而是从父级上下文查找this指针。所以,这里的this实际上就是全局上下文,也就是window对象。因为window对象中没有定义city属性,所以this.city显示为undefined,而window对象定义了自己的name属性,此时它的值是空字符串。因此,这里不能使用箭头函数的写法。读者只有把JavaScript的基础知识掌握扎实,学习使用框架才能事半功倍。
简单总结一下,通过几个小案例可以清晰地看出,使用Vue.js进行开发,本质上就是将一些特定的选项传递给Vue()构造函数,以创建视图模型。到目前为止,我们学习了这个选项对象的以下3种属性。
● el:指定Vue.js动态控制的DOM元素根节点。
● data:指定原始数据模型。
● methods:可以对原始数据模型的值做一些加工变形,然后用于视图中。
视图模型可以对原始数据模型的值做一些加工变形,然后与视图绑定。Vue.js 定义了丰富的数据处理机制,用于加工处理模型数据。除了上面的methods 外,在后面的章节中我们还会详细介绍其他功能强大的机制。
说明
业务模型往往倾向于描述数据的本来样子,例如对于一个用户对象,它的名字和所在城市就是它原始的信息,而网站上打招呼的欢迎词和用户本身并没有直接关系,它是通过用户的名字和城市信息构成的一句话,因此在视图模型中构造这个欢迎词是恰当的,欢迎词不应该混入业务模型中。希望读者能够很好地理解业务模型与视图模型的各自作用和特点。
3.属性绑定
知识点讲解
双花括号语法可以实现 HTML 元素的文本插值,但是如果我们希望绑定的是HTML元素的属性,就不能使用双花括号语法了,这时需要使用属性绑定指令。
例如,仍以上面的用户信息页面为例,我们希望根据用户的性别对页面的样式进行区分,因此在用户数据模型中增加一个性别字段,代码如下所示。本案例的完整代码可以参考本书配套资源文件“第1章/instance-06.html”。
1 let user = { 2 name: "Chance", 3 city: "Beijing", 4 sex: "male" 5 };
然后,在<style>标记部分,增加两种CSS样式类:male类显示浅蓝色背景,用于表示男性用户;female类显示浅红色背景,用于表示女性用户,代码如下所示。
1 <style> 2 .male{ 3 background-color: rgb(175, 203, 245); 4 } 5 .female{ 6 background-color: rgb(248, 213, 241); 7 } 8 </style>
接下来,在HTML中,动态绑定ul元素和class属性,这里要使用v-bind指令,将class属性绑定到上面data对象中新增加的sex字段上,代码如下所示。
1 <ul v-bind:class="sex"> 2 <li>姓名 : {{name}}</li> 3 <li>城市 : {{city}}</li> 4 </ul>
这时,ul元素的class属性的值就会根据用户数据中的sex字段的值而决定显示哪个样式了,效果如图1.13所示。
图1.13 动态绑定ul元素的class属性的效果
说明
前面曾经提到,英文中的responsive和reactive在中文中都翻译为“响应式”,实际上它们的含义不同。这里会遇到另一个特别常用的中文词汇“属性”,它实际上对应于英文中两个不同的单词,一个是property,另一个是attribute,二者含义的区别不是太大。JavaScript程序中对象的属性,是property,而HTML元素的属性,例如img元素的src属性,则是attribute。有时把attribute翻译为“特性”,以和property相区别。
v-bind指令有一个简写形式,即省略“v-bind”,只保留要绑定的属性前面的冒号。例如下面的第1行代码就用了v-bind指令的简写形式。
1 <ul :class="sex"> 2 <li>姓名 : {{name}}</li> 3 <li>城市 : {{city}}</li> 4 </ul>
说明
Vue.js 中常用的指令都有简写形式。除了最后两章的综合案例外,本书基本上不使用简写形式,以便读者加深对指令的印象。
4.插入HTML片段
知识点讲解
前面是用双花括号语法向HTML元素中插入文本,但是如果插入内容不是单纯的文本内容,而是带有HTML结构的内容,就要改用v-html指令了。
我们首先将sayHello()方法稍做修改,在字符串中加入一些HTML标记,代码如下所示。
1 methods:{ 2 sayHello(){ 3 return `您好,欢迎来自 <b>${this.city}</b> 的 <b>${this.name}</b> !` 4 } 5 }
然后在浏览器中可以看到HTML标记都作为文本直接显示出来了,如图1.14所示,但这不是我们希望的效果。
图1.14 使用双花括号语法直接显示HTML标记
这时将使用的双花括号语法改为v-html指令,代码如下所示。本例完整的案例代码可以参考本书配套资源文件“第1章/instance-07.html”。
1 <div id="app"> 2 <p v-html="sayHello()"></p> 3 <ul> 4 <li>姓名 : {{name}}</li> 5 <li>城市 : {{city}}</li> 6 </ul> 7 </div>
这时可以看到显示结果正是我们希望的,姓名和城市信息用粗体显示,如图1.15所示。
图1.15 使用v-html指令显示HTML片段
说明
<b>和<i>(<b>表示加粗,<i>表示斜体)等原来HTML4中的一些与文本样式相关的标记,在HTML5中保留了下来,仍是有效的标记,但是HTML5对它们从语义的角度进行了新的解释。
<b>标记在HTML5中解释为在普通文章中仅从文体上突出的、不包含任何额外重要性的一段文本。例如,文档概要中的关键字、评论中的产品名等。
而<i>标记在HTML5中解释为在普通文章中突出不同意见、语气或其他特性的一段文本。例如,一个分类名称、一个技术术语、外语中的一个谚语、一个想法等。
1.2.2 Vue实例的生命周期
知识点讲解
Vue.js会自动维护每个Vue实例的生命周期,也就是说,每个Vue实例都会经历一系列的从创建到销毁的过程。例如,创建实例对象、编译模板、将实例挂载到页面上,以及最终的销毁等。在这个过程中,Vue 实例在不同阶段的时间点向外部暴露出各自的回调函数,这些回调函数称为“钩子函数”,开发人员可以在这些不同阶段的钩子函数中定义业务逻辑。
例如,考虑上面的用户信息页面。在定义user对象的时候,我们并不知道用户的具体信息,通常是在页面加载后,通过AJAX向服务器发送请求,调用服务器上的某个API,从返回值中获取有用的信息来给 user 对象赋值。代码如下所示。本案例的完整代码可以参考本书配套资源文件“第1章/instance-08.html”。
1 <script> 2 let user = { 3 name : '', 4 city : '' 5 }; 6 let vm = new Vue({ 7 el: '#app', 8 data: user, 9 mounted(){ 10 user = getUserFromApi(); 11 }, 12 methods:{ 13 sayHello(){ 14 return ’您好,欢迎来自 <b>${this.city}</b> 的 <b>${this.name}</b> !' 15 } 16 } 17 }) 18 </script>
在上面的代码中,定义user对象的时候,name和city两个字段都初始化为空字符串。创建Vue实例的时候,在mounted()方法中,调用方法获取user对象的属性值。这里具体如何使用AJAX获取远程的数据,我们将在后面的章节中介绍。
mounted()钩子函数是最常用的一个钩子函数,在DOM文档渲染完毕以后调用,相当于JavaScript中的window.onload()方法。
这里可以考虑1.1节中的猜数游戏案例,如果我们希望每次设置一个新的随机数作为猜数的目标,显然我们需要在两处调用设定目标值的代码,一处是在mounted()钩子函数中,另一处是在每次用户猜数成功以后,参考如下代码中加粗的行。本案例的完整代码可以参考本书配套资源文件“第1章/basic-03.html”。
1 let vm = new Vue({ 2 el:"#app", 3 data: { 4 guessed: '', 5 key:0 6 }, 7 methods:{ 8 setKey(){ 9 this.key = Math.round(Math.random()*100); 10 } 11 }, 12 mounted(){ 13 this.setKey(); 14 }, 15 computed: { 16 result(){ 17 const value = parseInt(this.guessed); 18 if(isNaN(value)) 19 return "请猜一个1~100的整数"; 20 if(value === this.key){ 21 this.setKey(); 22 return "祝贺你,你猜对了,猜下一个数字吧"; 23 } 24 if(value > this.key) 25 return "太大了,往小一点猜"; 26 return "太小了,往大一点猜"; 27 } 28 } 29 });
可以看到,首先在methods中定义了一个setKey()方法,用于设定猜数目标变量key为一个100以内的随机整数。然后在mounted()方法中调用setKey()方法,就可以在用户第一次开始游戏之前设定好这个猜数目标。从这里可以清楚地看出mounted()钩子函数的作用。
当然,Vue实例的生命周期中的阶段不止挂载这一个,Vue.js也为开发人员提供了众多的生命周期钩子函数,因此重要的是理解每个钩子函数会在什么时间点被调用。
但是,目前我们还无法深入讲解每个阶段的含义,这里只能简单讲解各个钩子函数被调用的时间点。
● beforeCreate():在实例创建之前进行调用。
● created():在实例创建之后进行调用,此时尚未开始DOM编译。
● beforeMount():在挂载开始之前调用。
● mounted():实例被挂载后调用,这时页面的相关DOM节点被新创建的vm.$el替换了。相当于JavaScript中的window.onload()方法。
● beforeUpdate():每次页面中有元素需要更新时,在更新前会调用。
● updated():每次页面中有元素需要更新时,在更新完成后会调用。
● beforeDestroy():在销毁实例前进行调用,此时实例仍然有效。
● destroyed():在实例被销毁之后进行调用。
注意
和methods中定义的方法一样,Vue.js会为所有的生命周期钩子函数自动绑定this上下文到实例中,因此可以在钩子函数中对属性和方法进行引用。这意味着不能使用箭头函数来定义生命周期方法(例如created: ()=> this.callRemoteApi())。因为箭头函数绑定了上下文,箭头函数中的this不指向Vue实例对象。
读者需要认真理解JavaScript中的this指针,在默认情况下,this指针指向调用这个函数的对象。但是JavaScript还可以使用其他方式调用函数,这使得this指针可以指向其他任何特定的对象。
而我们在创建Vue实例的时候,在methods中定义的各个方法,其内部的this指针都指向Vue实例对象,这是Vue.js框架做的特殊处理的结果。
本章小结
知识点讲解
本章从Web开发的一些基本知识开始,介绍了Vue.js框架的基本特点及MVVM模式、Vue.js程序的安装的内容,还安排了一个动手实践,使读者能够初步体验Vue.js开发的基本方法。本章还讲解了Vue.js入门知识,通过Vue()构造函数创建根实例,将页面元素的文本和属性进行绑定。在设置传入Vue()构造函数的对象中,我们学习了3个属性:el、data、methods。希望读者通过简单的案例,掌握 Vue.js 中的核心原理,理解视图、业务模型及视图模型三者之间的关系和各自的作用。Web 开发的基础是 HTML、CSS 和JavaScript这3门基本的语言,因此,希望读者在正式学习之前,确认对这3门语言有基本的掌握。
习题1
一、关键词解释
前后端分离模式 MVVM 模式 Vue.js 声明式编程 命令行控制台 Vue 根实例 文本插值双向数据绑定 属性绑定 Vue指令 实例的生命周期 钩子函数
二、描述题
1.请简单描述一下Web开发技术大致经过了哪几个发展阶段。
2.请简单描述一下Vue.js的特性。
3.请简单描述一下MVVM模式包括哪几个核心部分。
4.请简单描述一下Vue.js的核心思想。
5.请简单描述一下本章介绍的Vue根实例的选项都有哪些,它们在Vue.js中起什么作用。
6.请简单介绍一下 Vue 实例生命周期的钩子函数有哪些,每个钩子函数分别会在什么时候被调用。