├── README.md ├── package.json └── public ├── 01-vif-vfor ├── README.md ├── test-v3.html └── test.html ├── 02-key ├── README.md ├── test-v3.html └── test.html ├── 03-v-model ├── README.md ├── test-v3.html └── test.html ├── 04-diff ├── README.md ├── test-v3.html └── test.html ├── 05-communication ├── README.md ├── test-v3.html └── test.html ├── 06-vuex └── README.md ├── 07-route-guard └── README.md ├── 08-vue-perf └── README.md ├── 09-nextTick-v3 ├── README.md └── test-v3.html ├── 10-reactivity └── README.md ├── 11-component-extends ├── README.md ├── test-mixin-v3.html └── test-slots-v3.html ├── 12-nextTick-v2 ├── README.md └── test.html ├── 13-reactivity-v2vsv3 └── README.md ├── 14-router ├── README.md └── test-v3.html ├── 15-watch-computed ├── README.md └── test-v3.html ├── 16-create-mount ├── README.md └── test-v3.html ├── 17-only-one-root ├── README.md ├── test-v2.html └── test-v3.html ├── 18-v3-feature └── README.md ├── 19-lifecycle ├── README.md ├── test-lc-change-v3.html └── test-lc-order-v3.html ├── 20-vdom ├── README.md └── test-render-v3.html ├── 21-cache-comp ├── README.md └── test-v3.html ├── 22-template-render ├── README.md └── test-v3.html ├── 24-v3-design └── README.md ├── 25-performance └── README.md ├── 26-one-root └── README.md ├── 27-vuex-module └── README.md ├── 28-route-lazy-load └── README.md ├── 29-ref-reactive └── README.md ├── 30-watch-watchEffect └── README.md ├── 31-SPA-SSR └── README.md ├── 32-directive └── README.md ├── 33-$attrs └── README.md ├── 34-v-once └── README.md ├── 35-recursive-component └── README.md ├── 36-async-component └── README.md ├── 37-error-handle └── README.md ├── 38-child-modify-parent └── README.md ├── 39-permission └── README.md ├── 40-create-vue-proj └── README.md ├── 41-best-practice └── README.md ├── 42-instance-mount └── README.md ├── 43-vue-loader └── README.md ├── 44-dynamic-route └── README.md ├── 45-vuex-design ├── README.md └── test.html ├── 46-mutations-actions └── README.md ├── 47-big-data-performance └── README.md ├── 48-watch-vuex-state ├── README.md └── test.html ├── 49-router-link-router-view └── README.md ├── assets ├── 201806191038393.png ├── 2bb46bdbccec729d280ac0f5eb4420566c010e08-1580031870677.png ├── 2bb46bdbccec729d280ac0f5eb4420566c010e08.png ├── 404460ceece985d433e1ed1f36cd4215.gif ├── 6c27078960e80bc33b954c0623b8bd9277db8de4.png ├── 998023-20180519212338609-1617459354.png ├── bz.jpeg ├── image-20200119155426142.png ├── image-20200124114525656.png ├── image-20200129121057488.png ├── image-20200129121322729.png ├── image-20200207134358051.png ├── logo.png ├── u=1519789747,1816893723&fm=26&gp=0.jpg ├── v2-6e88cc53a7e427f0ae8340cf930ac30d_hd.jpg ├── v2-bf76311258f100b789226ccbb2600071_hd-1579618596674.jpg ├── v2-bf76311258f100b789226ccbb2600071_hd.jpg └── vuex.png └── dist ├── vue.js └── vue.js.map /README.md: -------------------------------------------------------------------------------- 1 | # vue-interview 2 | 总结面试中经典的vue相关题目,分析最佳回答策略,期待您的`star`,您的支持是我坚持的最大的动力 3 | 4 | ## 面试群 5 | 关注`村长学前端`,回复“加群”,加入面试群一起卷~ 6 | i4 7 | 8 | ## 视频讲解 9 | 关注村长B站账号观看视频讲解 10 | [【Vue面试专题】金三银四必备!56道经典Vue面试题详解](https://www.bilibili.com/video/BV11i4y1Q7H2/) 11 | 12 | ## 目录 13 | 1. [v-if和v-for哪个优先级更高?](public/01-vif-vfor/README.md) 14 | 2. [你知道key的作用吗?](public/02-key/README.md) 15 | 3. [能说说双向绑定以及它的实现原理吗?](public/03-v-model/README.md) 16 | 4. [你了解diff算法吗?](public/04-diff/README.md) 17 | 5. [vue中组件之间的通信方式?](public/05-communication/README.md) 18 | 6. [简单说一说你对vuex理解?](public/06-vuex/README.md) 19 | 7. [vue-router中如何保护路由?](public/07-route-guard/README.md) 20 | 8. [你了解哪些Vue性能优化方法?](public/08-vue-perf/README.md) 21 | 9. [你知道nextTick吗,它是干什么的,实现原理是什么?](public/09-nextTick-v3/README.md) 22 | 10. [说一说你对vue响应式理解?](public/10-reactivity/README.md) 23 | 11. [你如果想要扩展某个Vue组件时会怎么做?](public/11-component-extends/README.md) 24 | 12. [nextTick实现原理](public/12-nextTick-v2/README.md) 25 | 13. [Vue2和Vue3中的响应式原理对比,分别的具体实现思路](public/13-reactivity-v2vsv3/README.md) 26 | 14. [说说 vue2/vue3 的生命周期异同](public/19-lifecycle/README.md) 27 | 15. [watch和computed的区别以及怎么选用?](public/15-watch-computed/README.md) 28 | 16. [说一下父组件和子组件创建和挂载顺序](public/16-create-mount/README.md) 29 | 17. [vue组件为什么只能有一个根元素?](public/17-only-one-root/README.md) 30 | 18. [你都知道哪些Vue3.0的新特性?](public/18-v3-feature/README.md) 31 | 19. [简述 Vue 的生命周期以及每个阶段做的事](public/19-lifecycle/README.md) 32 | 20. [说说你对虚拟 DOM 的理解?](public/20-vdom/README.md) 33 | 21. [怎么缓存当前的组件?](public/21-cache-comp/README.md) 34 | 22. [说说从 template 到 render 处理过程](public/22-template-render/README.md) 35 | 23. [如果让你从零写一个vue路由,说说你的思路](public/14-router/README.md) 36 | 24. [Vue 3.0的设计目标是什么?做了哪些优化?](public/24-v3-design/README.md) 37 | 25. [你了解哪些Vue性能优化方法?](public/25-performance/README.md) 38 | 26. [Vue组件为什么只能有一个根元素?](public/26-one-root/README.md) 39 | 27. [你有使用过vuex的module吗?](public/27-vuex-module/README.md) 40 | 28. [怎么实现路由懒加载呢?](public/28-route-lazy-load/README.md) 41 | 29. [ref和reactive异同](public/29-ref-reactive/README.md) 42 | 30. [watch和watchEffect异同](public/30-watch-watchEffect/README.md) 43 | 31. [SPA、SSR的区别是什么](public/31-SPA-SSR/README.md) 44 | 32. [你写过自定义指令吗?使用场景有哪些?](public/32-directive/README.md) 45 | 33. [说下\$attrs和$listeners的使用场景](public/33-%24attrs/README.md) 46 | 34. [v-once的使用场景有哪些?](public/34-v-once/README.md) 47 | 35. [什么是递归组件?举个例子说明下?](public/35-recursive-component/README.md) 48 | 36. [异步组件是什么?使用场景有哪些?](public/36-async-component/README.md) 49 | 37. [你是怎么处理vue项目中的错误的?](public/37-error-handle/README.md) 50 | 38. [子组件能修改父组件数据吗?](public/38-child-modify-parent/README.md) 51 | 39. [Vue要做权限管理该怎么做?控制到按钮级别的权限怎么做?](public/39-permission/README.md) 52 | 40. [从0到1自己构架一个vue项目,说说有哪些步骤、哪些重要插件、目录结构你会怎么组织](public/40-create-vue-proj/README.md) 53 | 41. [实际工作中,你总结的vue最佳实践有哪些?](public/41-best-practice/README.md) 54 | 42. [Vue实例挂载的过程中发生了什么?](public/42-instance-mount/README.md) 55 | 43. [vue-loader是什么?它有什么作用?](public/43-vue-loader/README.md) 56 | 44. [如何获取动态路由并获取其参数?](public/44-dynamic-route/README.md) 57 | 45. [如果让你从零开始写一个vuex,说说你的思路](public/45-vuex-design/README.md) 58 | 46. [vuex中actions和mutations有什么区别?](public/46-mutations-actions/README.md) 59 | 47. [使用vue渲染大量数据时应该怎么优化?说下你的思路!](public/47-big-data-performance/README.md) 60 | 48. [怎么监听vuex数据的变化?](public/48-watch-vuex-state/README.md) 61 | 49. [router-link和router-view是如何起作用的?](public/49-router-link-router-view/README.md) 62 | 63 | ### 欢迎把你想听的题目以issue的方式提给我 64 | ### 欢迎你加入村长的项目已pr形式提交题目和解答 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-interview", 3 | "version": "0.0.1", 4 | "description": "总结面试中经典的vue相关题目,分析最佳回答策略,欢迎star,你的支持是我最大的动力", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/57code/vue-interview.git" 12 | }, 13 | "keywords": ["vue", "interview"], 14 | "author": "57code", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/57code/vue-interview/issues" 18 | }, 19 | "homepage": "https://57code.github.io/vue-interview" 20 | } 21 | -------------------------------------------------------------------------------- /public/01-vif-vfor/README.md: -------------------------------------------------------------------------------- 1 | ## v-if和v-for哪个优先级更高? 2 | 3 | ### 分析: 4 | 此题考查常识,文档中曾有详细说明[v2](https://cn.vuejs.org/v2/style-guide/#%E9%81%BF%E5%85%8D-v-if-%E5%92%8C-v-for-%E7%94%A8%E5%9C%A8%E4%B8%80%E8%B5%B7%E5%BF%85%E8%A6%81)|[v3](https://vuejs.org/style-guide/rules-essential.html#avoid-v-if-with-v-for);也是一个很好的实践题目,项目中经常会遇到,能够看出面试者应用能力。 5 | 6 | 7 | 8 | ### 思路分析:总分总模式 9 | 10 | 1. 先给出结论 11 | 2. 为什么是这样的 12 | 3. 它们能放一起吗 13 | 4. 如果不能,那应该怎样 14 | 5. 总结 15 | 16 | ### 回答范例: 17 | 18 | 1. 在 `Vue 2` 中,`v-for` 优先于 `v-if` 被解析;但在 `Vue 3` 中,则完全相反,`v-if` 的优先级高于 `v-for`。 19 | 20 | 2. 我曾经做过实验,把它们放在一起,输出的渲染函数中可以看出会先执行循环再判断条件 21 | 22 | 3. 实践中也不应该把它们放一起,因为哪怕我们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表。 23 | 24 | 4. 通常有两种情况下导致我们这样做: 25 | 26 | - 为了过滤列表中的项目 (比如 `v-for="user in users" v-if="user.isActive"`)。此时定义一个计算属性 (比如 `activeUsers`),让其返回过滤后的列表即可。 27 | 28 | - 为了避免渲染本应该被隐藏的列表 (比如 `v-for="user in users" v-if="shouldShowUsers"`)。此时把 `v-if` 移动至容器元素上 (比如 `ul`、`ol`)即可。 29 | 30 | 5. 文档中明确指出**永远不要把 `v-if` 和 `v-for` 同时用在同一个元素上**,显然这是一个重要的注意事项。 31 | 32 | 6. 看过源码里面关于代码生成的部分, 33 | 34 | 35 | ### 知其所以然: 36 | 37 | 在 `Vue 2` 中做个测试,[test.html](./test.html) 38 | 两者同级时,渲染函数如下: 39 | 40 | 41 | ```js 42 | ƒ anonymous( 43 | ) { 44 | with(this){return _c('div',{attrs:{"id":"app"}},_l((items),function(item){return (item.isActive)?_c('div',{key:item.id},[_v("\n "+_s(item.name)+"\n ")]):_e()}),0)} 45 | } 46 | ``` 47 | 48 | 在 `Vue 3` 中做个测试,[test-v3.html](./test-v3.html) 49 | 两者同级时,渲染函数如下: 50 | 51 | ```js 52 | (function anonymous( 53 | ) { 54 | const _Vue = Vue 55 | 56 | return function render(_ctx, _cache) { 57 | with (_ctx) { 58 | const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, createCommentVNode: _createCommentVNode } = _Vue 59 | 60 | return shouldShowUsers 61 | ? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(items, (item) => { 62 | return (_openBlock(), _createElementBlock("div", { key: item.id }, _toDisplayString(item.name), 1 /* TEXT */)) 63 | }), 128 /* KEYED_FRAGMENT */)) 64 | : _createCommentVNode("v-if", true) 65 | } 66 | } 67 | }) 68 | ``` 69 | 70 | 71 | 源码中找答案: 72 | `Vue 2`:[compiler/codegen/index.js](https://github1s.com/vuejs/vue/blob/dev/src/compiler/codegen/index.js#L65-L69) 73 | `Vue 3`:[compiler-core/src/codegen.ts](https://github1s.com/vuejs/core/blob/main/packages/compiler-core/src/codegen.ts#L586-L587) 74 | 75 | -------------------------------------------------------------------------------- /public/01-vif-vfor/test-v3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |
21 |
22 | {{ item.name }} 23 |
24 |
25 | 26 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /public/01-vif-vfor/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 |
14 | {{ item.name }} 15 |
16 |
17 | 18 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/02-key/README.md: -------------------------------------------------------------------------------- 1 | ## 你知道key的作用吗? 2 | 3 | ### 分析: 4 | 这是一道特别常见的问题,主要考查大家对虚拟DOM和patch细节的掌握程度,能够反映面试者理解层次。 5 | 6 | 7 | 8 | 9 | 10 | ### 思路分析:总分总模式 11 | 12 | 1. 给出结论,key的作用是用于优化patch性能 13 | 2. key的必要性 14 | 3. 实际使用方式 15 | 4. 总结:可从源码层面描述一下vue如何判断两个节点是否相同 16 | 17 | 18 | 19 | 20 | 21 | ### 回答范例: 22 | 23 | 1. key的作用主要是为了更高效的更新虚拟DOM。 24 | 2. vue在patch过程中**判断两个节点是否是相同节点是key是一个必要条件**,渲染一组列表时,key往往是唯一标识,所以如果不定义key的话,vue只能认为比较的两个节点是同一个,哪怕它们实际上不是,这导致了频繁更新元素,使得整个patch过程比较低效,影响性能。 25 | 3. 实际使用中在渲染一组列表时key必须设置,而且必须是唯一标识,应该避免使用数组索引作为key,这可能导致一些隐蔽的bug;vue中在使用相同标签元素过渡切换时,也会使用key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。 26 | 4. 从源码中可以知道,vue判断两个节点是否相同时主要判断两者的key和元素类型等,因此如果不设置key,它的值就是undefined,则可能永远认为这是两个相同节点,只能去做更新操作,这造成了大量的dom更新操作,明显是不可取的。 27 | 28 | 29 | 30 | 31 | 32 | 测试代码,[test.html](./test.html) 33 | 34 | 上面案例重现的是以下过程 35 | 36 | ![img1](../assets/v2-6e88cc53a7e427f0ae8340cf930ac30d_hd.jpg) 37 | 38 | 不使用key 39 | 40 | ![img2](../assets/v2-bf76311258f100b789226ccbb2600071_hd.jpg) 41 | 42 | 如果使用key 43 | 44 | ``` 45 | // 首次循环patch A 46 | A B C D E 47 | A B F C D E 48 | 49 | // 第2次循环patch B 50 | B C D E 51 | B F C D E 52 | 53 | // 第3次循环patch E 54 | C D E 55 | F C D E 56 | 57 | // 第4次循环patch D 58 | C D 59 | F C D 60 | 61 | // 第5次循环patch C 62 | C 63 | F C 64 | 65 | // oldCh全部处理结束,newCh中剩下的F,创建F并插入到C前面 66 | ``` 67 | 68 | 69 | 70 | 源码中找答案:src\core\vdom\patch.js - sameVnode() -------------------------------------------------------------------------------- /public/02-key/test-v3.html: -------------------------------------------------------------------------------- 1 |

