作者:東起
鏈接:https://zhuanlan.zhihu.com/p/103763164
11、怎麼在vue中點擊別的區域輸入框不會失去焦點?
答:阻止事件的默認行爲
具體操作:監聽你想點擊後不會丟失 input 焦點的那個元素的 mousedown 事件,回調裏面調用 event.preventDefault(),會阻止使當前焦點丟失這一默認行爲。
12、vue中data的屬性可以和methods中的方法同名嗎?爲什麼?
答:不可以
因爲,Vue會把methods和data的東西,全部代理到Vue生成的對象中,會產生覆蓋所以最好不要同名
13、怎麼給vue定義全局的方法?
Vue.prototype.方法名稱
14、Vue 2.0 不再支持在 v-html 中使用過濾器怎麼辦?
解決方法:
①全局方法(推薦)
Vue.prototype.msg = function(msg){
return msg.replace("\n","<br>")
}
<div v-html="msg(content)"></div>
②computed方法
computed:{
content:function(msg){
return msg.replace("\n","<br>")
}
}
<div>{{content}}</div>
③$options.filters(推薦)
filters:{
msg:function(msg){
return msg.replace(/\n/g,"<br>")
}
},
data:{
content:"XXXX"
}
<div v-html="$options.filters.msg(content)"></div>
14、怎麼解決vue打包後靜態資源圖片失效的問題?
答:將靜態資源的存放位置放在src目錄下
16、怎麼解決vue動態設置img的src不生效的問題?
<img class="logo" :src="logo" alt="公司logo">
data() {
return {
logo:require("./../assets/images/logo.png"),
};
}
因爲動態添加src被當做靜態資源處理了,沒有進行編譯,所以要加上require
17、跟keep-alive有關的生命週期是哪些?描述下這些生命週期
activated和deactivated兩個生命週期函數
1.activated:當組件激活時,鉤子觸發的順序是created->mounted->activated
2.deactivated: 組件停用時會觸發deactivated,當再次前進或者後退的時候只觸發activated
18、你知道vue中key的原理嗎?說說你對它的理解
暫時沒弄明白,等會兒寫
19、vue中怎麼重置data?
答:Object.assign()
Object.assign()方法用於將所有可枚舉屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象。
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };
var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1); // { a: 1, b: 2, c: 3 }, 注意目標對象自身也會改變。
注意,具有相同屬性的對象,同名屬性,後邊的會覆蓋前邊的。
由於Object.assign()有上述特性,所以我們在Vue中可以這樣使用:
Vue組件可能會有這樣的需求:在某種情況下,需要重置Vue組件的data數據。此時,我們可以通過this.$data獲取當前狀態下的data,通過this.$options.data()獲取該組件初始狀態下的data。
然後只要使用**Object.assign(this.options.data())**就可以將當前狀態的data重置爲初始狀態。
20、vue怎麼實現強制刷新組件?
答:① v-if ② this.$forceUpdate
v-if
當v-if的值發生變化時,組件都會被重新渲染一遍。因此,利用v-if指令的特性,可以達到強制
<comp v-if="update"></comp>
<button @click="reload()">刷新comp組件</button>
data() {
return {
update: true
}
},
methods: {
reload() {
// 移除組件
this.update = false
// 在組件移除後,重新渲染組件
// this.$nextTick可實現在DOM 狀態更新後,執行傳入的方法。
this.$nextTick(() => {
this.update = true
})
}
}
this.$forceUpdate
<button @click="reload()">刷新當前組件</button>
methods: {
reload() {
this.$forceUpdate()
}
}
21、vue如何優化首頁的加載速度?
① 第三方js庫按CDN引入(一、cdn引入 二、去掉第三方庫引入的import 三、把第三方庫的js文件從打包文件裏去掉)
② vue-router路由懶加載
③ 壓縮圖片資源
④ 靜態文件本地緩存
http緩存:推薦網站:https://www.cnblogs.com/chinajava/p/5705169.html
service worker離線緩存:,缺點:需要在HTTPS站點下,推薦:http://lzw.me/a/pwa-service-worker.html
⑤ 服務器端SSR渲染
除了上面的方案以外,另一種方案也不容小視
我們先說說通常項目中是如何加載頁面數據:Vue組件生命週期中請求異步接口,在mounted之前應該都可以,據我瞭解絕大部分同學是在mounted的時候執行異步請求。但是我們可以把頁面需要的請求放到Vue-Router的守衛中執行,意思是在路由beforeEnter之前就可以請求待加載頁面中所有組件需要的數據,此時待加載頁面的Vue組件還沒開始渲染,而Vue組件開始渲染的時候我們就可以用Vuex裏面的數據了。
以上方法的實現思路:
圖意:每個頁面(Page)中都會有很多個Vue組件,可以在Vue組件中添加自定義屬性fetchData,fetchData裏面可以執行異步請求(圖中執行Vuex的Action),但是我們怎麼獲取到所有組件的fetchData方法並執行呢?如圖所示,在router.beforeResolve守衛中,我們看看router.beforeResolve的定義,所有組件內守衛和異步路由組件被解析之後,解析守衛就被調用,意思是即使頁面中有異步組件,它會等待異步組件解析之後執行,並且解析守衛在beforeEnter之前執行。那我們怎麼在解析守衛中獲取到待加載頁面的所有組件呢?通過router.getMatchedComponents方法。
這樣我們就可以在解析守衛中獲取到所有待加載組件的fetchData方法並執行,這樣無疑會在組件開始渲染之後獲取到所有數據,提高頁面加載速度。以上方法的實現思路:
很多人可能有個疑問,如果異步請求放在beforeCreate和created不是一樣嗎?答案是否定的,因爲這種方式可以將異步請求放到beforeCreate之前!
22、你瞭解vue的diff算法嗎?
推薦網站:https://www.cnblogs.com/wind-lanyan/p/9061684.html
23、vue能監聽到數組變化的方法有哪些?爲什麼這些方法能監聽到呢?
Vue.js觀察數組變化主要通過以下7個方法(push、pop、shift、unshift、splice、sort、reverse)
大家知道,通過Object.defineProperty()劫持數組爲其設置getter和setter後,調用的數組的push、splice、pop等方法改變數組元素時並不會觸發數組的setter,繼而數組的數據變化並不是響應式的,但是vue實際開發中卻是實時響應的,是因爲vue重寫了數組的push、splice、pop等方法
從源碼中可以看出,ob.dep.notify()將當前數組的變更通知給其訂閱者,這樣當使用重寫後方法改變數組後,數組訂閱者會將這邊變化更新到頁面中
24、說說你對proxy的理解?
Proxy用於修改某些操作的默認行爲,也可以理解爲在目標對象之前架設一層攔截,外部所有的訪問都必須先通過這層攔截,因此提供了一種機制,可以對外部的訪問進行過濾和修改。
var target = {
name: 'zhangsan',
age:20,
sex:'男'
}
var logHandler = {
get(target, key) {
console.log(`${key}被讀取`)
return target[key]
},
set(target, key, value) {
console.log(`${key}被設置爲${value}`)
target[key] = value
}
}
var demo = new Proxy(target, logHandler)
demo.name //name被讀取
var proxy = new Proxy(target, handler);
Proxy對象的所有用法,都是上面的這種形式。不同的只是handle參數的寫法。其中new Proxy用來生成Proxy實例,target是表示所要攔截的對象,handle是用來定製攔截行爲的對象。
我們可以將Proxy對象,設置到object.proxy屬性,從而可以在object對象上調用。
var object = { proxy: new Proxy(target, handler) };
Proxy對象也可以作爲其它對象的原型對象。
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
上面代碼中,proxy對象是obj的原型對象,obj本身並沒有time屬性,所以根據原型鏈,會在proxy對象上讀取屬性,從而被攔截。
同一個攔截函數,可以設置多個操作。
var handler = {
get: function (target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function (target, thisBinding, args) {
return args[0];
},
construct: function (target, args) {
return { value: args[1] };
}
};
var fproxy = new Proxy(function (x, y) {
return x + y;
}, handler);
fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true
25、怎麼緩存當前的組件?緩存後怎麼更新?
<keep-alive>
<router-view></router-view>
</keep-alive>
<!-- 這裏是需要keepalive的 -->
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<!-- 這裏不會被keepalive -->
<router-view v-if="!$route.meta.keepAlive"></router-view>
{
path: '',
name: '',
component: ,
meta: {keepAlive: true} // 這個是需要keepalive的
},
{
path: '',
name: '',
component: ,
meta: {keepAlive: false} // 這是不會被keepalive的
}
如果緩存的組件想要清空數據或者執行初始化方法,在加載組件的時候調用activated鉤子函數,如下:
activated: function () {
this.data = '';
}
26、axios怎麼解決跨域的問題?
使用axios直接進行跨域訪問不可行,我們需要配置代理
代理可以解決的原因:
因爲客戶端請求服務端的數據是存在跨域問題的,而服務器和服務器之間可以相互請求數據,是沒有跨域的概念(如果服務器沒有設置禁止跨域的權限問題),也就是說,我們可以配置一個代理的服務器可以請求另一個服務器中的數據,然後把請求出來的數據返回到我們的代理服務器中,代理服務器再返回數據給我們的客戶端,這樣我們就可以實現跨域訪問數據
1.配置BaseUrl
import axios from 'axios'
Vue.prototype.$axios = axios
axios.defaults.baseURL = '/api' //關鍵代碼
2.配置代理
在config文件夾下的index.js文件中的proxyTable字段中,作如下處理:
proxyTable: {
'/api': {
target:'http://api.douban.com/v2', // 你請求的第三方接口
changeOrigin:true,
// 在本地會創建一個虛擬服務端,然後發送請求的數據,並同時接收請求的數據,
//這樣服務端和服務端進行數據的交互就不會有跨域問題
pathRewrite:{ // 路徑重寫,
'^/api': ''
// 替換target中的請求地址,也就是說以後你在請求http://api.douban.com/v2/XXXXX
//這個地址的時候直接寫成/api即可。
}
}
}
1. 在具體使用axios的地方,修改url如下即可
axios.get("/movie/top250").then((res) => {
res = res.data
if (res.errno === ERR_OK) {
this.themeList=res.data;
}
}).catch((error) => {
console.warn(error)
})
原理:
因爲我們給url加上了前綴/api,我們訪問/movie/top250就當於訪問了:localhost:8080/api/movie/top250(其中localhost:8080是默認的IP和端口)。
在index.js中的proxyTable中攔截了/api,並把/api及其前面的所有替換成了target中的內容,因此實際訪問Url是http://api.douban.com/v2/movie/top250。
至此,純前端配置代理解決axios跨域得到解決
27、怎麼實現路由懶加載呢?
第一種(最常用):
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
第二種:
const router = new Router({
routes: [
{
path: '/index',
component: (resolve) => {
require(['../components/index'], resolve) // 這裏是你的模塊 不用import去引入了
}
}
]
})
第三種(官方推薦):
// r就是resolve
const list = r => require.ensure([], () => r(require('../components/list/list')), 'list');
// 路由也是正常的寫法 這種是官方推薦的寫的 按模塊劃分懶加載
const router = new Router({
routes: [
{
path: '/list/blog',
component: list,
name: 'blog'
}
]
})
28、怎樣動態加載路由?
一、思路
① 在vue-router對象中首先初始化公共路由,比如(首頁,404,login)等
② 用戶登陸成功後,根據用戶的角色信息,獲取對應權限菜單信息menuList,並將後臺返回的menuList轉換成我們需要的router數據結構
③ 通過**router.addRouter(routes)**方法,同時我們可以將轉後的路由信息保存於vuex,這樣我們可以在我們的SideBar組件中獲取我們的全部路由信息,並且渲染我們的左側菜單欄,讓動態路由實現。
二、實現
① 初始化公共路由
//只顯示主要代碼
export const routes= [
{ path: '/login', component: () => import('@/views/login/index'), hidden: true },
{ path: '/404', component: () => import('@/views/404'), hidden: true }
]
export default new Router({
scrollBehavior: () => ({ y: 0 }),
routes: routes
})
② 登陸成功後,獲取菜單信息 menuList,並轉換成router數組的結構
router.beforeEach((to, from, next) => {
NProgress.start()//進度條包 npm安裝
if (getToken()) {
/*有 token,已經登錄成功*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
if (store.getters.roles.length === 0) { // 判斷當前用戶是否已拉取完user_info信息
store.dispatch('GetInfo').then(res => { // 拉取user_info
const roles = res.roles
store.dispatch("GetMenu").then(data => {
initMenu(router, data);
});
next()
}).catch((err) => {
store.dispatch('FedLogOut').then(() => {
Message.error(err || 'Verification failed, please login again')
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
/* 無 token*/
if (whiteList.indexOf(to.path) !== -1) { // 在免登錄白名單,直接進入
next()
} else {
next('/login') // 否則全部重定向到登錄頁
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
③ 動態加載路由
import store from '../store'
export const initMenu = (router, menu) => {
if (menu.length === 0) {
return
}
let menus = formatRoutes(menu);
let unfound = { path: '*', redirect: '/404', hidden: true }
menus.push(unfound) //404組件最後添加
router.addRoutes(menus)
store.commit('ADD_ROUTERS',menus)
}
export const formatRoutes = (aMenu) => {
const aRouter = []
aMenu.forEach(oMenu => {
const {
path,
component,
name,
icon,
childrens
} = oMenu
if (!validatenull(component)) {
let filePath;
const oRouter = {
path: path,
component(resolve) {
let componentPath = ''
if (component === 'Layout') {
require(['../views/layout/Layout'], resolve)
return
} else {
componentPath = component
}
require([`../${componentPath}.vue`], resolve)
},
name: name,
icon: icon,
children: validatenull(childrens) ? [] : formatRoutes(childrens)
}
aRouter.push(oRouter)
}
})
return aRouter
}
④ 渲染菜單
<template>
<el-scrollbar wrapClass="scrollbar-wrapper">
<el-menu
mode="vertical"
:show-timeout="200"
:default-active="$route.path"
:collapse="isCollapse"
background-color="#304156"
text-color="#bfcbd9"
active-text-color="#409EFF"
>
<sidebar-item v-for="route in permission_routers" :key="route.name" :item="route" :base-path="route.path"></sidebar-item>
</el-menu>
</el-scrollbar>
</template>
<script>
import { mapGetters } from 'vuex'
import SidebarItem from './SidebarItem'
import { validatenull } from "@/utils/validate";
import { initMenu } from "@/utils/util";
export default {
components: { SidebarItem },
created() {
},
computed: {
...mapGetters([
'permission_routers',
'sidebar',
'addRouters'
]),
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>
就這樣我們動態加載路由就是實現了,關鍵點就是router.addRoute方法
⑤ 防坑
點擊刷新的時候頁面空白 控制檯也不報錯?
點擊刷新,vue-router會重新初始化,那麼我們之前的動態addRoute就不存在了,此時訪問一個不存在的頁面,所以我們的sidebar組件也就不會被訪問,那麼也無法獲取菜單信息,就導致頁面空白。所以我們需要把加載菜單信息這一步放在router的全局守衛beforeEach中就可以了。
export const initMenu = (router, menu) => {
if (menu.length === 0) {
return
}
let menus = formatRoutes(menu);
// 最後添加
let unfound = { path: '*', redirect: '/404', hidden: true }
menus.push(unfound)
router.addRoutes(menus)
store.commit('ADD_ROUTERS',menus)
}
//404組件一定要放在動態路由組件的最後,不然你刷新動態加載的頁面,會跳轉到404頁面的
29、切換到新路由時,頁面要滾動到頂部或保持原先的滾動位置怎麼做呢?
當創建一個 Router 實例,可以提供一個 scrollBehavior 方法:
注意: 這個功能只在 HTML5 history 模式下可用。
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滾動到哪個的位置
}
})
scrollBehavior 方法接收 to 和 from 路由對象。第三個參數 savedPosition 當且僅當 popstate 導航 (通過瀏覽器的 前進/後退 按鈕觸發) 時纔可用。
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
對於所有路由導航,簡單地讓頁面滾動到頂部。
返回 savedPosition,在按下 後退/前進 按鈕時,在滾動條位置,就會像瀏覽器的原生表現那樣:
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
模擬『滾動到錨點』的行爲
scrollBehavior (to, from, savedPosition) {
if (to.hash) {
return {
selector: to.hash
}
}
}
還可以利用路由元信息更細顆粒度地控制滾動。
routes: [
{ path: '/', component: Home, meta: { scrollToTop: true }},
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar, meta: { scrollToTop: true }}
]
const scrollBehavior = (to, from, savedPosition) => {
if (savedPosition) {
return savedPosition
} else {
const position = {}
if (to.hash) {
position.selector = to.hash
}
if (to.matched.some(m => m.meta.scrollToTop)) {
position.x = 0
position.y = 0
}
return position
}
}
還可以在main.js入口文件配合vue-router寫這個
router.afterEach((to,from,next) => {
window.scrollTo(0,0);
});
30、vue-router如何響應路由參數的變化?
當使用路由參數時,比如:
{path:’/list/:id’component:Foo}
從 /list/aside導航到 /list/foo,原來的組件實例會被複用。
因爲兩個路由都渲染同個組件Foo,比起銷燬再創建,複用則更加高效。
不過,這也意味着組件的生命週期鉤子不會再被調用。
如果跳轉到相同的路由還會報以下錯誤
這個時候我們需要重寫push方法,在src/router/index.js 裏面import VueRouter from 'vue-router'下面寫入下面方法即可
const routerPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return routerPush.call(this, location).catch(error=> error)
}
如何響應不同的數據呢?
① 複用組件時,想對路由參數的變化作出響應的話,你可以簡單地 watch (監測變化) $route 對象:
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 對路由變化作出響應...
}
}
}
② 使用beforeRouteUpdate
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
注意:
(1)從同一個組件跳轉到同一個組件。
(2)生命週期鉤子created和mounted都不會調用。
31、vue模板中爲什麼以_、$開始的變量無法渲染?
名字以 _ 或 data._property 訪問它們。
32、vue中,如何監聽一個對象內部的變化?
方法①:對整個obj深層監聽
watch:{
obj:{
handler(newValue,oldValue){
console.log('obj changed')
},
deep: true,//深度遍歷
immediate: true
//默認第一次綁定的時候不會觸發watch監聽,值爲true時可以在最初綁定的時候執行
}
}
方法② :指定key
watch: {
"dataobj.name": {
handler(newValue, oldValue) {
console.log("obj changed");
}
}
}
方法③:computed
computed(){
ar(){
return this.obj.name
}
}
33、v-for循環時爲什麼要加key?
key的作用主要是爲了高效的更新虛擬DOM,是因爲Virtual DOM 使用Diff算法實現的原因。
當某一層有很多相同的節點時,也就是列表節點時,Diff算法的更新過程默認情況下也是遵循以上原則。
比如一下這個情況
我們希望可以在B和C之間加一個F,Diff算法默認執行起來是這樣的:
即把C更新成F,D更新成C,E更新成D,最後再插入E,是不是很沒有效率?
所以我們需要使用key來給每個節點做一個唯一標識,Diff算法就可以正確的識別此節點,找到正確的位置區插入新的節點。
34、$nextTick用過嗎,有什麼作用?
在下次 DOM 更新循環結束之後執行延遲迴調。在修改數據之後立即使用這個方法,獲取更新後的 DOM。
解決的問題:有些時候在改變數據後立即要對dom進行操作,此時獲取到的dom仍是獲取到的是數據刷新前的dom,無法滿足需要,這個時候就用到了$nextTick。
35、vue和react的區別是什麼?
① React嚴格上只針對MVC的view層,Vue則是MVVM模式
② virtual DOM不一樣,vue會跟蹤每一個組件的依賴關係,不需要重新渲染整個組件樹.而對於React而言,每當應用的狀態被改變時,全部組件都會重新渲染,所以react中會需要shouldComponentUpdate這個生命週期函數方法來進行控制
③ 組件寫法不一樣, React推薦的做法是 JSX + inline style, 也就是把HTML和CSS全都寫進JavaScript了,即'all in js'; Vue推薦的做法是webpack+vue-loader的單文件組件格式,即html,css,jd寫在同一個文件;
④ 數據綁定: vue實現了數據的雙向綁定,react數據流動是單向的
⑤ state對象在react應用中不可變的,需要使用setState方法更新狀態;在vue中,state對象不是必須的,數據由data屬性在vue對象中管理