Vue

一、概念介绍

Vue.js和React.js分别是目前国内和国外最火的前端框架,框架跟类库/插件不同,框架是一套完整的解决方案,对项目的侵入性较大,当项目需要更换框架时,需要重构整个项目,类库/插件则是提供某一个小功能,对项目的侵入性较小,当使用某个类库/插件无法完成某些需求时,可以很容易切换到其它类库/插件来实现需求,例如从jQuery.js切换到zepto.js,从IScroll.js切换到ScrollMagic.js,只需要将所用到的代码替换一下即可,框架和类库可以提高开发效率,web前端的发展历程是原生JS、类似jQuery.js的类库、前端模板引擎、Vue.js/React.js/Angular.js框架,而且Vue+Weex和React Native都可以进行手机app的开发,阿里系用Vue+Weex比较多,Vue.js、Angular.js、和React.js并称为前端三大主流框架,它们三个都是一套构建用户界面的框架(MVVM框架),即只关注视图层(UI),但是Angular.js和React.js是老外所编写的,所以全部资料都是英文的,而Vue.js是国人(尤雨溪,华裔,前Google工程师)所编写的,所以全部资料都是中文的,Vue.js是一个渐进式框架,可以动态构建用户界面,编码简洁,体积小,运行效率高,适合移动/PC端开发,并且Vue.js中整合了Angular.js和React.js中的众多优点,比如参考了React.js的组件化和虚拟DOM技术,借鉴了Angular.js的模板和数据绑定技术,不仅易于上手,而且生态比较好,比如有着丰富的、便于跟已有项目和第三方类库/插件(比如Swiper.js、IScroll.js、...)相整合的第三方类库,以及可以轻松引入基于Vue.js的插件来进行大型项目的开发,英文官网是vuejs.org,中文官网是cn.vuejs.org,打开之后,点击GitHub-releases(发布版),然后就可以下载最新版本的压缩包了,解压缩之后打开dist文件夹,然后找到Vue.js就可以了,Vue.js的核心优势一,是无需操作DOM,直接通过数据来驱动界面更新,所以我们只需要关心如何获取数据、处理数据、以及编写业务逻辑代码,将处理好的数据交给Vue.js就可以了,然后Vue.js就会自动将数据渲染到模板(界面)上,Vue.js的核心优势二,是组件化开发,当把一个很大的界面拆分成多个小的界面时,每一个小的界面就叫做一个组件,将大界面拆分成一个个组件,然后再通过封装好的组件拼接成一个完整的网页,这个过程就叫做组件化,好处是可以简化Vue实例对象中的代码,并且可以提高复用性

[if !supportLists]二、[endif]Vue.js的基本使用

