第3章 Vue基础语法 前端框架很多,无论选择哪个框架,对于前端处理的事情依旧是模板渲染、处理用户数据交互、事件的绑定等,只不过每个框架的理念和写法不同而已。Vue通过声明一个唯一的页面实例new Vue({...})来标记当前页面的HTML结构、数据展示及相关事件的绑定。 3.1模板语法 Vue.js使用了基于HTML的模板语法,允许开发者声明式地将DOM与底层Vue实例的数据进行绑定。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。 在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。 简单地说: Vue模板语法实现前端渲染,前端渲染即是把数据填充到html标签中。数据(来自服务器)+模板(html标签)=前端渲染(产物是静态html内容)。 3.1.1插值 插值就是将数据插入html文档中,包含文本、html元素、元素属性等。 1. 文本插值 文本插值中用得最多的就是用双大括号的形式插入文本,如例31所示。 【例31】文本插值方式 //第3章/文本插值.html

{{message}}

在上面代码中,data的值更新之后不需要操作html,页面会自动更新数据。 也可以只绑定数据一次,以后在更新data的属性时不需要再更新页面数据,如例32所示。 【例32】使用vonce指令插值 //第3章/使用v-once指令插值.Html

{{message}}

在上面代码中页面只会呈现I LOVE YOU,当改变data中的 message属性值时,页面将不再刷新。 2. HTML插值 双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,需要使用vhtml,如例33所示。 【例33】使用vhtml指令将数据解释成HTML代码 //第3章/v-html指令.html

上面代码将 msg 属性值作为html元素插入h1标签的子节点中。 3. 属性插值 Mustache语法不能作用在 HTML attribute 上,遇到这种情况应该使用vbind:指令。 在开发的时候,有时属性不是固定不变的,有可能根据我们的一些数据动态地决定,例如图片标签()的src属性,我们可能从后端请求了一个包含图片地址的数组,需要将地址动态地绑定到src上,这时就不能简单地将src固定为某个地址。还有一个例子就是a标签的href属性。这时可以使用vbind:指令。其中,vbind:可以缩写成冒号:,如例34所示。 【例34】使用vbind:指令绑定属性 //第3章/使用v-bind指令.html
百度一下
服务器请求过来的数据,我们一般都会在data那里中转一下,中转过后再把需要的变量绑定到对应的属性上面。 vbind除了可以在开发中用在有特殊意义的属性外(src、href等),也可以绑定其他一些属性,如与class和style绑定,如例35和36所示。 【例35】vbind动态绑定属性class //第3章/v-bind动态绑定属性.html v-bind动态绑定属性class
{{name}}

{{name}}真好看!
既然是一个对象,那么该对象内的属性就可能不唯一,但只要有一项为真,对应的类名就会存在。通过vbind更新的类名和元素本身存在的类名不冲突,可以优雅地共存,如图31所示。 图31vbind动态绑定属性class 【例36 】vbind动态绑定属性style //第3章/v-bind动态绑定属性style.html v-bind动态绑定属性style
修饰文本
在大多情况下,标签中直接写一长串属性的样式不便于阅读和维护,所以一般写在dada或computed里,代码如下:
修饰文本
应用多个样式对象时,可以使用数组语法,如例37所示。 【例37】vbind绑定多个style属性 //第3章/v-bind绑定多个style属性.html v-bind动态绑定属性style
文本
4. 插值中使用JavaScript表达式 迄今为止,在模板中,我们一直都只绑定简单的 property 键值。但实际上,对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持,如例38所示。部分表达式格式代码如下: {{ number + 1 }} {{ ok ? 'YES' : 'NO' }} {{ message.split('').reverse().join('') }}
这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。有个限制需要注意,每个绑定只能包含单个表达式,所以下面的表达式不会生效,代码如下: {{ var a = 1 }} {{ if (ok) { return message } }} 【例38】使用JavaScript表达式 //第3章/使用JavaScript表达式.html

{{ number + 1 }}


{{msg + '~~~~~'}}


{{flag ? '条件为真' : '条件为假'}}