03-key的作用及原理?

2 | 3 |
4 |

{{item}}

5 |
6 | 7 | -------------------------------------------------------------------------------- /public/02-key/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 03-key的作用及原理? 6 | 7 | 8 | 9 |
10 |

{{item}}

11 |
12 | 13 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /public/03-v-model/README.md: -------------------------------------------------------------------------------- 1 | ## 能说说双向绑定以及它的实现原理吗? 2 | 3 | ### 题目分析: 4 | 双向绑定是vue的特色之一,开发中必然会用到的知识点,然而此题还问了实现原理,升级为深度考查。 5 | 6 | ### 思路分析:3w1h 7 | 8 | 1. 给出双绑定义 9 | 2. 双绑带来的好处 10 | 3. 在哪使用双绑 11 | 4. 使用方式 12 | 5. 扩展:使用细节、原理实现描述 13 | 14 | ### 回答范例: 15 | 16 | 1. vue中双向绑定是一个指令v-model,可以绑定一个动态值到视图,同时视图中变化能改变该值。v-model是语法糖,默认情况下相当于:value和@input。 17 | 2. 使用v-model可以减少大量繁琐的事件处理代码,提高开发效率,代码可读性也更好 18 | 3. 通常在表单项上使用v-model 19 | 4. 原生的表单项可以直接使用v-model,自定义组件上如果要使用它需要在组件内绑定value并处理输入事件 20 | 5. 我做过测试,输出包含v-model模板的组件渲染函数,发现它会被转换为value属性的绑定以及一个事件监听,事件回调函数中会做相应变量更新操作,这说明神奇魔法实际上是vue的编译器完成的。 21 | 22 | ### 可能的追问: 23 | 24 | 1. v-model和sync修饰符有什么区别 25 | 2. 自定义组件使用v-model如果想要改变事件名或者属性名应该怎么做 26 | 27 | 28 | 29 | 30 | 31 | 知其所以然:测试代码,[test.html](./test.html) 32 | 33 | 观察输出的渲染函数: 34 | 35 | ```js 36 | // 37 | _c('input', { 38 | directives: [{ name: "model", rawName: "v-model", value: (foo), expression: "foo" }], 39 | attrs: { "type": "text" }, 40 | domProps: { "value": (foo) }, 41 | on: { 42 | "input": function ($event) { 43 | if ($event.target.composing) return; 44 | foo = $event.target.value 45 | } 46 | } 47 | }) 48 | ``` 49 | 50 | ```js 51 | // 52 | _c('input', { 53 | directives: [{ name: "model", rawName: "v-model", value: (bar), expression: "bar" }], 54 | attrs: { "type": "checkbox" }, 55 | domProps: { 56 | "checked": Array.isArray(bar) ? _i(bar, null) > -1 : (bar) 57 | }, 58 | on: { 59 | "change": function ($event) { 60 | var $$a = bar, $$el = $event.target, $$c = $$el.checked ? (true) : (false); 61 | if (Array.isArray($$a)) { 62 | var $$v = null, $$i = _i($$a, $$v); 63 | if ($$el.checked) { $$i < 0 && (bar = $$a.concat([$$v])) } 64 | else { 65 | $$i > -1 && (bar = $$a.slice(0, $$i).concat($$a.slice($$i + 1))) } 66 | } else { 67 | bar = $$c 68 | } 69 | } 70 | } 71 | }) 72 | ``` 73 | 74 | ```js 75 | // 79 | _c('select', { 80 | directives: [{ name: "model", rawName: "v-model", value: (baz), expression: "baz" }], 81 | on: { 82 | "change": function ($event) { 83 | var $$selectedVal = Array.prototype.filter.call( 84 | $event.target.options, 85 | function (o) { return o.selected } 86 | ).map( 87 | function (o) { 88 | var val = "_value" in o ? o._value : o.value; 89 | return val 90 | } 91 | ); 92 | baz = $event.target.multiple ? $$selectedVal : $$selectedVal[0] 93 | } 94 | } 95 | }, [ 96 | _c('option', { attrs: { "value": "vue" } }, [_v("vue")]), _v(" "), 97 | _c('option', { attrs: { "value": "react" } }, [_v("react")]) 98 | ]) 99 | ``` 100 | -------------------------------------------------------------------------------- /public/03-v-model/test-v3.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 8 |
9 | 10 | -------------------------------------------------------------------------------- /public/03-v-model/test.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 8 |
9 | 10 | -------------------------------------------------------------------------------- /public/04-diff/README.md: -------------------------------------------------------------------------------- 1 | ## 你了解vue中的diff算法吗? 2 | 3 | 题目分析:vue基于虚拟DOM做更新,diff又是其核心部分,因此常被问道,此题考查面试者深度。 4 | 5 | 6 | 7 | ### 分析 8 | 9 | 必问题目,涉及vue更新原理,比较考查理解深度。 10 | 11 | 12 | 13 | 14 | 15 | ### 思路 16 | 17 | 1. diff算法是干什么的 18 | 2. 它的必要性 19 | 3. 它何时执行 20 | 4. 具体执行方式 21 | 5. 拔高:说一下vue3中的优化 22 | 23 | 24 | 25 | ### 回答范例 26 | 27 | 1.Vue中的diff算法称为patching算法,它由Snabbdom修改而来,虚拟DOM要想转化为真实DOM就需要通过patch方法转换。 28 | 29 | 2.最初Vue1.x视图中每个依赖均有更新函数对应,可以做到精准更新,因此并不需要虚拟DOM和patching算法支持,但是这样粒度过细导致Vue1.x无法承载较大应用;Vue 2.x中为了降低Watcher粒度,每个组件只有一个Watcher与之对应,此时就需要引入patching算法才能精确找到发生变化的地方并高效更新。 30 | 31 | 3.vue中diff执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行render函数获得最新的虚拟DOM,然后执行patch函数,并传入新旧两次虚拟DOM,通过比对两者找到变化的地方,最后将其转化为对应的DOM操作。 32 | 33 | 4.patch过程是一个递归过程,遵循深度优先、同层比较的策略;以vue3的patch为例: 34 | 35 | - 首先判断两个节点是否为相同同类节点,不同则删除重新创建 36 | - 如果双方都是文本则更新文本内容 37 | - 如果双方都是元素节点则递归更新子元素,同时更新元素属性 38 | - 更新子节点时又分了几种情况: 39 | - 新的子节点是文本,老的子节点是数组则清空,并设置文本; 40 | - 新的子节点是文本,老的子节点是文本则直接更新文本; 41 | - 新的子节点是数组,老的子节点是文本则清空文本,并创建新子节点数组中的子元素; 42 | - 新的子节点是数组,老的子节点也是数组,那么比较两组子节点,更新细节blabla 43 | 44 | 5. vue3中引入的更新策略:编译期优化patchFlags、block等 45 | 46 | ### 知其所以然 47 | 48 | patch关键代码 49 | 50 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L354-L355 51 | 52 | 53 | 54 | 调试 55 | -------------------------------------------------------------------------------- /public/04-diff/test-v3.html: -------------------------------------------------------------------------------- 1 |
2 |

diff

3 |

{{foo}}

4 |
5 | 6 | -------------------------------------------------------------------------------- /public/04-diff/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue源码剖析 6 | 7 | 8 | 9 | 10 |
11 |

虚拟DOM

12 |

{{foo}}

13 |
14 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /public/05-communication/README.md: -------------------------------------------------------------------------------- 1 | ## vue中组件之间的通信方式? 2 | 3 | 题目分析:vue是组件化开发框架,所以对于vue应用来说组件间的数据通信非常重要。此题主要考查大家vue基本功,对于vue基础api运用熟练度。另外一些边界知识如provide/inject/$attrs/$listeners则体现了面试者的知识面。 4 | 5 | 6 | ### 思路分析:总分 7 | 8 | 1. 总述知道的所有方式 9 | 2. 按组件关系阐述使用场景 10 | 11 | 12 | ### 回答范例: 13 | 14 | 1. 组件通信方式大体有以下8种: 15 | 16 | - props 17 | - $emit/$on 18 | - $children/$parent 19 | - $attrs/$listeners 20 | - ref 21 | - $root 22 | - eventbus 23 | - vuex 24 | 25 | 26 | 27 | 2. 根据组件之间关系讨论组件通信最为清晰有效 28 | 29 | - 父子组件 30 | 31 | - `props` 32 | - `$emit`/`$on` 33 | - `$parent` / `$children` 34 | - `ref` 35 | - `$attrs` / `$listeners` 36 | - 兄弟组件 37 | 38 | - `$parent` 39 | - `eventbus` 40 | 41 | - `vuex` 42 | 43 | - 跨层级关系 44 | 45 | - `provide`/`inject` 46 | - `$root` 47 | - `eventbus` 48 | - `vuex` 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /public/05-communication/test-v3.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ title }}

3 |

vue3取消了$on,$children,$listeners