创建一个Vue实例对象的格式为“<div id=“app”><p>{{name}}</p></div>  let vue = new Vue ({el: “#app”, data: {name: “李南江”} });(//也可以通过“new Vue ().$mount (“#app”);”的形式挂载到某个HTML标签上)”,{{}}叫做插值语法MVVM设计模式中的M(Model)指的是数据模型,用来保存数据,处理数据业务逻辑,V(View)指的是视图,用来展示数据,与用户交互,VM(View Model)指的是数据模型和视图之间的桥梁,当把M比作中国人,V比作美国人时,VM就是翻译,MVVM设计模式最大的特点就是支持数据的双向传递,数据可以从M经由VM传向V,也可以从V经由VM传向M,Vue.js就是基于MVVM设计模式的JS框架,页面上被控制的区域就是V,Vue实例对象就是VM,实例对象配置对象中的data/methods/computed对象等就是M,虽然Vue.js在默认情况下只支持数据的单向传递,即从M经由VM传向V,但是也提供了双向传递的能力,在Vue.js内部封装好的一些具有特定功能的自定义HTML属性/给HTML属性添加特定功能的操作符就叫做指令,在HTML标签上直接添加这些指令就可以了,使用v-model指令可以在<input>/<textarea>/<select>上创建value/checked/selected属性值的双向传递,并且v-model指令会忽略此<input>/<textarea>/<select>的初始value/checked/selected属性值,而总是将M作为数据来源(从V传向M的数据会自动加上引号),例如“<input type=“text”v-model=“msg”>  data: {msg: “知播渔”}”,“<select id=“app”v-model=“msg”><option value=“奔驰”>奔驰</option>  <option value=“宝马”>宝马</option>  <option value=“奥迪”>奥迪</option></select>  data: {msg: “奥迪”}”,使用v-once指令可以让此HTML标签内部被传递的值永远是M中初始化的值,不随着M的变化而变化,例如“<p id= “app”v-once>{{msg}}</p>  data: {msg: “hello”}”,由于Vue.js数据传递的过程是先将未传递数据的界面(V)展示给用户,然后再将模型(M)中的数据传递给界面(V),传完值之后先将新界面(V)放到web浏览器的缓存中,最后再将新界面(V)替换掉旧界面(V)并渲染出来,所以当用户网络比较慢或者网页性能比较差时,用户就会看到{{}},使用v-cloak指令可以让此HTML标签在被传完值之后再显示,但必须配合CSS代码“[v-cloak] {display: none;}”一起使用,即先隐藏未被传值的界面,等到被传完值之后再重新显示,使用v-text/v-html指令可以相当于原生JS里的innerText/innerHTML属性,例如“<p v-html=“msg”></p>  data: {msg: “<span>我是span</span>”}”,使用v-if指令可以决定此HTML标签的创建/删除,当v-if值是true时,就创建,反之就删除,v-if、v-else-if、和v-else指令可以配合使用,并且中间不能被断开,当v-if值是false时,就依次判断后续的v-else-if值,哪个是true就创建哪个,v-else-if和v-else指令不能单独使用,使用v-show指令可以决定此HTML标签行内样式display值是auto/none,当v-show值是true时,就是auto,反之就是none,当需要频繁切换HTML标签的显示/隐藏时,由于使用v-show指令无需频繁的创建/删除HTML标签,性能比较好,所以推荐使用v-show指令,否则使用v-if指令,使用v-for指令可以根据某个对象/数组/字符串/数字被for in循环遍历所执行的次数来多次创建此HTML标签,例如“<li v-for=“(value, key) in obj”>{{key}}---{{value}}</li>(//value必须有,而key可有可无,当不写key时,小括号就不用写了,key也可以换成index,obj也可以换成arr,value和key/index这两个变量只能在此HTML标签/此HTML标签的后代HTML标签中使用)  data: {obj: {name: “lnj”, age: 33, gender: “man”, class: “知播渔”} }”,v-for指令在创建HTML标签时,会先查看web浏览器的缓存中有没有此HTML标签,当有此HTML标签时,为了提升性能,就不会新建此HTML标签,而是采取就地复用的策略,当没有此HTML标签时,就会新建一个HTML标签,新建完之后先放到web浏览器的缓存中,然后再从web浏览器的缓存中将此HTML标签渲染到界面上(每个HTML标签只能被渲染一次),在Vue.js中,只要配置对象中的数据发生了更新,就会自动重新渲染界面,当在一个<ul>的开头处新增了一个<li>时,由于就地复用的策略,就会造成数据的对应关系发生混乱,为了解决这个问题,我们可以在创建HTML标签时,给每一个HTML标签v-bind上一个独一无二的key值(不能使用索引值充当key值,因为当ul的li被新增/删除时,索引值都会发生变化),这样一来,当v-for指令由于新增<li>而重新渲染界面时,会先判断<li>的key值是否跟web浏览器缓存中的匹配,匹配就复用,不匹配就新建<li>,使用v-bind指令可以给此HTML标签的HTML属性直接赋值(即字面量)/从配置对象/v-for指令的value和key中传递取值(即变量),当所作用的是class/style属性时,也可以使用数组/对象的形式赋值,例如“<input type=“text”v-bind:value=“age + 1”>(//冒号前的v-bind也可以省略,直接使用冒号就可以)  data: {age: 18}”,“<input type=“text”:value=“‘hello world’”>”,“:class=“从配置对象中传递的类名1, 从配置对象中传递的类名2, ...””,“:class=“[‘直接添加的类名1’, ‘直接添加的类名2’, ...]”(//当数组中的类名不加引号时,也会去M中查找数据)”,“:class=“[flag?‘直接添加的类名1’:‘直接添加的类名2’, ...]”(//数组中的每一个元素都可以是一个三目运算表达式)”,“:class=“[‘size’, ‘color’, {‘active’: true}]”(//也可以使用对象来代替三目运算表达式,当取值是true时,就代表此HTML标签拥有此类名,当取值是false时,就代表此HTML标签不拥有此类名)”,“<p :class=“obj”>我是段落</p>  data: {obj: {‘size’: false, ‘color’: false, ‘active’: true} }(//当所添加的类名太多时,可以从配置对象中传递一个对象)”,“:style=“{color: ‘red’, ‘font-size’: ‘50px’}”(//当CSS属性名称包含中划线时,必须加引号)”,“<p :style=“obj”>我是段落</p>  data: {obj: {color: ‘red’, ‘font-size’: ‘50px’} }(//从配置对象中传递一个对象)”,“<p :style=“[obj1, obj2]”>我是段落</p>(//当想要从配置对象中传递多个对象时,可以放到数组中)”,使用v-on指令可以给此HTML标签绑定事件(取值是从methods对象中所传递的回调函数),格式为“v-on:去掉on的事件名称.修饰符1.修饰符2...=“回调函数名称()”(//“v-on:”也可以换成“@”,修饰符可有可无,回调函数名称后面的小括号可写可不写,但传参的时候必须写,传入$event就代表原生JS中的事件对象)”,例如“<button v-on:click=“myFn ($event)”>我是按钮</button>  data: {gender: “man”}, methods: {myFn (e) {alert (e, this.gender);} }(//methods对象是专门用于存储函数的,当想要在组件配置对象的函数作用域中访问/调用自己配置对象中的属性/方法时,前面必须加上this,根实例组件配置对象的函数作用域中的this指针指代的是Vue实例对象,自定义组件配置对象的函数作用域中的this指针指代的是VueComponent实例对象)”,使用once修饰符可以让此HTML标签只触发一次事件的回调函数,使用prevent修饰符可以移除此HTML标签的默认事件(相当于event.preventDefault ();),使用stop修饰符可以阻止此HTML标签的事件冒泡(相当于event.stopPropagation ();),使用self修饰符可以让此HTML标签所绑定的事件只有此HTML标签自己才可以触发(冒泡和捕获都触发不了),使用capture修饰符可以让此HTML标签捕获到相应事件,监听键盘上特定按键所触发事件的修饰符就叫做按键修饰符,分为系统自带的和自定义的,格式为“@keyup.按键修饰符=“myFn””,系统自带的按键修饰符包括enter、tab、delete、esc、space、up、down、left、和right修饰符,自定义的按键修饰符就是键盘上某个按键的键码值,例如F2这个按键就是113,也可以通过一行代码给113起一个别名f2,例如“Vue.config.keyCodes.f2 = 113;”,在Vue.js中除了可以直接使用一些系统自带的全局指令以外,还可以自己注册一些自定义全局指令,在任何一个Vue实例对象控制的区域内都可以使用的自定义指令就叫做自定义全局指令,Vue类对象调用它的directive方法可以返回/注册一个自定义全局指令,注册一个自定义全局指令的格式为“Vue.directive (‘去掉v-的自定义指令名称’, {钩子函数名称: function (el, obj) {指令业务逻辑代码;} });(//形参el接收到的是使用此指令的DOM元素,形参obj接收到的是一个对象,即{name: 去掉v-的指令名, value: 使用此指令时所赋的值})”,bind钩子函数是当我们将此指令添加到HTML标签上时所执行的回调函数,inserted钩子函数是当添加此指令的HTML标签被添加到其父标签内部时所执行的回调函数,例如“<input type=“text”v-focus>  Vue.directive (“focus”, {inserted: function (el) {el.focus ();} });(//当将inserted换成bind时,会在还没渲染HTML标签时就过早的执行了回调函数,也就是会导致所执行的回调函数无效,所以必须使用inserted钩子函数)”,“<p v-color=“‘blue’”>我是段落</p>  Vue.directive (“color”, {bind: function (el, obj) {el.style.color = obj.value;} });”,在某一个特定Vue实例对象控制的区域内才可以使用的自定义指令就叫做自定义局部指令,注册一个自定义局部指令的格式为“<div id=“app2”><p v-color=“‘red’”>我是段落</p></div>  let vue = new Vue ({el: “#app2”,directives: {color: {bind: function (el, obj) {el.style.color = obj.value;} } } });”,在Vue.js中,可以在插值语法中编写合法的JS表达式,例如“<p>{{msg.split (“”).reverse ().join (“”)}}</p>  data: {msg: “abcdefg”}”,但是其缺点是没有代码提示,并且当语句过于复杂时不利于维护,所以需要在配置对象的computed对象中定义计算属性,虽然计算属性的取值是一个函数,但是在Vue.js中规定它就是属性,不是方法,所以在使用的时候一定不能在计算属性的名称后面加小括号,否则会报错,例如“<p>{{msg}}</p>  computed: {msg: function () {let res = “abcdef”.split (“”).reverse ().join (“”);  return res;} }”,使用computed对象中的计算属性和methods对象中的函数都可以获取到返回值,但是computed对象中的计算属性会将返回值缓存起来,只要数据没有发生变化,就不会重新执行函数来求值,而methods对象中的函数不会将返回值缓存起来,每一次调用都会重新执行函数来求值,所以当返回值不经常发生变化时,使用计算属性的性能更好,过滤器跟methods对象中的函数以及computed对象中的计算属性一样,都是用来处理数据的,但是过滤器一般用于格式化HTML标签中所插入的文本数据,Vue类对象调用它的filter方法可以返回/注册一个自定义全局过滤器,注册一个自定义全局过滤器的格式为“<p>{{msg | 过滤器名称 (123)}}</p>  Vue.filter (“过滤器名称”, function (value, data) {value = value.replace (/学院/g, “大学”);  return value;});(//“|”叫做管道符号,过滤器名称后面加不加小括号均可,加的话可以传参,形参value接收到的是msg,形参data接收到的是123,原HTML标签中的数据msg经过指定过滤器处理之后,再插入到原HTML标签中)”,过滤器只能在插值语法/v-bind指令中使用,并且是可以连续使用的,例如“<p>{{name | formartStr1 | formartStr2}}</p>”,注册一个自定义局部过滤器的格式为“<div id=“app2”><p>{{name | formartStr}}</p></div>  let vue = new Vue ({el: “#app2”,filters: {formartStr: function (value) {value = value.replace (/学院/g, “大学”);  return value;} } });”,使用<transition>可以给其单个子标签的显示/隐藏/创建/删除设置transition(可以搭配v-show/v-if指令使用),当从隐藏变为显示以及显示变为隐藏时,会按照“.v-enter(//显示前)”、“.v-enter-active(//显示中,transition属性设置在此)”、“.v-enter-to(//显示后)”、“.v-leave(//隐藏前)”、“.v-leave-active(//隐藏中,transition属性设置在此)”、和“.v-leave-to(//隐藏后)”这六个系统自带的过渡类名所设置的CSS属性来执行样式,当想要让一个显示状态/新建的HTML标签刚一进入页面/渲染出来就有transition时,给此<transition>添加一个appear属性就可以了,当想要让不同的子标签执行不同的transition时,可以给不同的<transition>的name属性设置不同的取值,并将不同的name值替换类名中的字母v作为前缀,再分别设置CSS属性就可以了,无论显示还是隐藏,在执行完transition之后,就会给被嵌套的子标签删除所绑定的类名,所以“.v-enter-to(//显示后)”所设置的CSS样式执行完之后就会失效,当不想让其失效时,可以改用Vue.js提供的JS钩子函数来实现transition,即给<transition>绑定其@before-enter值(显示前)、@enter值(显示中)、@after-enter值(显示后)、@enter-cancelled值(显示中途被取消)、@before-leave值(隐藏前)、@leave值(隐藏中)、@after-leave值(隐藏后)、和@leave-cancelled值(隐藏中途被取消)所指代的钩子函数,例如“<div id=“app”><button @click=“toggle”>我是按钮</button>  <transition appear v-bind:css=“false”(//这句可以跳过CSS的检测,避免被CSS代码所影响) @before-enter=“beforeEnter”@enter=“enter”@after-enter=“afterEnter”><div class=“box”v-show=“isShow”></div></transition></div>  el: “#app”,data: {isShow: true}, methods: {toggle () {this.isShow = !this.isShow;}, beforeEnter (el) {el.style.opacity = 0;}, enter (el, done) {el.offsetHeight;  el.style.transition = “all 3s”;  done ();}(//形参el接收到的是被嵌套的子标签所对应的DOM元素,显示/隐藏中的钩子函数作用域中必须加上一句“el.offsetWidth;”/“el.offsetHeight;”,否则没有transition,并且最后必须“done ();”,否则显示/隐藏后的钩子函数不会执行,当想要让一个显示状态的HTML标签刚一进入页面就有transition时,必须把“done ();”放到一个setTimeout中执行,延迟时间设置为0就可以了), afterEnter (el) {el.style.opacity = 1;  el.style.marginLeft = “500px”;}”,JS钩子函数还可以配合第三方velocity.js动画库来实现transition,引入完Vue.js之后,再引入velocity.js,例如“<script src=“https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js”></script>  enter (el, done) {Velocity (el, {opacity: 1, marginLeft: “500px”}, 3000);  done ();}(//也可以将3000换成{duration: 3000})”,除了使用系统自带的过渡类名以外,还可以自定义过渡类名,即利用<transition>的enter-class值(显示前)、enter-active-class值(显示中)、enter-to-class值(显示后)、leave-class值(隐藏前)、leave-active-class值(隐藏中)、和leave-to-class值(隐藏后)来充当过渡类名,然后再通过此过渡类名来给被嵌套的子标签设置CSS属性,这些自定义HTML属性也可以配合第三方Animate.css动画库来实现transition(特效),官网是“animate.style”,例如“<link href=“https://cdn.jsdelivr.net/npm/animate.css@3.5.1”rel=“stylesheet”type=“text/css”>  <transition appear enter-class=“”enter-active-class=“animated bounceInRight”enter-to-class=“”><div class=“box”v-show=“isShow”></div></transition>(//只需给显示/隐藏中的HTML属性绑定上所需要的特效名称即可,其中bounceInRight也可以换成其他的特效,详见官网)”,使用<transition-group>可以给其多个子标签的显示/隐藏/创建/删除设置transition,跟<transition>的用法一致,<transition-group>有一个tag属性,默认值是“span”,代表将其所有的子标签都嵌套在一对<span>内,也可以给tag属性自定义取值,当tag值是“ul”,其所有子标签又都是<li>时,<ul>就可以不用写了,其实使用<transition>/<transition-group>也可以给其内部所嵌套的单/多个<自定义组件>的显示/隐藏/创建/删除设置transition,但是不同<自定义组件>的显示和隐藏transition默认是同时执行的,除非给<transition>/<transition-group>添加一个mode属性,取值可以是out-in(当前<自定义组件>执行完隐藏的transition之后,另一个<自定义组件>再执行显示的transition)/in-out(另一个<自定义组件>执行完显示的transition之后,当前<自定义组件>再执行隐藏的transition),在Vue.js中注册自定义组件的流程是,首先创建(返回)一个组件构造器,然后利用此组件构造器再注册一个自定义组件,Vue类对象调用它的extend方法可以返回一个组件构造器(即一个叫做VueComponent的构造函数/类),格式为“let Profile = Vue.extend ({template: `<div><img src=“images/fm.jpg”>  <p>我是描述信息</p></div>`});(//template值有且只有一个根标签,否则会报错,全都嵌套在一对<div>中就可以了,在字符串模板``内编写HTML代码的弊端是没有提示,严重影响开发效率,所以可以将HTML代码写到“<script id=“info”type=“text/html”></script>”/“<template id=“info”></template>(//推荐)”中,然后把“#id值”赋值给template属性就可以了)”,new组件构造器所生成的VueComponent实例对象可以挂载到界面上任意一个HTML标签上,既可以免注册进行自我渲染,还可以将被挂载的HTML标签覆盖掉,例如“let LoadingContructor = Vue.extend (Loading);  let LoadingInstance = new LoadingContructor ();  LoadingInstance.$mount (oDiv);(//oDiv是一个DOM元素,也可以将其换成一个选择器字符串)”,利用VueComponent实例对象还可以动态的添加/修改自己配置对象中的属性,例如“LoadingInstance.isShow = true;”,Vue类对象调用它的component方法既可以注册一个自定义全局组件(可以在任意一个根实例/全局/局部组件的根标签中渲染),还可以返回传入全局组件名称的组件构造器,注册一个自定义全局组件的格式为“Vue.component (“abc”, Profile);(//参数1是组件名称,参数2是组件构造器,可以免调用extend方法,直接将参数2换成extend方法的实参即配置对象就可以了,系统会自动调用extend方法的,在注册自定义组件时,当此自定义组件名称使用了驼峰命名时,在渲染此<自定义组件>时必须转成短横线分隔命名)”,注册一个自定义局部组件的格式为“<div id=“app2”><abc></abc></div>  let vue2 = new Vue ({el: “#app2”,components: {abc: {template: “#info”} }});(//规律就是,先将方法名称变成对象名称,结尾加s,再将参数1和参数2即组件名称和配置对象组合成此对象内部的一个键值对就可以了,也就是将子组件通过components对象挂载到其父组件的配置对象中,<自定义局部组件>只能在其父组件的根标签中渲染,可以是根标签中的任意位置)”,Vue实例对象就是一个根实例组件,由于在根实例组件的配置对象中我们可以添加data和methods对象,所以在自定义全局/局部组件的配置对象中我们也可以添加data和methods对象,但是在自定义全局/局部组件的配置对象中data值是一个工厂函数,data对象以此工厂函数返回值的形式来添加,例如“data: function () {return {abcMsg: “学院”} }(//这种设计思路源自于组件的复用性,每当使用此组件创建一个新的区块时,都会调用一次data方法,所以每次返回的data对象就会跟当前所创建的区块绑定在一起,这样就有效的避免了数据混乱)”,不同的普通HTML标签可以使用v-if和v-else指令来实现创建/删除的切换,由于组件的本质就是一个自定义HTML标签,所以不同的<自定义组件>也可以使用v-if和v-else指令来实现创建/删除的切换,还可以使用<component>(动态组件)来实现不同<自定义组件>创建/删除的切换,格式为“<component v-bind:is=“需要创建的<自定义组件>名称”></component>(//推荐)”,<component>最好嵌套在<keep-alive>中使用,因为可以将被删除的<自定义组件>保存在web浏览器的缓存中,当再次创建时,直接从web浏览器的缓存中渲染,好处是可以保存被删除的<自定义组件>的状态,<router-view>也可以嵌套在<keep-alive>中使用,还可以给<keep-alive>添加一个include/exclude属性,取值是自定义组件的名称,意思是只有名称匹配的<自定义组件>才会/不会被缓存,详见cn.vuejs.org/v2/api/#keep-alive,任意一个父组件和在其配置对象的components对象中所注册的子组件,或任意一个组件和在其根标签中所渲染的全局组件,都叫做父子组件,例如“<template id=“father”><div><p>我是父组件</p>  <son></son></div></template>”,当想要将父组件配置对象中的某个值传递给其子组件时,首先第一步,在渲染<子组件>时通过v-bind指令配合自定义接收名称来接收传递过来的某个值,然后第二步,将“自定义接收名称”通过props数组/对象挂载到子组件的配置对象中(通过props对象的挂载方式详见cn.vuejs.org/v2/api/#props,其实v-bind指令配合props数组/对象也可以接收并挂载一个字面量数值),最后第三步,在子组件的根标签内部使用插值语法{{}}访问自定义接收名称就可以了(当子组件配置对象的函数作用域中需要访问props数组/对象中的值时,前面必须加上this),例如“<son v-bind:parentname=“name”v-bind:parentage=“age”></son>  son: {template: “#son”, props: [“parentname”, “parentage”]}(//parentname和parentage是自定义的接收名称,当第一步使用驼峰命名时,第二步和第三步必须转成短横线分隔命名,name和age是其父组件配置对象中的值)”,当想要将父组件methods对象中的某个方法传递给其子组件时,首先第一步,在渲染<子组件>时通过v-on指令配合自定义接收名称来接收传递过来的某个方法,然后第二步,在子组件的methods对象中再自定义一个方法,并在此方法的函数作用域中写上一句“this.$emit (“自定义接收名称”, 123, 456, ...);(//后续的实参可以传入其父组件所传递的方法中,使用形参接收一下就可以了,非必须,其实这种方式也可以实现将子组件配置对象中的某个值传递给其父组件)”,最后第三步,在子组件的根标签内部直接调用此自定义的方法就可以了,例如“<son @parentsay=“say”></son>  son: {template: “#son”, methods: {sonFn () {this.$emit (“parentsay”);} } }(//parentsay是自定义接收名称,允许使用短横线分隔命名,不允许使用驼峰命名,sonFn是自定义的方法名称)”,当想要将爷爷组件配置对象中的某个值/方法传递给其孙子组件时,只能一层一层逐级向下传递(非常麻烦),由于在渲染某个<自定义组件>时,在其开始和结束标签中所动态追加的任何内容都不会被渲染,所以当其部分内容是需要后期动态追加时,就必须使用插槽,自定义组件根标签内部的<slot>就叫做插槽,插槽不能直接设置CSS样式,当想要设置CSS样式时,必须先将其嵌套在一个HTML标签中,然后给此HTML标签设置CSS样式就可以了,插槽是可以设置默认数据的,例如“<slot>我是默认数据</slot>”,没有设置name属性的<slot>就叫做匿名插槽,后期所动态追加的任何内容都会替换掉整个匿名插槽,有几个匿名插槽,后期所动态追加的内容就会复制多少份,并将每一个匿名插槽都替换掉,但建议只写一个匿名插槽,设置了name属性的<slot>就叫做具名插槽,此具名插槽的name值就是它的名称,此时必须给所动态追加的HTML标签设置一个slot属性,当slot值跟哪个具名插槽的名称相同时,就会替换掉哪个具名插槽,在Vue2.6.0中,新增了v-slot指令,使用v-slot指令可以替代slot属性(推荐),但v-slot指令只能用在<template>上,所以将所动态追加的内容嵌套在一对<template>内就可以了,例如“<template v-slot:one><div>我是追加的内容1</div>  <div>我是追加的内容2</div></template>(//v-slot:可以用#代替)”,在后期动态追加内容时,当想要访问此自定义组件配置对象中的某个值时,就需要将此自定义组件根标签内部的插槽转成作用域插槽,接收了配置对象中某个值的插槽就叫做作用域插槽,首先第一步,在设置作用域插槽时通过v-bind指令配合自定义接收名称来接收传递过来的某个值,然后第二步,通过<template>的slot-scope值来设置作用域插槽的作用域名称,最后第三步,在所动态追加的内容中直接通过“作用域名称.自定义接收名称”的形式访问就可以了,例如“<template slot-scope=“abc”><div>我是填充的内容-{{abc.myname}}</div></template>  <slot v-bind:myname=“names”>我是默认内容 {{names}}</slot>(//myname是自定义接收名称,abc是作用域名称,slot-scope也可以换成#default,推荐,当此作用域插槽是具名插槽时,就将default换成插槽名称)”,当想要将一个组件配置对象中的某个值传递给其兄弟组件时,首先第一步,让其父组件将一个可以修改自己data对象中某个值的方法(需带有形参)传递给此组件,然后第二步,让此组件调用所传递过来的方法并将自己配置对象中的某个值传入,这样一来,就成功的将此值保存到其父组件的data对象中了,最后第三步,让其父组件再将此值传递给其兄弟组件就可以了(非常麻烦),vuex.js是Vue.js配套的公共数据管理工具,我们可以将共享的数据保存到Vuex对象配置对象的state对象中,方便整个程序中的任何组件都可以访问和修改state对象中所保存的公共数据,必须先引入Vue.js,再引入vuex.js(基于Vue.js的插件),创建Vuex对象的格式为“const store = new Vuex.Store ({state: {msg: “知播渔”} });”,然后将Vuex对象通过store属性挂载到祖先组件的配置对象中(当属性和取值相同时,只写一个就可以了),这样一来,在此祖先组件和其所有后代组件的根标签内部就都可以访问state对象中所保存的共享数据了,格式为“{{this.$store.state.msg}}”,不推荐直接修改Vuex对象中的共享数据,因为在多个组件都直接修改了共享数据的情况下,当后期数据一旦发生错误时,调试错误就需要把每一个修改了共享数据的组件都检查一遍,这样非常低效,非常的不利于维护,所以我们需要给Vuex对象的配置对象中添加一个mutations对象,专门用于保存修改共享数据的方法,例如“mutations: {mAdd (state, msg) {state.count = state.count + 1;} }(//参数1接收到的是state对象)”,在各组件配置对象的函数作用域中调用mAdd方法的格式为“this.$store.commit (“mAdd”, 123);(//123可以传入mutations对象中mAdd方法的参数2,非必须)”,还可以借助mapMutations辅助函数将mutations对象中所定义的方法映射到各组件的methods对象中,即将它映射为自己的方法,格式为“import {mapMutations} from ‘vuex’;  methods: {...mapMutations ([‘mAdd’]) }”,这样一来,就可以直接通过this来调用了,格式为“this.mAdd (123);”,Vuex对象配置对象中的actions对象是专门用于保存触发mutations对象中所定义方法的方法,例如“actions: {setAdd ({commit}, msg) {commit (“mAdd”, msg);} }(//形参msg所接收到的值会传入mAdd方法的参数2)”,在各组件配置对象的函数作用域中调用setAdd方法的格式为“this.$store.dispatch (“setAdd”, 123);(//123可以传入actions对象中的setAdd方法的参数2,非必须)”,还可以借助mapActions辅助函数将actions对象中所定义的方法映射到各组件的methods对象中,即将它映射为自己的方法,格式为“import {mapActions} from ‘vuex’;  methods: {...mapActions ([‘setAdd’]) }”,这样一来,就可以直接通过this来调用了,格式为“this.setAdd (123);”,Vuex对象配置对象中的getters对象是专门用于定义计算属性的,跟组件配置对象中的computed对象一样,在其中所定义的计算属性在执行时会将返回值缓存起来,只有返回值发生变化才会重新执行,在执行getters对象中所定义的计算属性时,参数1接收到的是state对象,调用计算属性的格式为“{{this.$store.getters.formart}}(//调用的时候后面一定不能添加小括号,否则会报错)”,还可以借助mapGetters辅助函数将getters对象中的计算属性映射到各组件的computed对象中,即将它映射为自己的计算属性,格式为“import {mapGetters} from ‘vuex’;  computed: {...mapGetters ([‘formart’]) }”,这样一来,就可以直接通过this来调用了,格式为“{{this.formart}}”,Vue-Router(路由)跟v-if/v-show指令一样,是用来切换不同组件的显示/隐藏的,但v-if/v-show是用标记(true/false)来实现切换,并且所切换的组件必须经过注册,而Vue-Router是用哈希(#/某一个组件的id值)来实现切换,并且所切换的组件可以不用经过注册,还能够在切换的时候给所匹配的组件传值(各种键值对),必须先引入Vue.js,再引入Vue-Router.js(基于Vue.js的插件),首先第一步,定义路由规则,格式为“const routes = [{path: “/组件1的<template>id值”, component: 组件1的配置对象}, {path: “/组件2的<template>id值”, component: 组件2的配置对象}, ...];(//数组中的每一个对象就是一条规则,其实path值可以任意拟定,但建议用<template>的id值,比较好辨认)”,然后第二步,根据所定义的路由规则routes创建路由对象,例如“const router = new VueRouter ({routes: routes});(//将路由规则routes通过routes属性挂载到路由对象router的配置对象中,在配置对象中还可以添加一个mode属性,取值可以是“hash”,默认值,使用URL的哈希来模拟一个完整的URL,当URL改变时,页面不会重新加载,取值可以是“history”,在URL中取消了#号,充分利用了history.pushState()来完成URL的跳转,而无须重新加载页面)”,另外,路由对象router调用它的push方法(实例方法)可以修改当前的路由地址(即URL的哈希值),格式为“router.push ({path: “/one”});”,然后第三步,将路由对象router通过router属性挂载到任意一个组件的配置对象中,格式为“router: router”,最后第四步,当路由地址被修改成path值时,再通过此被挂载组件的根标签内部的<router-view>(即路由出口)来渲染所匹配的组件,我们可以通过<a>修改当前URL的哈希值即路由地址(不专业),在Vue-Router.js中提供了一个<router-link>,它是专门用于修改路由地址的,例如“<router-link to=“/one”tag=“button”>切换到第一个界面</router-link>(//<router-link>只有被点击时才会生效,通过to值来设置路由地址,不用写#,<router-link>默认会被渲染成<a>,但是可以通过它的tag值来将其渲染成指定的标签)”,在路由地址被修改成功时,当想要将此路由地址所对应的<router-link>修改成指定的CSS样式时,第一种方法是给它的router-link-active类名设置指定的CSS样式就可以了,也就是说系统会自动给此路由地址所对应的<router-link>添加一个router-link-active类名,当路由地址被再次修改时,就会自动去掉,并将router-link-active类名添加给别的<router-link>,第二种方法是将一个自定义类名通过linkActiveClass属性挂载到路由对象router的配置对象中,例如“linkActiveClass: “nj-active””,再给此自定义类名nj-active设置指定的CSS样式就可以了,其实当<router-link>被点击时,就相当于利用路由对象router调用了它的push方法,所以说点击<router-link>和“router.push ({path: “/one”});(//在路由规则routes中所包含组件的配置对象中,等同于“this.$router.push ({path: “/one”});(//this.$router返回的就是路由对象router)”)”所起的作用是等同的,当想要设置此页面默认的路由地址时,可以重定向此路由,也就是给所定义的路由规则routes中添加一条规则就可以了,例如“{path: “/”, redirect: “/two”}(//path值是被重定向的值,redirect值是重定向之后的值)”,当点击<router-link>修改当前的路由地址时,有两种给所匹配的组件传值(各种键值对)的方法,第一种方法是,当想要给所匹配的组件传值时,在待修改的路由地址后面附加各种键值对的字符串,例如“<router-link to=“/one?name=lnj&age=33”>切换到第一个界面</router-link>”,当所匹配的组件想要访问所传过来的值时,在所匹配组件的配置对象中可以通过“this.$route;”的方式先返回路由对象router,只要能获取到路由对象router,就可以通过路由对象router获取到所传的值,比如我们可以先在所切换到组件的配置对象中添加一个created方法(一种生命周期方法),然后直接从created方法中访问就可以了,格式为“created: function () {console.log (this.$route);  console.log (this.$route.query.name);  console.log (this.$route.query.age);}”,第二种方法是,当想要给所匹配的组件传值时,键和值分开设置,首先在定义路由规则routes时,在path值中设置“键”,然后在待修改的路由地址中设置“值”,例如“<router-link to=“/one/zs/66”>切换到第一个界面</router-link>  {path: “/one/:name/:age”, component: one}”,当所匹配的组件想要访问所传过来的值时,格式为“created: function () {console.log (this.$route);  console.log (this.$route.params.name);  console.log (this.$route.params.age);}”,在一级路由的某个组件中所嵌套的子路由就叫做嵌套/二级路由,第一步将二级路由的路由规则routes通过children数组挂载到一级路由某个组件的那条路由规则中,例如“{path: “/one”, component: one, children: [{path: “onesub1”, component: onesub1}, {path: “onesub2”, component: onesub2}] }(//二级路由的路由规则routes中的path值既不用写完整的路径,也不用写“/”,因为“/”代表从当前URL的根部开始,但通过<router-link>的to值所修改的路由地址必须写完整的路径)”,第二步在此被挂载的一级路由组件根标签中通过<router-link>和<router-view>正常切换和渲染就可以了,例如“<template id=“one”><div class=“onepage”><p>我是第一个界面</p>  <router-link to=“/one/onesub1”tag=“button”>切换到第一个界面的子界面1</router-link>  <router-link to=“/one/onesub2”tag=“button”>切换到第一个界面的子界面2</router-link>  <router-view></router-view></div></template>”,无论是一级路由还是二级路由,总之记住一句话,挂载到哪个组件的配置对象/路由规则中,就在哪个组件的根标签中<router-view>,在添加了多个匿名的路由出口<router-view>时,一旦路由地址跟path值相匹配,所有的路由出口<router-view>中所显示的内容都是一样的,当想要让不同的路由出口<router-view>显示不同的内容时,就需要给不同的路由出口<router-view>分别设置一个不同的name值,被设置了name值的路由出口<router-view>就叫做命名视图,例如“<router-view name=“name1”></router-view>  <router-view name=“name2”></router-view>  {path: “/”, components: {name1: one, name2: two} }(//在路由规则routes中,将component属性变成components对象,结尾多了一个s,然后在此components对象中自定义不同的属性,取值分别是不同子组件的配置对象,然后给不同的路由出口<router-view>分别设置name值为不同的自定义属性名称就可以了)”,在组件的配置对象中可以添加一个watch对象,在其中可以定义一些监听数据变化的方法,方法名称跟data/props/computed对象/数组中的属性/计算属性名称是对应的关系,当data/props/computed对象/数组中的某个属性/计算属性的取值/返回值发生变化时,就自动执行watch对象中的同名方法,除此之外还可以定义监听路由地址变化的“$route.path”方法,格式为“watch: {“$route.path”: function (newValue, oldValue) {console.log (newValue, oldValue);} }(//参数1和2分别接收到的是变化之后和之前的路由地址字符串,“$route.path”方法经常用于判断当前界面是从哪个界面跳转过来的)”,跟webpack的生命周期方法一样,在Vue/VueComponent实例对象的创建、运行、和销毁阶段所自动执行的方法就叫做Vue的生命周期方法/钩子/函数/事件创建阶段会自动执行beforeCreate、created、beforeMount、和mounted方法,当Vue/VueComponent实例对象刚刚被创建出来但data和methods等对象还没有初始化时,会自动执行beforeCreate方法,例如“beforeCreate: function () {console.log (this.msg);(//undefined)  console.log (this.say);(//undefined)}, data: {msg: “知播渔”}, methods: {say () {console.log (“say”);} }”,当data和methods等对象已经初始化完成但还没有向根标签中传值时,会自动执行created方法,当已经向根标签中传完值但还没有将旧的根标签替换掉时,会自动执行beforeMount方法,当已经将旧的根标签替换掉(即此界面渲染完毕)时,会自动执行mounted方法运行阶段会自动执行beforeUpdate和updated方法,当配置对象中的值被修改了但界面还没有更新时,会自动执行beforeUpdate方法,当配置对象中的值被修改了并且界面也发生更新时,会自动执行updated方法销毁阶段会自动执行beforeDestroy和destroyed方法,当组件即将被销毁时,会自动执行beforeDestroy方法,只要组件不被销毁,beforeDestroy方法就不会自动执行,并且通过beforeDestroy方法我们可以最后访问/调用到data/methods对象中的属性/方法,当组件已经被销毁时,会自动执行destroyed方法,只要组件不被销毁,destroyed方法就不会自动执行,无论通过destroyed方法可不可以访问/调用到data/methods对象中的属性/方法,都不要在此方法中再去这样操作了,由于Vue.js的特点是无需操作DOM元素,直接使用数据来驱动界面更新,当使用原生JS的语法来获取DOM元素时,无论是系统自带的HTML标签还是<自定义组件>,我们只能获取到其所对应的DOM元素,无法获取到<自定义组件>的VueComponent实例对象,也就是说Vue.js官方不推荐我们使用原生JS的语法来获取DOM元素,所以我们可以通过添加ref属性来获取系统自带的HTML标签/<自定义组件>所对应的DOM元素/VueComponent实例对象,在此界面渲染完毕时,当ref属性添加给系统自带的HTML标签时(比如<p ref=“myppp”>我是原生的DOM</p>),在其所属组件的配置对象中可以获取(返回)其所对应的DOM元素,格式为“console.log (this.$refs.myppp);(//当此HTML标签使用了v-for指令时,返回值是所有DOM元素所组成的数组)”,当ref属性添加给<自定义组件>时(比如<one ref=“myOne”></one>),在其父组件的配置对象中可以获取(返回)其所对应的VueComponent实例对象,格式为“console.log (this.$refs.myOne);  console.log (this.$refs.myOne.msg);  this.$refs.myOne.say ();(//msg和say分别是其data和methods对象中的属性和方法,当想要通过this.$refs.myOne的形式来获取其根标签所对应的DOM元素时,格式为“console.log (this.$refs.myOne.$el);”)”,无论ref属性添加给系统自带的HTML标签还是<自定义组件>,给同一个界面的不同标签所添加的ref值一定不要出现雷同,否则所获取到的只是此界面上的最后一个DOM元素/VueComponent实例对象,Vue.js渲染自定义组件有两种方式,第一种方式被渲染的自定义组件必须经过注册,即在父组件的根标签中,把所注册的自定义组件当做自定义HTML标签来使用,但不会将父组件的根标签整体覆盖掉,第二种方式被渲染的自定义组件可以不用经过注册,即在父组件的配置对象中添加一个render方法,使用此方法来渲染,会将父组件的根标签整体覆盖掉,例如“render: function (createElement) {return createElement (“one”);}(//render值也可以换成“c => c (“one”)”,“one”是一个组件的名称,也可以将“one”换成此组件的配置对象)”,给Vue类对象的prototype对象动态的添加方法(也可以看做是实例方法)的格式为“Vue.prototype.$abc = function () {alert (123);};(//$abc是方法的名称,在任意一个自定义组件配置对象的函数作用域中都可以通过“this.方法名称 ();”的形式来调用)”,Vue/VueComponent实例对象调用它的$nextTick方法可以在此界面渲染完毕时执行所传入的回调函数,例如“this.$nextTick (function () {});”

三、Vue-CLI

Vue-CLI(Command Line Interface)是Vue.js官方提供的命令行脚手架工具,此工具已经帮我们搭建好了一套利用webpack管理的vue项目结构,全局安装Vue-CLI的流程是首先下载并安装NodeJS,网址是nodejs.org/zh-cn,NodeJS不是一个.js文件,而是JS的一种运行环境,即用Google V8引擎封装成的一个web服务器端的JS解释器,可以运行特定编程语言所编写的应用程序的平台就叫做运行环境,Windows、Linux、MacOS、iOS、Android这些都是运行环境,比如可以在Windows/Linux运行环境中运行通过C/C++所开发的应用程序,可以在MacOS/iOS运行环境中运行通过Object-C/Swift所开发的应用程序,安装了NodeJS之后,JS的运行就可以不用依赖web浏览器了,而NodeJS中包含npm(NodeJS Package Manager),即NodeJS的包管理工具,是专门用来管理远程代码仓库中的js文件的,每个js文件都有自己的唯一标识,当用户想要使用某个js文件时,只需引用对应的标识(命令),npm就会将其下载下来,安装完NodeJS之后,可以输入命令“node -v”敲回车来查看已安装的NodeJS的版本号,可以输入命令“npm -v”敲回车来查看其所包含的npm的版本号,由于npm下载插件是从国外服务器下载,受网络影响比较大,可能出现网络异常,所以淘宝团队就做了一个完整的npmjs.org镜像,叫做cnpm,同步频率目前为十分钟一次,以保证尽量与官方服务同步,我们可以用cnpm来代替npm,可以输入命令“npm install -g cnpm --registry=https://registry.npm.taobao.org”敲回车来全局安装一个cnpm,然后输入命令“npm i -g @vue/cli(//把i改成uninstall就代表卸载)”敲回车来全局安装Vue-CLI,耐心等待一会,比较耗时,安装完成后,可以输入命令“vue -V”/“vue --version”敲回车来查看所全局安装的Vue-CLI的版本号(另外,可以输入命令“npm list -g --depth 0(//--depth 0是用来限制层数的,否则会将模块依赖的模块也一并输出出来)”敲回车来查看都通过npm全局安装了什么插件,还可以输入命令“npm list 插件名字 -g”敲回车来查看某个全局安装插件的具体信息),然后输入命令“npm config get prefix”敲回车来查看npm配置文件的路径,比如路径是“C:\Users\刘远舟\AppData\Roaming\npm”,将其复制,然后右击“此电脑”,点击属性->高级系统设置->高级->环境变量->系统变量,双击“Path”,点击“新建”,然后将所复制的路径粘贴上,点击确定即可,然后打开Windows Powershell(管理员),输入命令“set-ExecutionPolicy RemoteSigned”敲回车,再输入y敲回车即可,然后打开vscode,点击终端->新终端->当前目标文件夹,输入命令“vue create 项目文件夹名称”敲回车来创建vue项目,这时会问你“您到默认npm注册表的连接似乎很慢,是否使用https://registry.npm.taobao.org更快的安装?”,输入y敲回车即可,然后会弹出几个选项,“Manually select features”代表手动配置,其余选项代表默认配置,比如我们选择了手动配置,会弹出一个选项列表,其中“Babel”和“Linter/Formatter”是系统默认选中的,还需要配置哪一项就按上下箭头键调整光标位置,然后按空格键就可以将其选中了,比方说我们又选择了Router、Vuex、和CSS Pre-processors,选好了之后敲一下回车就进入到了下一步,然后会问你“路由器是否使用历史记录模式?”,输入y敲回车,然后会让你选择一种CSS预处理器,Sass/SCSS(with dart-sass)和Sass/SCSS(with node-sass)就是性能上的差异,前者会快一些,选择前者就可以了,然后会问你选择“Linter/Formatter”的哪种规范,选项分别是“ESLint + Airbnb config”、“ESLint + Standard config”、和“ESLint + Prettier”,选择“ESLint + Standard config(//标准的规范)”就可以了,然后会让你“挑选lint的额外功能”,第一个选项是“Lint on save”,第二个选项是“Lint and fix on commit(requires Git)”,两个都选中就可以了,然后敲一下回车进入下一步,它会问你“以上这些配置信息是分别放到专用的配置文件中还是直接放到package.json中?”,为了方便后续管理,选择前者就可以了,最后它会问你“为了将来更方便的创建项目,是否将以上我们所选择的配置保存为默认?”,可以输入n,敲一下回车,然后就可以根据我们前面所选择的配置使用Vue-CLI去创建vue项目结构了,耐心等待一会,比较耗时,当出现了“Successfully created project...”时,就代表已经创建好了,这时在我们的目标文件夹中就可以看到我们所创建的vue项目文件夹了,双击打开它,会看到很多文件夹,node_modules文件夹中存储的是此vue项目所依赖的各种库(插件),public文件夹中存储的是当前的网页(index.html)及网页的图标,还可以放置永远不会改变的静态的资源/webpack所不支持的第三方库,当以后npm run build时,会将它们直接复制到dist文件夹中,而不会经过webpack的处理,我们需要通过绝对路径来引用它们,src文件夹中存储的是此项目的源码,assets子文件夹用来存储经过webpack处理的静态资源(图片/字体图标等),比如会对它进行压缩等,components子文件夹用来存储一些自定义的小/公共vue组件(xxx.vue),views子文件夹用来存储一些自定义的大/页面级/路由级vue组件(xxx.vue),router子文件夹用来存储路由相关的代码文件,首先创建一个index.js文件,然后在此文件的开头写上例如“import Vue from ‘vue’;  import Router from ‘vue-router’;  import One from ‘../components/One.vue’;(//常量One是One.vue组件的配置对象,此种加载方式属于完整加载,即在访问网页时,无论此vue组件有没有被用到,都会被加载进来,所以性能不是很好,当想要按需加载时,格式为“const One = resolve => {import (‘../views/One’).then (module => {resolve (module);} );};”)  import Two from ‘../components/Two.vue’;  Vue.use (Router);”,中间跟过去的书写格式一样,当路由对象router的配置对象中mode值是“history”时,npm run build之后初次运行没有问题,但是网页不能刷新,一旦刷新页面上就会报一个404的错误,除非在web服务器上面进行一些额外的配置,结尾写上“export default router;(//常量router是所创建的路由对象,意思是export此路由对象router,也就是将此路由对象router暴露出去)”,store子文件夹用来存储Vuex相关的代码文件,首先创建一个index.js文件,然后在此文件的开头写上“import Vue from ‘vue’;  import Vuex from ‘vuex’;  Vue.use (Vuex);”,中间跟过去的书写格式一样,结尾写上“export default store;(//常量store是所创建的Vuex对象,意思是export此Vuex对象store,也就是将此Vuex对象store暴露出去)”,或者写完开头之后直接写上“export default new Vuex.Store ({配置对象});”即可,当index.js上的代码过于多时,非常不利于维护,所以我们可以化整为零,根据单一原则将配置对象中的每一项内容都写到一个单独的js文件上,可以在store文件夹下再新建一些比如state.js、mutations.js、actions.js、和getters.js等,在这些文件内的书写格式为“export default {key: value, ...};”,然后再分别import到index.js中并赋值给配置对象里的state、mutations、actions、和getters等属性就可以了,由于我们需要在actions.js中所定义方法的函数作用域中触发mutations.js中所定义的方法,而且在触发时需要填入方法名称的字符串,由于字符串的特点是写错不会提示,非常不利于我们去维护,所以我们一般将mutations.js中的方法名称const成一个常量(当想要使用常量的名称来充当方法的名称时,必须加上中括号,否则是会报错的,会说你只定义未使用,但是在“this.$store.commit (‘xxx’);”/借助mapMutations辅助函数做映射的时候,正常写就可以,不用加中括号),可以在store文件夹下再新建一个mutations-type.js文件,在此文件中书写的格式为“export const SET_FULL_SCREEN = ‘SET_FULL_SCREEN’;”,然后再分别import到mutations.js和actions.js中并使用就可以了,格式为“import {SET_FULL_SCREEN} from ‘./mutations-type’;”,App.vue是render在根实例组件的根标签内部的一个vue组件,书写格式为“export default {name: ‘App’};(//大括号中为配置对象,意思是export此配置对象,也就是将此配置对象暴露出去)”,其根标签的id值必须与根实例组件根标签的id值相同,这样一来,通过render方法将根实例组件的根标签整体覆盖掉之后,自己就成为真正的根组件了,给vue文件中的<style>添加一个scoped属性,不用设置取值,可以使此CSS样式代码只适用于当前的这个vue文件(即局部样式),添加一个“lang=“scss””可以在这对<style>中书写sass代码,main.js是整个项目的JS入口,我们首先在main.js中import“Vue类对象”、“App.vue的配置对象”、“Vuex对象store”、和“路由对象router”,格式为“import Vue from ‘vue’;(//常量Vue是Vue这个类对象)  import App from ‘./App.vue’;(//常量App是App.vue的配置对象)  import store from ‘./store/index.js’;  import router from ‘./router/index.js’;”,然后创建Vue实例对象(即根实例组件),然后将store和router通过store和router属性挂载到其配置对象中,并render“App.vue”,即“render: c => c(App)”,当new出来一个实例对象却并没有使用它时,根据eslint的标准规范会报错,只需要在根实例组件上方添加一句“/*eslint-disable no-new*/”就可以了,当Vue-CLI的版本是1.x/2.x时,还会生成build和config文件夹,这两个文件夹中存储了webpack相关的配置,我们可以针对项目需求修改webpack配置,当Vue-CLI的版本是3.0及3.0以后时,就不会生成build和config文件夹,Vue.js官方这么做的目的是为了化繁为简,让初学者不用关心webpack,因为Vue-CLI已经帮你配置好了,并把webpack的配置文件隐藏起来了,所以只需要关心如何使用Vue.js就可以了,但是有时候默认的配置可能无法满足我们的需求,这时我们可以在项目的根目录下新建一个“vue.config.js”文件来修改webpack配置,Vue-CLI为了方便起见对webpack原生的属性进行了一层封装,可以查询网址“cli.vuejs.org/zh/config”来查看封装之后的属性是否能够满足我们的需求,当封装之后的属性可以满足我们的需求时,就使用封装之后的属性来修改webpack的配置,比如我们想要修改npm run build之后的目录名称,使之从dist变为bundle,我们可以在vue.config.js的配置对象中添加一句“outputDir: ‘bundle’”,当封装之后的属性不可以满足我们的需求时,就在vue.config.js的配置对象中添加一个configureWebpack对象,并在其中编写原生的webpack配置就可以了,比如我们想要添加一个展示版权的插件(内置插件,即在打包之后的文件中添加一句注释,告诉外界此代码是谁写的,参考网址是www.webpackjs.com/plugins/banner-plugin),我们可以首先在vue.config.js中导入webpack,格式为“const webpack = require (‘webpack’);”,然后在configureWebpack对象中添加一个plugins数组,在plugins数组中写上比如“new webpack.BannerPlugin ({banner: ‘知播渔教育-www.it666.com’})”就可以了,当我们的编辑器是vscode时,首先进入vscode的应用商店,给我们的编辑器安装一个eslint插件,然后进入vscode的终端,必须在此vue项目的根目录下,输入命令“npm i eslint --save-dev(//--save-dev可以用-D代替,代表此插件是在开发环境中所要用到的,而--save可以用-S代替,代表此插件是在生产环境中所要用到的)”敲回车来给我们的vue项目局部安装一个eslint插件(另外,在我们vue项目的根目录下可以输入命令“npm list --depth 0”敲回车来查看都给此vue项目局部安装了什么插件,还可以输入命令“npm list 插件名字”敲回车来查看所局部安装的某个插件的具体信息),切记一定不能全局安装eslint插件,因为此vue项目中包含很多跟eslint相关的插件,比如eslint-plugin-html、eslint-plugin-vue、和.eslintrc.js等等,并且全都是局部安装的,必须保持统一才能好使,并且一旦全局安装,就将eslint插件安装到了其它路径了,然后我们进入vscode的settings.json,在里面加上一句““eslint.autoFixOnSave”: true”,这样一来,在书写项目源代码时,当出现了不符合规范的格式时,一保存就自动修复了,当我们的编辑器是webstorm时,进入文件->设置,在左上角搜索eslint,找到之后点击一下,然后在右侧点击“Manual ESLint configuration”,然后看这一项“ESLint package”的路径是否是“我们vue项目文件夹\node_modules\eslint”,是的话就不用管它,然后点击确定即可,这时在编写代码的区域点击鼠标右键,就会看到有一项叫做“Fix ESLint Problems”,只要一点它,就自动修复了,当想要让此vue项目在运存中打包运行时,在我们项目的根目录下输入命令“npm run serve(//这里的serve指的就是使用webpack-dev-server(一个小型的NodeJS Express服务器)来打包运行)”敲回车(另外,可以使用命令“cd 项目文件夹名称(//cd是change directory的简写,改变目录)”将路径改成我们项目的根目录,另外,“cd ..”代表返回上一级目录,另外,按Ctrl+C并输入y敲回车代表取消/终止当前的操作,另外,cls代表清除屏幕上的所有显示,光标置于屏幕左上角,即CLear Screen),然后等待一会,出现了Local和Network网址就代表已经打包好了,我们可以点击一下local网址,就可以运行此打包好的vue项目了(在同一WiFi下的手机web浏览器同样可以访问此网址),由于是打包到了运存中,所以在我们的项目中就找不到dist文件夹,当想要让此vue项目在硬盘中打包运行时,首先需要在项目的根目录下新建一个vue.config.js文件,然后在里面写上“module.exports = {publicPath: ‘./’(//部署应用包的基本URL,不设置可能会出现打包后项目找不到资源的问题)};”,然后在我们项目的根目录下输入命令“npm run build(//这里的build指的就是使用webpack-dev-build来打包运行)”敲回车,然后等待一会,出现了DONE和INFO就代表已经打包好了,这时在我们的项目文件夹中就可以看到dist文件夹了,里面所有的文件都已经压缩过并进行了代码分割,ElementUI是饿了么前端团队推出的一款基于Vue.js的PC端UI框架,跟bootstrap一样,对原生的HTML标签进行了封装和美化,使用ElementUI可以让我们能够专注于业务逻辑而不是UI界面,官网是element.eleme.cn/#/zh-CN,进入官网,点“组件”,然后就看到安装方法了,首先在我们vue项目的根目录下输入命令“npm i element-ui -S”敲回车来局部安装ElementUI,当想要完整import时,需要在main.js文件中写上“import ElementUI from ‘element-ui’;  import ‘element-ui/lib/theme-chalk/index.css’;  Vue.use (ElementUI);”,然后就可以在官方文档中查看具体使用方法了,完整import的弊端是,无论我们有没有使用到某个组件,在npm run build时都会将elementUI中所有的组件都打包到dist文件夹中,这样就导致了dist文件夹的体积比较大,用户访问比较慢,为了解决这个问题,elementUI推出了按需import,使用什么组件就import什么组件就可以了,这样一来,在npm run build时只会将我们所import的组件打包到dist文件夹中,没有import的组件则不会被打包,这样dist文件夹的体积就减小很多了,当想要按需import时,首先需要在我们vue项目的根目录下输入命令“npm install babel-plugin-component -D”敲回车来局部安装babel-plugin-component插件,然后通过vscode编辑器打开我们vue项目根目录下的babel.config.js文件,将配置对象中的presets数组改成二维数组,然后在其内部的一维数组中再添加上一个{‘modules’: false}元素,然后在配置对象中再添加一个plugins数组(详见ElementUI官网->组件->快速上手),然后在main.js文件中写上例如“import {Row, Slider, Button} from ‘element-ui’;  Vue.use (Row);  Vue.use (Slider);  Vue.use (Button);”,另外,在main.js中可以加上一句“Vue.config.productionTip = false;(//阻止显示生产模式的消息)”,这样一来,在控制台就不会显示“You are running Vue in development mode.Make sure to turn on production mode when deploying for production.”了,MintUI是饿了么前端团队推出的一款基于Vue.js的移动端UI框架,跟bootstrap一样,对原生的HTML标签进行了封装和美化,使用MintUI可以让我们能够专注于业务逻辑而不是UI界面,官网是mint-ui.github.io/#!/zh-cn,进入官网,点击“开始使用”->中文(Vue2.0版),然后就看到安装方法了,首先在我们vue项目的根目录下输入命令“npm i mint-ui -S”敲回车来局部安装MintUI,当想要完整import时,需要在main.js文件中写上“import MintUI from ‘mint-ui’;  import ‘mint-ui/lib/style.css’;  Vue.use (MintUI);”,当想要按需import时,首先需要在我们vue项目的根目录下输入命令“npm install babel-plugin-component -D”敲回车来局部安装babel-plugin-component插件,然后通过vscode编辑器打开我们项目根目录下的babel.config.js文件,将配置对象中的presets数组改成二维数组,然后在其内部的一维数组中再添加上一个{‘modules’: false}元素,然后在配置对象中再添加一个plugins数组,详见MintUI官网->开始使用->中文(Vue2.0版)->Quickstart,但是要将plugins数组去掉一层,从三维数组变成二维数组,然后在main.js文件中写上例如“import {Button, Switch} from ‘mint-ui’;  import ‘mint-ui/lib/style.css’;  Vue.component (Button.name, Button);  Vue.component (Switch.name, Switch);(//当按需import时,也要import style.css文件,并且只能用Vue.component这样的格式)”,Vant是有赞前端团队推出的一款基于Vue.js的移动端UI框架,跟bootstrap一样,对原生的HTML标签进行了封装和美化,使用Vant可以让我们能够专注于业务逻辑而不是UI界面,官网是youzan.github.io/vant/#/zh-CN,进入官网,点击“快速上手”,然后就看到安装方法了,首先在我们vue项目的根目录下输入命令“npm i vant -S”敲回车来局部安装Vant,当想要按需import时,首先需要在我们vue项目的根目录下输入命令“npm i babel-plugin-import -D”敲回车来局部安装babel-plugin-import插件,然后通过vscode编辑器打开我们项目根目录下的babel.config.js文件,然后在配置对象中再添加一个plugins数组(详见Vant官网->快速上手->“对于使用 babel7 的用户,可以在babel.config.js中配置”),然后在main.js文件中写上例如“import {NavBar, Card, SubmitBar} from ‘vant’;  Vue.use (NavBar);  Vue.use (Card);  Vue.use (SubmitBar);”,当某一个vue组件/功能经常需要用到时,我们就可以将此vue组件/功能定义成一个基于Vue.js的插件(xxx.js),比如网络加载指示器,首先在src文件夹下创建一个plugin子文件夹,然后在此plugin子文件夹下再创建一个名称自定义的插件文件夹,此插件文件夹中可以放置一些vue文件,然后在此插件文件夹下再创建一个index.js文件用来书写此插件的JS代码,例如“import Loading from ‘./Loading.vue’;  export default {install: function (Vue, Options) {Vue.component (Loading.name, Loading);};(//参数1接收到的是Vue这个类对象,参数2接收到的是use方法的参数2)”,Vue类对象调用它的use方法可以在参数1插件是一个对象时给此vue项目的源代码中添加一个基于Vue.js的插件(当参数1插件是一个函数时,直接import就可以,无需use),例如“import Loading from ‘./plugin/loading/index.js’;(//常量Loading是此插件的配置对象)  Vue.use (Loading, {title: ‘皇帝不急太监急’});(//use方法需要在new Vue之前调用,在调用的时候会自动执行参数1插件配置对象里的install方法,并且参数2会传给install方法的参数2)”

四、实战项目--利用网易云音乐NodeJS版API来写一个音乐应用

当所有的人都通过it666的web服务器(即远程web服务器)向网易云音乐的web服务器发送数据请求时,就一定会产生请求过频的问题,这时网易云音乐的web服务器就会认为是在窃取它的数据,所以它就会短时间的禁封it666的web服务器(即远程web服务器)的ip,这时就会返回一个460的错误,解决的办法就是将it666的web服务器部署到本地,这样一来,我们请求数据就可以通过本地web服务器来发送请求了,由于本地web服务器只能我们自己使用,所以向网易云音乐的web服务器发送请求的频率就变低了,这样一来就能大大降低网易云音乐的web服务器禁封我们ip的概率了,由于it666的web服务器端的程序是使用NodeJS编写的,所以就必须在本地安装NodeJS,安装完之后进入“网易云音乐NodeJS版API”官网,网址是musicapi.leanapp.cn,然后点击查看文档->右上角的小猫,进入GitHub,然后将压缩包下载下来,解压缩之后双击打开此文件夹,将此窗口的地址栏中的地址改成cmd,然后敲回车,输入命令“npm install”敲回车来将此NodeJS项目所依赖的插件和包都安装到此文件夹,然后输入命令“node app.js”敲回车,当看到server running时,就代表这个本地web服务器已经部署好了,然后我们可以尝试着登录一下server running后面跟的网址,可以将网址中的localhost换成127.0.0.1,然后进入我们自己的vue项目中,找到我们自己封装好的网络工具类api.js,将地址换一下,例如“axios.defaults.baseURL = “http://127.0.0.1:3000”;(//设置全局的URL根地址)”,然后初始化index.html文件中的代码,比如设置国产web浏览器和IE浏览器的兼容、添加搜索优化的三大标签、和设置在苹果设备的safari浏览器下的一些快捷图标(详见public文件夹中的index.html文件),然后通过JS代码利用“rem+视口缩放”的方式来动态的适配当前移动端设备,格式为“<script>let scale = 1.0 / window.devicePixelRatio;(//计算缩放的大小,window对象访问它的devicePixelRatio属性可以返回当前设备的像素密度比)  let text = `<meta name=“viewport”content=“width=device-width, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no”>`;(//通过JS代码动态的设置视口,initial-scale、maximum、和minimum-scale分别代表初始、最大、和最小缩放比)  document.write (text);  document.documentElement.style.fontSize = window.innerWidth / 7.5 + “px”;(//通过JS代码动态的设置根标签即<html>的font-size值)  document.documentElement.setAttribute (‘data-dpr’, window.devicePixelRatio + ‘’);  document.documentElement.setAttribute (‘data-theme’, ‘theme’);</script>”,由于npm run build时会借助html-plugin插件将.html文件中的代码拷贝到dist目录,但是当.html文件的字符串模板中出现了变量时,html-plugin插件就无法处理了,所以打包时就会报错,这时就需要局部安装一个html-loader插件,登录npmjs官网,网址是“npmjs.com”,然后搜索html-loader,就看到安装命令了,可以输入命令“npm install -D html-loader”敲回车来给我们的vue项目局部安装一个html-loader插件,然后还需要进入vue.config.js文件配置一下html-loader插件(详见vue.config.js文件),由于适配移动端需要将px转成rem(数值除以100),所以需要借助postcss-pxtorem插件将px自动转成rem,可以输入命令“npm i -D postcss-pxtorem”敲回车来给我们的vue项目局部安装一个postcss-pxtorem插件,然后在vue项目的根目录下新建一个postcss.config.js文件,配置一下postcss-pxtorem插件就可以了(详见postcss.config.js文件),使用Vue-CLI创建的vue项目已经自动帮我们实现了CSS3和ES678语法webkit内核的兼容,就不用再借助webpack实现了,但是我们可以在vue项目的根目录下的.browserslistrc文件中再配置一下各种web浏览器的兼容(详见.browserslistrc文件),然后需要安装fastclick插件来解决移动端100~300ms的点击事件延迟问题,可以输入命令“npm install fastclick”敲回车来给我们的vue项目安装一个fastclick插件,然后在main.js中import并配置此插件,格式为“import fastclick from ‘fastclick’;  fastclick.attach (document.body);”,然后设置默认的全局样式,在assets文件夹下新建一个css文件夹,然后将base.scss、mixin.scss、reset.scss、和variable.scss(定义了一些常用的变量)文件放入其中,在base.scss中@import reset.scss、variable.scss、和mixin.scss,然后在main.js中import base.scss,格式为“import ‘./assets/css/base.scss’;”,在移动端开发中,一般情况下我们需要让字体大小在任何屏幕尺寸和像素密度比下都保持不变,但由于我们是通过“rem+视口缩放”来适配移动端的,所以当我们将font-size值写死时,屏幕大小和字体大小是正相关的,解决的方式是首先让postcss-pxtorem插件只转换width和height属性,也就是打开postcss.config.js文件,将propList数组由[‘*’]修改成[‘width’, ‘height’],其实这样比较麻烦,因为当以后我们再想转换其它CSS属性时,就得一个一个手动向里添加,所以我们可以不用做任何修改,当某个CSS属性值不想被转换时,就将单位名称px的首字母大写就可以了,然后当像素密度比是2时,就让font-size值放大两倍,当像素密度比是3时,就让font-size值放大三倍,也就是调用mixin.scss文件中封装好的font_size混合,再赋值给font-size属性就可以了,当我们需要向web服务器请求数据时,可以使用axios插件,所以可以输入命令“npm install axios”敲回车来给我们的vue项目安装一个axios插件,安装好之后我们可以将axios.get、axios.post、和axios.all封装到一个所export的对象中,在src文件夹下新建一个api文件夹,在api文件夹下新建一个network.js文件并打开它,首先需要import axios类对象,格式为“import axios from ‘axios’;”,然后需要进行一些全局的配置,例如“axios.defaults.baseURL = “http://127.0.0.1:3000”;(//设置全局的URL根地址)  axios.defaults.timeout = 3000;”,然后利用axios类对象在所export的对象中封装自己的get、post、和all方法(详见network.js文件),然后为了方便我们后续管理,在api文件夹下再新建一个index.js文件用于定义向web服务器数据接口的URL来get/post/all请求各种数据的函数,方便后期直接调用(各种数据接口的URL请详见网易云音乐api的官方文档binaryify.github.io/NeteaseCloudMusicApi/#),打开index.js文件后,首先import network.js文件中所export的自己封装的对象,格式为“import network from ‘./network.js’;”,然后将所定义的一些具名函数export出去(即暴露出去),例如“export const getBanner = () => Network.get (‘banner?type=2’);(//向web服务器请求<banner>的数据)”,然后找到相应的vue组件,例如<Recommend>,然后在<Recommend>的一对<script>中import相应的具名函数,格式为“import getBanner from ‘../api/index.js’;”,然后在<Recommend>的配置对象中的created方法的函数作用域中调用一下相应的具名函数,格式为“created () {getBanner ().then (data => console.log (data) ).catch (err => console.log (err) )}”,向web服务器所请求到的<banner>数据可以使用Swiper插件(一款触摸滑动插件)来展示(轮播),首先进入Swiper的官网www.swiper.com.cn,中文教程->在Vue中使用Swiper,然后找到Install,就看到安装方法了,可以输入命令“npm install swiper vue-awesome-swiper --save”敲回车来给我们的vue项目局部安装一个Swiper插件,找到Global Registration和Local Registration就看到如何全局import(即在main.js中import,此vue项目的所有vue组件就都可以使用了)和局部import(即在某个vue组件中import,只有某个vue组件可以使用)了,然后找到Component就看到如何搭建它的HTML结构了,然后在<Banner>中按照教程局部import Swiper插件,格式为“import ‘swiper/dist/css/swiper.css’;  import {swiper, swiperSlide} from ‘vue-awesome-swiper’;”,并将所import的<swiper>和<swiperSlide>的配置对象注册为<Banner>的局部组件,然后在data对象中配置一下swiperOption(详见Banner.vue文件),然后按照教程及自己的需求搭建一下HTML的三层结构,Swiper插件有一个bug,那就是当数据是从网络获取的时,自动轮播到最后一页就不轮播了,解决方式是在渲染<swiper>时,给它添加一个“v-if=“数据.length > 0””就可以了,当想要覆盖掉swiper.css中所设置的某个CSS样式时,<style>一定不能设置scoped属性,当原有的<style>设置了scoped属性时,可以在下面添加一对新的没有设置scoped属性的<style>(即全局样式),然后书写CSS样式代码就可以了,比如我们修改了分页器内部小圆点的CSS样式,当在<PlayerMiddle>中再次复用Swiper插件时,就没法再次使用相同的类名修改分页器内部小圆点的CSS样式了,这时只能使用自定义类名的方式,详见www.swiper.com.cn/api/pagination/79.html(分页器内部默认状态小圆点的类名设置)、www.swiper.com.cn/api/pagination/80.html(分页器内部激活状态小圆点的类名设置)、和PlayerMiddle.vue文件中的具体配置,然后在页面级的vue组件<Recommend>中的一对<script>内import<Banner>这个小vue组件并将它注册为自己的局部组件,当想要实现图片的懒加载(即渐进式加载图片,比较节省流量和性能)时,需要安装一个vue-lazyload插件,可以输入命令“npm i vue-lazyload -S”敲回车来给我们的vue项目局部安装一个vue-lazyload插件,使用教程详见“www.npmjs.com/package/vue-lazyload”的“Usage”,首先需要在main.js中import并use此插件,格式为“import VueLazyload from ‘vue-lazyload’;  Vue.use (VueLazyload, {loading: require (‘./assets/images/loading.png’) } );(//可以通过配置loading属性来设置图片还未加载好之前的占位图片)”,然后将各个vue组件中的<img>的src属性连同v-bind指令整体换成v-lazy属性就可以了,当想要仅让指定的区域可以滚动时,需要安装一个iscroll插件,可以输入命令“npm install iscroll”敲回车来给我们的vue项目局部安装一个iscroll插件,我们可以将iscroll封装成一个vue组件(比如就叫做<ScrollView>,编写此vue组件时别忘了写上一个<slot>即插槽),将来什么内容需要滚动,就把什么内容嵌套在一对<ScrollView>中就可以了(使用的时候需满足三层结构,即任意一对根标签(即iscroll容器)内部嵌套一对<ScrollView>,然后一对<ScrollView>内部再嵌套需要滚动的内容就可以了),我们可以在components文件夹中新建一个ScrollView.vue组件,然后在此vue组件中import IScroll这个类,格式为“import IScroll from ‘iscroll/build/iscroll-probe’;(//iscroll的专业版本,可以监听滚动的位置和细节,扩展性更好)”,然后按照教程“caibaojian.com/iscroll-5”搭建HTML结构并配置就可以了(详见ScrollView.vue文件),当想要让iscroll容器中的内容可以滚动时,iscroll容器(总之是包裹里边内容的上级HTML标签)的height值必须小于里边内容的height值,当iscroll容器中的数据是从本地/远程web服务器上获取的时,渲染完毕之后必须刷新一下,否则无法滚动,格式为“this.iscroll.refresh ();(//iscroll是IScroll实例对象)”,当想要监听iscroll容器中的内容是否全都渲染完毕时,可以使用MutationObserver实例对象,即观察者对象,网址是developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver,观察者对象可以监听某个DOM元素的子/后代节点对象及其属性的变化,所以我们可以使用观察者对象来监听<ScrollView>所对应DOM元素(只需要给<ScrollView>的根标签设置一下ref属性就可以获取到了)的子节点对象等的变化,只要发生了变化,就refresh一下就可以了,首先第一步,new一个观察者对象,格式为“let observer = new MutationObserver ((mutationList, observer) => {this.iscroll.refresh ();} );(//形参mutationList接收到的是所有发生变化的子/后代节点对象所组成的数组,形参observer接收到的是此观察者对象,只要被监听的内容发生了变化,就会自动执行所传入的回调函数)”,然后第二步,创建一个所监听内容的配置对象,格式为“let config = {childList: true(//监听子节点对象的变化), subtree: true(//监听后代节点对象的变化), attributeFilter: [‘height’, ‘offsetHeight’](//监听指定JS属性的变化)};”,最后第三步,告诉第一步所创建的观察者对象到底监听哪一个DOM元素,观察者对象调用它的observe方法可以监听参数1DOM元素的参数2配置对象,格式为“observer.observe (this.$refs.wrapper, config);”,完事之后滚动起来可能会非常卡顿,因为系统默认监听了拖拽事件,而iscroll插件也监听了拖拽事件,这样就会导致发生冲突,此时需要将系统默认的拖拽事件禁用掉,在base.scss中添加上一句“html, body {touch-action: none;}”就可以了,完事之后可能还是会有一丢丢的卡顿,只需要再给<ScrollView>增加一些配置就可以了(详见ScrollView.vue文件),这时就没有卡顿的问题了,但是一滚动就会将头部内容(<Header>和<Tabbar>)盖住,只需要给<ScrollView>的父标签(即iscroll容器)设置一个“overflow: hidden;”就可以了,当想要监听iscroll容器中内容滚动的距离值时,需要利用IScroll实例对象调用它的on方法(可以参考caibaojian.com/iscroll-5/customevents.html),详见<ScrollView>中的配置信息及on方法的使用,在很多界面上(比如歌单详情界面、专辑详情界面、最新歌曲列表、歌手详情界面、和歌曲搜索结果界面等)都需要控制默认播放器界面<NormalPlayer>的显示/隐藏,所以我们应该在一个全局共享变量中去控制,即使用Vuex对象来共享数据,当想要实现transition时,可以使用<transition>并配合velocity.js和velocity.ui.js插件,可以输入命令“npm install velocity-animate”敲回车来给我们的vue项目局部安装velocity.js和velocity.ui.js插件,import的格式为“import Velocity from ‘velocity-animate’;  import ‘velocity-animate/velocity.ui’;”,velocity.ui.js插件的使用方式可以参考官网“shouce.jb51.net/velocity/feature.html”->Plugins插件,然后找到“1-2内置特效”就可以看到了,使用的格式为“enter (el, done) {Velocity (el, ‘transition.shrinkIn’, {duration: 500}, function () {done ();} );}, leave (el, done) {Velocity(el, ‘transition.shrinkOut’, {duration: 500}, function () {done ();} );}(//切记,在enter钩子函数中,当transition执行完毕后,一定要“done ();”一下,否则leave钩子函数的transition不会生效,‘transition.shrinkIn’和‘transition.shrinkOut’特效可以换成任意特效,详见官网)”,当想要在手机真机上查看网页的控制台时,需要安装一个vconsole插件,可以输入命令“npm install vconsole”敲回车来给我们的vue项目安装一个vconsole插件,然后在main.js中import并use此插件,格式为“import VConsole from ‘vconsole’;  const vconsole = new VConsole ();  Vue.use (vconsole);”,然后在网页的右下角就会看到一个叫做vConsole的绿色按钮了,当整个vue项目都写完之后,就该将我们的vue项目打包上架了,首先输入命令“npm run build”敲回车来将我们的vue项目在硬盘中打包,打包完成之后,将dist文件夹中的所有文件都上传到web服务器的www文件夹中就可以了(上架),我们可以模拟一下上架,首先双击打开wamp软件,然后找到它的安装目录,进入www文件夹,然后将dist文件夹中的所有文件复制粘贴到www文件夹中就可以了,只有一个html文件的web应用就叫做单页web/SPA应用(Single Page web Application),所有的内容都在此html文件中呈现,并且此html文件只会加载一次,当用户与应用程序交互时,会动态更新页面来呈现不同的内容,我们通过Vue-CLI开发的项目其实就是典型的SPA应用,SPA应用的优点是具有良好的交互体验,不会重新加载整个html网页,只是局部更新,可以实现前后端分离开发,web浏览器负责页面的呈现与交互,而web服务器只需要负责输出数据,不需要负责填充数据,这样一来,既可以减轻web服务器的压力,也可以使多套前端程序共用一套后端程序,提升后端程序的复用性,SPA应用的缺点是SEO难度较高,因为只有一个html页面,无法针对不同的界面编写不同的SEO信息,当所有组件都采用完整加载的方式时,初次加载耗时较多,三种常见的网页渲染方式是客户端渲染、服务端渲染、和预渲染,web服务器只负责输出数据,web浏览器来负责填充数据的渲染方式就叫做客户端渲染(即CSR,Client Side Render),SPA应用就是典型的客户端渲染,客户端渲染的完整流程是,首先第一步,web浏览器逐行渲染向web服务器所请求到的html文件并找到所依赖的css和js文件,然后第二步,web浏览器再次向web服务器请求所依赖的css和js文件,此js文件中包括向web服务器请求数据的JS代码,加载完毕之后继续渲染此html文件,最后第三步,web浏览器根据所请求到的数据来填充html网页,客户端渲染最大的优点是交互体验好可以做局部的更新,最大的缺点是首屏加载慢,因为要等到请求完html文件才会去请求css和js文件,并且要等到所请求到的css和js文件加载完毕之后,才会根据JS代码的执行结果向web服务器请求数据并填充html网页,不是很注重SEO的强交互页面,建议采用客户端渲染,web服务器既负责输出数据又负责填充数据的渲染方式就叫做服务端渲染(即SSR,Server Side Render),服务端渲染的完整流程是,首先第一步,web服务器查找web浏览器所请求的html文件并填充数据,然后第二步,web浏览器渲染所请求到的包含数据的html文件,并找到所依赖的css和js文件,最后第三步,web浏览器再次向web服务器请求所依赖的css和js文件,加载完毕之后渲染此html网页,服务端渲染最大的优点是首屏加载快,因为web浏览器所请求到的html文件已经包含数据,所以只需要再次请求css和js文件就可以直接渲染,由于每次所请求到的都是一个独立完整的html文件,所以更利于SEO,最大的缺点是网络传输的数据量比较大,因为请求的是包含数据的完整html文件,比较注重SEO的新闻和电商类网站,建议采用服务端渲染,无需web服务器实时动态编译,在npm run build时针对特定的路由界面简单的生成静态的html文件的渲染方式就叫做预渲染,本质就是客户端渲染,只不过跟SPA应用不同的是预渲染可以生成多个html页面,最大的优点是由于有多个html页面,所以更利于SEO,最大的缺点是预编译慢,首屏加载也慢,当只需改善少数界面的SEO时,建议采用预渲染,由于我们这个vue项目就属于一个SPA应用,是存在SEO问题的,所以我们可以采用预渲染的方式来解决这个问题,首先输入命令“vue add prerender-spa”敲回车来给我们的vue项目安装一个vue-cli-plugin-prerender-spa插件,参考网址是www.npmjs.com/package/vue-cli-plugin-prerender-spa,安装过程中会问你“当前存储库中有未提交的更改,建议先提交保存它们?”,输入y敲回车就可以了,然后会问你“你需要将哪些路由对应的界面生成一个单独的html页面?”,我们可以先跳过,一会儿在后面再配置,直接敲回车就可以了,然后还会问你很多问题,让你输入y/n,全程输入y敲回车就可以了,安装完成之后,系统会在两个地方自动添加代码,第一个地方是在main.js文件中Vue实例对象的配置对象中,会自动添加上一个键值对“mounted: () => document.dispatchEvent (new Event (‘x-app-rendered’) )”,第二个地方是在vue.config.js的配置对象中,会自动添加上一个pluginOptions对象,在其内部的renderRoutes数组中,我们就可以将需要生成单独html页面的路由地址添加到其中了(详见vue.config.js文件),当我们配置完renderRoutes数组之后,系统就会修改renderRoutes数组中所对应的所有vue文件的代码格式,使之不符合eslint的编码规范,这样就会使打包失败,所以我们只需要将这些vue文件的代码格式都自动修复一下就可以了,当给我们的vue项目安装了vue-cli-plugin-prerender-spa插件时,路由对象router的配置对象中的mode值就必须是“history”,否则打包会失败,由于预渲染的本质就是在npm run build时模拟一个web浏览器(即PhantomJS浏览器,一款无界面web浏览器)来提前访问所有生成的html页面,并将所生成的网页源代码写入到一个个html文件中,所以系统就获取到了一个虚假的像素密度比(即window.devicePixelRatio),进而通过JS代码就动态生成了一个错误的<meta>,所以当我们打包完毕之后真正运行网页时,只要一刷新网页,就会通过JS代码在原位置又动态生成一个正确的<meta>,而那个错误的<meta>在正确的之后,所以优先级更高,这样一来,网页的缩放比就出错了,网页就走形了,解决的方式是在预渲染的过程中就借助正则表达式匹配到那个错误的<meta>字符串并将其replace成‘’,也就是将其扼杀在摇篮里(参考网址是www.npmjs.com/package/vue-cli-plugin-prerender-spa的“User post processing function”部分,详见vue.config.js文件),当我们给此vue项目的源代码中添加了一个“正在载入...”的插件时,也是由于预渲染的缘故,此插件的HTML结构代码也会提前写入html文件中,也是同样的道理,当打包之后运行此网页时,只要一刷新,页面上就会一直显示这个“正在载入...”的图像,所以我们就需要想办法在预渲染的过程中就将提前生成的此插件HTML元素removeChild掉,但使用正则表达式来匹配一个带内容的HTML标签字符串比较困难,所以我们可以使用选择器来查找,但vue.config.js中的代码都是NodeJS代码,会在NodeJS的环境中执行,所以我们无法直接操作DOM,只能借助jsdom插件,此插件可以将网页源代码字符串转成document对象,然后就可以像以前一样操作DOM了(参考网址是www.npmjs.com/package/jsdom),可以输入命令“npm install jsdom --save-d”敲回车来给我们的vue项目安装一个jsdom插件,首先在vue.config.js中写上“const jsdom = require (‘jsdom’);  const {JSDOM} = jsdom;”,然后在postProcess方法中配置一下就可以了(详见vue.config.js文件),由于给每个html页面都单独编写SEO信息非常麻烦,也非常不方便我们去管理,所以就需要安装一个vue-meta-info插件来统一编写与管理SEO信息,网址是www.npmjs.com/package/vue-meta-info,可以输入命令“npm install vue-meta-info --save”敲回车来给我们的vue项目安装一个vue-meta-info插件,首先在main.js中import并use此插件,格式为“import MetaInfo from ‘vue-meta-info’;  Vue.use (MetaInfo);”,然后在我们vue项目的根目录下新建一个vue-meta-info.js文件,在此文件所export的配置对象中我们就可以编写所有需要生成html页面的vue文件的SEO信息了(详见vue-meta-info.js文件),然后在相应的vue文件中,我们就可以import vue-meta-info.js的配置对象,并将相应的SEO信息通过metaInfo属性挂载到此vue文件的配置对象中了,然后我们需要将public文件夹中的index.html中的SEO三大标签注释掉,因为系统会自动添加,所以我们就不需要手动添加了,否则系统就有可能添加不上去了,当一切都处理完毕之后,我们就可以通过npm run build来打包了,这时可能会比较慢,比较耗时,这是因为系统会去查找renderRoutes数组中所对应的所有vue文件都依赖哪些文件,然后将所依赖的文件都组合在一起生成一个新的html文件,所以查找和组合的过程是非常耗时的,当打包完成之后,在dist文件夹中我们会发现有很多的html文件,数量跟renderRoutes数组中元素的个数相同,然后我们需要将app.html重命名成index.html,然后就可以正常的上架了