前言
我先夸夸夸!这次的面试体验真的好棒。整个面试的安排、内容、交流都特别舒服!反馈也很快!但还是记录一下一些回答得不是很好的问题
算法题
经典的回文算法题,直接上测试用例了
console.log(isPalindrome('level'));// true
console.log(isPalindrome('hello'));// false
console.log(isPalindrome('helloabbaolleh'));// true
1.循环
双指针
function isPalindrome(str) {
let startIndex = 0,
endIndex = str.length - 1,
result = true;
while (startIndex < endIndex) {
if (str[startIndex] !== str[endIndex]) {
result = false;
break;
}
startIndex++;
endIndex--;
}
return result;
}
js原型方法
function isPalindrome(str) {
let newStr = str.split('');
newStr = newStr.reverse().join('');
return str == newStr
}
reverse()是原地反转的
2.递归
面试官限制了一下不能使用循环和Array原型链的方法,很好的限制,能直接把问题推向另一个解法——递归。
function isPalindrome(str) {
return recursion(str)
}
function recursion(str) {
const length = str.length;
if (length < 2) return true
if (str[0] !== str[length - 1]) return false
return recursion(str.slice(1, -1))
}
复盘的时候想起来不能循环的话应该递归的!我的麻瓜脑子
v-for和v-if的优先级
v-for和v-if写在同一个标签上是vue2的经典坑,v-for的优先级高于v-if
原理
我当时回答的是跟编译时对template的处理生成的渲染函数有关,所以这里我也从渲染函数切入
使用v-for的渲染函数
<template>
<div>
<div v-for="item in list">{{ item.name }}</div>
</div>
</template>
function anonymous(
) {
with(this){return _c('div',_l((list),function(item){return _c('div',[_v(_s(item.name))])}),0)}
}
使用v-if的渲染函数
<template>
<div>
<div v-if="name">{{ name }}</div>
</div>
</template>
function anonymous(
) {
with(this){return _c('div',[(name)?_c('div',[_v(_s(name))]):_e()])}
}
同时使用
<template>
<div>
<div v-for="item in list" v-if="item.name">{{ item.name }}</div>
</div>
</template>
function anonymous(
) {
with(this){return _c('div',_l((list),function(item){return (item.name)?_c('div',[_v(_s(item.name))]):_e()}),0)}
}
通过比较渲染函数,我们可以看到是v-for的逻辑包裹着v-if的。所以当然是v-for优先。
v-for和v-if 在vue2文档中的描述
注意,v-for和v-if不能同时使用在vue2文档中时有相关描述的,但是这个描述记录在 风格指南 中,这样的代码其实是可以渲染的!只是并不推荐这么做,那么vue2推荐用法是什么呢?
<!-- 反例 -->
<ul>
<li v-for="user in users" v-if="user.isActive" :key="user.id">{{ user.name }}</li>
</ul>
<!-- 对应的好例子 -->
<ul>
<li v-for="user in activeUsers" :key="user.id">{{ user.name }}</li>
</ul>
<script>
export default {
// ...
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
}
</script>
注意看,这里是通过computed使得v-for只遍历有效元素,这才是实现 提高渲染性能 的手段。
而不是把代码写成这样:
<ul>
<li v-for="user in users" :key="user.id">
<p v-if="user.isActive">
{{ user.name }}
</p>
</li>
</ul>
这样唯一的用处是逃过eslint的代码检查= =,看下面这个例子就知道为什么了:
<template>
<div>
<div v-for="item in list">
<div v-if="item.name">{{ item.name }}</div>
</div>
</div>
</template>
function anonymous(
) {
with(this){return _c('div',_l((list),function(item){return _c('div',[(item.name)?_c('div',[_v(_s(item.name))]):_e()])}),0)}
}
我们可以看到,在这个例子中,依然遍历了整个list,甚至检查vm._vnode.children,这段代码的vnode结构还要再复杂一点,简直是适得其反。
基于vue2的小结
所以,对于 v-for和v-if不应该一起使用 的正确思考应该是:如果一个地方可以同时使用v-for和v-if时,一定存在可以减少遍历,从而实现性能优化的地方。而不是逃避代码风格审查=-=
vue3
在vue3中,恰恰相反,v-if变得优先级更高了。
这个改变的用意我觉得就在我们前文所述的内容中:在vue2中“不听不听”,也只是代码风格指南得问题,实际渲染并不会报错;但是在vue3中,如果是v-if逻辑优先的话就会出现item未声明的错误了。这将真正引起开发者的重视。
所以我把vue3的内容也加到这里来,以更好地解释这个经典坑。
data选项
为什么data总是写成函数形式,而不是对象形式。当时回答这个问题时,我回答了对象在js中是引用类型会因为内存共享引发问题,以及data在做merge的时候同键名会出现影响。这个回答我并没有很满意。所以在这里复盘一下,应该怎么正确回答。
首先在vue2的文档中是这样描述的:
data选项可以接受
Object和Function,但限制组件只接受function。当一个组件被定义,
data必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。
在这里就涉及到"js中对象是引用类型"这个点,如果不使用函数式的data选项,那么多个组件实例的数据就会串。
而关于merge这块的话,仔细想了下当data是函数时,函数执行获取data对象,依然会出现同键名的merge(常见于Vue.extend()创建的组件构造器函数使用时)。
所以这个问题的核心是:
- 组件会有多个实例
- JS的对象是引用类型,存在内存共享
Vue2父组件和子组件的生命周期执行顺序
这是我基于上一个问题想到的常见衍生问题,一并做下记录:
父beforeCreate -> 父created -> 父beforeMounted -> 子beforeCreate -> 子created- 子beforeMounterd -> 子mounted -> 父mounted
更新时:父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
销毁时:父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
异步任务执行顺序
当时问到这一题的时候是从vue的nextTick衍生的,当时回答的观点集中在:JS代码同步执行,会根据微任务相关API如Promise,queueMicroTask等将任务存入微任务队列,在事件循环的机制下,微任务队列中任务会依次执行。
但其实也可以回答,通过缓存队列的方式,减少创建的微任务数量。具体做法跟vue.nextTick中使用queue数组存储相关内容,仅创建一个微任务即可批量执行多个回调函数。这个在上一篇中有讲到。
总结
这次的面试体验真的很好,复盘以上内容跟也让我觉得很充实。好想去@