4 | 5 |
6 | 7 | -------------------------------------------------------------------------------- /public/05-communication/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 30 | 37 | 43 | 49 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /public/06-vuex/README.md: -------------------------------------------------------------------------------- 1 | ## 简单说一说你对vuex理解? 2 | ![vuex](../assets/vuex.png) 3 | ### 分析 4 | 此题考查实践能力,能说出用法只能60分。更重要的是对vuex设计理念和实现原理的解读。 5 | 6 | 7 | 8 | ### 回答策略:3w1h 9 | 10 | 1. 首先给vuex下一个定义 11 | 2. vuex解决了哪些问题,解读理念 12 | 3. 什么时候我们需要vuex 13 | 4. 你的具体用法 14 | 5. 简述原理,提升层级 15 | 16 | 17 | 18 | 首先是官网定义: 19 | 20 | > Vuex 是一个专为 Vue.js 应用程序开发的**状态管理模式**。它采用**集中式**存储管理应用的所有组件的状态,并以相应的规则保证状态以一种**可预测**的方式发生变化。 21 | 22 | 23 | 24 | ### 回答范例: 25 | 26 | 1. vuex是vue专用的状态管理库。它以全局方式集中管理应用的状态,并且可以保证状态变更的可预测性。 27 | 2. vuex主要解决的问题是多组件之间状态共享的问题,利用各种组件通信方式,我们虽然能够做到状态共享,但是往往需要在多个组件之间保持状态的一致性,这种模式很容易出现问题,也会使程序逻辑变得复杂。vuex通过把组件的共享状态抽取出来,以全局单例模式管理,这样任何组件都能用一致的方式获取和修改状态,响应式的数据也能够保证简洁的单向数据流动,我们的代码将变得更结构化且易维护。 28 | 3. vuex并非必须的,它帮我们管理共享状态,但却带来更多的概念和框架。如果我们不打算开发大型单页应用或者我们的应用并没有大量全局的状态需要维护,完全没有使用vuex的必要。一个简单的[store 模式](https://cn.vuejs.org/v2/guide/state-management.html#简单状态管理起步使用)就足够了。反之,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:Flux 架构就像眼镜:您自会知道什么时候需要它。 29 | 4. 我在使用vuex过程中有如下理解:首先是对核心概念的理解和运用,将全局状态放入state对象中,它本身一棵状态树,组件中使用store实例的state访问这些状态;然后有配套的mutation方法修改这些状态,并且只能用mutation修改状态,在组件中调用commit方法提交mutation;如果应用中有异步操作或者复杂逻辑组合,我们需要编写action,执行结束如果有状态修改仍然需要提交mutation,组件中调用这些action使用dispatch方法派发。最后是模块化,通过modules选项组织拆分出去的各个子模块,在访问状态时注意添加子模块的名称,如果子模块有设置namespace,那么在提交mutation和派发action时还需要额外的命名空间前缀。 30 | 5. vuex在实现单项数据流时需要做到数据的响应式,通过源码的学习发现是借用了vue的数据响应化特性实现的,它会利用Vue将state作为data对其进行响应化处理,从而使得这些状态发生变化时,能够导致组件重新渲染。 31 | -------------------------------------------------------------------------------- /public/07-route-guard/README.md: -------------------------------------------------------------------------------- 1 | ## vue-router中如何保护路由? 2 | 3 | 此题是考查项目实践能力,项目中基本都有路由守卫的需求,保护指定路由考查的就是这个知识点。 4 | 5 | 6 | 7 | ### 答题思路: 8 | 9 | 1. 阐述vue-router中路由保护策略 10 | 2. 描述具体实现方式 11 | 3. 简单说一下它们是怎么生效的 12 | 13 | 14 | 15 | ### 回答范例: 16 | 17 | 1. vue-router中保护路由安全通常使用导航守卫来做,通过设置路由导航钩子函数的方式添加守卫函数,在里面判断用户的登录状态和权限,从而达到保护指定路由的目的。 18 | 2. 具体实现有几个层级:全局前置守卫beforeEach、路由独享守卫beforeEnter或组件内守卫beforeRouteEnter。以全局守卫为例来说,可以使用`router.beforeEach((to,from,next)=>{})`方式设置守卫,每次路由导航时,都会执行该守卫,从而检查当前用户是否可以继续导航,通过给next函数传递多种参数达到不同的目的,比如如果禁止用户继续导航可以传递next(false),正常放行可以不传递参数,传递path字符串可以重定向到一个新的地址等等。 19 | 3. 这些钩子函数之所以能够生效,也和vue-router工作方式有关,像beforeEach只是注册一个hook,当路由发生变化,router准备导航之前会批量执行这些hooks,并且把目标路由to,当前路由from,以及后续处理函数next传递给我们设置的hook。 20 | 21 | 22 | 23 | ### 可能的追问: 24 | 25 | 1. 能不能说说全局守卫、路由独享守卫和组件内守卫区别? 26 | 27 | - 作用范围 28 | 29 | - 组件实例的获取 30 | 31 | ``` 32 | beforeRouteEnter(to,from,next) { 33 | next(vm => { 34 | 35 | }) 36 | } 37 | ``` 38 | 39 | 40 | 41 | - 名称/数量/顺序 42 | 43 | > 1. 导航被触发。 44 | > 2. 在失活的组件里调用离开守卫。 45 | > 3. 调用全局的 `beforeEach` 守卫。 46 | > 4. 在重用的组件里调用 `beforeRouteUpdate` 守卫 (2.2+)。 47 | > 5. 在路由配置里调用 `beforeEnter`。 48 | > 6. 解析异步路由组件。 49 | > 7. 在被激活的组件里调用 `beforeRouteEnter`。 50 | > 8. 调用全局的 `beforeResolve` 守卫 (2.5+)。 51 | > 9. 导航被确认。 52 | > 10. 调用全局的 `afterEach` 钩子。 53 | > 11. 触发 DOM 更新。 54 | > 12. 用创建好的实例调用 `beforeRouteEnter` 守卫中传给 `next` 的回调函数。 55 | 56 | 2. 你项目中的路由守卫是怎么做的? 57 | 58 | 3. 前后端路由一样吗? 59 | 60 | 4. 前端路由是用什么方式实现的? 61 | 62 | 5. 你前面提到的next方法是怎么实现的? -------------------------------------------------------------------------------- /public/08-vue-perf/README.md: -------------------------------------------------------------------------------- 1 | ## 你了解哪些Vue性能优化方法? 2 | 3 | ### 分析 4 | 5 | 这是一道综合实践题目,写过一定数量的代码之后小伙伴们自然会开始关注一些优化方法,答得越多肯定实践经验也越丰富,是很好的题目。 6 | 7 | ### 答题思路: 8 | 9 | 根据题目描述,这里主要探讨Vue代码层面的优化 10 | 11 | ### 回答范例 12 | 13 | - 我这里主要从Vue代码编写层面说一些优化手段,例如:代码分割、服务端渲染、组件缓存、长列表优化等 14 | 15 | - 最常见的路由懒加载:有效拆分App尺寸,访问时才异步加载 16 | 17 | ```js 18 | const router = createRouter({ 19 | routes: [ 20 | // 借助webpack的import()实现异步组件 21 | { path: '/foo', component: () => import('./Foo.vue') } 22 | ] 23 | }) 24 | ``` 25 | 26 | 27 | 28 | - `keep-alive`缓存页面:避免重复创建组件实例,且能保留缓存组件状态 29 | 30 | ```vue 31 | 32 | 33 | 34 | 35 | 36 | ``` 37 | 38 | 39 | 40 | - 使用`v-show`复用DOM:避免重复创建组件 41 | 42 | ```vue 43 | 54 | ``` 55 | 56 | 57 | 58 | - `v-for` 遍历避免同时使用 `v-if`:实际上在Vue3中已经是个错误写法 59 | 60 | ```vue 61 | 72 | 81 | ``` 82 | 83 | 84 | 85 | - v-once和v-memo:不再变化的数据使用`v-once` 86 | 87 | ```vue 88 | 89 | This will never change: {{msg}} 90 | 91 |
92 |

comment

93 |

{{msg}}

94 |
95 | 96 | 97 | 98 | 101 | ``` 102 | 103 | 按条件跳过更新时使用`v-momo`:下面这个列表只会更新选中状态变化项 104 | 105 | ```vue 106 |
107 |

ID: {{ item.id }} - selected: {{ item.id === selected }}

108 |

...more child nodes

109 |
110 | ``` 111 | 112 | > https://vuejs.org/api/built-in-directives.html#v-memo 113 | 114 | 115 | 116 | - 长列表性能优化:如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容 117 | 118 | ```html 119 | 124 | 130 | 131 | ``` 132 | 133 | > 一些开源库: 134 | > 135 | > - [vue-virtual-scroller](https://github.com/Akryum/vue-virtual-scroller) 136 | > - [vue-virtual-scroll-grid](https://github.com/rocwang/vue-virtual-scroll-grid) 137 | 138 | 139 | 140 | - 事件的销毁:Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。 141 | 142 | ```js 143 | export default { 144 | created() { 145 | this.timer = setInterval(this.refresh, 2000) 146 | }, 147 | beforeUnmount() { 148 | clearInterval(this.timer) 149 | } 150 | } 151 | ``` 152 | 153 | 154 | 155 | - 图片懒加载 156 | 157 | 对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。 158 | 159 | ```html 160 | 161 | ``` 162 | 163 | > 参考项目:[vue-lazyload](https://github.com/hilongjw/vue-lazyload) 164 | 165 | 166 | 167 | - 第三方插件按需引入 168 | 169 | 像`element-plus`这样的第三方组件库可以按需引入避免体积太大。 170 | 171 | ```js 172 | import { createApp } from 'vue'; 173 | import { Button, Select } from 'element-plus'; 174 | 175 | const app = createApp() 176 | app.use(Button) 177 | app.use(Select) 178 | ``` 179 | 180 | 181 | 182 | - 子组件分割策略:较重的状态组件适合拆分 183 | 184 | ```vue 185 | 190 | 191 | 205 | ``` 206 | 207 | 但同时也不宜过度拆分组件,尤其是为了所谓组件抽象将一些不需要渲染的组件特意抽出来,组件实例消耗远大于纯dom节点。参考:https://vuejs.org/guide/best-practices/performance.html#avoid-unnecessary-component-abstractions 208 | 209 | 210 | 211 | - 服务端渲染/静态网站生成:SSR/SSG 212 | 213 | 如果SPA应用有首屏渲染慢的问题,可以考虑SSR、SSG方案优化。参考[SSR Guide](https://vuejs.org/guide/scaling-up/ssr.html) 214 | 215 | -------------------------------------------------------------------------------- /public/09-nextTick-v3/README.md: -------------------------------------------------------------------------------- 1 | ## 你知道nextTick吗,它是干什么的,实现原理是什么? 2 | 3 | 这道题考查大家对vue异步更新队列的理解,有一定深度,如果能够很好回答此题,对面试效果有极大帮助。 4 | 5 | 6 | 7 | ### 答题思路: 8 | 9 | 1. nextTick是啥?下一个定义 10 | 2. 为什么需要它呢?用异步更新队列实现原理解释 11 | 3. 我再什么地方用它呢?抓抓头,想想你在平时开发中使用它的地方 12 | 4. 下面介绍一下如何使用nextTick 13 | 5. 最后能说出源码实现就会显得你格外优秀 14 | 15 | 16 | 17 | 先看看官方定义 18 | 19 | > Vue.nextTick( \[callback, context\] ) 20 | > 21 | > 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。 22 | > 23 | > ```js 24 | > // 修改数据 25 | > vm.msg = 'Hello' 26 | > // DOM 还没有更新 27 | > Vue.nextTick(function () { 28 | > // DOM 更新了 29 | > }) 30 | > ``` 31 | 32 | ### 回答范例: 33 | 34 | 1. nextTick是Vue提供的一个全局API,由于vue的异步更新策略导致我们对数据的修改不会立刻体现在dom变化上,此时如果想要立即获取更新后的dom状态,就需要使用这个方法 35 | 2. Vue 在更新 DOM 时是**异步**执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用。 36 | 3. 所以当我们想在修改数据后立即看到dom执行结果就需要用到nextTick方法。 37 | 4. 比如,我在干什么的时候就会使用nextTick,传一个回调函数进去,在里面执行dom操作即可。 38 | 5. 我也有简单了解nextTick实现,它会在callbacks里面加入我们传入的函数,然后用timerFunc异步方式调用它们,首选的异步方式会是Promise。这让我明白了为什么可以在nextTick中看到dom操作结果。 39 | -------------------------------------------------------------------------------- /public/09-nextTick-v3/test-v3.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ title }}

