1. vue实现双向绑定的原理?
MVVM 模式在于数据与视图的保持同步,意思是说数据改变时会自动更新视图,视图发生变化时会更新数据。
所以我们需要做的就是如何检测到数据的变化然后通知我们去更新视图,如何检测到视图的变化然后去更新数据。检测视图这个比较简单,无非就是我们利用事件的监听即可。那么如何才能知道数据属性发生变化呢?这个就是利用我们上面说到的 Object.defineProperty
当我们的属性发生变化时,它会自动触发 set 函数从而能够通知我们去更新视图。
数据双向绑定作为 Vue 核心功能之一,Vue 则采用的是数据劫持与发布订阅相结合的方式实现双向绑定。
其中数据劫持是利用了 Object.defineProperty() 方法重新定义了对象获取属性值get和设置属性值set的操作来实现的;
preview劫持了数据之后,我们就需要一个监听器 Observer 来监听属性的变化。得知属性发生变化之后我们需要一个 Watcher 订阅者来更新视图,我们还需要一个 compile 指令解析器,用于解析我们的节点元素的指令与初始化视图。
- Observer 监听器:用来监听属性的变化通知订阅者
- Watcher 订阅者:收到属性的变化,然后更新视图(这个过程中我们可能会有很多个订阅者 Watcher 所以我们要创建一个容器 Dep 去做一个统一的管理)
- Compile 解析器:解析指令,初始化模版,绑定订阅者
语法 Object.defineProperty(obj,prop,descriptor)
参数:obj:目标对象;prop:需要定义的属性或方法的名称;descriptor:目标属性所拥有的特性
可供定义的特性列表
可供定义的特性 | |
---|---|
value | 属性的值 |
writable | 如果为false,属性的值就不能被重写 |
get | 一旦目标属性被访问就会调回此方法,并将此方法的运算结果返回用户 |
set | 一旦目标属性被赋值,就会调回此方法 |
configurable | 如果为false,则任何尝试删除目标属性或修改属性性以下特性(writable, configurable, enumerable)的行为将被无效化。 |
enumerable | 是否能在for…in循环中遍历出来或在Object.keys中列举出来 |
var obj = { };
var name;
Object.defineProperty(obj, "data", {
//获取值
get:function () {return name;},
//设置值
set:function (val) {name = val;console.log(val);}
})
obj.data = 'aaa';//赋值调用set
console.log(obj.data); //取值调用get
当我们访问或设置对象的属性的时候,都会触发相对应的函数,然后在这个函数里返回或设置属性的值。既然如此,我们当然可以在触发函数的时候动一些手脚做点我们自己想做的事情,这也就是“劫持”操作。
在Vue中其实就是通过Object.defineProperty
来劫持对象属性的setter
和getter
操作,并“种下”一个监听器,当数据发生变化的时候发出通知。
注意: 该方法每次只能设置一个属性,那么就需要遍历对象来完成其属性的配置:
Object.keys(student).forEach(key => defineReactive(student, key))
另外还必须是一个具体的属性,这也非常的致命。假如后续需要扩展该对象,那么就必须手动为新属性设置 setter 和 getter 方法,这就是为什么不在 data 中声明的属性无法自动拥有双向绑定效果的原因 。这时需要调用 Vue.set()
手动设置。
针对 Array 类型的劫持
数组是一种特殊的对象,其下标实际上就是对象的属性,所以理论上是可以采用 Object.defineProperty()
方法处理数组对象。
但是 Vue 并没有采用上述方法劫持数组对象,原因分析:1、特殊的 length 属性,相比较对象的属性,数组下标变化地相对频繁,并且改变数组长度的方法也比较灵活,一旦数组的长度发生变化,那么在无法自动感知的情况下,开发者只能手动更新新增的数组下标,这可是一个很繁琐的工作。2、数组主要的操作场景还是遍历,而对于每一个元素都挂载一个 get 和 set 方法,恐怕也是不小的性能负担。
数组方法的劫持:最终 Vue 选择劫持一些常用的数组操作方法,从而知晓数组的变化情况:push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice'
。数组方法的劫持涉及到原型相关的知识,首先数组实例大部分方法都是来源于 Array.prototype
对象。顺便提一下,采用 Vue.set() 方法设置数组元素时,Vue 内部实际上是调用劫持后的 splice() 方法来触发更新。
总结:由上述内容可知,Vue 中的数据劫持分为两大部分:
针对 Object 类型,采用 Object.defineProperty()
方法劫持属性的读取和设置方法;
针对 Array 类型,采用原型相关的知识劫持常用的函数,从而知晓当前数组发生变化。
并且 Object.defineProperty()
方法存在以下缺陷:每次只能设置一个具体的属性,导致需要遍历对象来设置属性,同时也导致了无法探测新增属性;属性描述符 configurable 对其的影响是致命的。
发布订阅模式
在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
1568963964686数据无法双向绑定的情况
Object.freeze(); 或者 数据未在实例中进行初始化
var obj = { foo: 'bar'}
Object.freeze(obj)
new Vue({ el: '#app', data: obj})
Object.freeze()方法有三个特点:
1.使对象不可扩展,无法向其添加新属性。
2.为对象的所有属性将 configurable 特性设置为 false。在 configurable 为 false 时,无法更改属性的特性且无法删除属性。
3.为对象的所有数据属性将 writable 特性设置为 false。当 writable 为 false 时,无法更改数据属性值。
数据未在实例中进行初始化
只有当实例被创建时 data
中存在的属性才是响应式的。也就是说如果你添加一个新的属性在created生命周期完成后,那么数据将无法进行绑定,如直接使用vm.a=”xxx”,虽然a成了实例的一个属性,但是对它的任何修改将不是响应式的
2. vue组件之间的传值?
-
父传子: 先在父组件中绑定变量
<child :msg="parent"></child>
,parent是定义在父组件中的变量/值; 再在子组件中添加props属性接收父组件传递过来的变量props:['msg']
; 最后就可以在子组件中使用``来表示父组件中parent变量中的值了。 -
子传父: 先在子组件中绑定事件
@change="sendChild"
,触发的时候在setChild
事件中用$emit()
触发父组件中的函数,并将子组件中的变量作为参数传递;methods:{ sendChild:function(){ this.$emit('transparent',this.msg) } }
在父组件中绑定事件
<child @transparent="getChild"></child>
,当子组件触发这个事件的时候,就可以调用getChild方法获取到传递过来的参数;methods:{ getChild(msg){ this.user=msg; } }
1.兄弟组件互相传值,通过Vuex状态管理传值: 先通过npm加载vuex,创建store.js文件
//store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const state={name:'Alice'};
const mutations={
newName(state,message){
state.name=message
}
}
export default new Vuex.Store({state,mutations})
2.兄弟组件互相传值,引入bus.js文件,发布者订阅者模式:
import Bus from './bus.js'
//一个子组件触发
methods:{
Bus.$emit('触发的方法名',需要传递的值);
}
//一个子组件监听
mounted:{
bus.$on("方法名",(传递的值)=>{ })
}
3.兄弟组件互相传值$root
//一个子组件触发
this.$root.$emit('触发的方法名',需要传递的值);
//一个子组件监听
this.$root.$off("方法名");//每次进入先关闭一下
this.$root.$on("方法名",(传递的值)=>{ })
3.vue的生命周期有哪些?它们有什么不同?
生命周期钩子 | 组件状态 | 响应类型 | 最佳实践 |
---|---|---|---|
beforeCreate | 实例初始化之后,this指向创建的实例,不能访问到data、computed、watch、methods(都不存在)上的方法和数据 | 拿不到任何信息,无法篡改数据,一般做loding,这个时候的vue实例还什么都没有,但是$route对象是存在的,可以根据路由信息进行重定向之类的操作。 | 常用于初始化非响应式变量 |
created | 实例创建完成,可访问data、computed、watch、methods上的方法和数据,还未挂载到DOM,不能访问到ref属性内容为空数组 | ref属性内容为空数组;定义getter、setter存储器属性,在实例创建之后被调用,该阶段可以访问data,可以使用this。该阶段允许执行http请求操作。 | 常用于简单的ajax请求,页面的初始化 |
beforeMount | 在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数 | $el已被初始化,数据已加载完成,阔以篡改数据,并更新,不会触发beforeUpdate,updated。将HTML解析生成AST节点,再根据AST节点动态生成渲染函数。相关render函数首次被调用(划重点)。 | - |
mounted | 实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,$ref属性可以访问 | $el已被初始化,数据已加载完成,阔以篡改数据,并更新,并且触发beforeUpdate,updated,在这发起后端请求,拿回数据,配合路由钩子做一些事情;在挂载完成之后被调用,执行render函数生成虚拟dom,创建真实dom替换虚拟dom,并挂载到实例。可以操作dom,比如事件监听 | 常用于获取信息和操作,ajax请求 |
beforeUpdate | 响应式数据更新时调用,发生在虚拟DOM打补丁之前 | vm.data,并不会触发附加的重渲染过程。 | 适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器 |
updated | 虚拟DOM重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作 | 虚拟dom重新渲染后调用,若再次修改$vm.data,会再次触发beforeUpdate、updated,进入死循环 | 避免在这个钩子函数中操作数据,可能陷入死循环 |
beforeDestroy | 实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例 | 常用于销毁定时器、解绑全局事件、销毁插件对象等操作 | |
destroyed | 实例销毁后调用,调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁 | - |
注:
created和mounted之间的区别:
-
created和mounted中ajax请求的区别:created的时候视图未出现,请求较多的情况下,会出现白屏;
-
created 是在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图,比如初始化、获取屏幕高度调整、赋值等等;
而mounted是在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作;
- 初始化组件的时候,仅执行beforeCreated/created/beforeMount/mounted四个钩子函数;
- 当改变data中定义的响应式变量时,会执行beforeUpadate/updated;
- 初始化和销毁时的钩子函数只会执行一次,beforeUpadate/updated可执行多次;
- 挂载的时候,子组件完成挂载后,父组件才会挂载;
- 当子组件完成挂载后,父组件会主动执行一次beforeUpdate/updated钩子函数(仅首次);
- 销毁父组件时,先将子组件销毁后,才会销毁父组件;
- 总结来说,虚拟dom开始渲染是在beforeMount时,dom实例挂载完成在mounted阶段显示。那么接下来了解就是render函数。render函数最终返回的是createNodeDescription(节点描述),即俗称virtual node(虚拟节点)。
4. vuex的功能?能否进行兄弟组件之间的传值?
vuex专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
主要用于管理vue中的数据,可以兄弟组件互相传值;
image上图的解释:
① Vue Components 是我们的 vue 组件,组件会触发(dispatch)一些事件或动作( Actions); ② 我们在组件中发出的动作,肯定是想获取或者改变数据的,但是在 vuex 中,数据是集中管理的,我们不能直接去更改数据,所以会把这个动作提交(Commit)到 Mutations 中; ③ 然后 Mutations 就去改变(Mutate)State 中的数据; ④ 当 State 中的数据被改变之后,就会重新渲染(Render)到 Vue Components (组件)中去, Vue Components (组件)展示更新后的数据,完成一个流程。
Vuex 的核心是 Store(仓库),相当于是一个容器,一个Store实例中包含以下属性的方法:
state 定义属性(状态 、数据) getters 用来获取属性【就像计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算;getter 接受 state 作为其第一个参数;】 actions 定义方法(动作)【也可以直接修改state,通过提交 mutation 的方式,而非直接改变 this.$store.state.count
,是因为我们想要更明确地追踪到状态的变化;】 commit 提交变化,修改数据的唯一方式就是显示的提交 mutations mutations 定义变化,处理状态(数据)的改变【操作state中的数据store.commit('increment')
;】 mapGetters 用来获取属性(数据) mapActions 用来获取方法(动作)state:管理项目的数据(进行数据初始化);
mutation和action之间有什么区别?怎么写代码修改vuex中的数据?
Vuex.Store({
state,
actions,
mutation
});
action可以包含任意异步操作,提交的是mutation,而不是直接变更状态;
mutation是同步的。使用this.$store.commit('xxx')
来提交mutation。
(下文来自尤大大)
区分 actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。
事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发 mutation 就行。异步竞态怎么处理那是用户自己的事情。vuex 真正限制你的只有 mutation 必须是同步的这一点(在 redux 里面就好像 reducer 必须同步返回下一个状态一样)。
同步的意义在于这样每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。
如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态。
<div id="app1">
8
</div>
//store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state:{
count:0
}
});
new Vue({
el:'#app1',
store,
computed:{
count(){
return this.$store.state.count
}
}
})
vuex的使用
需要在根实例main.js中传入store
import store from './store'
import 'styles/reset.css'
import 'styles/border.css'
import 'styles/iconfont.css'
import 'swiper/dist/css/swiper.css'
Vue.config.productionTip = false
fastClick.attach(document.body)
Vue.use(VueAwesomeSwiper)
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
写在一个页面上的话,需要判断本地是否已经有这个数据了
1567605439082在src–store–index.js中声明state、mutations
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations
})
然后在src–store–state.js中声明公共使用的数据
let defaultCity = '上海'
try {
if (localStorage.city) {
defaultCity = localStorage.city
}
} catch (e) {}
export default {
city: defaultCity //调用的时候,调用city就可以了
}
在src–store–mutations.js中声明改变公共变量的方法
export default {
changeCity (state, city) { //调用的时候使用changeCity就可以了
state.city = city
try {
localStorage.city = city
} catch (e) {}
}
}
在实际使用的页面中,就可以直接使用了
// 调用数据
this.$store.state.city
//修改store中的内容
this.$store.dispatch('name',value)
//或者直接使用commit进行修改
this.$store.commit('name',value)
5. Axios调取数据?
[页面直接引用 <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
]
axios的特性
1.可以从浏览器中创建XHR对象 2、可以从nodeJS中创建HTTP请求 3、支持Promise 4、可以拦截请求和响应 5、可以转换请求数据和响应数据 6、可以取消请求 7、可以自动转换JSON数据 8、客户端支持防御XSRF
axios get 方法:仅仅请求后台数据
axios.get('index.php')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
aixos post方法:post请求更多的是要提交数据,params属性里的数据会出现在请求主体中。
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
多并发请求,一次性发几个请求
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
// acct为第一个请求的结果,perms为第二个请求的结果
}));
设置拦截器:
//请求拦截器
axios.interceptors.request.use(
config => {
btn.innerHTML='请求数据中';
return config;
},
// 错误时发生的事情
err => {
console.log(err)
});
// 响应应拦截器
axios.interceptors.response.use(
config => {
btn.innerHTML='请求数据成功';
return config;
},
// 错误时发生的事情
err => {
console.log(err)
});
设置自定义请求头:
先安装Axios:npm install axios --save
再在main.js中引入Axios:
import axios from 'axios'
Vue.prototype.$http = axios;
即可在组件中调用Axios:
this.$axios.get('index.php/url')
.then(response => {
console.log(response)
}).catch(error => {
console.log(error)
});
然后设置自定义的头请求:
axios.defaults.timeout = 5000;//请求超时的时间设定
axios.defaults.headers.post['Content-Type'] = 'application/json'; //axios默认的请求方式
axios.defaults.baseURL = 'http://localhost:8008';//axios默认的请求地址
axios.defaults.headers.common["token"] = "noname";//有些接口必须登录才可以调用,而登陆注册并未写好,后台给了一个故固定的token,写在了头里面
6. vue使用的UI框架?
animate.css是一款前端动画库,相似的有velocity-animate;
(element ui框架的按钮组件;
iView 是一套基于 Vue.js 的开源 UI 组件库,主要服务于 PC 界面的中后台产品
Vuetify.js根据材料设计规格提供 UI 布局;
基于 Vue.js 的 Bootstrap 组件;
cube-ui 是滴滴团队开发的基于 Vue.js 实现的精致移动端组件库;
vue-beauty 是一套基于 vue.js 和 ant-design样式 的PC端 UI 组件库;
AT-UI 是一个模块化的前端 UI 框架,基于Vue.js 的快速和强大的 Web 界面;专门为桌面应用程序构建;
Vue-Blu是基于Vue.js和Bulma开发的开源UI组件库。旨在为PC端的前端开发(特别是中后台产品)提供一个快速且灵活的解决方案。)
使用步骤:
- 首先
npm install animate.css --save
; - 然后在vue文件的script中引入
import animate from 'animate.css'
; - 最后绑定元素使用,如下:
<template>
<div class="song">
<p id="f" @click='fade'>hello</p>
</div>
</template>
methods:{
fade:function(){
$('#f').addClass('animated bounceOutLeft')
}
}
部分api常见:
fade: {
title: '淡入淡出',
fadeIn: '淡入',
fadeOut: '淡出',
},
bounce: {
title: '弹跳类',
bounceIn: '弹跳进入',
bounceOut: '弹跳退出',
},
zoom: {
title: '缩放类',
zoomIn: '放大进入',
zoomOut: '缩小退出',
},
rotate: {
title: '旋转类',
rotateIn: '顺时针旋转进入',
},
flip: {
title: '翻转类',
},
strong: {
title: '强调类',
bounce: '弹跳',
flash: '闪烁',
}
7.v-if和v-show之间的区别?
相同点:v-if与v-show都可以动态控制dom元素显示隐藏
不同点:
实现本质方法区别
- vue-show本质就是标签
display:为none;
,控制隐藏,DOM结构是一直存在的 - vue-if是动态的向DOM树内添加或者删除DOM元素
编译的区别
- v-show其实就是在控制css
- v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件
编译的条件
- v-show都会编译,初始值为false,只是将display设为none,但它也编译了
- v-if初始值为false,就不会编译了
性能
- v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,故v-show性能更好一点。
8.v-for中key值的作用?
key的作用主要是为了高效的更新虚拟DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
在用v-for更新已渲染的元素列表的时候,会使用就地复用的策略;这就是说列表数据修改的时候,他会根据key值去判断某个值是否修改,如果修改了就重新渲染,不然就复用之前的元素。
9.vue中插槽的作用?
插槽就是Vue实现的一套内容分发的API,将<slot></slot>
元素作为承载分发内容的出口,没有插槽的情况下在组件标签内些一些内容是不起任何作用的。
插槽内可以是任意内容。在<child-component>你好</child-component>
内放置一些内容,输出内容还是在组件中的内容,直接在父组件的<child-component>
标签中定义的内容不会被渲染。在子组件template中加入<slot>
元素占位,便能渲染父组件<child>
标签下的内容。
具名插槽,当需要多个插槽时,可以使用<slot>
的特性:name。这个特性可以用来定义额外的插槽。
<div id="root">
<child>
<header slot="header">header</header>
<footer slot="footer">footer</footer>
</child>
</div>
Vue.component('child',{
template:`<div>
<slot name="header">default header</slot>
<div>content</div>
<slot name="footer">default footer</slot>
</div>`
}
)
var vm=new Vue({
el:'#root'
})
//输出结果是 header content footer
插槽默认内容 ,插槽可以提供一个默认内容,如果如果父组件没有为这个插槽提供了内容,会显示默认的内容。如果父组件为这个插槽提供了内容,则默认的内容会被替换掉。
作用域插槽,作用域插槽就是父组件在调用子组件的时候给子组件传了一个插槽,这个插槽为作用域插槽,该插槽必须放在template标签里面,同时声明从子组件接收的数据放在一个自定义属性内,并定义该数据的渲染方式。(解决的问题:调用了两次child组件,因为调用的是同一个子组件,所以显示的内容完全一样。如何在每次调用时能有各自的渲染效果?)
<div id="root">
<child>
<template slot-scope="props"><!--该插槽必须放在template标签内-->
<li></li> <!--定义渲染方式-->
</template>
</child>
<child>
<template slot-scope="props">
<h1></h1><!--定义渲染方式-->
</template>
</child>
</div>
Vue.component('child',{
data: function(){
return {
list:[1,2,3,4]
}
},
template: `<div>
<ul>
<slot v-for="value in list" :value=value>//使用slot占位
</slot>
</ul>
</div>`
})
var vm=new Vue({
el: '#root'
})
10.vue中watch和computed有什么区别?分别在哪种场合下使用?监听的是data里面的值吗?
computed 计算属性 | watch 观察的动作 | methods |
---|---|---|
1. 数据会被缓存,只要依赖不发生改变,即使页面重新渲染,该方法也不会被调用 2.computed中的函数必须用return返回 | 1. 直接监测一个值的变化,监测值不发生变化,该方法就不会调用; 2. watch只会监听数据的值是否发生改变,而不会去监听数据的地址是否发生改变。也就是说,watch想要监听引用类型数据的变化,需要进行深度监听。 3.watch中的函数有两个参数,前者是newVal,后者是oldVal。 | 每次页面发生渲染,都会被重新调用 |
在computed中不要对data中的属性进行赋值操作。如果对data中的属性进行赋值操作了,就是data中的属性发生改变,从而触发computed中的函数,形成死循环了。 | 监听复杂数据类型需用深度监听(在被监听对象中使用handler );特殊情况下,watch无法监听到数组的变化,特殊情况就是说更改数组中的数据时,数组已经更改,但是视图没有更新。更改数组必须要用splice()或者$set。 |
|
使用场景:当一个值受多个属性影响的时候————购物车商品结算 | 使用场景:当一条数据的更改影响到多条数据的时候———搜索框 |
computed和watch区别:
1、 功能上:computed是计算属性,也就是依赖其它的属性计算所得出最后的值,是用于定义基于数据之上的数据。watch是监听一个值的变化,然后执行对应的回调,是在某个数据变化时做一些事情。
2、 是否调用缓存:computed中的函数所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取,而watch在每次监听的值发生变化的时候都会执行回调。
3、 是否调用return:computed中的函数必须要用return返回,watch中的函数不是必须要用return。
4、 如果一个值依赖多个属性(多对一),用computed
肯定是更加方便的。如果一个值变化后会引起一系列操作,或者一个值变化会引起一系列值的变化(一对多),用watch
更加方便一些。
11.数据请求应该添加在生命周期的哪里?
看实际情况,一般在 created(或beforeRouter) 里面就可以,如果涉及到需要页面加载完成之后的话就用 mounted。主要的区分在于是否需要操作DOM结构。 1.在created的时候,视图中的html并没有渲染出来,所以此时如果直接去操作html的dom节点,一定找不到相关的元素; 2.而在mounted中,由于此时html已经渲染出来了,所以可以直接操作dom节点,(此时document.getelementById 即可生效了);
12.vue组件中data为什么是函数
是因为js本身的特性带来的,跟vue本身设计无关。类比引用数据类型:Object是引用数据类型,如果不用function 返回,每个组件的data 都是内存的同一个地址,一个数据改变了其他也改变了;
//如果直接赋值,两个实例同时引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着改
var MyComponent = function() {}
MyComponent.prototype.data = {
a: 1,
b: 2,
}
// 上面是一个虚拟的组件构造器,真实的组件构造器方法很多
var component1 = new MyComponent()
var component2 = new MyComponent()
// 上面实例化出来两个组件实例,也就是通过<my-component>调用,创建的两个实例
component1.data.a === component2.data.a // true
component1.data.b = 5;
component2.data.b // 5
Vue.component('my-component', {
template: '<div>OK</div>',
data() {
return {} // 返回一个唯一的对象,不要和其他组件共用一个对象进行返回
},
})
data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响;
13.多页应用与单页应用的区别?以及优缺点
多页应用:
- 定义:每一次页面的跳转,后端都会返回一个新的html文件。
优点:1. 首屏事件快(只请求 html 文件就可以展示页面了,只经历了一个http请求);2. SEO(搜索引擎优化,可以识别 html 内的内容)效果好。
缺点:页面切换慢(每次跳转都要发送一个http请求)
单页应用:
- 定义:每一次页面的跳转,都是使用
JS
渲染。页面跳转不使用 a 标签,而是使用<router-link to="./list"></router-link>
,不请求html
文件,通过JS
感知到URL
的变化,可以用JS
动态得把当前页面清除掉,再把下一个页面挂载到当前页面上。
优点:页面切换快。不需要http请求。
缺点:首屏时间稍慢(除了请求一个html文件,还要请求一个 JS 文件); SEO差(不认识JS文件中的内容)
13.vue router[路由守卫]有哪些钩子函数,哪一个是可以在全局使用的
主要用来作用是拦截导航,让他完成跳转或取消。
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数。
有三种方式可以植入路由导航过程中:全局的;单个路由独享的;组件级的
①.全局导航钩子
- router.beforeEach 全局前置守卫 进入路由之前
- router.beforeResolve 全局解析守卫(2.5.0+) 在beforeRouteEnter调用之后调用
- router.afterEach 全局后置钩子 进入路由之后
// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => {
next();
});
router.beforeResolve((to, from, next) => {
next();
});
router.afterEach((to, from) => {
console.log('afterEach 全局后置钩子');
});
这三个参数 to 、from 、next 分别的作用:
to: Route,代表要进入的目标,它是一个路由对象;路由对象指的是平时通过this.$route获取到的路由对象。
from: Route,代表当前正要离开的路由,同样也是一个路由对象
next: Function,这是一个必须需要调用的方法,而具体的执行效果则依赖 next 方法调用的参数
next():进入该路由。进入管道中的下一个钩子,如果全部的钩子执行完了,则导航的状态就是 confirmed(确认的) next(false):取消进入路由,这代表中断掉当前的导航,即 to 代表的路由对象不会进入,被中断,此时该表 URL 地址会被重置到 from 路由对应的地址(也就是将要离开的路由地址)。 next(‘/’) 和 next({path: ‘/’}):在中断掉当前导航的同时,跳转到一个不同的地址 next(error):如果传入参数是一个 Error 实例,那么导航被终止的同时会将错误传递给 router.onError() 注册过的回调 注意:next 方法必须要调用,否则钩子函数无法 resolved
不同于前置守卫,后置钩子并没有 next 函数,也不会改变导航本身
②.路由独享的钩子:即单个路由独享的导航钩子,它是在路由配置上直接进行定义的
const router = new VueRouter({
routes: [{
path: '/file',
component: File,
beforeEnter: (to, from ,next) => {/*do someting*/}
}]
});
③.组件内的导航钩子: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;但是并不意味着在beforeRouteEnter
中无法访问组件实例,我们可以通过给 next 传入一个回调来访问组件实例。在导航被确认是,会执行这个回调,这时就可以访问组件实例了:
//注意,仅仅是 beforRouteEnter 支持给 next 传递回调,其他两个并不支持。因为归根结底,支持回调是为了解决 this 问题,而其他两个钩子的 this 可以正确访问到组件实例,所有没有必要使用回调
beforeRouteEnter(to, from, next) {
next (vm => {/*这里通过 vm 来访问组件实例解决了没有 this 的问题*/})
}
14.data为什么要用return?不是为了使每个组件有独立的数据?(摘自vue官方文档)
当一个组件被定义,data
必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data
仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data
函数,每次创建一个新实例后,我们能够调用 data
函数,从而返回初始数据的一个全新副本数据对象。
15.vue中的定时器,一般在哪个生命周期中清除
在beforeDestroy()里面清除定时器,
beforeDestroy() {
clearInterval(this.timer);
this.timer = null;
}
网友评论