一,v-show和v-if的区别
1,元素节点的存在性
v-if是创建和删除元素节点(false的时候不存在元素)
v-show是显示和隐藏元素节点(false的时候元素存在,只是display:none)
2,实现原理
v-if是真正的条件渲染,因为它会确保在切换过程中,条件块内的事件监听器和子组件适当地被销毁和创建。同时他也是惰性的,如果一开始的条件为false,则什么也不做,直到第一次条件变成真时,才会开始渲染条件块。
v-show就简单多了,不管初始条件是什么,元素都会被渲染,只是简单地改变display的值。
3,具体使用
v-if可以搭配v-else-if和v-else使用。一般来说,v-if有更大的切换开销。而v-show有更高的初始开销。因此,如果需要非常频繁地切换,则使用v-show,如果在运行时条件改变次数较少,则用v-if.
二,stylus和局部化css
局部化:
<style lang="stylus" scoped>
但又因为有scoped,导致无法在组件中修改全局组件的样式,比如swiper就是全局的组件,想要修改它的样式,那么就得使用样式穿透。
样式穿透
<style lang="stylus" scoped>
//这样表示穿透,不受scoped的影响
.wripper >>> .swiper-pagination-bullet-active
background :#fff
</style>
三,v-model的使用
可以实现数据的双向绑定,在input或者select或者文本域配合value使用。
四,解决移动端300ms的方案
引入fastclick
//导入移动端300ms延迟的问题
import fastClick from 'fastclick'
import './assets/styles/reset.css'
import './assets/styles/border.css'
//挂载移动端300ms的问题
fastClick.attach(document.body)
五,vue-loader是什么,使用的用途是什么?
我们开发过程中都是vue组件,但最后又成了index.html,这是因为webpack打包处理了。而vue-loader就是处理vue文件的一个加载器。webpack本身只能打包js文件,打包其他文件需要对应的加载器。
六,nextTick是什么,它的作用
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
应用场景:需要在视图更新之后,基于新的视图进行操作。
this.
n
e
x
t
T
i
c
k
(
)
方
法
主
要
是
用
在
数
据
改
变
,
d
o
m
改
变
应
用
场
景
中
。
v
u
e
中
数
据
和
d
o
m
渲
染
由
于
是
异
步
的
,
所
以
,
要
让
d
o
m
结
构
随
数
据
改
变
这
样
的
操
作
都
应
该
放
进
t
h
i
s
.
nextTick()方法主要是用在数据改变,dom改变应用场景中。vue中数据和dom渲染由于是异步的,所以,要让dom结构随数据改变这样的操作都应该放进this.
nextTick()方法主要是用在数据改变,dom改变应用场景中。vue中数据和dom渲染由于是异步的,所以,要让dom结构随数据改变这样的操作都应该放进this.nextTick()的回调函数中。
假设我们更改了某个dom元素内部的文本,而这时候我们想直接打印出这个被改变后的文本是需要dom更新之后才会实现的,也就好比我们将打印输出的代码放在setTimeout(fn, 0)中;
简单的说,就是vue在修改数据之后,视图不会立即更新,而是等同一事件循环所有的数据变化完成之后,再统一进行视图更新。
例子:
<template>
<section>
<h1 ref="hello">{{ value }}</h1>
<el-button type="danger" @click="get">点击</el-button>
</section>
</template>
<script>
export default {
data() {
return {
value: 'Hello World ~'
};
},
methods: {
get() {
this.value = '你好啊';
console.log(this.$refs['hello'].innerText); //Hello World ~
this.$nextTick(() => {
console.log(this.$refs['hello'].innerText);//你好啊
});
}
},
mounted() {
},
created() {
}
}
</script>
七,vue组件中的data为什么必须 是函数
在组件中,data必须是一个函数,因为每一个 vue 组件都是一个 vue 实例,通过 new Vue() 实例化,引用同一个对象,如果 data 直接是一个对象的话,那么一旦修改其中一个组件的数据,其他组件相同数据就会被改变,而 data 是函数的话,每个 vue 组件的 data 都因为函数有了自己的作用域,互不干扰。
八,对keep-alive的了解
它是Vue内置的一个组件,来缓存组件内部状态,避免重新渲染。(在开发Vue项目的时候,有一部分部分组件是没必要多次渲染的)
它不会在dom中体现出来
keep-alive属性:
include - 字符串或正则表达式。只有匹配的组件会被缓存。
exclude - 字符串或正则表达式。任何匹配的组件都不会被缓存。
但更多场景中,我们会使用keep-alive来缓存路由:
<template>
<div id="app">
<keep-alive exclude="Detail">
<router-view></router-view>
</keep-alive>
</div>
</template>
<style>
</style>
这里只有在第一次进入页面的时候,使用ajax获取数据,渲染页面,而后将数据存放在缓存中,下回页面跳转直接使用。
生命周期钩子:
在被keep-alive包含的组件/路由中,会多出两个生命周期的钩子:activated 与 deactivated
activated在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用
deactivated:组件被停用(离开路由)时调用
九,vue中key的作用
当Vue用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue将不是移动DOM元素来匹配数据项的改变,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。key属性的类型只能为 string或者number类型。
总结:key的作用是为了更高效地更新虚拟dom.
这涉及地是diff算法地问题(虚拟dom):
1,如果节点类型不同,直接干掉前面的所有节点,再创建并插入新的节点,不会再比较这个节点以后的子节点。
2,如果节点的类型相同,则会重新设置节点地属性,从而实现节点地更新。
当ABCDE节点类型相同,想要在bc之间插入一个F时:
也就是说:要使用key来给每一个节点做一个唯一的标识,diff算法可以正确地识别此节点,从而找到正确地位置插入新节点
1,首先是虚拟DOM与真实DOM:
在理解vue的diff算法之前需要先理解真实DOM和virtual DOM,在真实DOM的数据发生变化之后会发生回流重绘,导致整个DOM树的更新,显然这种方式很低效。所以在VUE中提出了virtual DOM,参照真实DOM生成一棵virtual DOM,当我们更新了某个节点数据生成了新的Vnode,将其和旧的Old Vnode进行对比,只将发生变化的地方更新在真实DOM上,同时将Old Vnode变成Vnode。在vue中我们将这个过程叫做给真实的DOM打补丁
2,diff算法的流程:
在1中,虚拟DOM与真实DOM对比地方式就是利用diff算法:
1》.调用patch函数比较Vnode和OldVnode,如果不一样直接return Vnode,即将Vnode真实化后替换掉DOM中的节点
2》.如果OldVnode和Vnode值得进一步比较则调用patchVnode方法进行进一步比较,分为以下几种情况:
Vnode有子节点,但是OldVnode没有,则将Vnode的子节点真实化后添加到真实DOM上
Vnode没有子节点,但是OldVnode上有,则将真实DOM上相应位置的节点删除掉
Vnode和OldVnode都有文本节点但是内容不一样,则将真实DOM上的文本节点替换为Vnode上的文本节点
Vnode和OldVnode上都有子节点,需要调用updateChildren函数进一步比较:
提取出Vnode和OldVnode中的子节点分别为vCh和OldCh,并且提出各自的起始和结尾变量标记为 oldS oldE newS newE
对这四个变量进行四次对比,如果相同进行修改相应的真实DOM上的节点,同时s和e向中间靠拢。如果不同:
在没有key的情况下直接在DOM的oldS位置的前面添加newS,同时newS+1。在有key的情况下会将newS和Oldch上的所有节点对比,如果有相同的则移动DOM并且将旧节点中这个位置置为null且newS+1。如果还没有则直接在DOM的oldS位置的前面添加newS且newS+1
直到出现任意一方的start>end,则有一方遍历结束,整个比较也结束
3,有key的情况
vue总是尽可能高效的渲染元素而不是从头渲染,这也是diff算法的精髓。但是有些情况下并不是符合需求的于是增加key,
有key的情况:添加了key可以优化v-for的性能,,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。key属性的类型只能为 string或者number类型。
十,vue的双向数据绑定
1,Object.defineproperty()方法
Object.defineProperty( ) 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
语法:
Object.defineProperty(obj, prop, descriptor)
参数
obj
需要被操作的目标对象
prop
目标对象需要定义或修改的属性的名称。
descriptor
将被定义或修改的属性的描述符。
返回值
被传递给函数的对象。
属性描述符
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个拥有可写或不可写值的属性。存取描述符是由一对 getter-setter 函数功能来描述的属性。描述符必须是两种形式之一;不能同时是两者。下面是存取描述符:
get:当访问对应的属性名时,执行这里面的内容
set:当给对应的属性赋值时,执行这里面的内容:
一个简单版的数据绑定:
也就是说,他不是直接把obj.name属性直接赋值给oDiv.innerHTML,而是通过Object.defineproperty劫持了obj.name的值,然后赋值给oDiv.innerHTML。
2,vue中数据双向绑定
总结:vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
实现mvvm主要包含两个方面:数据更新视图和视图更新数据,上面的例子就是视图更新数据,比较简单,因为view更新data其实只要通过事件监听就可以,比如上面的input事件监听。
重点还是data来更新view。
数据更新data的重点是如何知道数据变化了,变成什么了?
按上文的说法,利用Object.defineproperty来对属性设置一个set函数。每当数据改变的时候,必然触发这个函数,所以我们只要把一些需要更新的方法放在这里面就可以实现data更新view了
我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
1》.实现一个Observer
Observer是一个数据监听器,其实现核心方法就是前文所说的Object.defineProperty( )。如果要对所有属性都进行监听的话,那么可以通过递归方法遍历所有属性值,并对其进行Object.defineProperty( )处理。如下代码,实现了一个Observer。
<script type="text/javascript">
function defineReactive(data, key, val) {
observe(val); // 递归遍历所有子属性,因为子属性也可能是对象
Object.defineProperty(data, key, {
enumerable: true, //属性可以出现在for in 或者 Object.keys()的遍历中
configurable: true,//true时,可以删除当前属性,能够重新定义属性。
get: function() {
return val;
}, //如果是访问数据,则原始数据返回
//如果是变更了数据,就把 val = newVal;
set: function(newVal) {
val = newVal;
console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
}
});
}
//对data对象的所有子节点进行数据的监听
function observe(data) {
//若data不是一个对象或数组或者为空,则返回
if (!data || typeof data !== 'object') {
return;
}
//对data对象所有的属性key进行监听,
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key]);
});
};
//示例演示
var library = {
book1: {
name: ''
},
book2: ''
};
observe(library);
library.book1.name = 'vue权威指南'; // 属性name已经被监听了,现在值为:“vue权威指南”
library.book2 = '没有此书籍'; // 属性book2已经被监听了,现在值为:“没有此书籍”
</script>
思路分析中,需要创建一个可以容纳订阅者的消息订阅器Dep,订阅器Dep主要负责收集订阅者,然后在属性变化的时候执行对应订阅者的更新函数。所以显然订阅器需要有一个容器,这个容器就是list,将上面的Observer稍微改造下,植入消息订阅器:
<script type="text/javascript">
function defineReactive(data, key, val) {
observe(val); // 递归遍历所有子属性,因为子属性也可能是对象
var dep = new Dep(); //创建一个dep实例对象,它的原型链上有addsub和notify方法,本身有subs空数组
Object.defineProperty(data, key, {
enumerable: true, //属性可以出现在for in 或者 Object.keys()的遍历中
configurable: true,//true时,可以删除当前属性,能够重新定义属性。
get: function() {
if (是否需要添加订阅者) {
dep.addSub(watcher); // 在这里添加一个订阅者进subs数组
}
return val;
},
//如果是变更了数据,就把 val = newVal;
set: function(newVal) {
//如果数据不变。则返回
if (val === newVal) {
return;
}
val = newVal;
console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
dep.notify(); // 如果数据变化,通知所有订阅者
}
});
}
//对data对象的所有子节点进行数据的监听
function observe(data) {
//若data不是一个对象或数组或者为空,则返回
if (!data || typeof data !== 'object') {
return;
}
//对data对象所有的属性key进行监听,
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key]);
});
};
function Dep () {
this.subs = [];
}
Dep.prototype = { //重置dep的原型对象为这个,里面有两个方法
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
</script>
从代码上看,我们将订阅器Dep添加一个订阅者设计在getter里面,这是为了让Watcher初始化进行触发,因此需要判断是否要添加订阅者,至于具体设计方案,下文会详细说明的。在setter函数里面,如果数据变化,就会去通知所有订阅者,订阅者们就会去执行对应的更新的函数。到此为止,一个比较完整Observer已经实现了,接下来我们开始设计Watcher。
2》.实现Watcher
订阅者Watcher在初始化的时候需要将自己添加进订阅器Dep中,那该如何添加呢?我们已经知道监听器Observer是在get函数执行了添加订阅者Wather的操作的,所以我们只要在订阅者Watcher初始化的时候出发对应的get函数去执行添加订阅者操作即可,那要如何触发get的函数,再简单不过了,只要获取对应的属性值就可以触发了,核心原因就是因为我们使用了Object.defineProperty( )进行数据监听。这里还有一个细节点需要处理,我们只要在订阅者Watcher初始化的时候才需要添加订阅者,所以需要做一个判断操作,因此可以在订阅器上做一下手脚:在Dep.target上缓存下订阅者,添加成功后再将其去掉就可以了。订阅者Watcher的实现如下:
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.value = this.get(); // 将自己添加到订阅器的操作
}
Watcher.prototype = {
update: function() {
this.run();
},
run: function() {
var value = this.vm.data[this.exp];
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
get: function() {
Dep.target = this; // 缓存自己
var value = this.vm.data[this.exp] // 强制执行监听器里的get函数
Dep.target = null; // 释放自己
return value;
}
};
这时候,我们需要对监听器Observer也做个稍微调整,主要是对应Watcher类原型上的get函数。需要调整地方在于defineReactive函数:
function defineReactive(data, key, val) {
observe(val); // 递归遍历所有子属性
var dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
if (Dep.target) {. // 判断是否需要添加订阅者
dep.addSub(Dep.target); // 在这里添加一个订阅者
}
return val;
},
set: function(newVal) {
if (val === newVal) {
return;
}
val = newVal;
console.log('属性' + key + '已经被监听了,现在值为:“' + newVal.toString() + '”');
dep.notify(); // 如果数据变化,通知所有订阅者
}
});
}
Dep.target = null;
十一,在vue中使用插件的步骤
1,下载:npm install xxx
2,引入:
import xxx from 'xxx'
Vue.use(xxx)
十二,watch和computed的区别
1,computed是由data中的已知值来计算一个新的属性,并且将该属性挂载到vue实例上,而watch是监听已经存在且已经挂载到vue实例上的数据,所以使用wathc也可以监听computed计算属性的变化。
2,computed本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化和第一次访问computed属性,才会计算新的值。否则直接读取缓存。
而watch则是当数据发生变化便会调用执行函数。
3,从使用场景上来说,computed适合一个数据被多个数据影响。watch,适合一个数据影响多个数据。
十三,axios
axios 是什么
- Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。前端最流行的 ajax 请求库,
- react/vue 官方都推荐使用 axios 发 ajax 请求
axios 特点
- 基于 promise 的异步 ajax 请求库,支持promise所有的API
- 浏览器端/node 端都可以使用,浏览器中创建XMLHttpRequests
- 支持请求/响应拦截器
- 支持请求取消
- 可以转换请求数据和响应数据,并对响应回来的内容自动转换成 JSON类型的数据
- 批量发送多个请求
- 安全性更高,客户端支持防御 XSRF,就是让你的每个请求都带一个从cookie中拿到的key, 根据浏览器同源策略,假冒的网站是拿不到你cookie中得key的,这样,后台就可以轻松辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略。
十四,单页面应用的优缺点
1,单页面应用(SPA)
单页面应用,通俗一点讲就是只有一个主页面的应用,浏览器一开始要加载所有必须的html,css,js文件。所有的页面内容都包含在这个所谓的主页面中,但是在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,实质上只是局部资源的刷新。
2,多页面应用(MPA)
多页面(MPA),就是指一个应用中有多个页面,页面跳转时是整页刷新。
3,单页面的优点:
a,用户体验好、快,内容的改变不需要重新加载刷新年整个页面,基于这一点,spa对服务器的压力较小。
b,前后端分离
c,页面效果会比较炫酷(比如页面内容切换的专场动画)
4,单页面的缺点:
1,不利于seo
2,导航不可用,如果一定要导航需要自行实现前进后退(由于是单页面不能用浏览器的前进后退,所以需要自己建立堆栈管理)
3,初次加载耗时多
4,页面的复杂度提高很多。
十五,vue中路由的跳转方式
1,router-link的to(这是声明式导航)
2,使用js(编程式导航)
十六, r o u t e r 和 router和 router和route的区别
r
o
u
t
e
r
是
V
u
e
−
R
o
u
t
e
r
的
实
例
对
象
,
想
要
导
航
到
不
同
的
U
R
L
,
则
使
用
router是Vue-Router的实例对象,想要导航到不同的URL,则使用
router是Vue−Router的实例对象,想要导航到不同的URL,则使用router.push方法
$route则是当前router跳转对象,里面可以获取name,path,query,params等参数信息
十七,Vue-Router的导航钩子
vue-router 的导航钩子,主要用来作用是拦截导航,让他完成跳转或取消
1. 全局导航钩子:
全局导航钩子主要有两种钩子:前置守卫、后置钩子,
这三个参数 to 、from 、next 分别的作用:
to: Route,代表要进入的目标,它是一个路由对象
from: Route,代表当前正要离开的路由,同样也是一个路由对象
next: Function,这是一个必须需要调用的方法,而具体的执行效果则依赖 next 方法调用的参数
next():进入管道中的下一个钩子,如果全部的钩子执行完了,则导航的状态就是 confirmed(确认的)
next(false):这代表中断掉当前的导航,即 to 代表的路由对象不会进入,被中断,此时该表 URL 地址会被重置到 from 路由对应的地址
next(‘/’) 和 next({path: ‘/’}):在中断掉当前导航的同时,跳转到一个不同的地址
next(error):如果传入参数是一个 Error 实例,那么导航被终止的同时会将错误传递给 router.onError() 注册过的回调
注意:next 方法必须要调用,否则钩子函数无法 resolved.
一,前置导航钩子
可以在全局拦截所有的路由请求
//可以利用vue-router的导航钩子实现登陆与否的判断:未登录则跳转到登陆界面
router.beforeEach((to,from,next)=>{
//如果访问的是登陆界面,直接放行
if(to.path==='/login') return next()
//如果用户访问的不是登陆界面,则是需要获取客户端保存的token值,有则放行,没有则跳转到登陆界面
const tokenStr=window.sessionStorage.getItem('token')
if(!tokenStr) return next('/login')
next()
})
例如:
具体的代码:
https://gitee.com/ling-xu/vue-router-example
二,后置导航钩子
不同于前置守卫,后置钩子并没有 next 函数,也不会改变导航本身
2,路由中的导航钩子
只有一个:
const routes = [
{path: '/',name: 'Home',component: Home},
{path: '/fav', component: Fav},
{path: '/news', component: News},
{
path: '/me',
component: User,
beforeEnter:(to,from,next)=>{
//如果用户访问是登陆界面,则是需要获取客户端保存的token值,有则放行,没有则跳转登陆
const tokenStr=window.sessionStorage.getItem('token')
if(!tokenStr) return next('/login')
next()
}
},
{
path: '/login',
component: Login
}
]
比如上面的例子,如果其他页面都可以浏览,但是只有个人中心是需要登陆的,所以就需要用到路由中局部的导航钩子实现。
3,组件的钩子函数
组件内的导航钩子主要有这三种:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。他们是直接在路由组件内部直接进行定义的
和组件内的生命周期钩子一样的用法。
我们看一下他的具体用法:
const File = {
template: `<div>This is file</div>`,
beforeRouteEnter(to, from, next) {
// do someting
// 在渲染该组件的对应路由被 confirm 前调用
},
beforeRouteUpdate(to, from, next) {
// do someting
// 在当前路由改变,但是依然渲染该组件是调用
},
beforeRouteLeave(to, from ,next) {
// do someting
// 导航离开该组件的对应路由时被调用
}
}
需要注意是:
beforeRouteEnter 不能获取组件实例 this,因为当守卫执行前,组件实例被没有被创建出来,剩下两个钩子则可以正常获取组件实例 this