3 |
4 | 5 | -------------------------------------------------------------------------------- /public/10-reactivity/README.md: -------------------------------------------------------------------------------- 1 | ## 说一说你对vue响应式理解? 2 | 3 | 烂大街的问题,但却不是每个人都能回答到位。因为如果你只是看看别人写的网文,通常没什么底气,也经不住面试官推敲,但像我们这样即看过源码还造过轮子的,回答这个问题就会比较有底气。 4 | 5 | ### 答题思路: 6 | 7 | 1. 啥是响应式? 8 | 2. 为什么vue需要响应式? 9 | 3. 它能给我们带来什么好处? 10 | 4. vue的响应式是怎么实现的?有哪些优缺点? 11 | 5. vue3中的响应式的新变化 12 | 13 | 14 | 15 | 16 | 17 | ### 回答范例: 18 | 19 | 1. 所谓数据响应式就是能够使数据变化可以被检测并对这种变化做出响应的机制。 20 | 2. mvvm框架中要解决的一个核心问题是连接数据层和视图层,通过数据驱动应用,数据变化,视图更新,要做到这点的就需要对数据做响应式处理,这样一旦数据发生变化就可以立即做出更新处理。 21 | 3. 以vue为例说明,通过数据响应式加上虚拟DOM和patch算法,可以使我们只需要操作数据,完全不用接触繁琐的dom操作,从而大大提升开发效率,降低开发难度。 22 | 4. vue2中的数据响应式会根据数据类型来做不同处理,如果是对象则采用Object.defineProperty()的方式定义数据拦截,当数据被访问或发生变化时,我们感知并作出响应;如果是数组则通过覆盖该数组原型的方法,扩展它的7个变更方法,使这些方法可以额外的做更新通知,从而作出响应。这种机制很好的解决了数据响应化的问题,但在实际使用中也存在一些缺点:比如初始化时的递归遍历会造成性能损失;新增或删除属性时需要用户使用Vue.set/delete这样特殊的api才能生效;对于es6中新产生的Map、Set这些数据结构不支持等问题。 23 | 5. 为了解决这些问题,vue3重新编写了这一部分的实现:利用ES6的Proxy机制代理要响应化的数据,它有很多好处,编程体验是一致的,不需要使用特殊api,初始化性能和内存消耗都得到了大幅改善;另外由于响应化的实现代码抽取为独立的reactivity包,使得我们可以更灵活的使用它,我们甚至不需要引入vue都可以体验。 24 | 25 | 26 | ### 知其所以然 27 | 28 | vue2响应式: 29 | 30 | https://github1s.com/vuejs/vue/blob/HEAD/src/core/observer/index.js#L135-L136 31 | 32 | vue3响应式: 33 | 34 | https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/reactive.ts#L89-L90 35 | 36 | https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/ref.ts#L67-L68 37 | -------------------------------------------------------------------------------- /public/11-component-extends/README.md: -------------------------------------------------------------------------------- 1 | ## 你如果想要扩展某个Vue组件时会怎么做? 2 | 3 | 此题属于实践题,着重考察大家对vue常用api使用熟练度,答题时不仅要列出这些解决方案,同时最好说出他们异同。 4 | 5 | 6 | 7 | ### 答题思路: 8 | 9 | 按照逻辑扩展和内容扩展来列举,逻辑扩展有:mixins、extends、composition api;内容扩展有slots; 10 | 11 | 分别说出他们使用方法、场景差异和问题。 12 | 13 | 作为扩展,还可以说说vue3中新引入的composition api带来的变化 14 | 15 | 16 | 17 | ### 回答范例: 18 | 19 | 1. 常见的组件扩展方法有:mixins,slots,extends等 20 | 21 | 2. 混入mixins是分发 Vue 组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。 22 | 23 | ```js 24 | // 复用代码:它是一个配置对象,选项和组件里面一样 25 | const mymixin = { 26 | methods: { 27 | dosomething(){} 28 | } 29 | } 30 | // 全局混入:将混入对象传入 31 | Vue.mixin(mymixin) 32 | 33 | // 局部混入:做数组项设置到mixins选项,仅作用于当前组件 34 | const Comp = { 35 | mixins: [mymixin] 36 | } 37 | ``` 38 | 39 | 3. 插槽主要用于vue组件中的内容分发,也可以用于组件扩展。 40 | 41 | 子组件Child 42 | 43 | ```html 44 |
45 | 这个内容会被父组件传递的内容替换 46 |
47 | ``` 48 | 49 | 父组件Parent 50 | 51 | ```html 52 |
53 | 来自老爹的内容 54 |
55 | ``` 56 | 57 | 如果要精确分发到不同位置可以使用具名插槽,如果要使用子组件中的数据可以使用作用域插槽。 58 | 59 | 4. 组件选项中还有一个不太常用的选项extends,也可以起到扩展组件的目的 60 | 61 | ```js 62 | // 扩展对象 63 | const myextends = { 64 | methods: { 65 | dosomething(){} 66 | } 67 | } 68 | // 组件扩展:做数组项设置到extends选项,仅作用于当前组件 69 | // 跟混入的不同是它只能扩展单个对象 70 | // 另外如果和混入发生冲突,该选项优先级较高,优先起作用 71 | const Comp = { 72 | extends: myextends 73 | } 74 | ``` 75 | 76 | 5. 混入的数据和方法不能明确判断来源且可能和当前组件内变量产生命名冲突,vue3中引入的composition api,可以很好解决这些问题,利用独立出来的响应式模块可以很方便的编写独立逻辑并提供响应式的数据,然后在setup选项中有机组合使用。例如: 77 | 78 | ```js 79 | // 复用逻辑1 80 | function useXX() {} 81 | // 复用逻辑2 82 | function useYY() {} 83 | // 逻辑组合 84 | const Comp = { 85 | setup() { 86 | const {xx} = useXX() 87 | const {yy} = useYY() 88 | return {xx, yy} 89 | } 90 | } 91 | ``` 92 | 93 | 94 | 95 | ### 可能的追问 96 | 97 | Vue.extend方法你用过吗?它能用来做组件扩展吗? -------------------------------------------------------------------------------- /public/11-component-extends/test-mixin-v3.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ title }}

3 | 4 |
5 | 6 | -------------------------------------------------------------------------------- /public/11-component-extends/test-slots-v3.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ title }}

3 | abc 4 |
5 | 6 | -------------------------------------------------------------------------------- /public/12-nextTick-v2/README.md: -------------------------------------------------------------------------------- 1 | ## nextTick实现原理 2 | 此题属于原理题目,能够体现面试者对vue理解深度,答好了会加分很多。 3 | 4 | 5 | 6 | ### 答题思路: 7 | 8 | 1. 此题实际考查vue异步更新策略 9 | 2. 说出vue是怎么通过异步、批量的方式更新以提高性能的 10 | 3. 最后把源码中实现说一下 11 | 12 | 13 | 14 | ### 回答范例: 15 | 16 | 1. vue有个批量、异步更新策略,数据变化时,vue开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。然后在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。 17 | 18 | > [官方文档在这里](https://cn.vuejs.org/v2/guide/reactivity.html#%E5%BC%82%E6%AD%A5%E6%9B%B4%E6%96%B0%E9%98%9F%E5%88%97) 19 | 20 | 2. 源码中,修改一个数据,组件对应的watcher会尝试入队: 21 | 22 | ``` 23 | queue.push(watcher) 24 | ``` 25 | 26 | 并使用nextTick方法添加一个flushSchedulerQueue回调 27 | 28 | ``` 29 | nextTick(flushSchedulerQueue) 30 | ``` 31 | 32 | flushSchedulerQueue被加入callbacks数组 33 | 34 | ```js 35 | callbacks.push(() => { 36 | if (cb) { 37 | try { 38 | cb.call(ctx) // cb就是加入的回调 39 | } catch (e) { 40 | handleError(e, ctx, 'nextTick') 41 | } 42 | } 43 | }) 44 | ``` 45 | 46 | 然后以异步方式启动 47 | 48 | ```js 49 | if (!pending) { 50 | pending = true 51 | timerFunc() 52 | } 53 | ``` 54 | 55 | timerFunc的异步主要利用Promise等微任务方式实现 56 | 57 | ```js 58 | let timerFunc 59 | 60 | if (typeof Promise !== 'undefined' && isNative(Promise)) { 61 | const p = Promise.resolve() 62 | // timerFunc利用p.then向微任务队列添加一个flushCallbacks 63 | // 会异步调用flushCallbacks 64 | timerFunc = () => { 65 | p.then(flushCallbacks) 66 | } 67 | isUsingMicroTask = true 68 | } 69 | ``` 70 | 71 | flushCallbacks遍历callbacks,执行里面所有回调 72 | 73 | ```js 74 | function flushCallbacks () { 75 | pending = false 76 | const copies = callbacks.slice(0) 77 | callbacks.length = 0 78 | for (let i = 0; i < copies.length; i++) { 79 | copies[i]() 80 | } 81 | } 82 | ``` 83 | 84 | 其中就有前面加入的flushSchedulerQueue,它主要用于执行queue中所有watcher的run方法,从而使组件们更新 85 | 86 | ```js 87 | for (index = 0; index < queue.length; index++) { 88 | watcher = queue[index] 89 | watcher.run() 90 | } 91 | ``` 92 | 93 | 94 | 95 | 96 | 97 | #### 知其所以然,测试代码: 98 | 99 | [test.html](./test.html) 100 | 101 | 可以调试一下上述流程,看看是不是这样。 102 | 103 | 104 | 105 | ### 可能的追问 106 | 107 | 你平时什么时候会用到nextTick? -------------------------------------------------------------------------------- /public/12-nextTick-v2/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
{{message}}
12 | 13 | 14 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/13-reactivity-v2vsv3/README.md: -------------------------------------------------------------------------------- 1 | ## Vue2和Vue3中的响应式原理对比,分别的具体实现思路 2 | 此题非常好,既考察深度又考察广度,面试者要对两个版本的响应式原理都有深入理解才能答好。 3 | 4 | 5 | 6 | ### 答题思路: 7 | 8 | 1. 可以先说vue2响应式原理 9 | 2. 然后说出它的问题 10 | 3. 最后说出vue3是怎么解决的 11 | 12 | 13 | 14 | ### 回答范例: 15 | 16 | 1. vue2数据响应式实现根据对象类型做不同处理,如果是object,则通过`Object.defineProperty(obj,key,descriptor)`拦截对象属性访问 17 | 18 | ```js 19 | function defineReactive(obj, key, val) { 20 | Object.defineProperty(obj, key, { 21 | get() { 22 | return val 23 | }, 24 | set(v) { 25 | val = v 26 | notify() 27 | } 28 | }) 29 | } 30 | ``` 31 | 32 | 如果是数组,则覆盖数组的7个变更方法实现变更通知 33 | 34 | ```js 35 | const arrayProto = Array.prototype 36 | const arrayMethods = Object.create(arrayProto) 37 | 38 | ;['push','pop','shift','unshift','splice','sort','reverse'] 39 | .forEach(function (method) { 40 | const original = arrayProto[method] 41 | def(arrayMethods, method, function mutator (...args) { 42 | const result = original.apply(this, args) 43 | notify() 44 | return result 45 | }) 46 | }) 47 | ``` 48 | 49 | 50 | 51 | 2. 可以看到vue2中有几个问题: 52 | 53 | - 初始化时需要遍历对象所有key,如果对象层级较深,性能不好 54 | - 通知更新过程需要维护大量dep实例和watcher实例,额外占用内存较多 55 | - 动态新增、删除对象属性无法拦截,只能用特定set/delete api代替 56 | - 不支持新的Map、Set等数据结构 57 | 58 | 59 | 60 | 3. vue3中为了解决以上问题,使用原生的Proxy代替: 61 | 62 | ```js 63 | function defineReactive(obj) { 64 | return new Proxy(obj, { 65 | get(target, key) { 66 | track(target, key) 67 | return Reflect.get(target, key) 68 | }, 69 | set(target, key, val) { 70 | Reflect.set(target, key, val) 71 | trigger(target, key) 72 | }, 73 | deleteProperty(target, key) { 74 | Reflect.deleteProperty(target, key) 75 | trigger(target, key) 76 | } 77 | }) 78 | } 79 | ``` 80 | 81 | 可以同时支持object和array,动态属性增、删都可以拦截,新增数据结构均支持,对象嵌套属性运行时递归,用到才代理,也不需要维护特别多的依赖关系,性能取得很大进步。 82 | -------------------------------------------------------------------------------- /public/14-router/README.md: -------------------------------------------------------------------------------- 1 | ## 如果让你从零开始写一个vue路由,说说你的思路 2 | 3 | ### 思路分析: 4 | 5 | 首先思考vue路由要解决的问题:用户点击跳转链接内容切换,页面不刷新。 6 | 7 | - 借助hash或者history api实现url跳转页面不刷新 8 | - 同时监听hashchange事件或者popstate事件处理跳转 9 | - 根据hash值或者state值从routes表中匹配对应component并渲染之 10 | 11 | 12 | --- 13 | 14 | ### 回答范例: 15 | 16 | 一个SPA应用的路由需要解决的问题是**页面跳转内容改变同时不刷新**,同时路由还需要以插件形式存在,所以: 17 | 18 | 1. 首先我会定义一个`createRouter`函数,返回路由器实例,实例内部做几件事: 19 | - 保存用户传入的配置项 20 | - 监听hash或者popstate事件 21 | - 回调里根据path匹配对应路由 22 | 2. 将router定义成一个Vue插件,即实现install方法,内部做两件事: 23 | - 实现两个全局组件:router-link和router-view,分别实现页面跳转和内容显示 24 | - 定义两个全局变量:\$route和$router,组件内可以访问当前路由和路由器实例 25 | 26 | 27 | --- 28 | 29 | ### 知其所以然: 30 | 31 | - createRouter如何创建实例 32 | 33 | https://github1s.com/vuejs/router/blob/HEAD/src/router.ts#L355-L356 34 | 35 | - 事件监听 36 | 37 | https://github1s.com/vuejs/router/blob/HEAD/src/history/html5.ts#L314-L315 38 | RouterView 39 | 40 | - 页面跳转RouterLink 41 | 42 | https://github1s.com/vuejs/router/blob/HEAD/src/RouterLink.ts#L184-L185 43 | 44 | - 内容显示RouterView 45 | 46 | https://github1s.com/vuejs/router/blob/HEAD/src/RouterView.ts#L43-L44 -------------------------------------------------------------------------------- /public/14-router/test-v3.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ title }}

