站長資訊網
        最全最豐富的資訊網站

        29個Vue經典面試題(附源碼級詳解)

        本篇文章給大家總結分享29+個Vue經典面試題(附源碼級詳解),帶你梳理基礎知識,增強Vue知識儲備,值得收藏,快來看看吧!

        01-Vue 3.0的設計目標是什么?做了哪些優化?

        分析

        還是問新特性,陳述典型新特性,分析其給你帶來的變化即可。(學習視頻分享:vue視頻教程)

        思路

        從以下幾方面分門別類闡述:易用性、性能、擴展性、可維護性、開發體驗等

        范例

        • Vue3的最大設計目標是替代Vue2(皮一下),為了實現這一點,Vue3在以下幾個方面做了很大改進,如:易用性、框架性能、擴展性、可維護性、開發體驗等

        • 易用性方面主要是API簡化,比如v-model在Vue3中變成了Vue2中v-modelsync修飾符的結合體,用戶不用區分兩者不同,也不用選擇困難。類似的簡化還有用于渲染函數內部生成VNode的h(type, props, children),其中props不用考慮區分屬性、特性、事件等,框架替我們判斷,易用性大增。

        • 開發體驗方面,新組件Teleport傳送門、FragmentsSuspense等都會簡化特定場景的代碼編寫,SFC Composition API語法糖更是極大提升我們開發體驗。

        • 擴展性方面提升如獨立的reactivity模塊,custom renderer API等

        • 可維護性方面主要是Composition API,更容易編寫高復用性的業務邏輯。還有對TypeScript支持的提升。

        • 性能方面的改進也很顯著,例如編譯期優化、基于Proxy的響應式系統

        • 。。。

        可能的追問

        1. Vue3做了哪些編譯優化?
        2. ProxydefineProperty有什么不同?

        02-你了解哪些Vue性能優化方法?

        分析

        這是一道綜合實踐題目,寫過一定數量的代碼之后小伙伴們自然會開始關注一些優化方法,答得越多肯定實踐經驗也越豐富,是很好的題目。

        答題思路:

        根據題目描述,這里主要探討Vue代碼層面的優化

        回答范例

        • 我這里主要從Vue代碼編寫層面說一些優化手段,例如:代碼分割、服務端渲染、組件緩存、長列表優化等

        • 最常見的路由懶加載:有效拆分App尺寸,訪問時才異步加載

          const router = createRouter({   routes: [     // 借助webpack的import()實現異步組件     { path: '/foo', component: () => import('./Foo.vue') }   ] })
        • keep-alive緩存頁面:避免重復創建組件實例,且能保留緩存組件狀態

          <router-view v-slot="{ Component }">     <keep-alive>     <component :is="Component"></component>   </keep-alive> </router-view>
        • 使用v-show復用DOM:避免重復創建組件

          <template>   <div class="cell">     <!-- 這種情況用v-show復用DOM,比v-if效果好 -->     <div v-show="value" class="on">       <Heavy :n="10000"/>     </div>     <section v-show="!value" class="off">       <Heavy :n="10000"/>     </section>   </div> </template>
        • v-for 遍歷避免同時使用 v-if:實際上在Vue3中已經是個錯誤寫法

          <template>     <ul>       <li         v-for="user in activeUsers"         <!-- 避免同時使用,vue3中會報錯 -->         <!-- v-if="user.isActive" -->         :key="user.id">         {{ user.name }}       </li>     </ul> </template> <script>   export default {     computed: {       activeUsers: function () {         return this.users.filter(user => user.isActive)       }     }   } </script>
        • v-once和v-memo:不再變化的數據使用v-once

          <!-- single element --> <span v-once>This will never change: {{msg}}</span> <!-- the element have children --> <div v-once>   <h1>comment</h1>   <p>{{msg}}</p> </div> <!-- component --> <my-component v-once :comment="msg"></my-component> <!-- `v-for` directive --> <ul>   <li v-for="i in list" v-once>{{i}}</li> </ul>

          按條件跳過更新時使用v-momo:下面這個列表只會更新選中狀態變化項

          <div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">   <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>   <p>...more child nodes</p> </div>

          https://vuejs.org/api/built-in-directives.html#v-memo

        • 長列表性能優化:如果是大數據長列表,可采用虛擬滾動,只渲染少部分區域的內容

          <recycle-scroller   class="items"   :items="items"   :item-size="24" >   <template v-slot="{ item }">     <FetchItemView       :item="item"       @vote="voteItem(item)"     />   </template> </recycle-scroller>

          一些開源庫:

          • vue-virtual-scroller:https://github.com/Akryum/vue-virtual-scroller
          • vue-virtual-scroll-grid:https://github.com/rocwang/vue-virtual-scroll-grid
        • 事件的銷毀:Vue 組件銷毀時,會自動解綁它的全部指令及事件監聽器,但是僅限于組件本身的事件。

          export default {   created() {     this.timer = setInterval(this.refresh, 2000)   },   beforeUnmount() {     clearInterval(this.timer)   } }
        • 圖片懶加載

          對于圖片過多的頁面,為了加速頁面加載速度,所以很多時候我們需要將頁面內未出現在可視區域內的圖片先不做加載, 等到滾動到可視區域后再去加載。

          <img v-lazy="/static/img/1.png">

          參考項目:https://github.com/hilongjw/vue-lazyload

        • 第三方插件按需引入

          element-plus這樣的第三方組件庫可以按需引入避免體積太大。

          import { createApp } from 'vue'; import { Button, Select } from 'element-plus';  const app = createApp() app.use(Button) app.use(Select)
        • 子組件分割策略:較重的狀態組件適合拆分

          <template>   <div>     <ChildComp/>   </div> </template>  <script> export default {   components: {     ChildComp: {       methods: {         heavy () { /* 耗時任務 */ }       },       render (h) {         return h('div', this.heavy())       }     }   } } </script>

          但同時也不宜過度拆分組件,尤其是為了所謂組件抽象將一些不需要渲染的組件特意抽出來,組件實例消耗遠大于純dom節點。參考:https://vuejs.org/guide/best-practices/performance.html#avoid-unnecessary-component-abstractions

        • 服務端渲染/靜態網站生成:SSR/SSG

          如果SPA應用有首屏渲染慢的問題,可以考慮SSR、SSG方案優化。參考:https://vuejs.org/guide/scaling-up/ssr.html


        03-Vue組件為什么只能有一個根元素?

        這題現在有些落伍,vue3已經不用一個根了。因此這題目很有說頭!

        體驗一下

        vue2直接報錯,test-v2.html

        new Vue({   components: {     comp: {       template: `         <div>root1</div>         <div>root2</div>       `     }   } }).$mount('#app')

        29個Vue經典面試題(附源碼級詳解)

        vue3中沒有問題,test-v3.html

        Vue.createApp({   components: {     comp: {       template: `         <div>root1</div>         <div>root2</div>       `     }   } }).mount('#app')

        29個Vue經典面試題(附源碼級詳解)

        回答思路

        • 給一條自己的結論
        • 解釋為什么會這樣
        • vue3解決方法原理

        范例

        • vue2中組件確實只能有一個根,但vue3中組件已經可以多根節點了。
        • 之所以需要這樣是因為vdom是一顆單根樹形結構,patch方法在遍歷的時候從根節點開始遍歷,它要求只有一個根節點。組件也會轉換為一個vdom,自然應該滿足這個要求。
        • vue3中之所以可以寫多個根節點,是因為引入了Fragment的概念,這是一個抽象的節點,如果發現組件是多根的,就創建一個Fragment節點,把多個根節點作為它的children。將來patch的時候,如果發現是一個Fragment節點,則直接遍歷children創建或更新。

        知其所以然

        • patch方法接收單根vdom:

          https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L354-L355

          // 直接獲取type等,沒有考慮數組的可能性 const { type, ref, shapeFlag } = n2
        • patch方法對Fragment的處理:

          https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1091-L1092

          // a fragment can only have array children // since they are either generated by the compiler, or implicitly created // from arrays. mountChildren(n2.children as VNodeArrayChildren, container, ...)

        04-這是基本應用能力考察,稍微上點規模的項目都要拆分vuex模塊便于維護。

        體驗

        https://vuex.vuejs.org/zh/guide/modules.html

        const moduleA = {   state: () => ({ ... }),   mutations: { ... },   actions: { ... },   getters: { ... } } const moduleB = {   state: () => ({ ... }),   mutations: { ... },   actions: { ... } } const store = createStore({   modules: {     a: moduleA,     b: moduleB   } }) store.state.a // -> moduleA 的狀態 store.state.b // -> moduleB 的狀態 store.getters.c // -> moduleA里的getters store.commit('d') // -> 能同時觸發子模塊中同名mutation store.dispatch('e') // -> 能同時觸發子模塊中同名action

        思路

        • 概念和必要性

        • 怎么拆

        • 使用細節

        • 優缺點

        范例

        • 用過module,項目規模變大之后,單獨一個store對象會過于龐大臃腫,通過模塊方式可以拆分開來便于維護

        • 可以按之前規則單獨編寫子模塊代碼,然后在主文件中通過modules選項組織起來:createStore({modules:{...}})

        • 不過使用時要注意訪問子模塊狀態時需要加上注冊時模塊名:store.state.a.xxx,但同時getters、mutationsactions又在全局空間中,使用方式和之前一樣。如果要做到完全拆分,需要在子模塊加上namespace選項,此時再訪問它們就要加上命名空間前綴。

        • 很顯然,模塊的方式可以拆分代碼,但是缺點也很明顯,就是使用起來比較繁瑣復雜,容易出錯。而且類型系統支持很差,不能給我們帶來幫助。pinia顯然在這方面有了很大改進,是時候切換過去了。

        可能的追問

        • 用過pinia嗎?都做了哪些改善?


        05-怎么實現路由懶加載呢?

        分析

        這是一道應用題。當打包應用時,JavaScript 包會變得非常大,影響頁面加載。如果我們能把不同路由對應的組件分割成不同的代碼塊,然后當路由被訪問時才加載對應組件,這樣就會更加高效。

        // 將 // import UserDetails from './views/UserDetails' // 替換為 const UserDetails = () => import('./views/UserDetails')  const router = createRouter({   // ...   routes: [{ path: '/users/:id', component: UserDetails }], })

        參考:https://router.vuejs.org/zh/guide/advanced/lazy-loading.html

        思路

        • 必要性

        • 何時用

        • 怎么用

        • 使用細節

        回答范例

        • 當打包構建應用時,JavaScript 包會變得非常大,影響頁面加載。利用路由懶加載我們能把不同路由對應的組件分割成不同的代碼塊,然后當路由被訪問的時候才加載對應組件,這樣會更加高效,是一種優化手段。

        • 一般來說,對所有的路由都使用動態導入是個好主意。

        • component選項配置一個返回 Promise 組件的函數就可以定義懶加載路由。例如:

          { path: '/users/:id', component: () => import('./views/UserDetails') }

        • 結合注釋() => import(/* webpackChunkName: "group-user" */ './UserDetails.vue')可以做webpack代碼分塊

          vite中結合rollupOptions定義分塊

        • 路由中不能使用異步組件

        知其所以然

        component (和 components) 配置如果接收一個返回 Promise 組件的函數,Vue Router 只會在第一次進入頁面時才會獲取這個函數,然后使用緩存數據。

        https://github1s.com/vuejs/router/blob/HEAD/src/navigationGuards.ts#L292-L293


        06-ref和reactive異同

        這是Vue3數據響應式中非常重要的兩個概念,自然的,跟我們寫代碼關系也很大。

        體驗

        ref:https://vuejs.org/api/reactivity-core.html#ref

        const count = ref(0) console.log(count.value) // 0  count.value++ console.log(count.value) // 1

        reactive:https://vuejs.org/api/reactivity-core.html#reactive

        const obj = reactive({ count: 0 }) obj.count++

        回答思路

        • 兩者概念

        • 兩者使用場景

        • 兩者異同

        • 使用細節

        • 原理

        回答范例

        • ref接收內部值(inner value)返回響應式Ref對象,reactive返回響應式代理對象

        • 從定義上看ref通常用于處理單值的響應式,reactive用于處理對象類型的數據響應式

        • 兩者均是用于構造響應式數據,但是ref主要解決原始值的響應式問題

        • ref返回的響應式數據在JS中使用需要加上.value才能訪問其值,在視圖中使用會自動脫ref,不需要.value;ref可以接收對象或數組等非原始值,但內部依然是reactive實現響應式;reactive內部如果接收Ref對象會自動脫ref;使用展開運算符(…)展開reactive返回的響應式對象會使其失去響應性,可以結合toRefs()將值轉換為Ref對象之后再展開。

        • reactive內部使用Proxy代理傳入對象并攔截該對象各種操作(trap),從而實現響應式。ref內部封裝一個RefImpl類,并設置get value/set value,攔截用戶對值的訪問,從而實現響應式。

        知其所以然

        • reactive實現響應式:

          https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/reactive.ts#L90-L91

        • ref實現響應式:

          https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/ref.ts#L73-L74


        07-watch和watchEffect異同

        我們經常性需要偵測響應式數據的變化,vue3中除了watch之外又出現了watchEffect,不少同學會混淆這兩個api。

        體驗

        watchEffect立即運行一個函數,然后被動地追蹤它的依賴,當這些依賴改變時重新執行該函數。

        Runs a function immediately while reactively tracking its dependencies and re-runs it whenever the dependencies are changed.

        const count = ref(0)  watchEffect(() => console.log(count.value)) // -> logs 0  count.value++ // -> logs 1

        watch偵測一個或多個響應式數據源并在數據源變化時調用一個回調函數。

        Watches one or more reactive data sources and invokes a callback function when the sources change.

        const state = reactive({ count: 0 }) watch(   () => state.count,   (count, prevCount) => {     /* ... */   } )

        思路

        • 給出兩者定義

        • 給出場景上的不同

        • 給出使用方式和細節

        • 原理闡述

        范例

        • watchEffect立即運行一個函數,然后被動地追蹤它的依賴,當這些依賴改變時重新執行該函數。watch偵測一個或多個響應式數據源并在數據源變化時調用一個回調函數。

        • watchEffect(effect)是一種特殊watch,傳入的函數既是依賴收集的數據源,也是回調函數。如果我們不關心響應式數據變化前后的值,只是想拿這些數據做些事情,那么watchEffect就是我們需要的。watch更底層,可以接收多種數據源,包括用于依賴收集的getter函數,因此它完全可以實現watchEffect的功能,同時由于可以指定getter函數,依賴可以控制的更精確,還能獲取數據變化前后的值,因此如果需要這些時我們會使用watch。

        • watchEffect在使用時,傳入的函數會立刻執行一次。watch默認情況下并不會執行回調函數,除非我們手動設置immediate選項。

        • 從實現上來說,watchEffect(fn)相當于watch(fn,fn,{immediate:true})

        知其所以然

        watchEffect定義:https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiWatch.ts#L80-L81

        export function watchEffect(   effect: WatchEffect,   options?: WatchOptionsBase ): WatchStopHandle {   return doWatch(effect, null, options) }

        watch定義如下:https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiWatch.ts#L158-L159

        export function watch<T = any, Immediate extends Readonly<boolean> = false>(   source: T | WatchSource<T>,   cb: any,   options?: WatchOptions<Immediate> ): WatchStopHandle {   return doWatch(source as any, cb, options) }

        很明顯watchEffect就是一種特殊的watch實現。


        08-SPA、SSR的區別是什么

        我們現在編寫的Vue、React和Angular應用大多數情況下都會在一個頁面中,點擊鏈接跳轉頁面通常是內容切換而非頁面跳轉,由于良好的用戶體驗逐漸成為主流的開發模式。但同時也會有首屏加載時間長,SEO不友好的問題,因此有了SSR,這也是為什么面試中會問到兩者的區別。

        思路分析

        • 兩者概念

        • 兩者優缺點分析

        • 使用場景差異

        • 其他選擇

        回答范例

        • SPA(Single Page Application)即單頁面應用。一般也稱為 客戶端渲染(Client Side Render), 簡稱 CSR。SSR(Server Side Render)即 服務端渲染。一般也稱為 多頁面應用(Mulpile Page Application),簡稱 MPA。

        • SPA應用只會首次請求html文件,后續只需要請求JSON數據即可,因此用戶體驗更好,節約流量,服務端壓力也較小。但是首屏加載的時間會變長,而且SEO不友好。為了解決以上缺點,就有了SSR方案,由于HTML內容在服務器一次性生成出來,首屏加載快,搜索引擎也可以很方便的抓取頁面信息。但同時SSR方案也會有性能,開發受限等問題。

        • 在選擇上,如果我們的應用存在首屏加載優化需求,SEO需求時,就可以考慮SSR。

        • 但并不是只有這一種替代方案,比如對一些不常變化的靜態網站,SSR反而浪費資源,我們可以考慮預渲染(prerender)方案。另外nuxt.js/next.js中給我們提供了SSG(Static Site Generate)靜態網站生成方案也是很好的靜態站點解決方案,結合一些CI手段,可以起到很好的優化效果,且能節約服務器資源。

        知其所以然

        內容生成上的區別:

        SSR

        29個Vue經典面試題(附源碼級詳解)

        SPA

        29個Vue經典面試題(附源碼級詳解)

        部署上的區別

        29個Vue經典面試題(附源碼級詳解)


        09-vue-loader是什么?它有什么作用?

        分析

        這是一道工具類的原理題目,相當有深度,具有不錯的人才區分度。

        體驗

        使用官方提供的SFC playground可以很好的體驗vue-loader

        sfc.vuejs.org

        有了vue-loader加持,我們才可以以SFC的方式快速編寫代碼。

        <template>   <div class="example">{{ msg }}</div> </template>  <script> export default {   data() {     return {       msg: 'Hello world!',     }   }, } </script>  <style> .example {   color: red; } </style>

        思路

        • vue-loader是什么東東
        • vue-loader是做什么用的
        • vue-loader何時生效
        • vue-loader如何工作

        回答范例

        • vue-loader是用于處理單文件組件(SFC,Single-File Component)的webpack loader

        • 因為有了vue-loader,我們就可以在項目中編寫SFC格式的Vue組件,我們可以把代碼分割為<template>、<script>和<style>,代碼會異常清晰。結合其他loader我們還可以用Pug編寫<template>,用SASS編寫<style>,用TS編寫<script>。我們的<style>還可以單獨作用當前組件。

        • webpack打包時,會以loader的方式調用vue-loader

        • vue-loader被執行時,它會對SFC中的每個語言塊用單獨的loader鏈處理。最后將這些單獨的塊裝配成最終的組件模塊。

        知其所以然

        1、vue-loader會調用@vue/compiler-sfc模塊解析SFC源碼為一個描述符(Descriptor),然后為每個語言塊生成import代碼,返回的代碼類似下面:

        // source.vue被vue-loader處理之后返回的代碼  // import the <template> block import render from 'source.vue?vue&type=template' // import the <script> block import script from 'source.vue?vue&type=script' export * from 'source.vue?vue&type=script' // import <style> blocks import 'source.vue?vue&type=style&index=1'  script.render = render export default script

        2、我們想要script塊中的內容被作為js處理(當然如果是<script lang="ts">被作為ts處理),這樣我們想要webpack把配置中跟.js匹配的規則都應用到形如source.vue?vue&type=script的這個請求上。例如我們對所有*.js配置了babel-loader,這個規則將被克隆并應用到所在Vue SFC的

        import script from 'source.vue?vue&type=script'

        將被展開為:

        import script from 'babel-loader!vue-loader!source.vue?vue&type=script'

        類似的,如果我們對.sass文件配置了style-loader + css-loader + sass-loader,對下面的代碼:

        <style scoped lang="scss">

        vue-loader將會返回給我們下面結果:

        import 'source.vue?vue&type=style&index=1&scoped&lang=scss'

        然后webpack會展開如下:

        import 'style-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'

        1)當處理展開請求時,vue-loader將被再次調用。這次,loader將會關注那些有查詢串的請求,且僅針對特定塊,它會選中特定塊內部的內容并傳遞給后面匹配的loader。

        2)對于<script>塊,處理到這就可以了,但是<template><style>還有一些額外任務要做,比如:

        • 需要用Vue 模板編譯器編譯template,從而得到render函數
        • 需要對<style scoped>中的CSS做后處理(post-process),該操作在css-loader之后但在style-loader之前

        實現上這些附加的loader需要被注入到已經展開的loader鏈上,最終的請求會像下面這樣:

        // <template lang="pug"> import 'vue-loader/template-loader!pug-loader!source.vue?vue&type=template'  // <style scoped lang="scss"> import 'style-loader!vue-loader/style-post-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'

        10-你寫過自定義指令嗎?使用場景有哪些?

        分析

        這是一道API題,我們可能寫的自定義指令少,但是我們用的多呀,多舉幾個例子就行。

        體驗

        定義一個包含類似組件生命周期鉤子的對象,鉤子函數會接收指令掛鉤的dom元素:

        const focus = {   mounted: (el) => el.focus() }  export default {   directives: {     // enables v-focus in template     focus   } } <input v-focus />
        <input v-focus />

        思路

        • 定義

        • 何時用

        • 如何用

        • 常用指令

        • vue3變化

        回答范例

        • Vue有一組默認指令,比如v-model或v-for,同時Vue也允許用戶注冊自定義指令來擴展Vue能力

        • 自定義指令主要完成一些可復用低層級DOM操作

        • 使用自定義指令分為定義、注冊和使用三步:

          • 定義自定義指令有兩種方式:對象和函數形式,前者類似組件定義,有各種生命周期;后者只會在mounted和updated時執行
          • 注冊自定義指令類似組件,可以使用app.directive()全局注冊,使用{directives:{xxx}}局部注冊
          • 使用時在注冊名稱前加上v-即可,比如v-focus
        • 我在項目中常用到一些自定義指令,例如:

          • 復制粘貼 v-copy
          • 長按 v-longpress
          • 防抖 v-debounce
          • 圖片懶加載 v-lazy
          • 按鈕權限 v-premission
          • 頁面水印 v-waterMarker
          • 拖拽指令 v-draggable
        • vue3中指令定義發生了比較大的變化,主要是鉤子的名稱保持和組件一致,這樣開發人員容易記憶,不易犯錯。另外在v3.2之后,可以在setup中以一個小寫v開頭方便的定義自定義指令,更簡單了!

        知其所以然

        編譯后的自定義指令會被withDirective函數裝飾,進一步處理生成的vnode,添加到特定屬性中。

        https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiB9IGZyb20gJ3Z1ZSdcblxuY29uc3QgbXNnID0gcmVmKCdIZWxsbyBXb3JsZCEnKVxuXG5jb25zdCB2Rm9jdXMgPSB7XG4gIG1vdW50ZWQoZWwpIHtcbiAgICAvLyDojrflj5ZpbnB1dO+8jOW5tuiwg+eUqOWFtmZvY3VzKCnmlrnms5VcbiAgICBlbC5mb2N1cygpXG4gIH1cbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIDxoMT57eyBtc2cgfX08L2gxPlxuICA8aW5wdXQgdi1tb2RlbD1cIm1zZ1wiIHYtZm9jdXM+XG48L3RlbXBsYXRlPiIsImltcG9ydC1tYXAuanNvbiI6IntcbiAgXCJpbXBvcnRzXCI6IHtcbiAgICBcInZ1ZVwiOiBcImh0dHBzOi8vc2ZjLnZ1ZWpzLm9yZy92dWUucnVudGltZS5lc20tYnJvd3Nlci5qc1wiXG4gIH1cbn0ifQ==

        11-說下$attrs和$listeners的使用場景

        分析

        API考察,但$attrs和$listeners是比較少用的邊界知識,而且vue3有變化,$listeners已經移除,還是有細節可說的。

        思路

        • 這兩個api的作用

        • 使用場景分析

        • 使用方式和細節

        • vue3變化

        體驗

        一個包含組件透傳屬性的對象。

        An object that contains the component's fallthrough attributes.

        <template>     <child-component v-bind="$attrs">         將非屬性特性透傳給內部的子組件     </child-component> </template>

        范例

        • 我們可能會有一些屬性和事件沒有在props中定義,這類稱為非屬性特性,結合v-bind指令可以直接透傳給內部的子組件。

        • 這類“屬性透傳”常常用于包裝高階組件時往內部傳遞屬性,常用于爺孫組件之間傳參。比如我在擴展A組件時創建了組件B組件,然后在C組件中使用B,此時傳遞給C的屬性中只有props里面聲明的屬性是給B使用的,其他的都是A需要的,此時就可以利用v-bind="$attrs"透傳下去。

        • 最常見用法是結合v-bind做展開;$attrs本身不是響應式的,除非訪問的屬性本身是響應式對象。

        • vue2中使用

          listeners獲取事件,vue3中已移除,均合并到listeners獲取事件,vue3中已移除,均合并到

          attrs中,使用起來更簡單了。

        原理

        查看透傳屬性foo和普通屬性bar,發現vnode結構完全相同,這說明vue3中將分辨兩者工作由框架完成而非用戶指定:

        <template>   <h1>{{ msg }}</h1>   <comp foo="foo" bar="bar" /> </template>
        <template>   <div>     {{$attrs.foo}} {{bar}}   </div> </template> <script setup> defineProps({   bar: String }) </script>
        _createVNode(Comp, {     foo: "foo",     bar: "bar" })
        https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiB9IGZyb20gJ3Z1ZSdcbmltcG9ydCBDb21wIGZyb20gJy4vQ29tcC52dWUnXG5jb25zdCBtc2cgPSByZWYoJ0hlbGxvIFdvcmxkIScpXG48L3NjcmlwdD5cblxuPHRlbXBsYXRlPlxuICA8aDE+e3sgbXNnIH19PC9oMT5cbiAgPGNvbXAgZm9vPVwiZm9vXCIgYmFyPVwiYmFyXCIgLz5cbjwvdGVtcGxhdGU+IiwiaW1wb3J0LW1hcC5qc29uIjoie1xuICBcImltcG9ydHNcIjoge1xuICAgIFwidnVlXCI6IFwiaHR0cHM6Ly9zZmMudnVlanMub3JnL3Z1ZS5ydW50aW1lLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSIsIkNvbXAudnVlIjoiPHRlbXBsYXRlPlxuXHQ8ZGl2PlxuICAgIHt7JGF0dHJzLmZvb319IHt7YmFyfX1cbiAgPC9kaXY+XG48L3RlbXBsYXRlPlxuPHNjcmlwdCBzZXR1cD5cbmRlZmluZVByb3BzKHtcbiAgYmFyOiBTdHJpbmdcbn0pXG48L3NjcmlwdD4ifQ==

        12-v-once的使用場景有哪些?

        分析

        v-once是Vue中內置指令,很有用的API,在優化方面經常會用到,不過小伙伴們平時可能容易忽略它。

        體驗

        僅渲染元素和組件一次,并且跳過未來更新

        Render the element and component once only, and skip future updates.

        <!-- single element --> <span v-once>This will never change: {{msg}}</span> <!-- the element have children --> <div v-once>   <h1>comment</h1>   <p>{{msg}}</p> </div> <!-- component --> <my-component v-once :comment="msg"></my-component> <!-- `v-for` directive --> <ul>   <li v-for="i in list" v-once>{{i}}</li> </ul>

        思路

        • v-once是什么

        • 什么時候使用

        • 如何使用

        • 擴展v-memo

        • 探索原理

        回答范例

        • v-once是vue的內置指令,作用是僅渲染指定組件或元素一次,并跳過未來對其更新。

        • 如果我們有一些元素或者組件在初始化渲染之后不再需要變化,這種情況下適合使用v-once,這樣哪怕這些數據變化,vue也會跳過更新,是一種代碼優化手段。

        • 我們只需要作用的組件或元素上加上v-once即可。

        • vue3.2之后,又增加了v-memo指令,可以有條件緩存部分模板并控制它們的更新,可以說控制力更強了。

        • 編譯器發現元素上面有v-once時,會將首次計算結果存入緩存對象,組件再次渲染時就會從緩存獲取,從而避免再次計算。

        知其所以然

        下面例子使用了v-once:

        <script setup> import { ref } from 'vue'  const msg = ref('Hello World!') </script>  <template>   <h1 v-once>{{ msg }}</h1>   <input v-model="msg"> </template>

        我們發現v-once出現后,編譯器會緩存作用元素或組件,從而避免以后更新時重新計算這一部分:

        // ... return (_ctx, _cache) => {   return (_openBlock(), _createElementBlock(_Fragment, null, [     // 從緩存獲取vnode     _cache[0] || (       _setBlockTracking(-1),       _cache[0] = _createElementVNode("h1", null, [         _createTextVNode(_toDisplayString(msg.value), 1 /* TEXT */)       ]),       _setBlockTracking(1),       _cache[0]     ), // ...

        13-什么是遞歸組件?舉個例子說明下?

        分析

        遞歸組件我們用的比較少,但是在Tree、Menu這類組件中會被用到。

        體驗

        組件通過組件名稱引用它自己,這種情況就是遞歸組件。

        An SFC can implicitly refer to itself via its filename.

        <template>   <li>     <div> {{ model.name }}</div>     <ul v-show="isOpen" v-if="isFolder">       <!-- 注意這里:組件遞歸渲染了它自己 -->       <TreeItem         class="item"         v-for="model in model.children"         :model="model">       </TreeItem>     </ul>   </li> <script> export default {   name: 'TreeItem',   // ... } </script>

        思路

        • 下定義
        • 使用場景
        • 使用細節
        • 原理闡述

        回答范例

        • 如果某個組件通過組件名稱引用它自己,這種情況就是遞歸組件。

        • 實際開發中類似Tree、Menu這類組件,它們的節點往往包含子節點,子節點結構和父節點往往是相同的。這類組件的數據往往也是樹形結構,這種都是使用遞歸組件的典型場景。

        • 使用遞歸組件時,由于我們并未也不能在組件內部導入它自己,所以設置組件name屬性,用來查找組件定義,如果使用SFC,則可以通過SFC文件名推斷。組件內部通常也要有遞歸結束條件,比如model.children這樣的判斷。

        • 查看生成渲染函數可知,遞歸組件查找時會傳遞一個布爾值給resolveComponent,這樣實際獲取的組件就是當前組件本身。

        知其所以然

        遞歸組件編譯結果中,獲取組件時會傳遞一個標識符 _resolveComponent("Comp", true)

        const _component_Comp = _resolveComponent("Comp", true)

        就是在傳遞maybeSelfReference

        export function resolveComponent(   name: string,   maybeSelfReference?: boolean ): ConcreteComponent | string {   return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name }

        resolveAsset中最終返回的是組件自身:

        if (!res && maybeSelfReference) {     // fallback to implicit self-reference     return Component }
        https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/helpers/resolveAssets.ts#L22-L23   https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/helpers/resolveAssets.ts#L110-L111   https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiB9IGZyb20gJ3Z1ZSdcbmltcG9ydCBjb21wIGZyb20gJy4vQ29tcC52dWUnXG5jb25zdCBtc2cgPSByZWYoJ+mAkuW9kue7hOS7ticpXG5jb25zdCBtb2RlbCA9IHtcbiAgbGFiZWw6ICdub2RlLTEnLFxuICBjaGlsZHJlbjogW1xuICAgIHtsYWJlbDogJ25vZGUtMS0xJ30sXG4gICAge2xhYmVsOiAnbm9kZS0xLTInfVxuICBdXG59XG48L3NjcmlwdD5cblxuPHRlbXBsYXRlPlxuICA8aDE+e3sgbXNnIH19PC9oMT5cbiAgPGNvbXAgOm1vZGVsPVwibW9kZWxcIj48L2NvbXA+XG48L3RlbXBsYXRlPiIsImltcG9ydC1tYXAuanNvbiI6IntcbiAgXCJpbXBvcnRzXCI6IHtcbiAgICBcInZ1ZVwiOiBcImh0dHBzOi8vc2ZjLnZ1ZWpzLm9yZy92dWUucnVudGltZS5lc20tYnJvd3Nlci5qc1wiXG4gIH1cbn0iLCJDb21wLnZ1ZSI6Ijx0ZW1wbGF0ZT5cbiAgPGRpdj5cbiAgICB7e21vZGVsLmxhYmVsfX1cbiAgPC9kaXY+XG4gIDxDb21wIHYtZm9yPVwiaXRlbSBpbiBtb2RlbC5jaGlsZHJlblwiIDptb2RlbD1cIml0ZW1cIj48L0NvbXA+XG4gIDxjb21wMj48L2NvbXAyPlxuPC90ZW1wbGF0ZT5cbjxzY3JpcHQ+XG5cdGV4cG9ydCBkZWZhdWx0IHtcbiAgICBuYW1lOiAnQ29tcCcsXG4gICAgcHJvcHM6IHtcbiAgICAgIG1vZGVsOiBPYmplY3RcbiAgICB9LFxuICAgIGNvbXBvbmVudHM6IHtcbiAgICAgIGNvbXAyOiB7XG4gICAgICAgIHJlbmRlcigpe31cbiAgICAgIH1cbiAgICB9XG4gIH1cbjwvc2NyaXB0PiJ9

        14-異步組件是什么?使用場景有哪些?

        分析

        因為異步路由的存在,我們使用異步組件的次數比較少,因此還是有必要兩者的不同。

        體驗

        大型應用中,我們需要分割應用為更小的塊,并且在需要組件時再加載它們。

        In large applications, we may need to divide the app into smaller chunks and only load a component from the server when it's needed.

        import { defineAsyncComponent } from 'vue' // defineAsyncComponent定義異步組件 const AsyncComp = defineAsyncComponent(() => {   // 加載函數返回Promise   return new Promise((resolve, reject) => {     // ...可以從服務器加載組件     resolve(/* loaded component */)   }) }) // 借助打包工具實現ES模塊動態導入 const AsyncComp = defineAsyncComponent(() =>   import('./components/MyComponent.vue') )

        思路

        • 異步組件作用

        • 何時使用異步組件

        • 使用細節

        • 和路由懶加載的不同

        范例

        • 在大型應用中,我們需要分割應用為更小的塊,并且在需要組件時再加載它們。

        • 我們不僅可以在路由切換時懶加載組件,還可以在頁面組件中繼續使用異步組件,從而實現更細的分割粒度。

        • 使用異步組件最簡單的方式是直接給defineAsyncComponent指定一個loader函數,結合ES模塊動態導入函數import可以快速實現。我們甚至可以指定loadingComponent和errorComponent選項從而給用戶一個很好的加載反饋。另外Vue3中還可以結合Suspense組件使用異步組件。

        • 異步組件容易和路由懶加載混淆,實際上不是一個東西。異步組件不能被用于定義懶加載路由上,處理它的是vue框架,處理路由組件加載的是vue-router。但是可以在懶加載的路由組件中使用異步組件。

        知其所以然

        defineAsyncComponent定義了一個高階組件,返回一個包裝組件。包裝組件根據加載器的狀態決定渲染什么內容。

        https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiAsyncComponent.ts#L43-L44


        15-你是怎么處理vue項目中的錯誤的?

        分析

        這是一個綜合應用題目,在項目中我們常常需要將App的異常上報,此時錯誤處理就很重要了。

        這里要區分錯誤的類型,針對性做收集。

        然后是將收集的的錯誤信息上報服務器。

        思路

        • 首先區分錯誤類型

        • 根據錯誤不同類型做相應收集

        • 收集的錯誤是如何上報服務器的

        回答范例

        • 應用中的錯誤類型分為"接口異常"和“代碼邏輯異常

        • 我們需要根據不同錯誤類型做相應處理:接口異常是我們請求后端接口過程中發生的異常,可能是請求失敗,也可能是請求獲得了服務器響應,但是返回的是錯誤狀態。以Axios為例,這類異常我們可以通過封裝Axios,在攔截器中統一處理整個應用中請求的錯誤。代碼邏輯異常是我們編寫的前端代碼中存在邏輯上的錯誤造成的異常,vue應用中最常見的方式是使用全局錯誤處理函數app.config.errorHandler收集錯誤。

        • 收集到錯誤之后,需要統一處理這些異常:分析錯誤,獲取需要錯誤信息和數據。這里應該有效區分錯誤類型,如果是請求錯誤,需要上報接口信息,參數,狀態碼等;對于前端邏輯異常,獲取錯誤名稱和詳情即可。另外還可以收集應用名稱、環境、版本、用戶信息,所在頁面等。這些信息可以通過vuex存儲的全局狀態和路由信息獲取。

        實踐

        axios攔截器中處理捕獲異常:

        // 響應攔截器 instance.interceptors.response.use(   (response) => {     return response.data;   },   (error) => {     // 存在response說明服務器有響應     if (error.response) {       let response = error.response;       if (response.status >= 400) {         handleError(response);       }     } else {       handleError(null);     }     return Promise.reject(error);   }, );

        vue中全局捕獲異常:

        import { createApp } from 'vue'  const app = createApp(...)  app.config.errorHandler = (err, instance, info) => {   // report error to tracking services }

        處理接口請求錯誤:

        function handleError(error, type) {   if(type == 1) {     // 接口錯誤,從config字段中獲取請求信息     let { url, method, params, data } = error.config     let err_data = {        url, method,        params: { query: params, body: data },        error: error.data?.message || JSON.stringify(error.data),     })   } }

        處理前端邏輯錯誤:

        function handleError(error, type) {   if(type == 2) {     let errData = null     // 邏輯錯誤     if(error instanceof Error) {       let { name, message } = error       errData = {         type: name,         error: message       }     } else {       errData = {         type: 'other',         error: JSON.strigify(error)       }     }   } }

        16-如果讓你從零開始寫一個vuex,說說你的思路

        思路分析

        這個題目很有難度,首先思考vuex解決的問題:存儲用戶全局狀態并提供管理狀態API。

        • vuex需求分析
        • 如何實現這些需求

        回答范例

        • 官方說vuex是一個狀態管理模式和庫,并確保這些狀態以可預期的方式變更??梢娨獙崿F一個vuex

          • 要實現一個Store存儲全局狀態
          • 要提供修改狀態所需API:commit(type, payload), dispatch(type, payload)
        • 實現Store時,可以定義Store類,構造函數接收選項options,設置屬性state對外暴露狀態,提供commit和dispatch修改屬性state。這里需要設置state為響應式對象,同時將Store定義為一個Vue插件。

        • commit(type, payload)方法中可以獲取用戶傳入mutations并執行它,這樣可以按用戶提供的方法修改狀態。 dispatch(type, payload)類似,但需要注意它可能是異步的,需要返回一個Promise給用戶以處理異步結果。

        實踐

        Store的實現:

        class Store {     constructor(options) {         this.state = reactive(options.state)         this.options = options     }     commit(type, payload) {         this.options.mutations[type].call(this, this.state, payload)     } }

        知其所以然

        Vuex中Store的實現:https://github1s.com/vuejs/vuex/blob/HEAD/src/store.js#L19-L20


        17-vuex中actions和mutations有什么區別?

        題目分析

        mutationsactionsvuex帶來的兩個獨特的概念。新手程序員容易混淆,所以面試官喜歡問。

        我們只需記住修改狀態只能是mutations,actions只能通過提交mutation修改狀態即可。

        體驗

        看下面例子可知,Action 類似于 mutation,不同在于:

        • Action 提交的是 mutation,而不是直接變更狀態。
        • Action 可以包含任意異步操作。
        const store = createStore({   state: {     count: 0   },   mutations: {     increment (state) {       state.count++     }   },   actions: {     increment (context) {       context.commit('increment')     }   } })

        答題思路

        • 給出兩者概念說明區別

        • 舉例說明應用場景

        • 使用細節不同

        • 簡單闡述實現上差異

        回答范例

        • 官方文檔說:更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation,mutation 非常類似于事件:每個 mutation 都有一個字符串的類型 (type)和一個 回調函數 (handler) 。Action 類似于 mutation,不同在于:Action可以包含任意異步操作,但它不能修改狀態, 需要提交mutation才能變更狀態。

        • 因此,開發時,包含異步操作或者復雜業務組合時使用action;需要直接修改狀態則提交mutation。但由于dispatch和commit是兩個API,容易引起混淆,實踐中也會采用統一使用dispatch action的方式。

        • 調用dispatch和commit兩個API時幾乎完全一樣,但是定義兩者時卻不甚相同,mutation的回調函數接收參數是state對象。action則是與Store實例具有相同方法和屬性的上下文context對象,因此一般會解構它為{commit, dispatch, state},從而方便編碼。另外dispatch會返回Promise實例便于處理內部異步結果。

        • 實現上commit(type)方法相當于調用options.mutations[type](state);dispatch(type)方法相當于調用options.actions[type](store),這樣就很容易理解兩者使用上的不同了。

        知其所以然

        我們可以像下面這樣簡單實現commitdispatch,從而辨別兩者不同:

        class Store {     constructor(options) {         this.state = reactive(options.state)         this.options = options     }     commit(type, payload) {         // 傳入上下文和參數1都是state對象         this.options.mutations[type].call(this.state, this.state, payload)     }     dispatch(type, payload) {         // 傳入上下文和參數1都是store本身         this.options.actions[type].call(this, this, payload)     } }

        18-使用vue渲染大量數據時應該怎么優化?說下你的思路!

        分析

        企業級項目中渲染大量數據的情況比較常見,因此這是一道非常好的綜合實踐題目。

        思路

        • 描述大數據量帶來的問題

        • 分不同情況做不同處理

        • 總結一下

        回答

        • 在大型企業級項目中經常需要渲染大量數據,此時很容易出現卡頓的情況。比如大數據量的表格、樹。

        • 處理時要根據情況做不通處理:

          • 可以采取分頁的方式獲取,避免渲染大量數據
          • vue-virtual-scroller等虛擬滾動方案,只渲染視口范圍內的數據
          • 如果不需要更新,可以使用v-once方式只渲染一次
          • 通過v-memo可以緩存結果,結合v-for使用,避免數據變化時不必要的VNode創建
          • 可以采用懶加載方式,在用戶需要的時候再加載數據,比如tree組件子樹的懶加載
        • 總之,還是要看具體需求,首先從設計上避免大數據獲取和渲染;實在需要這樣做可以采用虛表的方式優化渲染;最后優化更新,如果不需要更新可以v-once處理,需要更新可以v-memo進一步優化大數據更新性能。其他可以采用的是交互方式優化,無線滾動、懶加載等方案。


        19-怎么監聽vuex數據的變化?

        分析

        vuex數據狀態是響應式的,所以狀態變視圖跟著變,但是有時還是需要知道數據狀態變了從而做一些事情。

        既然狀態都是響應式的,那自然可以watch,另外vuex也提供了訂閱的API:store.subscribe()。

        思路

        • 總述知道的方法
        • 分別闡述用法
        • 選擇和場景

        回答范例

        • 我知道幾種方法:

          • 可以通過watch選項或者watch方法監聽狀態
          • 可以使用vuex提供的API:store.subscribe()
        • watch選項方式,可以以字符串形式監聽$store.state.xx;subscribe方式,可以調用store.subscribe(cb),回調函數接收mutation對象和state對象,這樣可以進一步判斷mutation.type是否是期待的那個,從而進一步做后續處理。

        • watch方式簡單好用,且能獲取變化前后值,首選;subscribe方法會被所有commit行為觸發,因此還需要判斷mutation.type,用起來略繁瑣,一般用于vuex插件中。

        實踐

        watch方式

        const app = createApp({     watch: {       '$store.state.counter'() {         console.log('counter change!');       }     }   })

        subscribe方式:

          store.subscribe((mutation, state) => {     if (mutation.type === 'add') {       console.log('counter change in subscribe()!');     }   })

        20-router-link和router-view是如何起作用的?

        分析

        vue-router中兩個重要組件router-linkrouter-view,分別起到導航作用和內容渲染作用,但是回答如何生效還真有一定難度哪!

        思路

        • 兩者作用
        • 闡述使用方式
        • 原理說明

        回答范例

        • vue-router中兩個重要組件router-linkrouter-view,分別起到路由導航作用和組件內容渲染作用
        • 使用中router-link默認生成一個a標簽,設置to屬性定義跳轉path。實際上也可以通過custom和插槽自定義最終的展現形式。router-view是要顯示組件的占位組件,可以嵌套,對應路由配置的嵌套關系,配合name可以顯示具名組件,起到更強的布局作用。
        • router-link組件內部根據custom屬性判斷如何渲染最終生成節點,內部提供導航方法navigate,用戶點擊之后實際調用的是該方法,此方法最終會修改響應式的路由變量,然后重新去routes匹配出數組結果,router-view則根據其所處深度deep在匹配數組結果中找到對應的路由并獲取組件,最終將其渲染出來。

        知其所以然

        • RouterLink定義

        https://github1s.com/vuejs/router/blob/HEAD/src/RouterLink.ts#L184-L185

        • RouterView定義

        https://github1s.com/vuejs/router/blob/HEAD/src/RouterView.ts#L43-L44


        21-Vue-router 除了 router-link 怎么實現跳轉

        分析

        vue-router導航有兩種方式:聲明式導航編程方式導航

        體驗

        聲明式導航

        <router-link to="/about">Go to About</router-link>

        編程導航

        // literal string path router.push('/users/eduardo')  // object with path router.push({ path: '/users/eduardo' })  // named route with params to let the router build the url router.push({ name: 'user', params: { username: 'eduardo' } })

        思路

        • 兩種方式
        • 分別闡述使用方式
        • 區別和選擇
        • 原理說明

        回答范例

        • vue-router導航有兩種方式:聲明式導航編程方式導航
        • 聲明式導航方式使用router-link組件,添加to屬性導航;編程方式導航更加靈活,可傳遞調用router.push(),并傳遞path字符串或者RouteLocationRaw對象,指定path、name、params等信息
        • 如果頁面中簡單表示跳轉鏈接,使用router-link最快捷,會渲染一個a標簽;如果頁面是個復雜的內容,比如商品信息,可以添加點擊事件,使用編程式導航
        • 實際上內部兩者調用的導航函數是一樣的

        知其所以然

        https://github1s.com/vuejs/router/blob/HEAD/src/RouterLink.ts#L240-L241

        routerlink點擊跳轉,調用的是navigate方法

        29個Vue經典面試題(附源碼級詳解)

        navigate內部依然調用的push


        22-Vue3.0 性能提升體現在哪些方面?

        分析

        vue3在設計時有幾個目標:更小、更快、更友好,這些多數適合性能相關,因此可以圍繞介紹。

        思路

        • 總述和性能相關的新特性
        • 逐個說細節
        • 能說點原理更佳

        回答范例

        • 我分別從代碼、編譯、打包三方面介紹vue3性能方面的提升
        • 代碼層面性能優化主要體現在全新響應式API,基于Proxy實現,初始化時間和內存占用均大幅改進;
        • 編譯層面做了
        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 在线精品亚洲| 99re这里只有精品6| 国产色婷婷五月精品综合在线 | 国产精品久久久久影院色| 无码精品人妻一区二区三区影院 | 久久精品国产一区| 亚洲AV永久无码精品网站在线观看 | 污污网站国产精品白丝袜| 日韩精品在线免费观看| 国产精品毛片无遮挡| 少妇伦子伦精品无码STYLES| 亚洲国产精品毛片av不卡在线| 99久久精品国产一区二区蜜芽| 国产亚洲欧美精品永久| 久久丫精品国产亚洲av不卡| 中文字幕九七精品乱码| 欧美在线精品永久免费播放| 国产欧美日本亚洲精品一5| 999久久久国产精品| 秋霞午夜鲁丝片午夜精品久| 国产精品视频网| 91麻豆精品视频在线观看| 国语自产少妇精品视频| 亚洲精品成人片在线播放| 日韩经典精品无码一区| 亚洲а∨天堂久久精品9966| 欧美日韩成人精品久久久免费看| 精品无码三级在线观看视频| 国产精品一级毛片无码视频 | 亚洲国产精品久久久久久| 精品国产污污免费网站| 国产精品福利一区二区| 99久久99久久久精品齐齐 | 免费精品精品国产欧美在线欧美高清免费一级在线 | 欧美日韩成人精品久久久免费看 | 久久国产热精品波多野结衣AV| 国产精品videossex白浆| 久久精品一区二区三区不卡| 国产精品久久成人影院| 99久久婷婷免费国产综合精品| 国产剧情国产精品一区|