6、vue
1.理解MVVM思想
- 什么是MVVM?
- MVVM是Model-View-ViewModel的缩写。
- MVVM最早由微软提出来,它借鉴了桌面应用程序的MVC思想。
- 在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。
- 把Model和View关联起来的就是ViewModel。
- ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。
2.vue.js介绍
-
Vue是一套用于构建用户界面的渐进式的轻量级框架
-
Vue是MVVM设计模式的具体实现方案
-
Vue只关注视图层,便于与第三方库或既有项目整合
-
Vue可以与现代化的工具链和各种类库结合使用,为复杂的SPA提供驱动
3.vue起步
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统。
数据和 DOM 已经被建立了关联,所有东西都是响应式的。
- el
- 类型:string | Element
- 限制:只在用 new 创建实例时生效。
- 详细:提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。
- 可以是 CSS 选择器
- 也可以是一个 HTMLElement 实例。
- 在实例挂载之后,元素可以用 vm.$el 访问。
- 如果在实例化时存在这个选项,实例将立即进入编译过程,否则,需要显式调用 vm.$mount() 手动开启编译。
- data
- 类型:Object | Function
- 限制:组件的定义只接受 function。
- 详细:Vue 实例的数据对象
- Vue 会递归地把 data 的 property 转换为 getter/setter,从而让 data 的 property 能够响应数据变化。
- 对象必须是纯粹的对象 (含有零个或多个的 key/value 对):浏览器 API 创建的原生对象,原型上的 property 会被忽略。
- 大概来说,data 应该只能是数据 - 不推荐观察拥有状态行为的对象。
Title
{{message}}
4.模板语法
-
Vue.js中使用的是基于html的模版语法,可以将Dom元素绑定至Vue实例中的数据。
-
数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值:
Message: {{ msg }} - 页面中模版即就是通过{{}}方式来实现模版数据绑定。
4.1 数据绑定的方式
-
单向绑定
-
方式1:使用两对大括号{{}}
-
方式2:使用v-text、v-html
-
-
双向绑定
-
v-model指令实现
-
5.指令
5.1. 指令(绑定)
-
v-text
- 预期:string 用法:数据绑定
- 更新元素的
textContent - 如果要更新部分的
textContent,需要使用{{ Mustache }}插值{{msg}}
-
v-html
- 预期:string 用法:数据绑定
- 更新元素的
innerHTML。注意:内容按普通 HTML 插入 - 不会作为 Vue 模板进行编译 - 如果试图使用
v-html组合模板,可以重新考虑是否通过使用组件来替代
-
v-model
- 预期:随表单控件类型不同而不同。
- 限制:
- 、
- 修饰符:
- .lazy - 取代 input 监听 change 事件
- .number - 输入字符串转为有效的数字
- .trim - 输入首尾空格过滤
- 用法:
- 在表单控件或者组件上创建双向绑定。
-
v-once?
- 数据只绑定一次,只渲染元素和组件一次。
单向绑定案例:
插入data中message属性名对应值:{{message}}
v-html指令插入html代码
v-text指令插入html代码
v-once绑定一次{{message}}
双向绑定案例:
Title
v-once绑定一次{{message}}
input的双向值{{message}}
男
女
gender:{{gender}}
吃
喝
玩
乐
hobbies:{{hobbies}}
city:{{city}}
你可以添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步:
message:{{message}}
可以自动去除用户输入的字符首尾的空白字符,可以给v-model添加trim修饰符:
message:{{message}}
转换用户输入的值为数值类型 ,可以给v-model天剑number修饰符:
message:{{message}}
5.2. v-bind(绑定)
- v-bind
- 使用v-bind指令进行DOM属性绑定
- v-bind:属性名="属性值" 等价于 :属性名="属性值"
-
style和class属性用法:
-
style: .txt{color:#ff0000;} .bgcolor{background-color: #ccc;}
-
html:
文本内容
-
-
v-bind属性数据绑定代码:
v-bind绑定属性值
v-bind绑定属性值(简化版)
- v-bind属性操作class和style的实例代码
- 我们可以传给
v-bind:class一个对象。 - 以动态地切换 class 也可以在对象中传入更多字段来动态切换多个 class。
- 我们可以传给
绑定class属性
绑定内联样式
绑定样式对象
5.3 v-if、v-else-if、v-else(条件渲染)
-
v-if、v-elseif、v-else
-
v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。
-
可以使用v-else-if指令来充当 v-if 的“else-if 块",可以连续使用。
-
可以使用 v-else 指令来表示 v-if 的“else块”。
-
v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别。
-
v-else-if 也必须紧跟在带 v-if 或者 v-else-if 的元素之后。
-
控制切换一个元素是否显示也相当简单:
-
-
实例代码
显示
哈哈
A
B
C
非ABC
5.4 v-for
- v-for??
- 基于源数据多次渲染元素或模板块,对数组或对象进行循环操作。
- 遍历一个数组和数组对象
- 我们可以用 v-for 指令基于一个数组来渲染一个列表。
- v-for 指令需要使用 item in items 形式的特殊语法
- items 是源数据数组,而 item 则是被迭代的数组元素的别名。
-
实例代码:
- {{item}}
-
索引为:{{index}} ; 元素值为:{{item}}
-
{{item.name}}
-
{{index}}--{{item.id}}--{{item.name}}
编号 用户名
{{item.id}}
{{item.name}}
5.5 v-on(绑定事件)
-
v-on
-
用来绑定事件
-
用法:v-on:事件="函数"
-
简写方式@事件="函数"
-
6.计算属性
-
计算属性
-
计算属性也是用来存储数据的,但具有以下几个特点:
-
数据可以进行逻辑处理操作
-
可以对计算属性中的数据进行监视
-
-
- 计算属性缓存 vs 方法
-
- 我们可以将同一函数定义为一个方法而不是一个计算属性。
- 两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要
message还没有发生改变,多次访问reversedMessage计算属性会立即返回之前的计算结果,而不必再次执行函数。 -
注意:
-
计算属性是基于它的依赖进行更新的,只有在相关依赖发生改变时才能更新。
-
计算属性是缓存的,只要相关依赖没有改变,多次访问计算属性得到的值是之前缓存的计算结果,不会多次执行
-
-
get和set方法
-
计算属性由两部分组成:get和set,分别用来获取计算属性和设置计算属性的操作。
-
默认只有get方法。
-
-
代码实例:
普通属性
{{message}}
{{message.split('').reverse().join('')}}
计算属性
{{reverseMessage}}
{{reverseMessage}}
绑定2次数据,缓存关闭控制台打印2次,说明调用了2次
{{myReverseMessage}}
{{myReverseMessage}}
{{reverseMessageMethod()}}
{{reverseMessageMethod()}}
7.监听属性
7.1 watch
监听属性watch它是一个对data的数据监听回调, 当依赖的data的数据变化时, 会执行回调。在回调中会传入newVal和oldVal两个参数。Vue实列将会在实例化时调用$watch(), 他会遍历watch对象的每一个属性。
watch的使用场景是:当在data中的某个数据发生变化时, 我们需要做一些操作, 或者当需要在数据变化时执行异步或开销较大的操作时. 我们就可以使用watch来进行监听。
watch分为普通监听和深度监听
7.2 普通监听
缺点:不能深度监听(对象属性的改变),刷新或首次加载不能执行。
普通监听代码示例
{{message}}
7.3. handler方法immediate属性
如上watch有一个特点是: 第一次初始化页面的时候, 是不会去执行age这个属性监听的, 只有当age值发生改变的时候才会执行监听计算. 因此我们上面第一次初始化页面的时候, 'message' 属性值默认为空字符串。那么我们现在想要第一次初始化页面的时候也希望它能够执行 'age' 进行监听, 最后能把结果返回给 'message' 值来。因此我们需要修改下我们的 watch的方法,需要引入handler方法和immediate属性
- handle:watch中需要具体执行的方法。
- immediate:true立即执行handle方法(首次加载、刷新、数据改变)都会执行,缺点监听不到对象属性中发生的改变。
代码如下所示:
{{message}}
7.4 deep属性
deep:true深度检测。例如数组对象中的某个属性改变执行handle方法。
watch里面有一个属性为deep,含义是:是否深度监听某个对象的属性的值, 该值默认为false。
信息:{{obj.message}}
年龄:
如上述代码, 如果我们不把 deep: true添加的话,当我们在输入框中输入值的时候,改变obj.age值后,obj对象中的handler函数是不会被执行到的。受JS的限制, Vue不能检测到对象属性的改变。它只能监听到obj这个对象的变化,比如说对obj变化操作会被监听到,但是对obj的内部属性的值变化监听不到。
7.5 watch 和 computed的区别是:
相同点:他们两者都是观察页面数据变化的。
不同点:computed只有当依赖的数据变化时才会计算, 当数据没有变化时, 它会读取缓存数据。 watch每次都需要执行函数。watch更适用于数据变化时的异步操作。
8.VUE生命周期钩子
8.1 vue数据是怎么驱动视图的
一堆数据放在那里是不会有任何作用的,它必须通过我们的View Model(视图模型)才能操控视图。
图中的model其实就是数据,一般我们写成js对象的格式;
中间的这个所谓View Model,就是
var vm = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
我们把数据放到了vm里,然后把各种directive(命令)放到视图里,这样我们就可以以vm为媒介,通过改变数据来改变视图。
误区
vm实际上是Vue这个类的一个实例,非常容易混淆的是,你会很容易的以为
vm.el == '#app';
vm.data== {
message: 'Hello Vue!'
};
vm.data.message =='Hello Vue!';
实际上这都是错误的,
以上虽然是错误的写法,有时候我们还是希望获得这些属性的,通过vm你需要这样写:
vm.$el === document.getElementById('app') //结果为true
vm.$data.message ==='Hello Vue!' //结果为true
这个$符代表的就是vm的真实属性了。
8.2 vue生命周期钩子
-
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
-
比如 created 钩子可以用来在一个实例被创建之后执行代码:
new Vue({
data: {
a: 1
},
created: function () {
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
}
})
// => "a is: 1"
-
vue实例从创建到销毁的过程,称为生命周期,共有八个阶段
message:{{message}}
简化版生命周期
9.VUE完整示例new vue()参数详解
-
1.1
el:"#id", //DOM成员-
提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。
-
-
1.2
template:"`", //DOM成员 -
一个字符串模板作为 Vue 实例的标识使用。模板将会 替换 挂载的元素。挂载元素的内容都将被忽略,除非模板的内容有分发 slot
-
-
1.3
render: (h)=>{h(App)}, //DOM成员-
字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。
-
-
1.4 data //数据成员
-
data():{ return{ } }, -
Vue实例的数据对象。Vue 将会递归将 data 的属性转换为 getter/setter,从而让 data 的属性能够响应数据变化
-
-
1.5 methods //数据成员
-
methods:{ func(){ } } -
methods将被混入到 Vue 实例中,可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用
-
方法中的 this自动绑定为 Vue 实例
-
-
1.6 watch //监听属性
-
watch:{ key:value $route:function (newValue, oldValue) { //监控路由 } } -
整个为一个对象,键是需要观察的表达式,值是对应回调函数
-
-
1.7 computed //计算属性
-
computed:{ getTotalCount(){ const totalCount=0; return totalCount; } }, -
vue的计算属性,将被混入到 Vue 实例中。所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 实例
-
-
1.8 props //数据成员
-
props:['counts','ids'], -
用于父子组件的eventbus传值,是数组或对象,props的成员是子组件接收的来自父组件的数据
-
-
1.9 propsData //数据成员
-
创建实例时传递 props。主要作用是方便测试
-
-
1.10 filters //资源
-
filters('filterName',(input,function(){ return newvalue })) -
包含 Vue 实例可用过滤器的哈希表。
-
-
1.11 directives //资源
-
包含 Vue 实例可用指令的哈希表。
-
-
1.12 components //组件
-
(即该组件的子实例)这里是包含 Vue 实例可用组件的哈希表。
-
-
1.13 name //杂项
-
允许组件模板递归地调用自身。注意,组件在全局用 Vue.component() 注册时,全局 ID 自动作为组件的 name。
-
-
1.14 parent //杂项
-
指定已创建的实例之父实例,在两者之间建立父子关系。子实例可以用 this.$parent 访问父实例,子实例被推入父实例的 $children 数组中。
-
-
1.15 mixins //杂项
-
mixins 选项接受一个混合对象的数组。Mixin钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。
-
-
1.16 extends //杂项
-
允许声明扩展另一个组件。这主要是为了便于扩展单文件组件。这和 mixins 类似,区别在于,组件自身的选项会比要扩展的源组件具有更高的优先级。
-
-
1.17 delimiters //杂项
-
改变纯文本插入分隔符。
-
-
1.18 functional //杂项
-
使组件无状态(没有 data )和无实例(没有 this 上下文)。他们用一个简单的 render 函数返回虚拟节点使他们更容易渲染。
-
10.1 组件的定义
组件(Component)是 Vue.js 最强大的功能之一。
组件可以扩展 HTML 元素,封装可重用的代码.
vue组件是把页面(html代码,CSS代码)进行模块化
如下图所示,一个页面分四个部分,每个部分是个vue组件,而页面成了vue组件的容器。
vue.js中创建组件有三个步骤:定义组件,注册组件以及使用组件
定义组件
-
方式1:先创建一组件构造器,然后由组件构造器创建组件。如:var myCom = Vue.extend({})
-
方式2:使用Vue.component()直接创建组件。
定义组件名的方式有两种:
-
使用 kebab-case:使用 kebab-case (短横线分隔命名) 定义一个组件(kebab发音/kI'ba:b/)
-
使用 PascalCase:(驼峰式命名) 定义一个组件
当使用 kebab-case (短横线分隔命名) 定义一个组件时,你也必须在引用这个自定义元素时使用 kebab-case,例如 。当使用 PascalCase (驼峰式命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 和 都是可接受的。
10.2 组件的分类
-
全局组件
-
局部组件
通过选项components定义,但只能在当前Vue实例中使用
10.3 引用模版
将组件内容放到
注意:template组件中,有且只有一个根元素
代码示例
全局组件和局部组件
{{message}}
{{message}}
{{message}}
{{info}}
百度
11. 组件间数据的通信
11.1 父子组件
在一个组件内部定义另一个组件,称为父子组件
子组件只能在父组件内部使用
默认情况下,子组件无法访问父组件中的数据,每个组件实例的作用域是独立的
11.2 组件间数据通信
11.2.1 子组件访问父组件的数据(父传子)
-
在调用子组件时,绑定想要获取的父组件中的数据在子组件内部,使用props选项声明获取的数据,即接收来自父组 件的数据。
-
即父组件通过props向下传递数据给子组件。
-
注:组件中的数据存在方式共有三种:data、props、computed
props传递数据两种形式
-
数组方式: props:['msg', 'username', 'age', 'userObj']
-
对象方式:该方式提供了对数据的校验、类型监测、默认值设置操作。
prop命名,可以在在组件中使用postTitle(驼峰名称),在html中是使用短横线命名post-title,如下代码示例
Vue.component('blog-post', {
// 在 JavaScript 中是 驼峰命名 的
props: ['postTitle'],
template: '{{ postTitle }}
'
})
父组件传子组件的代码实例
{{message}}
{{childMessage}}
父组件传入的值:{{parentMessage}}
11.2.2 父组件访问子组件的数据(子传父)
-
第一步:在子组件中使用 vm.$emit(事件名,数据) 触发一个自定义事件,事件名自定义。
-
第二步:父组件在使用子组件的地方监听子组件触发的事件,并在父组件中定义方法,用来获取数据。
-
总结:子组件通过events(事件)给父组件发送消息,实际上就是子组件把自己的数据发送到父组件
Title
{{info}}
11.2.3 非父子通信
适用于组件间全部通信方式。
创建一个Vue实例作为中央事件总线,通过它来监听($on)和触发($emit)事件。
//实例Vue实例作为中央事件总线
var Event=new Vue();
//发送事件
Event.$emit(事件名,数据);
//监听事件
Event.$on(事件名,data => {});
假设兄弟组件有三个,分别是 A、B、C 组件,C 组件如何获取 A 或者 B 组件的数据
{{msg}}
{{msg}}
{{msg}}
{{name}}
{{age}}
11.2.4 总结组件之间的通讯
11.3 单向数据流
props是单向绑定的,当父组件的属性变化时,将传导给子组件,但是不会反过来,即子组件中使用的父组件数据发生变化,无法传导给父组件。而且不允许子组件直接修改父组件中的数据,会直接报错,这样更有利于组件间的解耦
解决方式:
-
方式1:如果子组件想把它作为局部数据来使用,可以将数据存入另一个变量中再操作,不影响父组件中的数据。
-
方式2:如果子组件想修改数据并且同步更新到父组件
-
可以将父组件中的数据包装成对象,然后在子组件中修改对象的属性(因为对象是引用类型,指向同一个内存空间),推荐使用。
-
12.过滤器
常见的场景:当我们从后端请求到数据列表时,我们需要对其中符合条件的数据进行筛选。
如当我们拿到数据,希望把英文首字母大写,等等。
过滤器分为两种:
-
单个组件的过滤器,也叫做局部过滤器,
-
vue实例全局的过滤器,它可以被应用在任何地方。
过滤器使用地方两个位置:
???双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。
-
{{ message | filterA }}双括号插值内 -
v-bind绑定的值的地方{{ message }}
过滤器也可以使用多个,会依次执行:
例如:{{ message | filterA |filterB }} 这个例子中会把message 的当做参数传入A过滤器进行过滤,A过滤器过滤完的数据返回值会传入B过滤器
全局过滤器:Vue.filter("过滤器名称",函数);
Title
{{message | upperCase}}
{{message | upperCase}}
局部过滤器:定义局部的过滤器 定义格式:fliters:{过滤器名称:函数}
Title
{{msg | capitalize}}
13.路由
13.1 定义
-
路由,其实就是指向的意思,当我点击页面上的home按钮时,页面中就要显示home的内容,如果点击页面上的about 按钮,页面中就要显示about 的内容。
13.2 分类
13.2.1 后端路由
例如,分配一个站点,服务器地址是:http://192.168.1.200:8899,在这个网站中提供了三个界面
http://192.168.1.200:8899/index.html 主页
http://192.168.1.200:8899/about/about.html 关于我们页面
http://192.168.1.200:8899/feedback.html 反馈界面
当我们在浏览器输入 http://192.168.1.200:8899/index.html 来访问界面的时候,web 服务器就会接收到这个请求,然后把 index.html 解析出来,并找到相应的 index.html 并展示出来,这就是路由的分发,路由的分发是通过路由功能来完成的
13.2.2 前端路由
1、虽然前端路由和后端路由的实现方式不一样,但是原理都有是相同的,其中一个方式
前端路由的功能都是通过 hash 「散列值」 来实现的,hash 能兼容低版本的浏览器
2、后端路由每次仿问一个页面都要向浏览器发送请求,然后服务端再响应解析,在这个过程中肯定会存在延迟,但是前端路由中仿问一个新的界面的时候只是浏览器的路径改变了,没有和服务端交互「所以不存在延迟」,这个对用户体验来说是大大的提高。如下所示:
http://192.168.1.200:8080/#/index.html
http://192.168.1.200:8080/#/about/about.html
http://192.168.1.200:8080/#/feedback.html
由于 web 服务器不会解析 # 后面的东西「所以通过 hash 能提高性能」,但是客户端的 js 可以拿到 # 后面的东西,有一个方法是 window.location.hash 来读取,使用这个方法来匹配到不同的方法上
3、举个例子
http://www.xxx.com/path/a/b/c.html?key1=Tiger&key2=Chain&key3=abc#/path/d/e.html
我们把这个地址分析一下
http:协议
www.xxx.com:域名
/path/a/b/c.html:路由,即服务器上的资源
?key1=Tiger&key2=Chain&key3=abc:这 Get 请求的参数
#/path/d/e.html:hash 也叫散列值,也叫锚点
上面的 hash 是和浏览器交互的,其它的都是和服务器进行交互
13.3 Vue 路由
路由案例
推荐使用官方支持的 库
Title
跳转到main
跳转到message
跳转到mine
{{title}}
{{title}}
{{title}}
嵌套路由
实际应用界面,通常由多层嵌套的组件组合而成。
比如,我们 “首页”组件中,还嵌套着 “登录”和 “注册”组件,那么URL对应就是/home/login和/home/register
Title
首页
新闻
{{title}}
登录
注册
{{title}}
{{title}},{{this.$route.query.id}},{{this.$route.query.username}},{{this.$route.query.age}}
{{title}},{{this.$route.params.name}}
14.使用axios进行ajax操作
14.1 Axios简介
vue本身不支持发送AJAX请求,需要使用vue-resource、axios等插件实现。
axios是一个基于Promise的HTTP请求客户端,用来发送请求,也是vue2.0官方推荐的,同时不再对vue-resource进行更新和维护。
参考:GitHub上搜索axios,查看API文档
14.2 Axios特点
Axios 是一个基于 Promise 的 HTTP 库,可以用在浏览器和 node.js 中。本质上也是对原生XHR(XmlHttpRequest)的封装,只不过它是Promise 的实现版本,符合新的ES规范,有如下特点:
从浏览器中创建 XMLHttpRequests
从 node.js 创建 http 请求
支持 Promise API
拦截请求和响应
转换请求数据和响应数据
取消请求
自动转换 JSON 数据
客户端支持防御 XSRF
14.3 Axios基本用法
axios({options})
axios.get(url,{options});
GET传参方式:
1.通过url传参
2.通过params选项传参
POST传参方式:
axios.post(url,data,{options});
默认情况下,axios将JavaScript对象序列化为JSON。要以application / x-www-form-urlencoded格式发送数据,您可以使用以下选项之一。
传参方式:
1.自己拼接为键值对
2.使用transformRequest,在请求发送前将请求数据进行转换
3.如果使用模块化开发,可以使用qs模块进行转换
Vue中axios中箭头函数的this和function(response)函数体中的this的区别
1、在methods下的函数this指向的是当前创建的vue实例, 2、axios与后台交互后回调函数的内部的this指向window而并非指向当前的vue实例, 3、若想拿到后台回传的数据更新data里的数据,不能在回调函数中直接使用this,要用外部函数定义的变量(如:_this)存储的this,也就是当前vue的实例。 4、使用箭头函数之后,箭头函数指向的函数内部的this已经绑定了外部的vue实例了
Axios的get请求、post请求和传参方式代码示例
Title
{{value}}
{{student.id}}
{{student.name}}
{{student.age}}
{{student.email}}
后端代码:
@WebServlet(urlPatterns = {"/student/list"})
public class StudentServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String p1 = req.getParameter("p1");
String p2 = req.getParameter("p2");
System.out.println("p1=" + p1 + ",p2=" + p2);
//设置响应数据格式和编码
resp.setContentType("application/json;charset=utf-8");
//允许跨域请求(后台允许跨域请求)
resp.setHeader("Access-Control-Allow-Origin", "*");
//获取数据
List students = getStudents();
Result result = new Result(students);
//使用Gson把result对象转换json字符串
Gson gson = new Gson();
String json = gson.toJson(result);
//输出json字符串到前端
resp.getWriter().write(json);
}
/**
* 获取学生数据集合
*
* @return
*/
private List getStudents() {
List students = new ArrayList<>();
students.add(new Student(1L,"李四",20,"mm@163.com"));
students.add(new Student(2L,"王五",21,"mm@163.com"));
students.add(new Student(3L,"赵六",22,"mm@163.com"));
students.add(new Student(4L,"钱七",23,"mm@163.com"));
students.add(new Student(5L,"贵八",24,"mm@163.com"));
return students;
}
}
public class Result {
List students;
public Result() {
}
public Result(List students) {
this.students = students;
}
public List getStudents() {
return students;
}
public void setStudents(List students) {
this.students = students;
}
}
public class Student {
private Long id;
private String name;
private Integer age;
private String email;
public Student() {
}
public Student(Long id, String name, Integer age, String email) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
export和import在es5不支持,所以直接下面的代码直接在浏览器不能运行
15.1 export
模块是独立的文件,该文件内部的所有的变量外部都无法获取。如果希望获取某个变量,必须通过export输出
// profile.js
export let firstName = 'Michael';
export let lastName = 'Jackson';
export let year = 1958;
// profile.js
let firstName = 'Michael';
let lastName = 'Jackson';
let year = 1958;
?
export {firstName, lastName, year};
除了输出变量,还可以输出函数或者类(class),
export function multiply(x, y) {
return x * y;
};
还可以批量输出,同样是要包含在大括号里,也可以用as重命名:
function v1() { ... }
function v2() { ... }
?
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
export 命令规定的是对外接口,必须与模块内部变量建立一一对应的关系
main.js import profile.js文件
//profile.js
// 写法一
export let m = 1;
?
// 写法二
let m = 1;
export {m};
?
// 写法三
let n = 1;
export {n as m};
?
?
// 报错
export 1;
?
// 报错
let m = 1;
export m;
15.2 import
export定义了模块的对外接口后,其他JS文件就可以通过import来加载这个模块,
import {firstName, lastName, year} from './profile'; //使用相对路径或者绝对路径
?
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
import命令接受一对大括号,里面指定要从其他模块导入的变量名,必须与被导入模块(profile.js)对外接口的名称相同。
如果想重新给导入的变量一个名字,可以用as关键字,
import { lastName as surname } from './profile'
import后的from 可以指定需要导入模块的路径名,可以是绝对路径,也可以是相对路径, .js路径可以省略,如果只有模块名,不带有路径,需要有配置文件指定。
注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。(是在编译阶段执行的)
module的整体加载
除了指定加载某个输出值,还可以用(*)指定一个对象,所有的变量都会加载在这个对象上。
// circle.js。输出两个函数
?
export function area(radius) {
return Math.PI * radius * radius;
}
?
export function circumference(radius) {
return 2 * Math.PI * radius;
}
?
?
// main.js 加载在个模块
?
import { area, circumference } from './circle';
?
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
?
//上面写法是逐一指定要加载的方法,整体加载的写法如下。
import * as circle from './circle';
?
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
注意,模块整体加载所在的那个对象(上例是circle),应该是可以静态分析的,所以不允许运行时改变。
import * as circle from './circle';
// 下面两行都是不允许的
circle.foo = 'hello';
circle.area = function () {};
export default
之前的例子中,使用import导入时,都需要知道模块中所要加载的变量名或函数名,用户可能不想阅读源码,只想直接使用接口,就可以用export default命令,为模块指定输出
// export-default.js
export default function () {
console.log('foo');
}
其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
export default也可以用于非匿名函数前。
下面比较一下默认输出和正常输出。
// 第一组
export default function crc32() { // 输出
// ...
}
import crc32 from 'crc32'; // 输入
// 第二组
export function crc32() { // 输出
// ...
};
import {crc32} from 'crc32'; // 输入
在一个文件或模块中,export、import可以有多个,export default仅有一个,通过export方式导出,在导入时要加{ },export default则不需要
16.掌握cli方式搭建项目、安装依赖
16.1 vue-cli 是vue.js的脚手架,用于自动生成vue.js模板工程的。
-
脚手架工具简单讲就是自动将项目需要的环境、依赖等信息都配置好
16.2 安装node.js
使用安装包,安装成功后 cmd 运行 npm -v 命令查看版本
16.3 设置node.js prefix(全局)和cache(缓存)路径(根据自己的电脑磁盘空间大小选择盘符,路径不能用中文或者特殊字符)
设置全局模块存放路径(路径自定义,保证磁盘有足够的空间)
npm config set prefix "C:\tools\nodejs\node_global"
设置缓存文件夹(路径自定义,保证磁盘有足够的空间)
npm config set cache "C:\tools\nodejs\node_cache"
设置成功后,之后用命令npm install XXX -g安装以后模块就在C:\develop_tool\nodejs\node_cache里
16.4 设置环境变量(非常重要)
说明:设置环境变量可以使得住任意目录下都可以使用cnpm、vue等命令,而不需要输入全路径。
编辑系统变量PATH中添加
C:\tools\nodejs\node_global
根据步骤3设置的路径进行配置
添加系统变量NODE_PATH,输入路径
C:\tools\nodejs\node_global\node_modules
根据步骤3设置的路径进行配置
16.5、基于 Node.js 安装cnpm(淘宝镜像)
npm作为国外的node仓库安装工具,国内用户在安装相关的资源的时候,会出现安装失败,以及速度很慢的情况。为了解决npm安装的问题,国内出现了很多npm的镜像网址,taobao的npm镜像算是使用频率比较高的了。
(1)使用阿里定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:
npm install -g cnpm --registry=https://registry.npm.taobao.org
(2)检测cnpm版本,如果安装成功可以看到cnpm的基本信息。
cnpm -v
16.6 安装vue命令行工具,即vue-cli 脚手架
cnpm install vue-cli -g
检查vue-cli脚手架版本,如果安装成功可以看到vue-cli脚手架的版本信息
vue -V或者vue --version
16.7 使用vue-cli来构建vue项目
cmd 打开命令窗口 路径切换到 vue项目的文件夹
运行如下命令,安装@vue/cli-init
为了防止卡在downloading template
npm install -g @vue/cli-init
使用命令创建vue模板项目
vue init webpack-simple 项目名
vue init webpack 项目名
webpack-simple和webpack 是项目模板,分别为简单模式和完全模式。
简单模式仅包含基于vue.js开发必要的一些资源和包,使用该模式可以快速地投入到开发。
完全模式则还包括ESLink、单元测试等功能。
16.8 目录切换到项目目录下
1、直接用命令提示符输入命令
如:cd C:\Users\hzw\Documents\WebstormProjects\my-vue-cli
执行 npm run dev
出现如下就说明启动成功了
2、用WebStorm输入命令
出现如下就说明启动成功了
项目运行成功后,使用浏览器打开对应页面如下:
http://localhost:8080/
注:这里是默认服务启动的是本地的8080端口,所以请确保你的8080端口不被别的程序所占用。
若8080被占用,需要修改一下当前vue项目的配置文件 config里的index.js的 port
16.9 项目目录解析
maven 编译打包程序 + jar依赖
npm js库维护
webpack 编译(把es6的语法 转换成es5,让浏览器都支持)打包
16.10 加载过程
16.11 加载过程
App.vue中声明了路由插座,插座用来根据路由配置与浏览器地址选择加载的视图。
路由文件声明了当路径为空时,以HellowWorld组件作为元素。
16.12 vue文件结构
- vue文件是vue工程化开发时的组件的编程载体。
- vue文件并不能独立运行,而是要通过webpack打包并封装为一个vue组件对象。
17.npm、webpack和node关系
17.1 什么是webpack?(了解)
- Webpack 是一个前端资源加载/打包工具。
- 将根据模块的依赖关系进行静态分析
- 然后将这些模块按照指定的规则生成对应的静态资源。
- WebPack可以看做是模块打包机:
- 它做的事情是,分析你的项目结构,
- 找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等)
- 并将其转换和打包为合适的格式供浏览器使用。
17.2 webpack的核心作用
- 模块化开发中,我们会编写大量模块,如果不打包就上线,那么页面加载或交互时,将会发起大量请求。
- 为了性能优化,需要使用webpack这样的打包器对模块进行打包整合,以减少请求数。
- 就像简单的vue项目,所有组件最终都将被打包到一个app.js中。
- 相较于无差别打包依赖模块的传统打包器,webpack的核心优势在于它从入口文件出发,递归构建依赖关系图。
- 通过这样的依赖梳理,webpack打包出的bundle不会包含重复或未使用的模块,实现了按需打包,极大的减少了冗余。
如图:
webpack是一个工具,这个工具可以帮你处理好各个包/模块之间的依赖关系(modules with dependencies),并将这些复杂依赖关系的静态文件打包成一个或很少的静态文件,提供给浏览器访问使用;除此之外,webpack因为可以提高兼容性,可以将一些浏览器尚不支持的新特性转换为可以支持格式,进而减少由新特性带来的浏览器的兼容性问题
17.3 什么是npm
官网:https://www.npmjs.com/
介绍了webpack,我们可能会疑问,我的JS,CSS,HTML文件分开写,挺好的呀,为什么要使用webpack工具,进行复杂的各项配置。在传统前端开发模式下,我们确实是按照JS/CSS/HTML文件分开写的模式就可以,但是随着前端的发展,社区的壮大,各种前端的库和框架层出不穷,我们项目中可能会使用很多额外的库,如何有效管理这些引入的库文件是一个大问题,而且我们知道基于在HTML中使用引入的方式,有两个弊端,一个是会重复引入,二是当库文件数量很多时管理成为一个大难题。 面对这样的局面,为了简化开发的复杂度,前端社区涌现了很多实践方法。模块化就是其中一项成功实践,而npm就是这样在社区(node)中产生的
npm 由三个独立的部分组成:
-
网站
-
注册表(registry)
-
命令行工具 (CLI)
网站 是开发者查找包(package)、设置参数以及管理 npm 使用体验的主要途径。 注册表 是一个巨大的数据库,保存了每个包(package)的信息。 CLI 通过命令行或终端运行。开发者通过 CLI 与 npm 打交道。
一般来说提起npm有两个含义,一个是说npm官方网站,一个就是说npm包管理工具。npm社区或官网是一个巨大的Node生态系统,社区成员可以随意发布和安装npm生态中的包,也就是不用在重复造轮子了,别人造好了,你直接安装到你的项目中就可以使用,但是因为前面说了,当包引入数量很多时管理就成为了一个问题,这个就是npm为开发者行了方便之处,npm已经为你做好了依赖和版本的控制,也就是说使用npm可以让你从繁杂的依赖安装和版本冲突中解脱出来,进而关注你的业务而不是库的管理。
而webpack就是将你从npm中安装的包打包成更小的浏览器可读的静态资源,这里需要注意的是,webpack只是一个前端的打包工具,打包的是静态资源,和后台没有关系,虽然webpack依赖于node环境
17.4 什么是node?
实node和nodejs两个概念没有太大差别,唯一的区别就是,人们说起node的时候语境更多的是再说node环境,而说nodejs时更多的是在说node是一门可以提供后端能力的技术。本质上来说,node就是nodejs,nodejs就是node,简单的说 Node.js 就是运行在服务端的 JavaScript。
17.5 webpack npm node之间关系?
-
webpack是npm生态中的一个模块,我们可以通过全局安装webpack来使用webpack对项目进行打包;
-
webpack的运行依赖于node的环境,没有node是不能打包的,但是webpack打包后的项目本身只是前端静态资源和后台没有关系,也就是说不依赖与于node,只要有后台能力的都可以部署项目
-
npm是于Node社区中产生的,是nodejs的官方包管理工具,当你下载安装好node的时候,npm cli 就自动安装好了
-
正是因为npm的包管理,使得项目可以模块化的开发,而模块化的开发带来的这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常繁琐的,这就是webpack工具存在的意义
18.使用ElementUI
18.1 功能简介
ElementUl:一套为开发者、设计师和产品经理准备的基于Vue 2.0的桌面端组件库。
- 提供了组件化布局的基础功能。
- 提供了常用的网页元素默认样式。
- 提供了国际化等功能。
- 对表单做了扩展功能,增加了时间、日期滑块以及手机端常用的输入类型。
- 官网:https://element.eleme.cn/#/zh-CN/component/installation
18.2 功能说明
- Basic基础组件: Layout布局、lcon图标、Button按钮
- Form表单组件:Radio单选框、Input输入框、Form表单
- Data数据组件:Table表格、Progress进度条、Pagination分页
- Notice提示组件: Alert警告、Message消息提示、Notification通知
- Navigation导航组件:NavMenu导航菜单、Tabs标签页、Dropdown下拉菜单
- Others其它组件:Carousel走马灯、Calendar日历、Drawer抽屉
18.3 安装
1、cmd 切换到项目目录,执行命令
npm i element-ui -S
2、在main.js文件中加入代码
//导入ui组件js文件
import ElementUI from 'element-ui'
//导入ui组件的css文件
import 'element-ui/lib/theme-chalk/index.css
//声明插件安装
Vue.use(ElementUI)
18.4 使用
在src文件夹中的component文件夹下创建.vue结尾的文件,组件开发,在router文件夹index.js中配置路由
18.5 常用组件标签演示
Icon图标
18.6 表单组件
rules属性配置验证规则
- rules对象采用键值对 {prop: [{}]} 格式配置,属性值:
- type 类型验证:string/number/boolean/array/object/date/email/any等
- required 必填属性:true/false
- pattern 模板类型:正则表达式
- min 最少位数:number
- max 最多位数:number
- message 检测提示:string
- validator 验证器:function(rule, value, callback)
- trigger 验证触发事件:focus/blur/change
Vue的ref属性和refs
- ref 被用来给DOM元素或子组件注册引用信息。
- 引用信息会根据父组件的 $refs 对象进行注册。
- 如果在普通的DOM元素上使用,引用信息就是元素;
- 如果用在子组件上,引用信息就是组件实例
注意:只要想要在Vue中直接操作DOM元素,就必须用ref属性进行注册
18.7 ElementUI登录小案例
登录
重置
1、当写vue项目时,当涉及到频繁的组件之间的数据通讯、一个组件需要多次派发事件时,我们的代码就会变得复杂、冗余、难以维护
2、Vuex是一个专为Vue.js应用程序开发的状态管理模式。我们可以把一些共享的数据保存至vuex中,方便各个组件修改或获取公共状态。
3、vue的单向数据流的简洁性在以下情况下很容易被破坏
-
多个视图依赖于同一状态
-
来自不同视图的行为需要改变同一状态
解决思想:
我们可以把组件的共享状态提取出来,作为全局单例模式管理。这样,组件树构成了一个巨大的视图,不论在树的哪个位置,都能获取状态或触发行为。
4、vuex的示意图
19.2 下载以及安装
1、vuex的安装方式
-
步骤1:在package.json文件中的dependencies属性中增加vue的依赖包声明
"dependencies": { "vuex": "^3.0.1", "vuex-persistedstate": "^2.5.4" } -
步骤2:使用cmd指令进入package.json所在目录执行安装指令,安装后在node_modules目录下生成vuex、vuex-persistedstate包
npm install
??
-
步骤3:在独立的store配置文件中引入vuex组件(如:store.js)
import Vue from'vue' import Vuex from 'vuex' import createPersistedState from "vuex-persistedstate" //使用vuex插件 Vue.use(Vuex) -
步骤4:创建vue全局实例对象,并设定持久化插件,写在store.js中
export default new Vuex.Store({ //全局状态 state:{loginStatus:false}, //同步数据提交 mutations:{}, //引入持久化组件 plugins:[createPersistedState()] }) -
步骤5:在main.js中引入vuex实例,并以插件的方式添加到组件实例对象中
import store from './store‘ ? new Vue({ el: '#app', router, store, components: { App }, template: '' })
vue的所有组件可以通过this.$store.state访问全局状态
从state上获取的count:{{count}}
?
?
store.js
import Vue from 'vue'
import Vuex from 'vuex'
?
Vue.use(Vuex)
?
export default new Vuex.Store({
state: { //要设置的全局访问的state对象
count: 0
}
})
19.3 state核心组件
1、vuex使用单一状态树state对象来作为“唯一数据源”,专门用于存放数据。
-
在组件中访问state下的数据,通过this.$store.state来访问
-
不能通过this.$store.state进行赋值,会造成显示与存储不统一
-
一般建议当需要访问state的属性时,通过计算属性访问,避免对数据赋值
2、getters属性:当需要访问的数据需要通过对state中的数据需要进行运算或逻辑处理才能返回结果时,可以使用getters。
-
getters可以简单的把它理解为store中的计算属性,当store里的值发生改变时,getters会返回重新计算的值。
-
getters下的方法第一个参数为state,第二个参数为getters,意味着在getters方法中可以访问state以及其他getters的函数返回值。
从state上获取的count:{{count}}
从getters上获取的getCount:{{this.$store.getters.getCount}}
?
?
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: { //要设置的全局访问的state对象
count: 0
},
//通过复杂的计算得到数据可以使用getters
getters: {
//store里的值发生改变时,getter会返回重新计算的值
getCount(state, getters) {
return state.count;
}
}
})
19.4 Mutations组件
1、mutations组件:通过mutation更新state中数据,不可以直接操作store中的数据,只有正确提交mutations,才能保证state中的数据在个组件中渲染与存储一致。
2、mutations组件的提交:
this.$store.commit(“login”,playLoad)
-
login为事件类型,与mutations中声明保持一致
-
playLoad:为对象或基本类型的数据,提交荷载
注意事项:
Vuex的store中的状态是响应式的,当我们变更状态时,监视状态的Vue组件也会自动更新。这也意味着Vuex中的mutation也需要与使用Vue一样遵守一些注意事项:
-
在store中初始化好所有所需属性
-
当需要在对象上添加新属性时,应该使用Vue.set(obj,'newProp',123),或者以新对象替换老对象
state.obj={...state.obj,newProp:123}
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: { //要设置的全局访问的state对象
count: 0
}
//通过复杂的计算得到数据可以使用getters
getters: {
//store里的值发生改变时,getter会返回重新计算的值
getCount(state, getters) {
return state.count;
}
},
//通过mutations改变store存储的数据
mutations: {
//state存储数据对象,num为相加或者相减的数据
addCount(state, num) {
state.count = state.count + num;
},
subCount(state, num) {
//count为0不能再做减法
if (state.count > 0) {
state.count = state.count - num;
} else {
state.count = 0;
}
}
}
})
VueTest.vue
从state上获取的count:{{count}}
从getters上获取的getCount:{{this.$store.getters.getCount}}
VuexTest1
VueTest1.vue
通过this.$store.state.count获取值{{count}}
通过getters方法获取状态数据{{this.$store.getters.getCount}}
19.5 Actions组件
1、Actions组件:Mutations组件的异步数据更新
-
actions组件与Mutations组件的作用都是对state数据属性赋值,并同步数据显示渲染。
-
Mutations组件仅适用于同步数据赋值,而actions适用于异步数据赋值
-
Actions组件最终通过提交Mutations中的事件类型对state属性赋值
2、Actions组件的声明
-
context为当前vuex实例对象,与this.$store一致,可以调用commit方法
-
playload为荷载
-
通过调用dispath触发action组件事件类型
store.dispatch(addCountAsync',playLoad)
19.6 模块化管理
由于vuex使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿
-
解决方案:将store分模块管理,每个模块都有自己内部的state、mutation、action、getter
19.7 持久存储
问题:在登录成功后,把用户信息保存在vuex中作为全局共享数据,在页面刷新之后数据会丢失
-
分析:由于vuex中的数据保存在运行内存中,当页面刷新时,页面会重载vue实例,vuex中的数据会被重新赋值
-
解决思路:把vuex中的state数据保存一份到客户端存储中
-
localStorage:永久本地存储。本案例采用vuex-persistedstate中间件处理,简化api调用
-
sessionStorage:存储至页面关闭
-
cookie:手动设置有效时间,但存储量过小
-
19.8 案例
VueTest.vue
从state上获取的count:{{count}}
从getters上获取的getCount:{{this.$store.getters.getCount}}
VuexTest1
VueTest1.vue
通过this.$store.state.count获取值{{count}}
通过getters方法获取状态数据{{this.$store.getters.getCount}}
VuexTest2
VueTest2.vue
通过this.$store.state.count获取值{{count}}
通过getters方法获取状态数据{{this.$store.getters.getCount}}
路由配置
{
path: "/v-test",
name: "VuexTest",
component: VuexTest,
children:[
{
path: "/v-test1",
name: "VuexTest1",
component: VuexTest1,
children:[
{
path: "/v-test2",
name: "VuexTest2",
component: VuexTest2
}
]
}
]
}
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from "vuex-persistedstate"
Vue.use(Vuex)
export default new Vuex.Store({
state: { //要设置的全局访问的state对象
count: 0
},
plugins: [
//持久化state
createPersistedState({
//指定存储位置(默认位置是local storage)
//sessionStorage表示会话存储,当浏览器关闭后,存储就消失了
//localStorage 表示本地存储,当浏览器关闭后 ,存储不消失,再次打开网站 数据恢复
//storage:window.sessionStorage,
//reducer: 指定需要缓存的状态属性
reducer(val) {
console.log(val);
return {
count: val.count
}
}
})
],
//通过复杂的计算得到数据可以使用getters
getters: {
//store里的值发生改变时,getter会返回重新计算的值
getCount(state, getters) {
return state.count;
}
},
//通过mutations改变store存储的数据
mutations: {
//state存储数据对象,num为相加或者相减的数据
addCount(state, num) {
state.count = state.count + num;
},
subCount(state, num) {
//count为0不能再做减法
if (state.count > 0) {
state.count = state.count - num;
} else {
state.count = 0;
}
}
},
actions: {
myAddCount(context, num) {
//context为当前vuex实例对象,与this.store一致,可以调用commit方法
//第一个参数addCount为mutations定义的方法
//context.commit("addCount", num);
setTimeout(()=>{
context.commit("addCount", num);
},2000);
},
mySubCount(context, num) {
//context为当前vuex实例对象,与this.store一致,可以调用commit方法
//第一个参数subCount为mutations定义的方法
context.commit("subCount", num);
}
}
})