3 | a | 4 | b 5 | 6 |
7 | 8 | 9 | -------------------------------------------------------------------------------- /public/15-watch-computed/README.md: -------------------------------------------------------------------------------- 1 | ## watch和computed的区别以及选择? 2 | 3 | 两个重要API,反应应聘者熟练程度。 4 | 5 | 6 | 7 | ### 思路分析 8 | 9 | 1. 先看两者定义,列举使用上的差异 10 | 2. 列举使用场景上的差异,如何选择 11 | 3. 使用细节、注意事项 12 | 4. vue3变化 13 | 14 | 15 | 16 | ### 回答范例 17 | 18 | 1. 计算属性可以**从组件数据派生出新数据**,最常见的使用方式是设置一个函数,返回计算之后的结果,computed和methods的差异是它具备缓存性,如果依赖项不变时不会重新计算。侦听器**可以侦测某个响应式数据的变化并执行副作用**,常见用法是传递一个函数,执行副作用,watch没有返回值,但可以执行异步操作等复杂逻辑。 19 | 2. 计算属性常用场景是简化行内模板中的复杂表达式,模板中出现太多逻辑会使模板变得臃肿不易维护。侦听器常用场景是状态变化之后做一些额外的DOM操作或者异步操作。选择采用何用方案时首先看是否需要派生出新值,基本能用计算属性实现的方式首选计算属性。 20 | 3. 使用过程中有一些细节,比如计算属性也是可以传递对象,成为既可读又可写的计算属性。watch可以传递对象,设置deep、immediate等选项。 21 | 4. vue3中watch选项发生了一些变化,例如不再能侦测一个点操作符之外的字符串形式的表达式; reactivity API中新出现了watch、watchEffect可以完全替代目前的watch选项,且功能更加强大。 22 | 23 | 24 | 25 | ### 可能追问 26 | 27 | 1. watch会不会立即执行? 28 | 2. watch 和 watchEffect有什么差异 29 | 30 | 31 | 32 | ### 知其所以然 33 | 34 | computed的实现 35 | 36 | https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/computed.ts#L79-L80 37 | 38 | 39 | 40 | ComputedRefImpl 41 | 42 | https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/computed.ts#L26-L27 43 | 44 | 45 | 46 | 缓存性 47 | 48 | https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/computed.ts#L59-L60 49 | 50 | https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/computed.ts#L45-L46 51 | -------------------------------------------------------------------------------- /public/15-watch-computed/test-v3.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ title }}

3 |

{{ reverseTitle }}

4 |
5 | 6 | -------------------------------------------------------------------------------- /public/16-create-mount/README.md: -------------------------------------------------------------------------------- 1 | ## 说一下 Vue 子组件和父组件创建和挂载顺序 2 | 3 | 这题考查大家对创建过程的理解程度。 4 | 5 | 6 | 7 | ### 思路分析 8 | 9 | 1. 给结论 10 | 2. 阐述理由 11 | 12 | --- 13 | 14 | ### 回答范例 15 | 16 | 1. 创建过程自上而下,挂载过程自下而上;即: 17 | - parent created 18 | - child created 19 | - child mounted 20 | - parent mounted 21 | 2. 之所以会这样是因为Vue创建过程是一个递归过程,先创建父组件,有子组件就会创建子组件,因此创建时先有父组件再有子组件;子组件首次创建时会添加mounted钩子到队列,等到patch结束再执行它们,可见子组件的mounted钩子是先进入到队列中的,因此等到patch结束执行这些钩子时也先执行。 22 | 23 | 24 | --- 25 | 26 | ### 知其所以然 27 | 28 | 观察beforeCreated和created钩子的处理 29 | 30 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/componentOptions.ts#L554-L555 31 | 32 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/componentOptions.ts#L741-L742 33 | 34 | 观察beforeMount和mounted钩子的处理 35 | 36 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1310-L1311 37 | 38 | 39 | 40 | 测试代码,[test-v3.html](test-v3.html) 41 | 42 | -------------------------------------------------------------------------------- /public/16-create-mount/test-v3.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ title }}

3 | 4 |
5 | 6 | -------------------------------------------------------------------------------- /public/17-only-one-root/README.md: -------------------------------------------------------------------------------- 1 | ## ## Vue组件为什么只能有一个根元素? 2 | 3 | 这题现在有些落伍,`vue3`已经不用一个根了。因此这题目很有说头! 4 | 5 | --- 6 | 7 | ### 体验一下 8 | 9 | vue2直接报错,test-v2.html 10 | 11 | ```js 12 | new Vue({ 13 | components: { 14 | comp: { 15 | template: ` 16 |
root1
17 |
root2
18 | ` 19 | } 20 | } 21 | }).$mount('#app') 22 | ``` 23 | 24 | 25 | 26 | --- 27 | 28 | vue3中没有问题,test-v3.html 29 | 30 | ```js 31 | Vue.createApp({ 32 | components: { 33 | comp: { 34 | template: ` 35 |
root1
36 |
root2
37 | ` 38 | } 39 | } 40 | }).mount('#app') 41 | ``` 42 | 43 | 44 | 45 | --- 46 | 47 | ### 回答思路 48 | 49 | - 给一条自己的结论 50 | - 解释为什么会这样 51 | - `vue3`解决方法原理 52 | 53 | --- 54 | 55 | ### 范例 56 | 57 | - `vue2`中组件确实只能有一个根,但`vue3`中组件已经可以多根节点了。 58 | - 之所以需要这样是因为`vdom`是一颗单根树形结构,`patch`方法在遍历的时候从根节点开始遍历,它要求只有一个根节点。组件也会转换为一个`vdom`,自然应该满足这个要求。 59 | - `vue3`中之所以可以写多个根节点,是因为引入了`Fragment`的概念,这是一个抽象的节点,如果发现组件是多根的,就创建一个Fragment节点,把多个根节点作为它的children。将来patch的时候,如果发现是一个Fragment节点,则直接遍历children创建或更新。 60 | 61 | --- 62 | 63 | ### 知其所以然 64 | 65 | - patch方法接收单根vdom: 66 | 67 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L354-L355 68 | 69 | ```ts 70 | // 直接获取type等,没有考虑数组的可能性 71 | const { type, ref, shapeFlag } = n2 72 | ``` 73 | 74 | - patch方法对Fragment的处理: 75 | 76 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1091-L1092 77 | 78 | ```ts 79 | // a fragment can only have array children 80 | // since they are either generated by the compiler, or implicitly created 81 | // from arrays. 82 | mountChildren(n2.children as VNodeArrayChildren, container, ...) 83 | ``` -------------------------------------------------------------------------------- /public/17-only-one-root/test-v2.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 | -------------------------------------------------------------------------------- /public/17-only-one-root/test-v3.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 | -------------------------------------------------------------------------------- /public/18-v3-feature/README.md: -------------------------------------------------------------------------------- 1 | ## 你知道哪些vue3新特性 2 | 3 | ### 分析 4 | 5 | 官网列举的最值得注意的新特性:https://v3-migration.vuejs.org/ 6 | 7 | ![image-20220210165307624](https://tva1.sinaimg.cn/large/e6c9d24ely1h0wjzxntraj21a60f2q4z.jpg) 8 | 9 | --- 10 | 11 | 也就是下面这些: 12 | 13 | - Composition API 14 | - SFC Composition API语法糖 15 | - Teleport传送门 16 | - Fragments片段 17 | - Emits选项 18 | - 自定义渲染器 19 | - SFC CSS变量 20 | - Suspense 21 | 22 | 以上这些是api相关,另外还有很多框架特性也不能落掉。 23 | 24 | --- 25 | 26 | ### 回答范例 27 | 28 | 1. api层面Vue3新特性主要包括:Composition API、SFC Composition API语法糖、Teleport传送门、Fragments 片段、Emits选项、自定义渲染器、SFC CSS变量、Suspense 29 | 30 | 2. 另外,Vue3.0在框架层面也有很多亮眼的改进: 31 | 32 | - 更快 33 | - 虚拟DOM重写 34 | - 编译器优化:静态提升、patchFlags、block等 35 | - 基于Proxy的响应式系统 36 | - 更小:更好的摇树优化 37 | - 更容易维护:TypeScript + 模块化 38 | - 更容易扩展 39 | - 独立的响应化模块 40 | - 自定义渲染器 41 | 42 | --- 43 | 44 | ### 知其所以然 45 | 46 | 体验编译器优化 47 | 48 | https://sfc.vuejs.org/ 49 | 50 | 51 | 52 | reactive实现 53 | 54 | https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/reactive.ts#L90-L91 -------------------------------------------------------------------------------- /public/19-lifecycle/README.md: -------------------------------------------------------------------------------- 1 | ## 简述 Vue 的生命周期以及每个阶段做的事 2 | 3 | 必问题目,考查vue基础知识。 4 | 5 | 6 | 7 | ### 思路 8 | 9 | 1. 给出概念 10 | 2. 列举生命周期各阶段 11 | 3. 阐述整体流程 12 | 4. 结合实践 13 | 5. 扩展:vue3变化 14 | 15 | 16 | 17 | ### 回答范例 18 | 19 | 1.每个Vue组件实例被创建后都会经过一系列初始化步骤,比如,它需要数据观测,模板编译,挂载实例到dom上,以及数据变化时更新dom。这个过程中会运行叫做生命周期钩子的函数,以便用户在特定阶段有机会添加他们自己的代码。 20 | 21 | 22 | 23 | 2.Vue生命周期总共可以分为8个阶段:**创建前后, 载入前后, 更新前后, 销毁前后**,以及一些特殊场景的生命周期。vue3中新增了三个用于调试和服务端渲染场景。 24 | 25 | | 生命周期v2 | 生命周期v3 | 描述 | 26 | | ------------- | ------------------- | ---------------------------------------- | 27 | | beforeCreate | beforeCreate | 组件实例被创建之初 | 28 | | created | created | 组件实例已经完全创建 | 29 | | beforeMount | beforeMount | 组件挂载之前 | 30 | | mounted | mounted | 组件挂载到实例上去之后 | 31 | | beforeUpdate | beforeUpdate | 组件数据发生变化,更新之前 | 32 | | updated | updated | 数据数据更新之后 | 33 | | beforeDestroy | **beforeUnmount** | 组件实例销毁之前 | 34 | | destroyed | **unmounted** | 组件实例销毁之后 | 35 | | activated | activated | keep-alive 缓存的组件激活时 | 36 | | deactivated | deactivated | keep-alive 缓存的组件停用时调用 | 37 | | errorCaptured | errorCaptured | 捕获一个来自子孙组件的错误时被调用 | 38 | | - | **renderTracked** | 调试钩子,响应式依赖被收集时调用 | 39 | | - | **renderTriggered** | 调试钩子,响应式依赖被触发时调用 | 40 | | - | **serverPrefetch** | ssr only,组件实例在服务器上被渲染前调用 | 41 | 42 | 43 | 44 | 3.`Vue`生命周期流程图: 45 | 46 | ![Component lifecycle diagram](https://gitee.com/57code/picgo/raw/master/lifecycle.cec11dcc.png) 47 | 48 | 4.结合实践: 49 | 50 | **beforeCreate**:通常用于插件开发中执行一些初始化任务 51 | 52 | **created**:组件初始化完毕,可以访问各种数据,获取接口数据等 53 | 54 | **mounted**:dom已创建,可用于获取访问数据和dom元素;访问子组件等。 55 | 56 | **beforeUpdate**:此时`view`层还未更新,可用于获取更新前各种状态 57 | 58 | **updated**:完成`view`层的更新,更新后,所有状态已是最新 59 | 60 | **beforeUnmount**:实例被销毁前调用,可用于一些定时器或订阅的取消 61 | 62 | **unmounted**:销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器 63 | 64 | 65 | 66 | #### 可能的追问 67 | 68 | 1. setup和created谁先执行? 69 | 2. setup中为什么没有beforeCreate和created? 70 | 71 | 72 | 73 | #### 知其所以然 74 | 75 | vue3中生命周期的派发时刻: 76 | 77 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/componentOptions.ts#L554-L555 78 | 79 | vue2中声明周期的派发时刻: 80 | 81 | https://github1s.com/vuejs/vue/blob/HEAD/src/core/instance/init.js#L55-L56 82 | -------------------------------------------------------------------------------- /public/19-lifecycle/test-lc-change-v3.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ title }}

3 | 4 | 5 | 6 |
7 | 8 | -------------------------------------------------------------------------------- /public/19-lifecycle/test-lc-order-v3.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ title }}

