Vue常见面试题
Vue常见问题
1、Vue中props是单向数据流 子组件不能直接修改父组件的值
- 具体原因如下
- 源码里更新组件调用
initProps
方法时,会执行defineReactive
,这个方法会调用Object.defineProperty
的get/set
方法,调用set方法时会做校验是不是根组件,是不会更新子组件的; - 最重要的一个原因单向数据流为了不引起数据混乱
- 源码里更新组件调用
2、组件传值方法
this.$attrs
:子组件中通过v-bind="Name"
来传入组件内部,通过this.$attrs[dataName]
来获取所有除了不是pros的数据;禁用继承实例中 inheritAttrs: false;- 注意 class跟style不属于属性值
Vue3.0中被移除的this.$listeners
:子组件中通过v-on="$listeners"
来传入组件内部,使用this.$listeners[methodeName]
来获取方法;this.$children
:获取子组件集合(类似于原生获取子元素集合children
);this.$parent
:直接获取父元素,可以拿方法和数据;this.$refs.name
:获取该节点的方法和数据;- 使用
provide
(一个函数或者对象)和inject
(一个数组或者字符串)实现父组件对 子孙组件 注入一个依赖,不管层次多深都可以拿到。// 父组件 和data() 同级 data() { return { name: 'Derry父组件', age: 9999, } }, created() { const timer = setTimeout(() => { this.age += 1; clearTimeout(timer); }, 1000) }, // 第一种对象的写法 //provide: { // title: '测试provide' //}, // 第二种函数的形式 用函数的形式可以使用data中的数据 否则将报错 provide(): { return { // 用函数的形式可以使用data中的数据 childAge: this.age.length, title: () => this.age, } }, // 如果需要响应式,添加 Vue.computed(() => `${this.name.length}测试provide`)和watch中使用监听, 也可以使用对象; // ----------------- // 子孙组件 data() { return { name: 'Jerry子孙组件', } }, inject: ['title'], created() { console.log(this.title); // 测试provide }, computed: { ageChange() { // 这里需要使用调用 return this.title(); }, }, // 再用watch监听 watch: { ageChange(val) { console.log('祖组件值发生变化', val); }, },
- 运用
new Vue()
一个新实例导入导出作为桥梁来通信,再需要的传值的两个组件里引入空实例通信;
- 运用
3、Vue中如何实现单页应用的
-
在
new Router({})
配置mode
参数,mode
值可选hash
或者History
。 -
hash模式
hash(#)
是URL
的锚点,同时每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置;但不会对服务端请求数据;- 使用
hashchange()
监听hash值变化,使用window.location.href
重新赋值;- 用法实例
// 对hash值变化进行监听 window.onhashchange = function(e) { // 实现页面显示隐藏 switch(e) { case e === 'index': window.location.href = 'index.html'; break; ... default: console.log(`Sorry, we are out of ${e}.`); } }
-
History模式
- 使用 HTML5 History 中提供的一种功能,能在不刷新页面的情况下修改
URL
, 使用History.pushState()
实现URL
跳转无需刷新。pushState
新建历史记录 和replaceState
修改历史记录 两个API以及浏览器的popState
事件监听 历史栈的改变,只要历史栈有信息发生改变,就会触发该事件。这种模式同样也是不会向后端发起请求的。- 用法实例
// History变化事件监听 window.onpopstate = function(e) { alert(2); } // 状态对象 let stateObj = { foo: "bar", }; /* * @description pushState - 新建历史记录 replaceState - 修改历史记录 * @params stateObj {Object} 状态对象state是一个JavaScript对象 * @params title {String} 标题 * @params URL {String} URL地址 新URL必须与当前URL同源 */ history.pushState(stateObj, "page 2", "bar.html"); history.replaceState(stateObj, "page 3", "bar2.html");
- window.history.forward() == window.history.go(1); -- 前进;
- window.history.back() == window.history.go(-1); -- 后退;
- 但当用户直接在用户栏输入地址并带有参数时:
-
Hash
模式:xxx.com/#/id=5
请求地址为xxx.com
,没有问题; -
History
模式:xxx.com/id=5
请求地址为xxx.com/id=5
,如果后端没有对应的路由处理,就会返回404错误; -
前端解决办法: 在路由拦截(
router.beforEach(async(to, from, next)=>{})
)里判断,如果没有匹配的路由,跳转默认的index.html页面。- 有个问题不再返回 404 页面,所以后端处理比较好。
-
后端解决办法:
- 在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
- 给个警告,因为这么做以后,你的服务器就不再返回 404 错误页面,因为对于所有错误路径都会返回 index.html 文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后再给出一个 404 页面。或者,如果你使用 Node.js 服务器,你可以用服务端路由匹配到来的 URL,并在没有匹配到路由的时候返回 404,以实现回退。
-
- 使用 HTML5 History 中提供的一种功能,能在不刷新页面的情况下修改
-
Remark-单页应用缺点:对
SEO
不友好- 单页应用实际是把视图(View)渲染从Server交给浏览器,Server只提供JSON格式数据,视图和内容都是通过本地JavaScript来组织和渲染。而搜索搜索引擎抓取的内容,需要有完整的HTML和内容,单页应用架构的站点,并不能很好的支持搜索。
4、Vuex为什么刷新后 数据就清空了
- 因为JS数据保存在浏览器的堆栈内存里面,所以一刷新浏览器就把堆栈内存清空,所以就Vuex没有了数据。
- 解决方法:使用
cookie
\localStorage
\sessionStroage
Remark:1、sessionStroage没有过期时间限制,数据会在浏览器窗口关闭时清空。2、localStorage
也不能设置过期时间,只能手动删除。
5、Vue中如何实现路由缓存,原理是什么
- 使用
keep-alive
-
/* * @description 第一种 * @attr :max="缓存的最大数量" * @attr :include="符合条件的JS数组或者正则表达式" * @attr :exclude="不符合条件的JS数组或者正则表达式" */ <keep-alive :exclude="不符合条件的JS数组或者正则表达式"> <router-view /> </keep-alive> /* * 第二种可以直接使用路由中的缓存字段 */ <keep-alive> <router-view v-if="符合条件的JS表达式" /> </keep-alive> <router-view v-if="!符合条件的JS表达式">
- 总结:keep-alive 组件是抽象组件,在对应父子关系时会跳过抽象组件,它只对包裹的子组件做处理,主要是根据LRU策略缓存组件 VNode,最后在 render 时返回子组件的 VNode。缓存渲染过程会更新 keep-alive 插槽,重新再 render 一次,从缓存中读取之前的组件 VNode 实现状态缓存。
-
6、Vue实现异步刷新的原理
- 从eventLoop角度出发
- 例如
for
循环从 1 到 100;结果只监听到 100;为什么中间2-99没有变化?其实是for
循环是栈里面的事件,优先执行完后,再看任务队列中的watch
事件,所以for
执行完之后就是100了,再被监听到就是100;
- 例如
- this.$nextTick()原理:
- Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。
- Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
- 首先修改数据,这是同步任务。同一事件循环的所有的同步任务都在主线程上执行,形成一个执行栈,此时还未涉及 DOM 。
- 然后Vue 开启一个异步队列,并缓冲在此事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。
- vue的降级策略(兼容)
- promise -> MutationObserver ->(macro-task) setTimeout 创建一个新的任务,优先使用 Promise,如果浏览器不支持,再尝试MutationObserver。又不支持,只能用 setTimeout 创建 task 了。MutationObserver 是 h5 新加的一个功能,其功能是监听dom节点的变动,在所有 dom 变动完成后,执行回调函数。
7、Vue中使用环境变量和使用插值
-
public文件夹中 使用插值
- <%= VALUE %> 用来做不转义插值;
- <%- VALUE %> 用来做 HTML 转义插值;
- <% expression %> 用来描述 JavaScript 流程控制。
- 除了被 html-webpack-plugin 暴露的默认值之外,所有客户端环境变量也可以直接使用。例如,BASE_URL 的用法:
<!-- index文件中 设置页签图标 --> <link rel="ico" href="<%= BASE_URL %>favicon.ico"></link>
-
你可以在你的项目根目录中放置下列文件来指定环境变量:
.env # 在所有的环境中被载入 .env.local # 在所有的环境中被载入,但会被 git 忽略 .env.[mode] # 只在指定的模式中被载入 .env.[mode].local # 只在指定的模式中被载入,但会被 git 忽略
-
一个环境文件只包含环境变量的“键=值”对:
VUE_APP_NOT_SECRET_CODE=some_value
。 -
请注意,只有
NODE_ENV
,BASE_URL
和以VUE_APP_
开头的变量将通过 webpack.DefinePlugin 静态地嵌入到客户端侧的代码中。这是为了避免意外公开机器上可能具有相同名称的私钥。 -
代码中访问变量例如
console.log(process.env.VUE_APP_HELLO)
。 -
也可以再vue.config.js中设置环境变量
// vue.config.js process.env.VUE_APP_VERSION = require('./package.jon').version;
-
除了
VUE_APP_*
变量之外,在你的应用代码中始终可用的还有两个特殊的变量:NODE_ENV
- 会是 "development"、"production" 或 "test" 中的一个。具体的值取决于应用运行的模式(可以不用设置)。BASE_URL
- 会和 vue.config.js 中的 publicPath 选项相符,即你的应用会部署到的基础路径。
// vue.config.js Module.exports = { publicPath: process.env.NODE_ENV !== "development" ? '/pollution-ses/' : '/', }
-
8、Vue事件$emit发送后$on监听失效
- 发送
$emit('handleEven')
,接收@handle-even="handle"
- 失效原因
html
对大小写不敏感,并且v-on
事件监听器中都会转成全小写(转成@handleeven
),监听不到handleEven
,所以不存在驼峰式写法。 - 推荐始终使用
kebab-case
的事件名。
- 失效原因
9、Vue中使用原生事件失效
- 主要原因是未使用
.native
事件修饰符。 - 如果添加
.native
还是静默失效,那么可能是组件根元素的问题 - 解决方法:可以使用
v-on="$listeners"
,$listeners"
它是一个对象,里面包含了作用在这个组件上的所有监听器;
10、Vue中Router异步组件和添加处理加载状态
- 工厂函数中返回一个 Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以这样使用动态导入:
Vue.component(
'async-webpack-example',
// 这个动态导入会返回一个 `Promise` 对象。
() => import('./my-async-component')
)
// 或者局部注册组件中使用键值对方式
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
- 异步组件工厂函数可以返回一个如下格式的对象:
onst AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
11、elementUI 设置表头高度: 利用diff算法, 添加动态 key值, 刷新表格.
<el-table
:data="
tableData.slice((currentPage - 1) * pageSize, currentPage * pageSize)
"
style="width: 100%"
:header-cell-style="{
textAlign: 'center',
height: '40px',
}"
:row-style="{height: '60px'}"
stripe
>
<el-table-column label="ID" align="center" prop="id" width="50"></el-table-column>
</el-table>
<!-- 以上并无效果 添加动态利用diff算法,动态刷新表格 添加动态 key 值才生效 -->
<el-table
:data="
tableData.slice((currentPage - 1) * pageSize, currentPage * pageSize)
"
style="width: 100%"
:key="Math.random()"
stripe
>
<el-table-column label="ID" align="center" prop="id" width="50"></el-table-column>
</el-table>