├── 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 |
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 | 
37 |
38 | 不使用key
39 |
40 | 
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 |
6 |
7 |
--------------------------------------------------------------------------------
/public/02-key/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 03-key的作用及原理?
6 |
7 |
8 |
9 |
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 |
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 | 
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 |
44 |
45 |
46 |
47 |
48 |
49 |
52 |
53 |
54 | ```
55 |
56 |
57 |
58 | - `v-for` 遍历避免同时使用 `v-if`:实际上在Vue3中已经是个错误写法
59 |
60 | ```vue
61 |
62 |
63 | -
66 |
67 | :key="user.id">
68 | {{ user.name }}
69 |
70 |
71 |
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 |
125 |
129 |
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 |
186 |
187 |
188 |
189 |
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 |
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 | 
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 | 
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 | 
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 |
48 |
49 |
50 |
51 |
52 |
53 |
56 |
57 |
58 | ```
59 |
60 |
61 | ---
62 |
63 | - `v-for` 遍历避免同时使用 `v-if`:实际上在Vue3中已经是个错误写法
64 |
65 | ```vue
66 |
67 |
68 | -
71 |
72 | :key="user.id">
73 | {{ user.name }}
74 |
75 |
76 |
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 |
132 |
136 |
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 |
197 |
198 |
199 |
200 |
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 |
28 |
29 |
30 | ---
31 |
32 | SPA
33 |
34 |
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 |
26 |
27 | 将非属性特性透传给内部的子组件
28 |
29 |
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 |
49 | {{ msg }}
50 |
51 |
52 | ```
53 |
54 | ```vue
55 |
56 |
57 | {{$attrs.foo}} {{bar}}
58 |
59 |
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 |
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 |
63 | {{ msg }}
64 |
65 |
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 |
17 |
18 | {{ model.name }}
19 |
27 |
28 |
34 | ```
35 |
36 | ---
37 |
38 | ### 思路
39 |
40 | - 下定义
41 | - 使用场景
42 | - 使用细节
43 | - 原理阐述
44 |
45 | ---
46 |
47 | ### 回答范例
48 |
49 | 1. 如果某个组件通过组件名称引用它自己,这种情况就是递归组件。
50 | 2. 实际开发中类似Tree、Menu这类组件,它们的节点往往包含子节点,子节点结构和父节点往往是相同的。这类组件的数据往往也是树形结构,这种都是使用递归组件的典型场景。
51 | 3. 使用递归组件时,由于我们并未也不能在组件内部导入它自己,所以设置组件`name`属性,用来查找组件定义,如果使用SFC,则可以通过SFC文件名推断。组件内部通常也要有递归结束条件,比如model.children这样的判断。
52 | 4. 查看生成渲染函数可知,递归组件查找时会传递一个布尔值给`resolveComponent`,这样实际获取的组件就是当前组件本身。
53 |
54 | ---
55 |
56 | ### 知其所以然
57 |
58 | 递归组件编译结果中,获取组件时会传递一个标识符 `_resolveComponent("Comp", true)`
59 |
60 | ```js
61 | const _component_Comp = _resolveComponent("Comp", true)
62 | ```
63 |
64 | 就是在传递`maybeSelfReference`
65 |
66 | ```js
67 | export function resolveComponent(
68 | name: string,
69 | maybeSelfReference?: boolean
70 | ): ConcreteComponent | string {
71 | return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name
72 | }
73 | ```
74 |
75 | resolveAsset中最终返回的是组件自身:
76 |
77 | ```js
78 | if (!res && maybeSelfReference) {
79 | // fallback to implicit self-reference
80 | return Component
81 | }
82 | ```
83 |
84 | ---
85 |
86 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/helpers/resolveAssets.ts#L22-L23
87 |
88 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/helpers/resolveAssets.ts#L110-L111
89 |
90 | https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiB9IGZyb20gJ3Z1ZSdcbmltcG9ydCBjb21wIGZyb20gJy4vQ29tcC52dWUnXG5jb25zdCBtc2cgPSByZWYoJ+mAkuW9kue7hOS7ticpXG5jb25zdCBtb2RlbCA9IHtcbiAgbGFiZWw6ICdub2RlLTEnLFxuICBjaGlsZHJlbjogW1xuICAgIHtsYWJlbDogJ25vZGUtMS0xJ30sXG4gICAge2xhYmVsOiAnbm9kZS0xLTInfVxuICBdXG59XG48L3NjcmlwdD5cblxuPHRlbXBsYXRlPlxuICA8aDE+e3sgbXNnIH19PC9oMT5cbiAgPGNvbXAgOm1vZGVsPVwibW9kZWxcIj48L2NvbXA+XG48L3RlbXBsYXRlPiIsImltcG9ydC1tYXAuanNvbiI6IntcbiAgXCJpbXBvcnRzXCI6IHtcbiAgICBcInZ1ZVwiOiBcImh0dHBzOi8vc2ZjLnZ1ZWpzLm9yZy92dWUucnVudGltZS5lc20tYnJvd3Nlci5qc1wiXG4gIH1cbn0iLCJDb21wLnZ1ZSI6Ijx0ZW1wbGF0ZT5cbiAgPGRpdj5cbiAgICB7e21vZGVsLmxhYmVsfX1cbiAgPC9kaXY+XG4gIDxDb21wIHYtZm9yPVwiaXRlbSBpbiBtb2RlbC5jaGlsZHJlblwiIDptb2RlbD1cIml0ZW1cIj48L0NvbXA+XG4gIDxjb21wMj48L2NvbXAyPlxuPC90ZW1wbGF0ZT5cbjxzY3JpcHQ+XG5cdGV4cG9ydCBkZWZhdWx0IHtcbiAgICBuYW1lOiAnQ29tcCcsXG4gICAgcHJvcHM6IHtcbiAgICAgIG1vZGVsOiBPYmplY3RcbiAgICB9LFxuICAgIGNvbXBvbmVudHM6IHtcbiAgICAgIGNvbXAyOiB7XG4gICAgICAgIHJlbmRlcigpe31cbiAgICAgIH1cbiAgICB9XG4gIH1cbjwvc2NyaXB0PiJ9
91 |
92 |
--------------------------------------------------------------------------------
/public/36-async-component/README.md:
--------------------------------------------------------------------------------
1 | ## 36-异步组件是什么?使用场景有哪些?
2 |
3 | ### 分析
4 |
5 | 因为异步路由的存在,我们使用异步组件的次数比较少,因此还是有必要两者的不同。
6 |
7 |
8 |
9 | ### 体验
10 |
11 | 大型应用中,我们需要分割应用为更小的块,并且在需要组件时再加载它们。
12 |
13 | > 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.
14 |
15 | ```js
16 | import { defineAsyncComponent } from 'vue'
17 | // defineAsyncComponent定义异步组件
18 | const AsyncComp = defineAsyncComponent(() => {
19 | // 加载函数返回Promise
20 | return new Promise((resolve, reject) => {
21 | // ...可以从服务器加载组件
22 | resolve(/* loaded component */)
23 | })
24 | })
25 | // 借助打包工具实现ES模块动态导入
26 | const AsyncComp = defineAsyncComponent(() =>
27 | import('./components/MyComponent.vue')
28 | )
29 | ```
30 |
31 | ---
32 |
33 | ### 思路
34 |
35 | 1. 异步组件作用
36 | 2. 何时使用异步组件
37 | 3. 使用细节
38 | 4. 和路由懒加载的不同
39 |
40 | ---
41 |
42 | ### 范例
43 |
44 | 1. 在大型应用中,我们需要分割应用为更小的块,并且在需要组件时再加载它们。
45 | 2. 我们不仅可以在路由切换时懒加载组件,还可以在页面组件中继续使用异步组件,从而实现更细的分割粒度。
46 | 3. 使用异步组件最简单的方式是直接给defineAsyncComponent指定一个loader函数,结合ES模块动态导入函数import可以快速实现。我们甚至可以指定loadingComponent和errorComponent选项从而给用户一个很好的加载反馈。另外Vue3中还可以结合Suspense组件使用异步组件。
47 | 4. 异步组件容易和路由懒加载混淆,实际上不是一个东西。异步组件不能被用于定义懒加载路由上,处理它的是vue框架,处理路由组件加载的是vue-router。但是可以在懒加载的路由组件中使用异步组件。
48 |
49 | ---
50 |
51 | ### 知其所以然
52 |
53 | defineAsyncComponent定义了一个高阶组件,返回一个包装组件。包装组件根据加载器的状态决定渲染什么内容。
54 |
55 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiAsyncComponent.ts#L43-L44
56 |
57 |
--------------------------------------------------------------------------------
/public/37-error-handle/README.md:
--------------------------------------------------------------------------------
1 | ## 37-你是怎么处理vue项目中的错误的?
2 |
3 | ### 分析
4 |
5 | 这是一个综合应用题目,在项目中我们常常需要将App的异常上报,此时错误处理就很重要了。
6 |
7 | 这里要区分错误的类型,针对性做收集。
8 |
9 | 然后是将收集的的错误信息上报服务器。
10 |
11 |
12 |
13 | ### 思路
14 |
15 | 1. 首先区分错误类型
16 | 2. 根据错误不同类型做相应收集
17 | 3. 收集的错误是如何上报服务器的
18 |
19 |
20 |
21 | ### 回答范例
22 |
23 | 1. 应用中的错误类型分为"`接口异常`"和“`代码逻辑异常`”
24 | 2. 我们需要根据不同错误类型做相应处理:`接口异常`是我们请求后端接口过程中发生的异常,可能是请求失败,也可能是请求获得了服务器响应,但是返回的是错误状态。以Axios为例,这类异常我们可以通过封装Axios,在拦截器中统一处理整个应用中请求的错误。`代码逻辑异常`是我们编写的前端代码中存在逻辑上的错误造成的异常,vue应用中最常见的方式是使用全局错误处理函数`app.config.errorHandler`收集错误。
25 | 3. 收集到错误之后,需要统一处理这些异常:分析错误,获取需要错误信息和数据。这里应该有效区分错误类型,如果是请求错误,需要上报接口信息,参数,状态码等;对于前端逻辑异常,获取错误名称和详情即可。另外还可以收集应用名称、环境、版本、用户信息,所在页面等。这些信息可以通过vuex存储的全局状态和路由信息获取。
26 |
27 |
28 |
29 | ### 实践
30 |
31 | axios拦截器中处理捕获异常:
32 |
33 | ```js
34 | // 响应拦截器
35 | instance.interceptors.response.use(
36 | (response) => {
37 | return response.data;
38 | },
39 | (error) => {
40 | // 存在response说明服务器有响应
41 | if (error.response) {
42 | let response = error.response;
43 | if (response.status >= 400) {
44 | handleError(response);
45 | }
46 | } else {
47 | handleError(null);
48 | }
49 | return Promise.reject(error);
50 | },
51 | );
52 | ```
53 |
54 | vue中全局捕获异常:
55 |
56 | ```js
57 | import { createApp } from 'vue'
58 |
59 | const app = createApp(...)
60 |
61 | app.config.errorHandler = (err, instance, info) => {
62 | // report error to tracking services
63 | }
64 | ```
65 |
66 | 处理接口请求错误:
67 |
68 | ```js
69 | function handleError(error, type) {
70 | if(type == 1) {
71 | // 接口错误,从config字段中获取请求信息
72 | let { url, method, params, data } = error.config
73 | let err_data = {
74 | url, method,
75 | params: { query: params, body: data },
76 | error: error.data?.message || JSON.stringify(error.data),
77 | })
78 | }
79 | }
80 | ```
81 |
82 | 处理前端逻辑错误:
83 |
84 | ```js
85 | function handleError(error, type) {
86 | if(type == 2) {
87 | let errData = null
88 | // 逻辑错误
89 | if(error instanceof Error) {
90 | let { name, message } = error
91 | errData = {
92 | type: name,
93 | error: message
94 | }
95 | } else {
96 | errData = {
97 | type: 'other',
98 | error: JSON.strigify(error)
99 | }
100 | }
101 | }
102 | }
103 | ```
104 |
--------------------------------------------------------------------------------
/public/38-child-modify-parent/README.md:
--------------------------------------------------------------------------------
1 | ## 子组件可以直接改变父组件的数据么,说明原因
2 |
3 | ### 分析
4 |
5 | 这是一个实践知识点,组件化开发过程中有个单项数据流原则,不在子组件中修改父组件是个常识问题。
6 |
7 | 参考文档:https://staging.vuejs.org/guide/components/props.html#one-way-data-flow
8 |
9 | ---
10 |
11 | ### 思路
12 |
13 | 1. 讲讲单项数据流原则,表明为何不能这么做
14 | 2. 举几个常见场景的例子说说解决方案
15 | 3. 结合实践讲讲如果需要修改父组件状态应该如何做
16 |
17 | ---
18 |
19 | ### 回答范例
20 |
21 | 1. 所有的 prop 都使得其父子之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。另外,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器控制台中发出警告。
22 | const props = defineProps(['foo'])
23 | // ❌ 下面行为会被警告, props是只读的!
24 | props.foo = 'bar'
25 |
26 | ---
27 |
28 | 2. 实际开发过程中有两个场景会想要修改一个属性:
29 | - 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data,并将这个 prop 用作其初始值:
30 | const props = defineProps(['initialCounter'])
31 | const counter = ref(props.initialCounter)
32 | - 这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
33 | const props = defineProps(['size'])
34 | // prop变化,计算属性自动更新
35 | const normalizedSize = computed(() => props.size.trim().toLowerCase())
36 | 3. 实践中如果确实想要改变父组件属性应该emit一个事件让父组件去做这个变更。注意虽然我们不能直接修改一个传入的对象或者数组类型的prop,但是我们还是能够直接改内嵌的对象或属性。
37 |
--------------------------------------------------------------------------------
/public/39-permission/README.md:
--------------------------------------------------------------------------------
1 | ## Vue要做权限管理该怎么做?控制到按钮级别的权限怎么做?
2 |
3 | ### 分析
4 |
5 | 综合实践题目,实际开发中经常需要面临权限管理的需求,考查实际应用能力。
6 |
7 | 权限管理一般需求是两个:页面权限和按钮权限,从这两个方面论述即可。
8 |
9 |
10 |
11 |
12 | ---
13 |
14 | ### 思路
15 |
16 | 1. 权限管理需求分析:页面和按钮权限
17 | 2. 权限管理的实现方案:分后端方案和前端方案阐述
18 | 3. 说说各自的优缺点
19 |
20 | ---
21 |
22 | ### 回答范例
23 |
24 | 1. 权限管理一般需求是**页面权限**和**按钮权限**的管理
25 |
26 | 2. 具体实现的时候分后端和前端两种方案:
27 |
28 | 前端方案会**把所有路由信息在前端配置**,通过路由守卫要求用户登录,用户**登录后根据角色过滤出路由表**。比如我会配置一个`asyncRoutes`数组,需要认证的页面在其路由的`meta`中添加一个`roles`字段,等获取用户角色之后取两者的交集,若结果不为空则说明可以访问。此过滤过程结束,剩下的路由就是该用户能访问的页面,**最后通过`router.addRoutes(accessRoutes)`方式动态添加路由**即可。
29 |
30 | 后端方案会**把所有页面路由信息存在数据库**中,用户登录的时候根据其角色**查询得到其能访问的所有页面路由信息**返回给前端,前端**再通过`addRoutes`动态添加路由**信息
31 |
32 | 按钮权限的控制通常会**实现一个指令**,例如`v-permission`,**将按钮要求角色通过值传给v-permission指令**,在指令的`moutned`钩子中可以**判断当前用户角色和按钮是否存在交集**,有则保留按钮,无则移除按钮。
33 |
34 | 3. 纯前端方案的优点是实现简单,不需要额外权限管理页面,但是维护起来问题比较大,有新的页面和角色需求就要修改前端代码重新打包部署;服务端方案就不存在这个问题,通过专门的角色和权限管理页面,配置页面和按钮权限信息到数据库,应用每次登陆时获取的都是最新的路由信息,可谓一劳永逸!
35 |
36 | ---
37 |
38 | ### 知其所以然
39 |
40 | 路由守卫
41 |
42 | https://github1s.com/PanJiaChen/vue-element-admin/blob/HEAD/src/permission.js#L13-L14
43 |
44 | 路由生成
45 |
46 | https://github1s.com/PanJiaChen/vue-element-admin/blob/HEAD/src/store/modules/permission.js#L50-L51
47 |
48 | 动态追加路由
49 |
50 | https://github1s.com/PanJiaChen/vue-element-admin/blob/HEAD/src/permission.js#L43-L44
51 |
52 | ---
53 |
54 | ### 可能的追问
55 |
56 | 1. 类似`Tabs`这类组件能不能使用`v-permission`指令实现按钮权限控制?
57 |
58 | ```html
59 |
60 | ⽤户管理
61 | ⻆⾊管理
62 |
63 | ```
64 |
65 | ---
66 |
67 | 2. 服务端返回的路由信息如何添加到路由器中?
68 |
69 | ```js
70 | // 前端组件名和组件映射表
71 | const map = {
72 | //xx: require('@/views/xx.vue').default // 同步的⽅式
73 | xx: () => import('@/views/xx.vue') // 异步的⽅式
74 | }
75 | // 服务端返回的asyncRoutes
76 | const asyncRoutes = [
77 | { path: '/xx', component: 'xx',... }
78 | ]
79 | // 遍历asyncRoutes,将component替换为map[component]
80 | function mapComponent(asyncRoutes) {
81 | asyncRoutes.forEach(route => {
82 | route.component = map[route.component];
83 | if(route.children) {
84 | route.children.map(child => mapComponent(child))
85 | }
86 | })
87 | }
88 | mapComponent(asyncRoutes)
89 | ```
90 |
--------------------------------------------------------------------------------
/public/40-create-vue-proj/README.md:
--------------------------------------------------------------------------------
1 | ## 19-从0到1自己构架一个vue项目,说说有哪些步骤、哪些重要插件、目录结构你会怎么组织
2 |
3 | 综合实践类题目,考查实战能力。没有什么绝对的正确答案,把平时工作的重点有条理的描述一下即可。
4 |
5 |
6 |
7 | ### 思路
8 |
9 | 1. 构建项目,创建项目基本结构
10 | 2. 引入必要的插件:
11 | 3. 代码规范:prettier,eslint
12 | 4. 提交规范:husky,lint-staged
13 | 5. 其他常用:svg-loader,vueuse,nprogress
14 | 6. 常见目录结构
15 |
16 | ---
17 |
18 | ### 回答范例
19 |
20 | 1. 从0创建一个项目我大致会做以下事情:项目构建、引入必要插件、代码规范、提交规范、常用库和组件
21 |
22 | 2. 目前vue3项目我会用vite或者create-vue创建项目
23 |
24 | 3. 接下来引入必要插件:路由插件vue-router、状态管理vuex/pinia、ui库我比较喜欢element-plus和antd-vue、http工具我会选axios
25 |
26 | 4. 其他比较常用的库有vueuse,nprogress,图标可以使用vite-svg-loader
27 |
28 | 5. 下面是代码规范:结合prettier和eslint即可
29 |
30 | 6. 最后是提交规范,可以使用husky,lint-staged,commitlint
31 |
32 | ---
33 |
34 | 7. 目录结构我有如下习惯:
35 | `.vscode`:用来放项目中的 vscode 配置
36 |
37 | `plugins`:用来放 vite 插件的 plugin 配置
38 |
39 | `public`:用来放一些诸如 页头icon 之类的公共文件,会被打包到dist根目录下
40 |
41 | `src`:用来放项目代码文件
42 |
43 | `api`:用来放http的一些接口配置
44 |
45 | `assets`:用来放一些 CSS 之类的静态资源
46 |
47 | `components`:用来放项目通用组件
48 |
49 | `layout`:用来放项目的布局
50 |
51 | `router`:用来放项目的路由配置
52 |
53 | `store`:用来放状态管理Pinia的配置
54 |
55 | `utils`:用来放项目中的工具方法类
56 |
57 | `views`:用来放项目的页面文件
58 |
59 |
--------------------------------------------------------------------------------
/public/41-best-practice/README.md:
--------------------------------------------------------------------------------
1 | ## 实际工作中,你总结的vue最佳实践有哪些?
2 |
3 | 看到这样的题目,可以用以下图片来回答:
4 |
5 |
6 |
7 | ---
8 |
9 | ### 思路
10 |
11 | 查看vue官方文档:
12 |
13 | 风格指南:https://vuejs.org/style-guide/
14 |
15 | 性能:https://vuejs.org/guide/best-practices/performance.html#overview
16 |
17 | 安全:https://vuejs.org/guide/best-practices/security.html
18 |
19 | 访问性:https://vuejs.org/guide/best-practices/accessibility.html
20 |
21 | 发布:https://vuejs.org/guide/best-practices/production-deployment.html
22 |
23 | ---
24 |
25 | ### 回答范例
26 |
27 | 我从编码风格、性能、安全等方面说几条:
28 |
29 | 1. 编码风格方面:
30 | - 命名组件时使用“多词”风格避免和HTML元素冲突
31 | - 使用“细节化”方式定义属性而不是只有一个属性名
32 | - 属性名声明时使用“驼峰命名”,模板或jsx中使用“肉串命名”
33 | - 使用v-for时务必加上key,且不要跟v-if写在一起
34 | 2. 性能方面:
35 | - 路由懒加载减少应用尺寸
36 | - 利用SSR减少首屏加载时间
37 | - 利用v-once渲染那些不需要更新的内容
38 | - 一些长列表可以利用虚拟滚动技术避免内存过度占用
39 | - 对于深层嵌套对象的大数组可以使用shallowRef或shallowReactive降低开销
40 | - 避免不必要的组件抽象
41 | ---
42 |
43 | 3. 安全:
44 | - 不使用不可信模板,例如使用用户输入拼接模板:`template: + userProvidedString +
`
45 | - 小心使用v-html,:url,:style等,避免html、url、样式等注入
46 | 4. 等等......
47 |
48 | ---
49 |
50 |
51 |
--------------------------------------------------------------------------------
/public/42-instance-mount/README.md:
--------------------------------------------------------------------------------
1 | ## Vue实例挂载的过程中发生了什么?
2 |
3 | ### 分析
4 |
5 | 挂载过程完成了最重要的两件事:
6 |
7 | 1. 初始化
8 | 2. 建立更新机制
9 |
10 | 把这两件事说清楚即可!
11 |
12 | ---
13 |
14 | ### 回答范例
15 |
16 | 1. 挂载过程指的是app.mount()过程,这个是个初始化过程,整体上做了两件事:**初始化**和**建立更新机制**
17 | 2. 初始化会**创建组件实例**、**初始化组件状态**、**创建各种响应式数据**
18 | 3. 建立更新机制这一步会立即执行一次组件更新函数,这会首次执行组件渲染函数并执行patch将前面获得vnode转换为dom;同时首次执行渲染函数会创建它内部响应式数据和组件更新函数之间的依赖关系,这使得以后数据变化时会执行对应的更新函数。
19 |
20 | ---
21 |
22 | ### 知其所以然
23 |
24 | 测试代码,test-v3.html
25 |
26 |
27 |
28 | mount函数定义
29 |
30 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/apiCreateApp.ts#L277-L278
31 |
32 | 首次render过程
33 |
34 | https://github1s.com/vuejs/core/blob/HEAD/packages/runtime-core/src/renderer.ts#L2303-L2304
35 |
36 | ---
37 |
38 | ### 可能的追问
39 |
40 | 1. 响应式数据怎么创建
41 | 2. 依赖关系如何建立
42 |
43 |
--------------------------------------------------------------------------------
/public/43-vue-loader/README.md:
--------------------------------------------------------------------------------
1 | ## vue-loader是什么?它有什么作用?
2 | ### 分析
3 |
4 | 这是一道工具类的原理题目,相当有深度,具有不错的人才区分度。
5 |
6 | ### 体验
7 |
8 | 使用官方提供的SFC playground可以很好的体验vue-loader。
9 |
10 | 有了vue-loader加持,我们才可以以SFC的方式快速编写代码。
11 |
12 | [https://sfc.vuejs.org](https://sfc.vuejs.org/#eyJBcHAudnVlIjoiPHNjcmlwdCBzZXR1cD5cbmltcG9ydCB7IHJlZiB9IGZyb20gJ3Z1ZSdcblxuY29uc3QgbXNnID0gcmVmKCdIZWxsbyBXb3JsZCEnKVxuPC9zY3JpcHQ+XG5cbjx0ZW1wbGF0ZT5cbiAgPGgxPnt7IG1zZyB9fTwvaDE+XG4gIDxpbnB1dCB2LW1vZGVsPVwibXNnXCI+XG48L3RlbXBsYXRlPiIsImltcG9ydC1tYXAuanNvbiI6IntcbiAgXCJpbXBvcnRzXCI6IHtcbiAgICBcInZ1ZVwiOiBcImh0dHBzOi8vc2ZjLnZ1ZWpzLm9yZy92dWUucnVudGltZS5lc20tYnJvd3Nlci5qc1wiXG4gIH1cbn0ifQ==)
13 |
14 | ```TypeScript
15 |
16 | {{ msg }}
17 |
18 |
19 |
28 |
29 |
34 | ```
35 |
36 | ### 思路
37 |
38 | - `vue-loader`是什么东东
39 | - `vue-loader`是做什么用的
40 | - `vue-loader`何时生效
41 | - `vue-loader`如何工作
42 |
43 | ### 回答范例
44 |
45 | 1. `vue-loader`是用于处理单文件组件(SFC,Single-File Component)的webpack loader
46 | 2. 因为有了`vue-loader`,我们就可以在项目中编写SFC格式的Vue组件,我们可以把代码分割为、
2 |
--------------------------------------------------------------------------------
/public/46-mutations-actions/README.md:
--------------------------------------------------------------------------------
1 | ## vuex中actions和mutations有什么区别?
2 |
3 | ### 题目分析
4 |
5 | `mutations`和`actions`是`vuex`带来的两个独特的概念。新手程序员容易混淆,所以面试官喜欢问。
6 |
7 | 我们只需记住修改状态只能是`mutations`,`actions`只能通过提交`mutation`修改状态即可。
8 |
9 | ---
10 |
11 | ### 体验
12 |
13 | 看下面例子可知,`Action` 类似于 `mutation`,不同在于:
14 |
15 | - `Action` 提交的是 `mutation`,而不是直接变更状态。
16 | - `Action` 可以包含任意异步操作。
17 |
18 | ```js
19 | const store = createStore({
20 | state: {
21 | count: 0
22 | },
23 | mutations: {
24 | increment (state) {
25 | state.count++
26 | }
27 | },
28 | actions: {
29 | increment (context) {
30 | context.commit('increment')
31 | }
32 | }
33 | })
34 | ```
35 |
36 | ---
37 |
38 | ### 答题思路
39 |
40 | 1. 给出两者概念说明区别
41 | 2. 举例说明应用场景
42 | 3. 使用细节不同
43 | 4. 简单阐述实现上差异
44 |
45 | ---
46 |
47 | ### 回答范例
48 |
49 | 1. 官方文档说:更改 Vuex 的 store 中的状态的唯一方法是提交 `mutation`,`mutation` 非常类似于事件:每个 `mutation` 都有一个字符串的**类型 (type)\**和一个\**回调函数 (handler)**。`Action` 类似于 `mutation`,不同在于:`Action`可以包含任意异步操作,但它不能修改状态, 需要提交`mutation`才能变更状态。
50 | 2. 因此,开发时,包含异步操作或者复杂业务组合时使用action;需要直接修改状态则提交mutation。但由于dispatch和commit是两个API,容易引起混淆,实践中也会采用统一使用dispatch action的方式。
51 | 3. 调用dispatch和commit两个API时几乎完全一样,但是定义两者时却不甚相同,mutation的回调函数接收参数是state对象。action则是与Store实例具有相同方法和属性的上下文context对象,因此一般会解构它为`{commit, dispatch, state}`,从而方便编码。另外dispatch会返回Promise实例便于处理内部异步结果。
52 | 4. 实现上commit(type)方法相当于调用`options.mutations[type](state)`;`dispatch(type)`方法相当于调用`options.actions[type](store)`,这样就很容易理解两者使用上的不同了。
53 |
54 | ---
55 |
56 | ### 知其所以然
57 |
58 | 我们可以像下面这样简单实现`commit`和`dispatch`,从而辨别两者不同:
59 |
60 | ```js
61 | class Store {
62 | constructor(options) {
63 | this.state = reactive(options.state)
64 | this.options = options
65 | }
66 | commit(type, payload) {
67 | // 传入上下文和参数1都是state对象
68 | this.options.mutations[type].call(this.state, this.state, payload)
69 | }
70 | dispatch(type, payload) {
71 | // 传入上下文和参数1都是store本身
72 | this.options.actions[type].call(this, this, payload)
73 | }
74 | }
75 | ```
76 |
77 |
--------------------------------------------------------------------------------
/public/47-big-data-performance/README.md:
--------------------------------------------------------------------------------
1 | ## 使用vue渲染大量数据时应该怎么优化?说下你的思路!
2 | ### 分析
3 |
4 | 企业级项目中渲染大量数据的情况比较常见,因此这是一道非常好的综合实践题目。
5 |
6 | ### 思路
7 |
8 | 1. 描述大数据量带来的问题
9 | 2. 分不同情况做不同处理
10 | 3. 总结一下
11 |
12 | ### 回答
13 |
14 | 1. 在大型企业级项目中经常需要渲染大量数据,此时很容易出现卡顿的情况。比如大数据量的表格、树。
15 | 2. 处理时要根据情况做不通处理:
16 | - 可以采取分页的方式获取,避免渲染大量数据
17 | - [vue-virtual-scroller](https://github.com/Akryum/vue-virtual-scroller)等虚拟滚动方案,只渲染视口范围内的数据
18 | - 如果不需要更新,可以使用`v-once`方式只渲染一次
19 | - 通过[v-memo](https://vuejs.org/api/built-in-directives.html#v-memo)可以缓存结果,结合`v-for`使用,避免数据变化时不必要的VNode创建
20 | - 可以采用懒加载方式,在用户需要的时候再加载数据,比如tree组件子树的懒加载
21 | 3. 总之,还是要看具体需求,首先从设计上避免大数据获取和渲染;实在需要这样做可以采用虚表的方式优化渲染;最后优化更新,如果不需要更新可以v-once处理,需要更新可以v-memo进一步优化大数据更新性能。其他可以采用的是交互方式优化,无线滚动、懒加载等方案。
22 |
--------------------------------------------------------------------------------
/public/48-watch-vuex-state/README.md:
--------------------------------------------------------------------------------
1 | ## 41-怎么监听vuex数据的变化?
2 |
3 | ### 分析
4 |
5 | vuex数据状态是响应式的,所以状态变视图跟着变,但是有时还是需要知道数据状态变了从而做一些事情。
6 |
7 | 既然状态都是响应式的,那自然可以`watch`,另外vuex也提供了订阅的API:`store.subscribe()`。
8 |
9 | ---
10 |
11 | ### 思路
12 |
13 | - 总述知道的方法
14 |
15 | - 分别阐述用法
16 |
17 | - 选择和场景
18 |
19 |
20 |
21 | ---
22 |
23 | ### 回答范例
24 |
25 | - 我知道几种方法:
26 |
27 | - 可以通过watch选项或者watch方法监听状态
28 | - 可以使用vuex提供的API:store.subscribe()
29 |
30 | - watch选项方式,可以以字符串形式监听`$store.state.xx`;subscribe方式,可以调用store.subscribe(cb),回调函数接收mutation对象和state对象,这样可以进一步判断mutation.type是否是期待的那个,从而进一步做后续处理。
31 |
32 | - watch方式简单好用,且能获取变化前后值,首选;subscribe方法会被所有commit行为触发,因此还需要判断mutation.type,用起来略繁琐,一般用于vuex插件中。
33 |
34 |
35 |
36 | ### 实践
37 |
38 | watch方式
39 |
40 | ```js
41 | const app = createApp({
42 | watch: {
43 | '$store.state.counter'() {
44 | console.log('counter change!');
45 | }
46 | }
47 | })
48 | ```
49 |
50 | subscribe方式:
51 |
52 | ```js
53 | store.subscribe((mutation, state) => {
54 | if (mutation.type === 'add') {
55 | console.log('counter change in subscribe()!');
56 | }
57 | })
58 | ```
59 |
--------------------------------------------------------------------------------
/public/48-watch-vuex-state/test.html:
--------------------------------------------------------------------------------
1 | {{$store.state.counter}}
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/public/49-router-link-router-view/README.md:
--------------------------------------------------------------------------------
1 | ## router-link和router-view是如何起作用的?
2 |
3 | ### 分析
4 |
5 | vue-router中两个重要组件`router-link`和`router-view`,分别起到导航作用和内容渲染作用,但是回答如何生效还真有一定难度哪!
6 |
7 | ### 思路
8 |
9 | - 两者作用
10 | - 阐述使用方式
11 | - 原理说明
12 |
13 | ### 回答范例
14 |
15 | - vue-router中两个重要组件`router-link`和`router-view`,分别起到路由导航作用和组件内容渲染作用
16 | - 使用中router-link默认生成一个a标签,设置to属性定义跳转path。实际上也可以通过custom和插槽自定义最终的展现形式。router-view是要显示组件的占位组件,可以嵌套,对应路由配置的嵌套关系,配合name可以显示具名组件,起到更强的布局作用。
17 | - router-link组件内部根据custom属性判断如何渲染最终生成节点,内部提供导航方法navigate,用户点击之后实际调用的是该方法,此方法最终会修改响应式的路由变量,然后重新去routes匹配出数组结果,router-view则根据其所处深度deep在匹配数组结果中找到对应的路由并获取组件,最终将其渲染出来。
18 |
19 | ### 知其所以然
20 |
21 | - RouterLink定义
22 |
23 | https://github1s.com/vuejs/router/blob/HEAD/src/RouterLink.ts#L184-L185
24 |
25 | - RouterView定义
26 |
27 | https://github1s.com/vuejs/router/blob/HEAD/src/RouterView.ts#L43-L44
--------------------------------------------------------------------------------
/public/assets/201806191038393.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/201806191038393.png
--------------------------------------------------------------------------------
/public/assets/2bb46bdbccec729d280ac0f5eb4420566c010e08-1580031870677.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/2bb46bdbccec729d280ac0f5eb4420566c010e08-1580031870677.png
--------------------------------------------------------------------------------
/public/assets/2bb46bdbccec729d280ac0f5eb4420566c010e08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/2bb46bdbccec729d280ac0f5eb4420566c010e08.png
--------------------------------------------------------------------------------
/public/assets/404460ceece985d433e1ed1f36cd4215.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/404460ceece985d433e1ed1f36cd4215.gif
--------------------------------------------------------------------------------
/public/assets/6c27078960e80bc33b954c0623b8bd9277db8de4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/6c27078960e80bc33b954c0623b8bd9277db8de4.png
--------------------------------------------------------------------------------
/public/assets/998023-20180519212338609-1617459354.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/998023-20180519212338609-1617459354.png
--------------------------------------------------------------------------------
/public/assets/bz.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/bz.jpeg
--------------------------------------------------------------------------------
/public/assets/image-20200119155426142.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/image-20200119155426142.png
--------------------------------------------------------------------------------
/public/assets/image-20200124114525656.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/image-20200124114525656.png
--------------------------------------------------------------------------------
/public/assets/image-20200129121057488.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/image-20200129121057488.png
--------------------------------------------------------------------------------
/public/assets/image-20200129121322729.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/image-20200129121322729.png
--------------------------------------------------------------------------------
/public/assets/image-20200207134358051.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/image-20200207134358051.png
--------------------------------------------------------------------------------
/public/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/logo.png
--------------------------------------------------------------------------------
/public/assets/u=1519789747,1816893723&fm=26&gp=0.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/u=1519789747,1816893723&fm=26&gp=0.jpg
--------------------------------------------------------------------------------
/public/assets/v2-6e88cc53a7e427f0ae8340cf930ac30d_hd.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/v2-6e88cc53a7e427f0ae8340cf930ac30d_hd.jpg
--------------------------------------------------------------------------------
/public/assets/v2-bf76311258f100b789226ccbb2600071_hd-1579618596674.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/v2-bf76311258f100b789226ccbb2600071_hd-1579618596674.jpg
--------------------------------------------------------------------------------
/public/assets/v2-bf76311258f100b789226ccbb2600071_hd.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/v2-bf76311258f100b789226ccbb2600071_hd.jpg
--------------------------------------------------------------------------------
/public/assets/vuex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-interview/e511ba947a2fa7876ace8de47fe31f4d46ae4588/public/assets/vuex.png
--------------------------------------------------------------------------------