3 |
4 | 5 | -------------------------------------------------------------------------------- /public/20-vdom/README.md: -------------------------------------------------------------------------------- 1 | ## 说说你对虚拟 DOM 的理解? 2 | 3 | ### 分析 4 | 5 | 现有框架几乎都引入了虚拟 DOM 来对真实 DOM 进行抽象,也就是现在大家所熟知的 VNode 和 VDOM,那么为什么需要引入虚拟 DOM 呢?围绕这个疑问来解答即可! 6 | 7 | 8 | 9 | ### 思路 10 | 11 | 1. vdom是什么 12 | 2. 引入vdom的好处 13 | 3. vdom如何生成,又如何成为dom 14 | 4. 在后续的diff中的作用 15 | 16 | 17 | 18 | ### 回答范例 19 | 20 | 1. 虚拟dom顾名思义就是虚拟的dom对象,它本身就是一个 `JavaScript` 对象,只不过它是通过不同的属性去描述一个视图结构。 21 | 22 | 2. 通过引入vdom我们可以获得如下好处: 23 | 24 | **将真实元素节点抽象成 VNode,有效减少直接操作 dom 次数,从而提高程序性能** 25 | 26 | - 直接操作 dom 是有限制的,比如:diff、clone 等操作,一个真实元素上有许多的内容,如果直接对其进行 diff 操作,会去额外 diff 一些没有必要的内容;同样的,如果需要进行 clone 那么需要将其全部内容进行复制,这也是没必要的。但是,如果将这些操作转移到 JavaScript 对象上,那么就会变得简单了。 27 | - 操作 dom 是比较昂贵的操作,频繁的dom操作容易引起页面的重绘和回流,但是通过抽象 VNode 进行中间处理,可以有效减少直接操作dom的次数,从而减少页面重绘和回流。 28 | 29 | **方便实现跨平台** 30 | 31 | - 同一 VNode 节点可以渲染成不同平台上的对应的内容,比如:渲染在浏览器是 dom 元素节点,渲染在 Native( iOS、Android) 变为对应的控件、可以实现 SSR 、渲染到 WebGL 中等等 32 | - Vue3 中允许开发者基于 VNode 实现自定义渲染器(renderer),以便于针对不同平台进行渲染。 33 | 34 | 3. vdom如何生成?在vue中我们常常会为组件编写模板 - template, 这个模板会被编译器 - compiler编译为渲染函数,在接下来的挂载(mount)过程中会调用render函数,返回的对象就是虚拟dom。但它们还不是真正的dom,所以会在后续的patch过程中进一步转化为dom。 35 | 36 | 37 | 38 | ![image-20220209153820845](https://gitee.com/57code/picgo/raw/master/image-20220209153820845.png) 39 | 40 | 4. 挂载过程结束后,vue程序进入更新流程。如果某些响应式数据发生变化,将会引起组件重新render,此时就会生成新的vdom,和上一次的渲染结果diff就能得到变化的地方,从而转换为最小量的dom操作,高效更新视图。 41 | 42 | 43 | 44 | ### 知其所以然 45 | 46 | vnode定义: 47 | 48 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/vnode.ts#L127-L128 49 | 50 | 51 | 52 | 观察渲染函数: 53 | 54 | file:///Users/yangtao/projects/vue-interview/public/21-vdom/test-render-v3.html 55 | 56 | 57 | 58 | 创建vnode: 59 | 60 | createElementBlock: 61 | 62 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/vnode.ts#L291-L292 63 | 64 | createVnode: 65 | 66 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/vnode.ts#L486-L487 67 | 68 | 69 | 70 | mount: 71 | 72 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1171-L1172 73 | 74 | 75 | 76 | 调试mount过程:mountComponent 77 | 78 | file:///Users/yangtao/projects/vue-interview/public/21-vdom/test-render-v3.html 79 | 80 | -------------------------------------------------------------------------------- /public/20-vdom/test-render-v3.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ title }}

3 |
4 | 5 | -------------------------------------------------------------------------------- /public/21-cache-comp/README.md: -------------------------------------------------------------------------------- 1 | ## 怎么缓存当前的组件?缓存后怎么更新? 2 | 3 | 缓存组件使用keep-alive组件,这是一个非常常见且有用的优化手段,vue3中keep-alive有比较大的更新,能说的点比较多。 4 | 5 | 6 | ### 思路 7 | 8 | 1. 缓存用keep-alive,它的作用与用法 9 | 2. 使用细节,例如缓存指定/排除、结合router和transition 10 | 3. 组件缓存后更新可以利用activated或者beforeRouteEnter 11 | 4. 原理阐述 12 | 13 | --- 14 | 15 | ### 回答范例 16 | 17 | 1. 开发中缓存组件使用keep-alive组件,keep-alive是vue内置组件,keep-alive包裹动态组件component时,会缓存不活动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,防止重复渲染DOM。 18 | 19 | ```vue 20 | 21 | 22 | 23 | ``` 24 | 25 | 2. 结合属性include和exclude可以明确指定缓存哪些组件或排除缓存指定组件。vue3中结合vue-router时变化较大,之前是`keep-alive`包裹`router-view`,现在需要反过来用`router-view`包裹`keep-alive`: 26 | 27 | ```vue 28 | 29 | 30 | 31 | 32 | 33 | ``` 34 | 35 | --- 36 | 37 | 3. 缓存后如果要获取数据,解决方案可以有以下两种: 38 | 39 | - beforeRouteEnter:在有vue-router的项目,每次进入路由的时候,都会执行`beforeRouteEnter` 40 | 41 | ```js 42 | beforeRouteEnter(to, from, next){ 43 | next(vm=>{ 44 | console.log(vm) 45 | // 每次进入路由执行 46 | vm.getData() // 获取数据 47 | }) 48 | }, 49 | ``` 50 | 51 | - actived:在`keep-alive`缓存的组件被激活的时候,都会执行`actived`钩子 52 | 53 | ```js 54 | activated(){ 55 | this.getData() // 获取数据 56 | }, 57 | ``` 58 | 59 | --- 60 | 61 | 4. keep-alive是一个通用组件,它内部定义了一个map,缓存创建过的组件实例,它返回的渲染函数内部会查找内嵌的component组件对应组件的vnode,如果该组件在map中存在就直接返回它。由于component的is属性是个响应式数据,因此只要它变化,keep-alive的render函数就会重新执行。 62 | 63 | 64 | 65 | --- 66 | 67 | 68 | ### 知其所以然 69 | 70 | KeepAlive定义 71 | 72 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/components/KeepAlive.ts#L73-L74 73 | 74 | 缓存定义 75 | 76 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/components/KeepAlive.ts#L102-L103 77 | 78 | 缓存组件 79 | 80 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/components/KeepAlive.ts#L215-L216 81 | 82 | 获取缓存组件 83 | 84 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/components/KeepAlive.ts#L241-L242 85 | 86 | 87 | 88 | 测试缓存特性,test-v3.html -------------------------------------------------------------------------------- /public/21-cache-comp/test-v3.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ title }}

3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | -------------------------------------------------------------------------------- /public/22-template-render/README.md: -------------------------------------------------------------------------------- 1 | ## 说说从 template 到 render 处理过程 2 | 3 | ### 分析 4 | 5 | 问我们template到render过程,其实是问vue`编译器`工作原理。 6 | 7 | 8 | 9 | ### 思路 10 | 11 | 1. 引入vue编译器概念 12 | 2. 说明编译器的必要性 13 | 3. 阐述编译器工作流程 14 | 15 | --- 16 | 17 | ### 回答范例 18 | 19 | 1. Vue中有个独特的编译器模块,称为“compiler”,它的主要作用是将用户编写的template编译为js中可执行的render函数。 20 | 2. 之所以需要这个编译过程是为了便于前端程序员能高效的编写视图模板。相比而言,我们还是更愿意用HTML来编写视图,直观且高效。手写render函数不仅效率底下,而且失去了编译期的优化能力。 21 | 3. 在Vue中编译器会先对template进行解析,这一步称为parse,结束之后会得到一个JS对象,我们成为抽象语法树AST,然后是对AST进行深加工的转换过程,这一步成为transform,最后将前面得到的AST生成为JS代码,也就是render函数。 22 | 23 | --- 24 | 25 | ### 知其所以然 26 | 27 | vue3编译过程窥探: 28 | 29 | https://github1s.com/vuejs/core/blob/HEAD/packages/compiler-core/src/compile.ts#L61-L62 30 | 31 | 32 | 33 | 测试,test-v3.html 34 | 35 | 36 | 37 | ### 可能的追问 38 | 39 | 1. Vue中编译器何时执行? 40 | 2. react有没有编译器? 41 | 42 | -------------------------------------------------------------------------------- /public/22-template-render/test-v3.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ title }}

3 |

观察渲染函数

4 |
{{renderFunc}}
6 |
7 | 8 | -------------------------------------------------------------------------------- /public/24-v3-design/README.md: -------------------------------------------------------------------------------- 1 | ## Vue 3.0的设计目标是什么?做了哪些优化? 2 | 3 | ### 分析 4 | 5 | 还是问新特性,陈述典型新特性,分析其给你带来的变化即可。 6 | 7 | ### 思路 8 | 9 | 从以下几方面分门别类阐述:易用性、性能、扩展性、可维护性、开发体验等 10 | 11 | --- 12 | 13 | ### 范例 14 | 15 | 1. Vue3的最大设计目标是替代Vue2(皮一下),为了实现这一点,Vue3在以下几个方面做了很大改进,如:易用性、框架性能、扩展性、可维护性、开发体验等 16 | 2. 易用性方面主要是API简化,比如`v-model`在Vue3中变成了Vue2中`v-model`和`sync`修饰符的结合体,用户不用区分两者不同,也不用选择困难。类似的简化还有用于渲染函数内部生成VNode的`h(type, props, children)`,其中`props`不用考虑区分属性、特性、事件等,框架替我们判断,易用性大增。 17 | 3. 开发体验方面,新组件`Teleport`传送门、`Fragments` 、`Suspense`等都会简化特定场景的代码编写,`SFC Composition API`语法糖更是极大提升我们开发体验。 18 | 4. 扩展性方面提升如独立的`reactivity`模块,`custom renderer` API等 19 | 5. 可维护性方面主要是`Composition API`,更容易编写高复用性的业务逻辑。还有对TypeScript支持的提升。 20 | 6. 性能方面的改进也很显著,例如编译期优化、基于`Proxy`的响应式系统 21 | 7. 。。。 22 | 23 | ### 可能的追问 24 | 25 | 1. Vue3做了哪些编译优化? 26 | 2. `Proxy`和`defineProperty`有什么不同? -------------------------------------------------------------------------------- /public/25-performance/README.md: -------------------------------------------------------------------------------- 1 | ## 25-你了解哪些Vue性能优化方法? 2 | 3 | ### 分析 4 | 5 | 这是一道综合实践题目,写过一定数量的代码之后小伙伴们自然会开始关注一些优化方法,答得越多肯定实践经验也越丰富,是很好的题目。 6 | 7 | ### 答题思路: 8 | 9 | 根据题目描述,这里主要探讨Vue代码层面的优化 10 | 11 | --- 12 | 13 | ### 回答范例 14 | 15 | - 我这里主要从Vue代码编写层面说一些优化手段,例如:代码分割、服务端渲染、组件缓存、长列表优化等 16 | 17 | - 最常见的路由懒加载:有效拆分App尺寸,访问时才异步加载 18 | 19 | ```js 20 | const router = createRouter({ 21 | routes: [ 22 | // 借助webpack的import()实现异步组件 23 | { path: '/foo', component: () => import('./Foo.vue') } 24 | ] 25 | }) 26 | ``` 27 | 28 | 29 | --- 30 | 31 | - `keep-alive`缓存页面:避免重复创建组件实例,且能保留缓存组件状态 32 | 33 | ```vue 34 | 35 | 36 | 37 | 38 | 39 | ``` 40 | 41 | 42 | --- 43 | 44 | - 使用`v-show`复用DOM:避免重复创建组件 45 | 46 | ```vue 47 | 58 | ``` 59 | 60 | 61 | --- 62 | 63 | - `v-for` 遍历避免同时使用 `v-if`:实际上在Vue3中已经是个错误写法 64 | 65 | ```vue 66 | 77 | 86 | ``` 87 | 88 | 89 | --- 90 | 91 | - v-once和v-memo:不再变化的数据使用`v-once` 92 | 93 | ```vue 94 | 95 | This will never change: {{msg}} 96 | 97 |
98 |

comment

99 |

{{msg}}

100 |
101 | 102 | 103 | 104 | 107 | ``` 108 | 109 | 按条件跳过更新时使用`v-momo`:下面这个列表只会更新选中状态变化项 110 | 111 | ```vue 112 |
113 |

ID: {{ item.id }} - selected: {{ item.id === selected }}

114 |

...more child nodes

