signal page application
单页面应用,仅在页面初始化的时候加载相应的html
,css
和js
.一旦页面的加载完成,SPA
不会因为用户操作而进行页面的重新加载或跳转,取而代之的是利用路由机制实现html
内容的变换,UI
与用户交互,避免页面的重新加载.
优点:
缺点:
Model-view-ViewModel (MVVM) 是一个软甲架构的设计模式.又微软 WPF 和 Silverlight 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动变成方式.
Model–View–ViewModel (MVVM) 是一个软件架构设计模式,由微软 WPF 和 Silverlight 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动编程方式。
MVC:Model–View–Controller(MVC)模式.
MVVM:model view viewModel 视图模式。MVVM 的出现促进了前端开发与后端业务逻辑的分离,极大地提高了前端开发效率,MVVM 的核心是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用。
(1)View 层
View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建。
<div id="app">
<p>{
{message}}p>
<button v-on:click="showMessage()">Click mebutton>
div>
(2)Model 层
Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。
{
"url": "/your/server/data/api",
"res": {
"success": true,
"name": "IoveC",
"domain": "www.cnblogs.com"
}
}
(3)ViewModel 层
ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状态的,比如页面的这一块展示什么,而页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层。
var app = new Vue({
el: '#app',
data: {
// 用于描述视图状态
message: 'Hello Vue!',
},
methods: {
// 用于描述视图行为
showMessage(){
let vm = this;
alert(vm.message);
}
},
created(){
let vm = this;
// Ajax 获取 Model 层的数据
ajax({
url: '/your/server/data/api',
success(res){
vm.message = res;
}
});
}
})
MVVM 框架实现了双向绑定,这样 ViewModel 的内容会实时展现在 View 层,前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新。这样 View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。
v-show 通过display设置为none来控制元素的隐藏与现实,导尿管v-show为fasle时,将页面中已经渲染的 DOM 元素的 display 设置为 none,且是行内样式.用于频繁切换的场景
v-if 是真正的重新渲染,确保在每次切换的过程中条件块内的事件监听器和子组件都适当的销毁和创建,当v-if 为false 时,根本不会有元素渲染在页面中.用于不频繁切换的场景
computed : 是计算属性,依赖其他的属性值,用来计算复杂的逻辑运算,并且 computed 的值会有缓存,只有它依赖的属性值发生改变,下一次重新获取 computed的时候才会重新计算,
methods : methods 将被混入到 Vue 实例中。可以直接通过 VM 实例访问这些方法,或者在指令表达式中使用。方法中的 this 自动绑定为 Vue 实例。
watch : 监听,用于监听数据的变化,其中可以监听的数据来源有三部分data,prop,computed
,两个参数,一个是新值.一个是旧值.
注意,不应该使用箭头函数来定义 watcher 函数 (例如 searchQuery: newValue => this.updateAutocomplete(newValue))。理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,this.updateAutocomplete 将是 undefined。
运用场景:
当我们需要数值计算,并且依赖于其他数据是,应该使用 computed ,因为可以利用 computed 的缓存特性,避免每次获取值时,都需要重新计算.
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
methods存储方法,computed用来存储需要处理的数据,methods每一次都会调用,computed只有依赖的属性值发生变化的时候才会重新计算.
使用后会相互影响。
子组件直接访问父组件,或者是子组件访问跟组件。
父组件访问子组件:使用$children或$refs
子组件访问父组件:使用$parent
this.$children是一个数组类型,它包含所有子组件对象。
这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
定义
Vue.prototype.$bus = new Vue()
发出事件$bus.$emit(‘事件名’,参数)
触发事件$bus.$on(‘事件名’)
全局变量, 状态管理模式
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
它们有两个非常重要的属性:
router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:
通过create生命周期函数来验证
beforeCreate:组件实例被创建之初,组件的属性生效之前
created:在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,el 还不可用
beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。
mounted:实例被挂载后调用,这时 el 被新创建的 vm.el也在文档内。
beforeUpdate:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
updated:由于数据更新导致虚拟DOM重新渲染和打补丁,在这之后调用该钩子。
当这个钩子被调用时,组件DOM已经更新,所有现在可以执行依赖于DOM的操作。
updated不会保证所有的子组件也都一起被重绘,如果希望等整个视图重绘完毕,可以在updated里面使用vm.$nextTick
beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
destroyed:实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
activated:keep-alive 组件的相关事件,被keep-alive缓存的组件启用的时候调用
deactivated:keep-alive 组件的相关事件,被keep-alive缓存的组件停掉的时候调用
渲染
父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount => 子mounted => 父mounted
更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
销毁
父beforeDestroy => 子beforeDestroy => 子destroy => 父destroy
<chile @hook:mounted = 'doSomethind'></child>
created/beforeMounted/mounted
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
核心概念和方法
(1)hash 模式的实现原理
URL的hash也就是锚点(#), 本质上是改变window.location的href属性.
我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 ‘#search’:
https://www.word.com#search
hash 路由模式的实现主要是基于下面几个特性:
URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;
可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。
(2)history 模式的实现原理
history.pushState()
和 history.repalceState()
。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
history 路由模式的实现主要基于存在下面几个特性:
pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。
vue-router 的导航钩子,主要用来作用是拦截导航,让他完成跳转或取消。
有三种方式可以植入路由导航过程中:
全局的(beforeEach,afterEach
)
单个路由独享的(beforeEnter
)
组件级的(beforeRouterEnter,beforeRouterLeave,beforeUpdate
)
1. 全局导航钩子
全局导航钩子主要有两种钩子:前置守卫、后置钩子,
注册一个全局前置守卫:
const router = new VueRouter({
... });
router.beforeEach((to, from, next) => {
// do someting
});
这三个参数 to 、from 、next 分别的作用:
to: Route,代表要进入的目标,它是一个路由对象
from: Route,代表当前正要离开的路由,同样也是一个路由对象
next: Function,这是一个必须需要调用的方法,而具体的执行效果则依赖 next 方法调用的参数
router.afterEach((to, from) => {
// do someting
});
不同于前置守卫,后置钩子并没有 next 函数,也不会改变导航本身
cont router = new VueRouter({
routes: [
{
path: '/file',
component: File,
beforeEnter: (to, from ,next) => {
// do someting
}
}
]
});
至于他的参数的使用,和全局前置守卫是一样的
他们是直接在路由组件内部直接进行定义的
我们看一下他的具体用法:
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
需要注意是:
beforeRouteEnter 不能获取组件实例 this,因为当守卫执行前,组件实例被没有被创建出来,剩下两个钩子则可以正常获取组件实例 this
但是并不意味着在 beforeRouteEnter 中无法访问组件实例,我们可以通过给 next 传入一个回调来访问组件实例。在导航被确认是,会执行这个回调,这时就可以访问组件实例了,如:
beforeRouteEnter(to, from, next) {
next (vm => {
// 这里通过 vm 来访问组件实例解决了没有 this 的问题
})
}
注意,仅仅是 beforRouteEnter 支持给 next 传递回调,其他两个并不支持。因为归根结底,支持回调是为了解决 this 问题,而其他两个钩子的 this 可以正确访问到组件实例,所有没有必要使用回调
最后是完整的导航解析流程:
基本概念 : 子组件更改 props 中的数据不会触发父组件数据的改变, 但是由于响应式原理,父组件数据的改变会导致子组件 props 中值的改变
目的:防止子组件意外的改变父组件中的状态,从而导致应用的数据流向难以理解.
结果 : 子组件的prop数据自动刷新,但是子组件内部不能改变prop的值
解决方案 : 只能通过自定义事件,父组件接受到事件后,有父组件修改数据,
有两种常见的视图改变props值的场景:
props: ['num'],
data: function () {
return {
counter: this.num
}
}
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
由于JavaScript的限制,vue不能检测如下数据变化:
所以修改数组的值可以可以通过同种方式实现
//方案一
//使用数组的splice方法修改
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
//方案二
//通过vue提供的set方法设置
Vue.set(this.arr,1,2)
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
第二个问题的解决方法
// Array.prototype.splice
vm.items.splice(newLength)
v-model其实是一个语法糖,它的背后本质上是包含两个操作:
也就是说下面的代码:等同于下面的代码:
//以input表单元素为例
input type="text" v-model="message">
等同于
<input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
上面的说法仅仅是针对与输入框,那么其他的表单元素呢,如单选,多选,下拉框等
v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
如果我们需要在客户端编译模板(例如,想template选项传入一个字符串,或者需要将模板中的非DIN的HTML挂载到一个元素),这是就需要带有编译器的版本,因为需要完整构建版本.
//这种情况需要编译器(compiler)
new Vue({
template : `{
{hello}}`
})
//这种情况不需要
new Vue({
render(h) {
return h('div',this.hello);
}
})
具体二者之间的差别我们先来看看vue的运行过程
template->ast(abstract syntax tree)->render->virtual dom->UI
1、1. runtime-compiler:template->ast(abstract syntax tree)->render->virtual dom->UI
runtime-only:性能更高、代码量更少
runtime-only :render->virtual dom->UI
render函数的基本用法:
function(createElement) {
//用法1
return createElement('标签',{
标签的属性},[标签中的内容]);
return createElement('h2',{
class:box},['Hello world]);
//用法2
//传入一个组件对象
return createElemen(cpn)
}
key让每个item元素有一个唯一标识的身份,可以使用下标值或者唯一的id值,主要是为了vue能够精准的追踪到每一个元素,高效的更新虚拟DOM,使用v-for遍历数据的时候需要加上key关键字.
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
Vue不能检测以下数组的变动:
- 当你利用索引值直接设置一个数组项时,例如:vm.items[index] = newValue
- 当你修改数组的长度时,例如:vm.items.length = ewLength
一个例子:
let vm = new Vue({
data : {
items :[1,2,3,4],
}
})
//利用下标修改数组值
vm.items[0] = 5; //不是响应式的
//直接修改数组长度
vm.items.length = 6; //不是响应式的
第一个问题(不能通过下标值修改数组元素)的解决方案:
Vue.set(vm.items, indexOfItem, newValue)
vm.items.splice(indexOfItem, 1, newValue)
你也可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名:
vm.$set(vm.items, indexOfItem, newValue)
第二个问题(不能直接改变数组长度)的解决方案
使用splice方法
vm.items.splice(newLength)
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如:
var vm = new Vue({
data:{
a:1
}
})
// `vm.a` 是响应式的
vm.b = 2
// `vm.b` 是非响应式的
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。例如,对于:
Vue.set(vm.someObject, 'b', 2)
还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名:
this.$set(this.someObject,'b',2)
有时你可能需要为已有对象赋值多个新 property,比如使用 Object.assign() 或 _.extend()。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({
}, this.someObject, {
a: 1, b: 2 })