3.1.2指令 指令在上面已经使用过了,例如vbind:和vhtml,指令就是指这些带有v前缀的特殊属性。指令属性的值预期是单一JavaScript表达式(除了vfor)。指令的职责就是当其表达式的值改变时相应地将某些行为应用到DOM上。 1. 参数 有一些指令能够接收一个“参数”,在指令名称之后以冒号表示。如vbind指令可以用于响应式地更新HTML attribute,代码如下: ... 在这里 href 是参数,告知vbind指令将该元素的 href attribute 与表达式 url 的值绑定。 2. 动态参数 从版本2.6.0开始,可以用方括号将JavaScript表达式作为一个指令的参数括起来,响应式使得Vue更加灵活多变,其动态参数也有其含义,代码如下: ... 这里的attributeName会被作为一个JavaScript表达式进行动态求值,求得的值将作为最终的参数使用。例如,如果Vue实例有一个data属性attributeName,其值为'href',那么这个绑定将等价于vbind:href。 同样地,可以使用动态参数作为一个动态的事件名绑定并处理函数,格式代码如下: ... 同样地,当eventName的值为'focus'时,von:[eventName]等价于von:focus。 当然动态参数的值也有约束,动态参数预期会求出一个字符串,异常情况下此值为null。这个特殊的null值可以被显性地移出绑定。任何其他非字符串类型的值都会触发一个警告。 3. 修饰符 修饰符(Modifiers)是以半角句号“.”指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 von 指令对于触发的事件调用event.preventDefault(),代码如下:
...
在后面章节对von和vfor等功能的探索中,将会看到修饰符的其他例子。 3.1.3过滤器 过滤器是对即将显示的数据做进一步的筛选和处理,然后进行显示,值得注意的是过滤器并没有改变原来的数据,只是在原数据的基础上产生新的数据。 过滤器分全局过滤器和局部过滤器,全局过滤器在项目中使用频率非常高。 1. 定义过滤器  全局过滤器 Vue.filter('过滤器名称', function (value1[,value2,...] ) { //逻辑代码 })  局部过滤器 new Vue({ filters: { '过滤器名称': function (value1[,value2,...] ) { // 逻辑代码 } } }) 2. 过滤器使用的地方 Vue.js允许自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方: 双花括号插值和vbind表达式 (后者从2.1.0+版开始支持)。过滤器应该被添加在JavaScript表达式的尾部,由“管道”符号指示,格式代码如下:
{{数据属性名称 | 过滤器名称}}
{{数据属性名称 | 过滤器名称(参数值)}}
3. 实例 局部过滤器在实例中的使用如例39所示。 【例39】在价格前面加上人民币符号(¥) //第3章/局部过滤器.html

计算机价格: {{price | addPriceIcon}}

传递多个参数的局部过滤器如例310所示。 【例310】在局部过滤器中传递多个参数 //第3章/在局部过滤器中传递多个参数.html
{{value1|multiple(value2,value3)}}
全局过滤器在实例中的使用如例311所示。 【例311】全局过滤器的使用 //第3章/全局过滤器.html

{{viewContent | addNamePrefix}}

3.2实例及选项 Vue通过构造函数来实例化一个Vue对象: var vm=new Vue({})。在实例化时,我们会传入一些选项对象,包含数据选项、属性选项、方法选项、生命周期钩子等常用选项。而且Vue的核心是一个响应式的数据绑定系统,建立绑定后,DOM将和数据保持同步,这样无须手动维护DOM,就能使代码更加简洁易懂,从而提升效率。 3.2.1数据选项 一般地,当模板内容较简单时,使用data选项配合表达式即可。当涉及复杂逻辑时,则需要用到methods、computed、watch等方法。 data是Vue实例的数据对象。Vue使用递归法将data的属性转换为getter/setter,从而让data属性能够响应数据变化,代码如下:
{{ message }}
Vue实例创建之后,在控制台输入vm.$data即可访问原始数据对象,如图32所示。 图32访问原始数据对象 在script标签中添加一些输出信息即可查看控制台从而观察Vue实例是否代理了data对象的所有属性,代码如下: 被代理的属性是响应式的,也就是说值的任何改变都将触发视图的重新渲染。设置属性也会影响到原始数据,反之亦然。 但是,以“_”或“$”开头的属性不会被Vue实例代理,因为它们可能和Vue内置的属性或方法冲突。可以使用如vm.$data._property的方式访问这些属性,代码如下: 3.2.2属性选项 Vue为组件开发提供了属性(props)选项,可以使用它为组件注册动态属性,来处理业务之间的差异性,使代码可以在相似的应用场景复用。 props选项可以是数组或者对象类型,用于接收从父组件传递过来的参数,并允许为其赋默认值、类型检查和规则校验等,如例312所示。 【例312】props选项的使用 //第3章/props选项的使用.Html Hello World
3.2.3方法选项 可以通过选项属性methods对象来定义方法,并且使用von指令来监听DOM事件,如例313所示。 【例313】通过methods对象来定义方法 //第3章/通过methods对象来定义方法.html
3.2.4计算属性 在项目开发中,我们展示的数据往往需要处理。除了在模板中绑定表达式或利用过滤器外,Vue还提供了计算属性方法,计算属性是当其依赖属性的值发生变化时,这个属性的值会自动更新,与之相关的DOM部分也会同步自动更新,从而减轻在模板中的业务负担,保证模板的结构清晰和可维护性。 有时候需要在{{}}中进行一些计算再展示出数据,如例314所示。 【例314】在页面中展示学生的成绩、总分和平均分 //第3章/学生成绩信息.html
学科 分数
数学
英语
语文
总分 {{Math + English + Chinese}}
平均分 {{(Math + English + Chinese)/3}}
图33学生成绩、总分和 平均分的展示 执行结果如图33所示。 虽然通过{{ }}运算,可以解决我们的问题,但是代码结构不清晰,特别是当运算比较复杂的时候,我们不能复用功能代码。这时,大家不难想到用methods来封装功能代码,但事实上,Vue提供了一个更好的解决方案——计算属性。计算属性是Vue实例中的一个配置选项: computed,通常计算相关的函数,返回最后计算出来的值。也就是我们可以把这些计算的过程写到一个计算属性中,然后让它动态地计算,如例315所示。 【例315】使用计算属性展示学生的成绩、总分和平均分 //第3章/使用计算属性展示学生成绩.html
学科 成绩
数学
英语
语文
总分 {{sum}}
平均分 {{average}}
计算属性一般通过其他的数据计算出一个新数据,它的一个好处是能把新的数据缓存下来,当其他的依赖数据没有发生改变时,它调用的是缓存数据,这就极大地提高了程序的性能和数据提取的速度。而如果将计算过程写在methods中,数据就不会缓存下来,所以每次都会重新计算。这也是我们没有采用methods的原因,如例316所示。 【例316】计算属性和方法的比较 //第3章/计算属性和方法的比较.html

原始字符串: "{{ message }}"

计算属性反向字符串: "{{ reversedMessage1 }}"

methods方法反向字符串: "{{ reversedMessage2() }}"

计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。这就意味着只要 message 没有发生改变,多次访问 reversedMessage1 计算属性会立即返回之前的计算结果,而不必再次执行函数。相比而言,只要发生重新渲染,methods总会调用并执行该函数。 假设有一个性能开销比较大的计算属性A,它需要遍历一个极大的数组和做大量的计算。可能有其他的计算属性依赖于A,如果没有缓存,则将不可避免地多次执行A的getter,这样便极大地降低了数据的提取速度,从而导致用户体验感不强。如果不希望有缓存,则用methods替代。 实例315和实例316只提供了getter读取值的方法,实际上除了getter,还可以设置计算属性的setter,如例317所示。 【例317】读取和设置值 //第3章/读取和设置值.html 3.2.5表单控件 1. 基础用法 可以用vmodel指令在表单 注意: 在文本区域插值()并不会生效,应用 vmodel 来代替。 (3) 单个复选框,绑定到布尔值,代码如下:
(4) 多个复选框,绑定到同一个数组,代码如下:

Checked names: {{ checkedNames }}
(5) 单选按钮的双向数据绑定,代码如下:


选中值为: {{ picked }}
(6) 选择列表,下拉列表的双向数据绑定。  单选时,代码如下:
Selected: {{ selected }}
注意: 如果vmodel表达式的初始值未能匹配任何选项,则
Selected: {{ selected }}  动态选项,用 vfor 渲染,代码如下:
Selected: {{ selected }}
2. 绑定value (1) 对于单选按钮、勾选框及选择列表选项,vmodel 绑定的 value 通常是静态字符串,代码如下:
(2) 实现复选框功能。有时我们想绑定 value 到 Vue 实例的一个动态属性上,这时可以用 vbind 实现,并且这个属性的值可以不是字符串,代码如下:
{{toggle}}
当选中时输出字符串a; 当没有选中时输出字符串b。 (3) 单选按钮,代码如下: 当选中时在页面控制台输入vm.pick 的值和vm.a的值相等。 (4) 选列表设置,代码如下: 当选中时在页面控制台输入typeof vm.selected,则输出'object'; 如果输入vm.selected.number,则输出值为123。 3. 修饰符  .lazy 在默认情况下,vmodel每次在input事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。此时可以添加.lazy修饰符,从而转为在 change事件之后进行同步,代码如下:  .number 如果想自动将用户的输入值转为数值类型,可以给 vmodel 添加.number 修饰符,代码如下: 通常这很有用,因为即使在type="number" 时,HTML输入元素的值也总会返回字符串。如果这个值无法被parseFloat()解析,则会返回原始的值。  .trim 如果要自动过滤用户输入的首尾空白字符,可以给 vmodel 添加.trim 修饰符,代码如下: 3.2.6生命周期 Vue实例有一个完整的生命周期,也就是开始创建、初始化数据、编译模板、挂载DOM、渲染→更新→渲染、卸载等一系列过程,我们称之为Vue的生命周期。通俗地说就是Vue实例从创建到销毁的过程,如图35所示。 图35Vue生命周期 可以看到在Vue的整个生命周期中有很多钩子函数,这就给了用户在不同阶段添加自己代码的机会,下面先列出所有的钩子函数,然后再一一详解。 (1) beforeCreate: 在实例初始化之后,数据观测(data observer)和event/watcher 事件配置之前被调用。 (2) Created: 实例已经创建完成之后被调用。在这一步,实例已完成以下配置,数据观测(data observer),属性和方法的运算,以及watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。 (3) beforeMount: 在挂载开始之前被调用,相关的 render 函数首次被调用。 (4) Mounted: el 被新创建的 vm.$el 替换,并挂载到实例上之后调用该钩子。 (5) beforeUpdate: 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。可以在这个钩子中进一步更改状态,这不会触发附加的重新渲染过程。 (6) Updated: 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以现在可以执行依赖于 DOM 的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不会被调用。 (7) beforeDestroy: 实例销毁之前调用。在这一步,实例仍然完全可用。 (8) Destroyed: Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会被解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不会被调用。 下面通过实例代码来演示Vue实例在创建过程中调用的几个生命周期钩子,如例318所示。 【例318】生命周期钩子函数的演示 //第3章/生命周期钩子函数.html

{{message}}

运行后打开console可以看到打印出来的内容,如图36所示。 图36Vue实例在创建过程中调用的几个生命周期钩子函数 接下来将详细解释生命周期钩子函数。 (1) 在beforeCreate和created钩子函数之间的生命周期。 在这个生命周期之间,首先进行初始化事件,然后进行数据观测,此时可以看到在created的时候数据已经和data属性进行了绑定(放在data中的属性的值发生改变时,视图也会改变)。 注意: 此时还是没有el选项。 (2) created钩子函数和beforeMount间的生命周期,如图37所示。 图37created和beforeMount函数间的生命周期 在这一阶段发生的事情还是比较多的。首先会判断对象是否有el选项。如果有就继续向下编译,如果没有el选项,则停止编译,也就意味着停止了生命周期,直到在该Vue实例上调用vm.$mount(el)。此时注释掉代码中的:el: '#app',然后可以看到程序运行到created时就停止了,如图38所示。 图38没有el选项的生命周期 如果在后面继续调用vm.$mount(el),则代码继续向下执行,如图39所示。 图39调用vm.$mount(el)的生命周期 接着往下看,template参数选项的有无对生命周期的影响,如例319所示。 (1) 如果Vue实例对象中有template参数选项,则将其作为模板编译成render函数。 (2) 如果没有template选项,则将外部HTML作为模板编译。 (3) 可以看到template中的模板优先级要高于outer HTML的优先级。 【例319】Vue对象中增加了template选项 //第3章/Vue对象中增加了template选项.html Vue生命周期学习

{{message + '这是在outer HTML中的'}}

运行结果如图310所示。 图310在Vue对象中增加了template选项 将Vue对象中template的选项注释掉后显示的结果如图311所示。 图311在Vue对象中去掉template选项 注意: el进行DOM绑定要在template之前,因为Vue需要通过el找到对应的outer template。 在Vue对象中还有一个render函数,它是以createElement作为参数的,然后进行渲染操作,并且我们可以直接嵌入JSX,代码如下: var vm = new Vue({ el: '#app', render: function(createElement) { return createElement('h1', 'this is createElement') } }) 运行程序后可以看到页面渲染的结果如图312所示。 图312render函数渲染结果 所以综合排名优先级: render函数选项>template选项>outer HTML。 (1) beforeMount和mounted 钩子函数间的生命周期,如图313所示。 图313beforeMount和mounted 钩子函数间的生命周期 可以看到此时给Vue实例对象添加$el成员,并且替换掉挂载的DOM元素。在之前console中打印的结果可以看出beforeMount之前el的属性还是undefined。 (2) 接下来讲解mounted钩子函数的生命周期。 在mounted之前h1还是通过{{message}}进行占位的,因为此时还没有挂载到页面上,还是在JavaScript中以虚拟DOM的形式存在。在mounted之后可以看到h1的内容发生了变化,如图314所示。 图314mounted函数前后内容变化 (3) beforeUpdate钩子函数和updated钩子函数间的生命周期,如图315所示。 图315beforeUpdate和updated钩子函数间的生命周期 当Vue发现data中的数据发生了改变时,会触发对应组件的重新渲染,先后调用beforeUpdate和updated钩子函数。我们在console中输入如下信息: vm.message = '触发组件更新' 此时可以发现触发了组件的更新,如图316所示。 图316组件更新状态 (4) beforeDestroy和destroyed钩子函数间的生命周期,如图317所示。 图317beforeDestroy和destroyed钩子函数间的生命周期 beforeDestroy钩子函数在实例销毁之前调用。在这一步,实例仍然完全可用。而destroyed钩子函数在Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会被解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 3.3模板渲染 当我们获取后台数据之后,会按照一定的规则加载到前端写好的模板中,并显示在浏览器中,这个过程称为渲染。 3.3.1条件渲染 1. vif、velseif和velse vif、velseif、velse这3个指令后面跟的是表达式。Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素/组件,如例320所示。 【例320】vif、velseif 、velse应用 //第3章/条件指令.html

当status为1时,显示该行

当status为2时,显示该行

当status为3时,显示该行

否则显示该行

我们需要注意多个vif、velseif和velse之间需要紧挨着,如果在它们之间插入一行新代码,则代码如下:

当status为1时,显示该行

当status为2时,显示该行

当status为3时,显示该行

否则显示该行

此时编译浏览器会报错,如图318所示。 图318vif、velseif和velse之间不挨着时显示的错误信息 2. vshow 实际上同vif效果等同,当绑定事件的元素符合引号中的条件时,该元素才显示,代码如下:

当status为1时,显示该行

当status为2时,显示该行

3. vif和vshow的区别 (1) 控制显示的方法不同: 该方法和vif区别在于,vshow实际通过修改DOM元素的display属性来实现节点的显示和隐藏,而vif则通过添加/删除DOM节点来实现。 (2) 编译条件: vif是惰性的,如果初始条件为假,则什么也不做,此时不会去渲染该元素,只有在条件第一次变为真时才开始局部编译; vshow则不管初始条件是什么,都被编译,然后被缓存,而且将DOM元素保留,只是简单地基于CSS进行切换,即vif条件为true时才会被加载渲染,而为false时不加载。vshow不管为true还是false,都会被加载渲染。 (3) 性能消耗: vif有更高的切换消耗,而vshow有更高的初始渲染消耗。 (4) 使用场景: 因此,如果需要非常频繁地切换,则使用 vshow 较好。如果在运行时条件很少改变,只需一次显示或隐藏,则使用 vif 较好。 4. key Vue会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染,如例321所示,因此可能造成下面这样的问题。 【例321】Vue高效地渲染元素 //第3章/Vue高效地渲染元素.html

页面中输入信息后单击“切换”按钮,此时文本框里的内容并没有清空。 Vue为我们提供了一种方式来声明“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key 属性,代码如下:

3.3.2列表渲染 1. vfor循环用于数组 vfor指令根据一组数组的选项列表进行渲染。 我们可以用 vfor 指令基于一个数组来渲染一个列表。vfor 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名(为当前遍历的元素提供别名,可以任意起名),如例322所示。 【例322】对数组选项进行列表渲染 //第3章/数组选项列表渲染.html 图319列表循环结果 我们定义一个数组类型的数据items,用vfor将
  • 标签循环渲染,效果如图319所示。 vfor还支持一个可选的第2个参数为当前项的索引,索引从0开始,代码如下: 图320含有index选项的 列表渲染结果 分隔符in前的语句使用括号,第2项就是items当前项的索引,运行的结果如图320所示。 注意: 可以用of代替in作为分隔符。 2. vfor用于对象 vfor通过一个对象的属性来遍历并输出结果,如例323所示。 【例323】vfor来遍历对象 //第3章/v-for来遍历对象.html 渲染后的结果如图321所示。 遍历对象属性时,有两个可选参数,分别是键名和索引,代码如下: 渲染后的结果如图322所示。 图321遍历对象结果 图322遍历对象的渲染结果 3. vfor用于整数 vfor还可以迭代整数,代码如下: 3.3.3template标签用法 上述例子中,vif和vshow指令都包含在一个根元素中,那是否有方法可以将指令作用到多个兄弟DOM元素上呢?Vue提供了内置标签