115 |
116 | ``` 117 | 118 | > https://vuejs.org/api/built-in-directives.html#v-memo 119 | 120 | 121 | --- 122 | 123 | - 长列表性能优化:如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容 124 | 125 | ```html 126 | 131 | 137 | 138 | ``` 139 | 140 | > 一些开源库: 141 | > 142 | > - [vue-virtual-scroller](https://github.com/Akryum/vue-virtual-scroller) 143 | > - [vue-virtual-scroll-grid](https://github.com/rocwang/vue-virtual-scroll-grid) 144 | 145 | 146 | --- 147 | 148 | - 事件的销毁:Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。 149 | 150 | ```js 151 | export default { 152 | created() { 153 | this.timer = setInterval(this.refresh, 2000) 154 | }, 155 | beforeUnmount() { 156 | clearInterval(this.timer) 157 | } 158 | } 159 | ``` 160 | 161 | 162 | --- 163 | 164 | - 图片懒加载 165 | 166 | 对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。 167 | 168 | ```html 169 | 170 | ``` 171 | 172 | > 参考项目:[vue-lazyload](https://github.com/hilongjw/vue-lazyload) 173 | 174 | 175 | --- 176 | 177 | - 第三方插件按需引入 178 | 179 | 像`element-plus`这样的第三方组件库可以按需引入避免体积太大。 180 | 181 | ```js 182 | import { createApp } from 'vue'; 183 | import { Button, Select } from 'element-plus'; 184 | 185 | const app = createApp() 186 | app.use(Button) 187 | app.use(Select) 188 | ``` 189 | 190 | 191 | --- 192 | 193 | - 子组件分割策略:较重的状态组件适合拆分 194 | 195 | ```vue 196 | 201 | 202 | 216 | ``` 217 | 218 | 但同时也不宜过度拆分组件,尤其是为了所谓组件抽象将一些不需要渲染的组件特意抽出来,组件实例消耗远大于纯dom节点。参考:https://vuejs.org/guide/best-practices/performance.html#avoid-unnecessary-component-abstractions 219 | 220 | 221 | --- 222 | 223 | - 服务端渲染/静态网站生成:SSR/SSG 224 | 225 | 如果SPA应用有首屏渲染慢的问题,可以考虑SSR、SSG方案优化。参考[SSR Guide](https://vuejs.org/guide/scaling-up/ssr.html) -------------------------------------------------------------------------------- /public/26-one-root/README.md: -------------------------------------------------------------------------------- 1 | ## 26-Vue组件为什么只能有一个根元素? 2 | 3 | 这题现在有些落伍,`vue3`已经不用一个根了。因此这题目很有说头! 4 | 5 | --- 6 | 7 | ### 体验一下 8 | 9 | vue2直接报错,test-v2.html 10 | 11 | ```js 12 | new Vue({ 13 | components: { 14 | comp: { 15 | template: ` 16 |
root1
17 |
root2
18 | ` 19 | } 20 | } 21 | }).$mount('#app') 22 | ``` 23 | 24 | 25 | 26 | --- 27 | 28 | vue3中没有问题,test-v3.html 29 | 30 | ```js 31 | Vue.createApp({ 32 | components: { 33 | comp: { 34 | template: ` 35 |
root1
36 |
root2
37 | ` 38 | } 39 | } 40 | }).mount('#app') 41 | ``` 42 | 43 | 44 | 45 | --- 46 | 47 | ### 回答思路 48 | 49 | - 给一条自己的结论 50 | - 解释为什么会这样 51 | - `vue3`解决方法原理 52 | 53 | --- 54 | 55 | ### 范例 56 | 57 | - `vue2`中组件确实只能有一个根,但`vue3`中组件已经可以多根节点了。 58 | - 之所以需要这样是因为`vdom`是一颗单根树形结构,`patch`方法在遍历的时候从根节点开始遍历,它要求只有一个根节点。组件也会转换为一个`vdom`,自然应该满足这个要求。 59 | - `vue3`中之所以可以写多个根节点,是因为引入了`Fragment`的概念,这是一个抽象的节点,如果发现组件是多根的,就创建一个Fragment节点,把多个根节点作为它的children。将来patch的时候,如果发现是一个Fragment节点,则直接遍历children创建或更新。 60 | 61 | --- 62 | 63 | ### 知其所以然 64 | 65 | - patch方法接收单根vdom: 66 | 67 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L354-L355 68 | 69 | ```ts 70 | // 直接获取type等,没有考虑数组的可能性 71 | const { type, ref, shapeFlag } = n2 72 | ``` 73 | 74 | - patch方法对Fragment的处理: 75 | 76 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L1091-L1092 77 | 78 | ```ts 79 | // a fragment can only have array children 80 | // since they are either generated by the compiler, or implicitly created 81 | // from arrays. 82 | mountChildren(n2.children as VNodeArrayChildren, container, ...) 83 | ``` 84 | 85 | -------------------------------------------------------------------------------- /public/27-vuex-module/README.md: -------------------------------------------------------------------------------- 1 | ## 27-你有使用过vuex的module吗? 2 | 3 | 这是基本应用能力考察,稍微上点规模的项目都要拆分vuex模块便于维护。 4 | 5 | --- 6 | 7 | ### 体验 8 | 9 | https://vuex.vuejs.org/zh/guide/modules.html 10 | 11 | ```js 12 | const moduleA = { 13 | state: () => ({ ... }), 14 | mutations: { ... }, 15 | actions: { ... }, 16 | getters: { ... } 17 | } 18 | const moduleB = { 19 | state: () => ({ ... }), 20 | mutations: { ... }, 21 | actions: { ... } 22 | } 23 | const store = createStore({ 24 | modules: { 25 | a: moduleA, 26 | b: moduleB 27 | } 28 | }) 29 | store.state.a // -> moduleA 的状态 30 | store.state.b // -> moduleB 的状态 31 | store.getters.c // -> moduleA里的getters 32 | store.commit('d') // -> 能同时触发子模块中同名mutation 33 | store.dispatch('e') // -> 能同时触发子模块中同名action 34 | ``` 35 | 36 | --- 37 | 38 | ### 思路 39 | 40 | 1. 概念和必要性 41 | 2. 怎么拆 42 | 3. 使用细节 43 | 4. 优缺点 44 | 45 | --- 46 | 47 | ### 范例 48 | 49 | 1. 用过module,项目规模变大之后,单独一个store对象会过于庞大臃肿,通过模块方式可以拆分开来便于维护 50 | 2. 可以按之前规则单独编写子模块代码,然后在主文件中通过`modules`选项组织起来:`createStore({modules:{...}})` 51 | 3. 不过使用时要注意访问子模块状态时需要加上注册时模块名:`store.state.a.xxx`,但同时`getters`、`mutations`和`actions`又在全局空间中,使用方式和之前一样。如果要做到完全拆分,需要在子模块加上`namespace`选项,此时再访问它们就要加上命名空间前缀。 52 | 4. 很显然,模块的方式可以拆分代码,但是缺点也很明显,就是使用起来比较繁琐复杂,容易出错。而且类型系统支持很差,不能给我们带来帮助。pinia显然在这方面有了很大改进,是时候切换过去了。 53 | 54 | --- 55 | 56 | ### 可能的追问 57 | 58 | 1. 用过pinia吗?都做了哪些改善? 59 | -------------------------------------------------------------------------------- /public/28-route-lazy-load/README.md: -------------------------------------------------------------------------------- 1 | ## 28-怎么实现路由懒加载呢? 2 | 3 | ### 分析 4 | 5 | 这是一道应用题。当打包应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问时才加载对应组件,这样就会更加高效。 6 | 7 | ```js 8 | // 将 9 | // import UserDetails from './views/UserDetails' 10 | // 替换为 11 | const UserDetails = () => import('./views/UserDetails') 12 | 13 | const router = createRouter({ 14 | // ... 15 | routes: [{ path: '/users/:id', component: UserDetails }], 16 | }) 17 | ``` 18 | 19 | 参考https://router.vuejs.org/zh/guide/advanced/lazy-loading.html 20 | 21 | --- 22 | 23 | ### 思路 24 | 25 | 1. 必要性 26 | 2. 何时用 27 | 3. 怎么用 28 | 4. 使用细节 29 | 30 | 31 | --- 32 | 33 | ### 回答范例 34 | 35 | 1. 当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。利用路由懒加载我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样会更加高效,是一种优化手段。 36 | 37 | 2. 一般来说,对所有的路由**都使用动态导入**是个好主意。 38 | 39 | 3. 给`component`选项配置一个返回 Promise 组件的函数就可以定义懒加载路由。例如: 40 | 41 | `{ path: '/users/:id', component: () => import('./views/UserDetails') }` 42 | 43 | 4. 结合注释`() => 44 | import(/* webpackChunkName: "group-user" */ './UserDetails.vue')`可以做webpack代码分块 45 | 46 | vite中结合[rollupOptions](https://router.vuejs.org/zh/guide/advanced/lazy-loading.html#%E4%BD%BF%E7%94%A8-vite)定义分块 47 | 48 | 5. 路由中不能使用异步组件 49 | 50 | --- 51 | 52 | ### 知其所以然 53 | 54 | `component` (和 `components`) 配置如果接收一个返回 Promise 组件的函数,Vue Router **只会在第一次进入页面时才会获取这个函数**,然后使用缓存数据。 55 | 56 | https://github1s.com/vuejs/router/blob/HEAD/src/navigationGuards.ts#L292-L293 57 | -------------------------------------------------------------------------------- /public/29-ref-reactive/README.md: -------------------------------------------------------------------------------- 1 | ## 29-ref和reactive异同 2 | 3 | 这是`Vue3`数据响应式中非常重要的两个概念,自然的,跟我们写代码关系也很大。 4 | 5 | --- 6 | 7 | ### 体验 8 | 9 | ref:https://vuejs.org/api/reactivity-core.html#ref 10 | 11 | ```js 12 | const count = ref(0) 13 | console.log(count.value) // 0 14 | 15 | count.value++ 16 | console.log(count.value) // 1 17 | ``` 18 | 19 | 20 | --- 21 | 22 | reactive:https://vuejs.org/api/reactivity-core.html#reactive 23 | 24 | ```js 25 | const obj = reactive({ count: 0 }) 26 | obj.count++ 27 | ``` 28 | 29 | --- 30 | 31 | ### 回答思路 32 | 33 | 1. 两者概念 34 | 2. 两者使用场景 35 | 3. 两者异同 36 | 4. 使用细节 37 | 5. 原理 38 | 39 | --- 40 | 41 | ### 回答范例 42 | 43 | 1. `ref`接收内部值(inner value)返回响应式`Ref`对象,`reactive`返回响应式代理对象 44 | 2. 从定义上看`ref`通常用于处理单值的响应式,`reactive`用于处理对象类型的数据响应式 45 | 3. 两者均是用于构造响应式数据,但是`ref`主要解决原始值的响应式问题 46 | 4. ref返回的响应式数据在JS中使用需要加上`.value`才能访问其值,在视图中使用会自动脱ref,不需要`.value`;ref可以接收对象或数组等非原始值,但内部依然是`reactive`实现响应式;reactive内部如果接收Ref对象会自动脱ref;使用展开运算符(...)展开reactive返回的响应式对象会使其失去响应性,可以结合toRefs()将值转换为Ref对象之后再展开。 47 | 5. reactive内部使用Proxy代理传入对象并拦截该对象各种操作(trap),从而实现响应式。ref内部封装一个RefImpl类,并设置get value/set value,拦截用户对值的访问,从而实现响应式。 48 | 49 | --- 50 | 51 | ### 知其所以然 52 | 53 | reactive实现响应式: 54 | 55 | https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/reactive.ts#L90-L91 56 | 57 | ref实现响应式: 58 | 59 | https://github1s.com/vuejs/core/blob/HEAD/packages/reactivity/src/ref.ts#L73-L74 -------------------------------------------------------------------------------- /public/30-watch-watchEffect/README.md: -------------------------------------------------------------------------------- 1 | ## 30-watch和watchEffect异同 2 | 3 | 我们经常性需要侦测响应式数据的变化,vue3中除了watch之外又出现了watchEffect,不少同学会混淆这两个api。 4 | 5 | --- 6 | 7 | ### 体验 8 | 9 | `watchEffect`立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数。 10 | 11 | > Runs a function immediately while reactively tracking its dependencies and re-runs it whenever the dependencies are changed. 12 | 13 | ```js 14 | const count = ref(0) 15 | 16 | watchEffect(() => console.log(count.value)) 17 | // -> logs 0 18 | 19 | count.value++ 20 | // -> logs 1 21 | ``` 22 | 23 | --- 24 | 25 | `watch`侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数。 26 | 27 | > Watches one or more reactive data sources and invokes a callback function when the sources change. 28 | 29 | ```js 30 | const state = reactive({ count: 0 }) 31 | watch( 32 | () => state.count, 33 | (count, prevCount) => { 34 | /* ... */ 35 | } 36 | ) 37 | ``` 38 | 39 | --- 40 | 41 | ### 思路 42 | 43 | 1. 给出两者定义 44 | 2. 给出场景上的不同 45 | 3. 给出使用方式和细节 46 | 4. 原理阐述 47 | 48 | --- 49 | 50 | ### 范例 51 | 52 | 1. `watchEffect`立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数。`watch`侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数。 53 | 54 | 2. `watchEffect(effect)`是一种特殊`watch`,传入的函数既是依赖收集的数据源,也是回调函数。如果我们不关心响应式数据变化前后的值,只是想拿这些数据做些事情,那么`watchEffect`就是我们需要的。watch更底层,可以接收多种数据源,包括用于依赖收集的getter函数,因此它完全可以实现watchEffect的功能,同时由于可以指定getter函数,依赖可以控制的更精确,还能获取数据变化前后的值,因此如果需要这些时我们会使用watch。 55 | 3. `watchEffect`在使用时,传入的函数会立刻执行一次。`watch`默认情况下并不会执行回调函数,除非我们手动设置`immediate`选项。 56 | 4. 从实现上来说,`watchEffect(fn)`相当于`watch(fn,fn,{immediate:true})` 57 | 58 | --- 59 | 60 | ### 知其所以然 61 | 62 | `watchEffect`定义:https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiWatch.ts#L80-L81 63 | 64 | ```js 65 | export function watchEffect( 66 | effect: WatchEffect, 67 | options?: WatchOptionsBase 68 | ): WatchStopHandle { 69 | return doWatch(effect, null, options) 70 | } 71 | ``` 72 | 73 | 74 | --- 75 | 76 | `watch`定义如下:https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiWatch.ts#L158-L159 77 | 78 | ```js 79 | export function watch = false>( 80 | source: T | WatchSource, 81 | cb: any, 82 | options?: WatchOptions 83 | ): WatchStopHandle { 84 | return doWatch(source as any, cb, options) 85 | } 86 | ``` 87 | 88 | 很明显`watchEffect`就是一种特殊的`watch`实现。 -------------------------------------------------------------------------------- /public/31-SPA-SSR/README.md: -------------------------------------------------------------------------------- 1 | ## 31-SPA、SSR的区别是什么 2 | 3 | 我们现在编写的Vue、React和Angular应用大多数情况下都会在一个页面中,点击链接跳转页面通常是内容切换而非页面跳转,由于良好的用户体验逐渐成为主流的开发模式。但同时也会有首屏加载时间长,SEO不友好的问题,因此有了SSR,这也是为什么面试中会问到两者的区别。 4 | 5 | ### 思路分析 6 | 7 | 1. 两者概念 8 | 2. 两者优缺点分析 9 | 3. 使用场景差异 10 | 4. 其他选择 11 | --- 12 | 13 | ### 回答范例 14 | 15 | 1. SPA(Single Page Application)即**单页面应用**。一般也称为 **客户端渲染**(Client Side Render), 简称 CSR。SSR(Server Side Render)即 **服务端渲染**。一般也称为 **多页面应用**(Mulpile Page Application),简称 MPA。 16 | 2. SPA应用只会首次请求html文件,后续只需要请求JSON数据即可,因此用户体验更好,节约流量,服务端压力也较小。但是首屏加载的时间会变长,而且SEO不友好。为了解决以上缺点,就有了SSR方案,由于HTML内容在服务器一次性生成出来,首屏加载快,搜索引擎也可以很方便的抓取页面信息。但同时SSR方案也会有性能,开发受限等问题。 17 | 3. 在选择上,如果我们的应用存在首屏加载优化需求,SEO需求时,就可以考虑SSR。 18 | 4. 但并不是只有这一种替代方案,比如对一些不常变化的静态网站,SSR反而浪费资源,我们可以考虑[预渲染](https://github.com/chrisvfritz/prerender-spa-plugin)(prerender)方案。另外nuxt.js/next.js中给我们提供了SSG(Static Site Generate)静态网站生成方案也是很好的静态站点解决方案,结合一些CI手段,可以起到很好的优化效果,且能节约服务器资源。 19 | --- 20 | 21 | ### 知其所以然 22 | 23 | 内容生成上的区别: 24 | 25 | SSR 26 | 27 | ss 28 | 29 | 30 | --- 31 | 32 | SPA 33 | 34 | sp 35 | 36 | --- 37 | 38 | 部署上的区别 39 | 40 | 部署上区别 -------------------------------------------------------------------------------- /public/32-directive/README.md: -------------------------------------------------------------------------------- 1 | ## 32-你写过自定义指令吗?使用场景有哪些? 2 | 3 | ### 分析 4 | 5 | 这是一道API题,我们可能写的自定义指令少,但是我们用的多呀,多举几个例子就行。 6 | 7 | --- 8 | 9 | ### 体验 10 | 11 | 定义一个包含类似组件生命周期钩子的对象,钩子函数会接收指令挂钩的dom元素: 12 | 13 | ```js 14 | const focus = { 15 | mounted: (el) => el.focus() 16 | } 17 | 18 | export default { 19 | directives: { 20 | // enables v-focus in template 21 | focus 22 | } 23 | } 24 | 25 | ``` 26 | 27 | ```html 28 | 29 | ``` 30 | 31 | --- 32 | 33 | ### 思路 34 | 35 | 1. 定义 36 | 2. 何时用 37 | 3. 如何用 38 | 4. 常用指令 39 | 5. vue3变化 40 | 41 | --- 42 | 43 | ### 回答范例 44 | 45 | 1. Vue有一组默认指令,比如`v-mode`l或`v-for`,同时Vue也允许用户注册自定义指令来扩展Vue能力 46 | 2. 自定义指令主要完成一些可复用低层级DOM操作 47 | 3. 使用自定义指令分为定义、注册和使用三步: 48 | - 定义自定义指令有两种方式:对象和函数形式,前者类似组件定义,有各种生命周期;后者只会在mounted和updated时执行 49 | - 注册自定义指令类似组件,可以使用app.directive()全局注册,使用{directives:{xxx}}局部注册 50 | - 使用时在注册名称前加上v-即可,比如v-focus 51 | 4. 我在项目中常用到一些自定义指令,例如: 52 | - 复制粘贴 v-copy 53 | - 长按 v-longpress 54 | - 防抖 v-debounce 55 | - 图片懒加载 v-lazy 56 | - 按钮权限 v-premission 57 | - 页面水印 v-waterMarker 58 | - 拖拽指令 v-draggable 59 | 5. vue3中指令定义发生了比较大的变化,主要是钩子的名称保持和组件一致,这样开发人员容易记忆,不易犯错。另外在v3.2之后,可以在setup中以一个小写v开头方便的定义自定义指令,更简单了! 60 | 61 | --- 62 | 63 | ### 知其所以然 64 | 65 | 编译后的自定义指令会被withDirective函数装饰,进一步处理生成的vnode,添加到特定属性中。 66 | 67 | https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiB9IGZyb20gJ3Z1ZSdcblxuY29uc3QgbXNnID0gcmVmKCdIZWxsbyBXb3JsZCEnKVxuXG5jb25zdCB2Rm9jdXMgPSB7XG4gIG1vdW50ZWQoZWwpIHtcbiAgICAvLyDojrflj5ZpbnB1dO+8jOW5tuiwg+eUqOWFtmZvY3VzKCnmlrnms5VcbiAgICBlbC5mb2N1cygpXG4gIH1cbn1cbjwvc2NyaXB0PlxuXG48dGVtcGxhdGU+XG4gIDxoMT57eyBtc2cgfX08L2gxPlxuICA8aW5wdXQgdi1tb2RlbD1cIm1zZ1wiIHYtZm9jdXM+XG48L3RlbXBsYXRlPiIsImltcG9ydC1tYXAuanNvbiI6IntcbiAgXCJpbXBvcnRzXCI6IHtcbiAgICBcInZ1ZVwiOiBcImh0dHBzOi8vc2ZjLnZ1ZWpzLm9yZy92dWUucnVudGltZS5lc20tYnJvd3Nlci5qc1wiXG4gIH1cbn0ifQ== -------------------------------------------------------------------------------- /public/33-$attrs/README.md: -------------------------------------------------------------------------------- 1 | ## 33-说下\$attrs和$listeners的使用场景 2 | 3 | ### 分析 4 | 5 | API考察,但\$attrs和$listeners是比较少用的边界知识,而且vue3有变化,$listeners已经移除,还是有细节可说的。 6 | 7 | --- 8 | 9 | ### 思路 10 | 11 | 1. 这两个api的作用 12 | 2. 使用场景分析 13 | 3. 使用方式和细节 14 | 4. vue3变化 15 | 16 | --- 17 | 18 | ### 体验 19 | 20 | 一个包含组件透传属性的对象。 21 | 22 | > An object that contains the component's fallthrough attributes. 23 | 24 | ```vue 25 | 30 | ``` 31 | 32 | --- 33 | 34 | ### 范例 35 | 36 | 1. 我们可能会有一些属性和事件没有在props中定义,这类称为非属性特性,结合v-bind指令可以直接透传给内部的子组件。 37 | 2. 这类“属性透传”常常用于包装高阶组件时往内部传递属性,常用于爷孙组件之间传参。比如我在扩展A组件时创建了组件B组件,然后在C组件中使用B,此时传递给C的属性中只有props里面声明的属性是给B使用的,其他的都是A需要的,此时就可以利用v-bind="$attrs"透传下去。 38 | 3. 最常见用法是结合v-bind做展开;$attrs本身不是响应式的,除非访问的属性本身是响应式对象。 39 | 4. vue2中使用$listeners获取事件,vue3中已移除,均合并到$attrs中,使用起来更简单了。 40 | 41 | --- 42 | 43 | ### 原理 44 | 45 | 查看透传属性foo和普通属性bar,发现vnode结构完全相同,这说明vue3中将分辨两者工作由框架完成而非用户指定: 46 | 47 | ```vue 48 | 52 | ``` 53 | 54 | ```vue 55 | 60 | 65 | ``` 66 | 67 | ```js 68 | _createVNode(Comp, { 69 | foo: "foo", 70 | bar: "bar" 71 | }) 72 | ``` 73 | 74 | --- 75 | 76 | https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiB9IGZyb20gJ3Z1ZSdcbmltcG9ydCBDb21wIGZyb20gJy4vQ29tcC52dWUnXG5jb25zdCBtc2cgPSByZWYoJ0hlbGxvIFdvcmxkIScpXG48L3NjcmlwdD5cblxuPHRlbXBsYXRlPlxuICA8aDE+e3sgbXNnIH19PC9oMT5cbiAgPGNvbXAgZm9vPVwiZm9vXCIgYmFyPVwiYmFyXCIgLz5cbjwvdGVtcGxhdGU+IiwiaW1wb3J0LW1hcC5qc29uIjoie1xuICBcImltcG9ydHNcIjoge1xuICAgIFwidnVlXCI6IFwiaHR0cHM6Ly9zZmMudnVlanMub3JnL3Z1ZS5ydW50aW1lLmVzbS1icm93c2VyLmpzXCJcbiAgfVxufSIsIkNvbXAudnVlIjoiPHRlbXBsYXRlPlxuXHQ8ZGl2PlxuICAgIHt7JGF0dHJzLmZvb319IHt7YmFyfX1cbiAgPC9kaXY+XG48L3RlbXBsYXRlPlxuPHNjcmlwdCBzZXR1cD5cbmRlZmluZVByb3BzKHtcbiAgYmFyOiBTdHJpbmdcbn0pXG48L3NjcmlwdD4ifQ== -------------------------------------------------------------------------------- /public/34-v-once/README.md: -------------------------------------------------------------------------------- 1 | ## 34-v-once的使用场景有哪些? 2 | 3 | ### 分析 4 | 5 | `v-once`是Vue中内置指令,很有用的API,在优化方面经常会用到,不过小伙伴们平时可能容易忽略它。 6 | 7 | ### 体验 8 | 9 | 仅渲染元素和组件一次,并且跳过未来更新 10 | 11 | > Render the element and component once only, and skip future updates. 12 | 13 | ```html 14 | 15 | This will never change: {{msg}} 16 | 17 |
18 |

comment

19 |

{{msg}}

20 |
21 | 22 | 23 | 24 |
    25 |
  • {{i}}
  • 26 |
27 | ``` 28 | 29 | 30 | 31 | ### 思路 32 | 33 | 1. `v-once`是什么 34 | 2. 什么时候使用 35 | 3. 如何使用 36 | 4. 扩展`v-memo` 37 | 5. 探索原理 38 | 39 | 40 | 41 | ### 回答范例 42 | 43 | 1. `v-once`是vue的内置指令,作用是仅渲染指定组件或元素一次,并跳过未来对其更新。 44 | 2. 如果我们有一些元素或者组件在初始化渲染之后不再需要变化,这种情况下适合使用`v-once`,这样哪怕这些数据变化,vue也会跳过更新,是一种代码优化手段。 45 | 3. 我们只需要作用的组件或元素上加上v-once即可。 46 | 4. vue3.2之后,又增加了`v-memo`指令,可以有条件缓存部分模板并控制它们的更新,可以说控制力更强了。 47 | 5. 编译器发现元素上面有v-once时,会将首次计算结果存入缓存对象,组件再次渲染时就会从缓存获取,从而避免再次计算。 48 | 49 | 50 | 51 | ### 知其所以然 52 | 53 | 下面例子使用了v-once: 54 | 55 | ```html 56 | 61 | 62 | 66 | ``` 67 | 68 | 我们发现v-once出现后,编译器会缓存作用元素或组件,从而避免以后更新时重新计算这一部分: 69 | 70 | ```js 71 | // ... 72 | return (_ctx, _cache) => { 73 | return (_openBlock(), _createElementBlock(_Fragment, null, [ 74 | // 从缓存获取vnode 75 | _cache[0] || ( 76 | _setBlockTracking(-1), 77 | _cache[0] = _createElementVNode("h1", null, [ 78 | _createTextVNode(_toDisplayString(msg.value), 1 /* TEXT */) 79 | ]), 80 | _setBlockTracking(1), 81 | _cache[0] 82 | ), 83 | // ... 84 | ``` 85 | -------------------------------------------------------------------------------- /public/35-recursive-component/README.md: -------------------------------------------------------------------------------- 1 | ## 35-什么是递归组件?举个例子说明下? 2 | 3 | ### 分析 4 | 5 | 递归组件我们用的比较少,但是在Tree、Menu这类组件中会被用到。 6 | 7 | --- 8 | 9 | ### 体验 10 | 11 | 组件通过组件名称引用它自己,这种情况就是递归组件。 12 | 13 | > An SFC can implicitly refer to itself via its filename. 14 | 15 | ```html 16 |