数据结构与算法
这一块在笔试、面试的代码题中考核较多,其中常考的数据结构主要有:数组、链表、队列、栈、Set、Map、哈希表等,不同数据结构有不同的方法以及储存原理,这些算是技术岗的必备知识。算法部分主要分为两大块,排序算法与一些其他算法题。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
排序算法根据考频高低主要有:快速排序、归并排序、堆排序、冒泡排序、插入排序、选择排序、希尔排序、桶排序、基数排序、Timsort这十种,这类考核点要么是算法的时间、空间复杂度、稳定度,要么是直接手写代码,故在理解算法原理的同时,对JS语言版的排序算法代码也要加强记忆。
- 二叉树层序遍历
- B 树的特性,B 树和 B+树的区别
- 尾递归
- 如何写一个大数阶乘?递归的方法会出现什么问题?
- 把多维数组变成一维数组的方法
- 知道的排序算法 说一下冒泡快排的原理
- Heap 排序方法的原理?复杂度?
- 几种常见的排序算法,手写
- 数组的去重,尽可能写出多个方法
- 如果有一个大的数组,都是整型,怎么找出最大的前 10 个数
- 知道数据结构里面的常见的数据结构
- 找出数组中第 k 大的数组出现多少次,比如数组【1,2, 4,4,3,5】第二大的数字是 4,出现两次,所以返回 2
- 合并两个有序数组
- 给一个数,去一个已经排好序的数组中寻找这个数的位 置(通过快速查找,二分查找)
可以看出,原有的三项都不变,只是新增了小林这个人,这才是最理想的结果
用index
和用随机数
都是同理,随机数
每次都在变,做不到专一性,很渣男
,也很消耗性能,所以,拒绝渣男
,选择老实人
26. 说说nextTick的用处?
我举个例子,在vue中:
this.name = ‘林三心’
this.age = 18
this.gender = ‘男’
我们修改了三个变量,那问题来了,是每修改一次,DOM就更新一次吗?不是的,Vue采用的是异步更新
的策略,通俗点说就是,同一事件循环内
多次修改,会统一
进行一次视图更新
,这样才能节省性能嘛
看懂了上面,那你应该也看得懂下面的例子了吧:
name: ‘小林’
this.name = ‘林三心’
console.log(this.$refs.testDiv.innerHTML) // 这里是啥呢
答案是“小林”,前面说了,Vue是异步更新
,所以数据一更新,视图却还没更新,所以拿到的还是上一次的旧视图数据,那么想要拿到最新视图数据怎么办呢?
this.name = ‘林三心’
this.$nextTick(() => {
console.log(this.$refs.testDiv.innerHTML) // 林三心
})
27. Vue的SSR是什么?有什么好处?
-
SSR
就是服务端渲染 -
基于
nodejs serve
服务环境开发,所有html
代码在服务端渲染 -
数据返回给前端,然后前端进行“激活”,即可成为浏览器识别的html代码
-
SSR
首次加载更快,有更好的用户体验,有更好的seo优化,因为爬虫能看到整个页面的内容,如果是vue项目,由于数据还要经过解析,这就造成爬虫并不会等待你的数据加载完成,所以其实Vue项目的seo体验并不是很好
最强王者
28. Vue响应式是怎么实现的?
整体思路是数据劫持+观察者模式
对象内部通过 defineReactive
方法,使用 Object.defineProperty
将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现。当页面使用对应属性时,每个属性都拥有自己的dep
属性,存放他所依赖的 watcher
(依赖收集),当属性变化后会通知自己对应的 watcher
去更新(派发更新)。
想详细了解过程,建议阅读我的Vue源码解析系列[5]
const { arrayMethods } = require(‘./array’)
class Observer {
constructor(value) {
Object.defineProperty(value, ‘ob’, {
value: this,
enumerable: false,
writable: true,
configurable: true
})
if(Array.isArray(value)) {
value.proto = arrayMethods
this.observeArray(value)
} else {
this.walk(value)
}
}
walk(data) {
let keys = Object.keys(data)
for(let i = 0; i < keys.length; i++) {
const key = keys[i]
const value = data[key]
defineReactive(data, key, value)
}
}
observeArray(items) {
for(let i = 0; i < items.length; i++) {
observe(items[i])
}
}
}
function defineReactive(data, key, value) {
const childOb = observe(value)
const dep = new Dep()
Object.defineProperty(data, key, {
get() {
console.log(‘获取值’)
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set(newVal) {
if (newVal === value) return
observe(newVal)
value = newVal
dep.notify()
}
})
}
function observe(value) {
if (Object.prototype.toString.call(value) === ‘[object Object]’ || Array.isArray(value)) {
return new Observer(value)
}
}
function dependArray(value) {
for(let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.ob && e.ob.dep.depend()
if (Array.isArray(e)) {
dependArray(e)
}
}
}
// array.js
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
‘push’,
‘pop’,
‘shift’,
‘unshift’,
‘splice’,
‘reverse’,
‘sort’
]
methodsToPatch.forEach(method => {
arrayMethods[method] = function (…args) {
const result = arrayProto[method].apply(this, args)
const ob = this.ob
var inserted
switch (method) {
case ‘push’:
case ‘unshift’:
inserted = args
break;
case ‘splice’:
inserted = args.slice(2)
default:
break;
}
if (inserted) ob.observeArray(inserted)
ob.dep.notify()
return result
}
})
29. 为什么只对对象劫持,而要对数组进行方法重写?
因为对象最多也就几十个属性,拦截起来数量不多,但是数组可能会有几百几千项,拦截起来非常耗性能,所以直接重写数组原型上的方法,是比较节省性能的方案
30. Vue的模板编译原理?
因为这个问题讲起来可能比较长,所以:
建议看我这篇「Vue源码学习\(二\)」你不知道的-模板编译原理[6]
31. Vue的computed和watch的原理?
因为这个问题讲起来可能比较长,所以:
建议看我这篇「Vue源码学习\(四\)」立志写一篇人人都看的懂的computed,watch原理[7]
32. Vue.set方法的原理?
function set(target, key, val) {
// 判断是否是数组
if (Array.isArray(target)) {
// 判断谁大谁小
target.length = Math.max(target.length, key)
// 执行splice
target.splice(key, 1, val)
return val
}
const ob = target.ob
// 如果此对象没有不是响应式对象,直接设置并返回
if (key in target && !(key in target.prototype) || !ob) {
target[key] = val
return val
}
// 否则,新增属性,并响应式处理
defineReactive(target, key, val)
return val
}
33. Vue.delete方法的原理?
function del (target, key) {
// 判断是否为数组
if (Array.isArray(target)) {
// 执行splice
target.splice(key, 1)
return
}
const ob = target.ob
// 对象本身就没有这个属性,直接返回
if (!(key in target)) return
// 否则,删除这个属性
delete target[key]
// 判断是否是响应式对象,不是的话,直接返回
if (!ob) return
// 是的话,删除后要通知视图更新
ob.dep.notify()
}
34. nextTick的原理?
let callbacks = []; //回调函数
let pending = false;
function flushCallbacks() {
pending = false; //把标志还原为false
// 依次执行回调
for (let i = 0; i < callbacks.length; i++) {
callbacksi;
}
}
let timerFunc; //先采用微任务并按照优先级优雅降级的方式实现异步刷新
if (typeof Promise !== “undefined”) {
// 如果支持promise
const p = Promise.resolve();
timerFunc = () => {
p.then(flushCallbacks);
};
} else if (typeof MutationObserver !== “undefined”) {
// MutationObserver 主要是监听dom变化 也是一个异步方法
let counter = 1;
const observer = new MutationObserver(flushCallbacks);
const textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true,
});
timerFunc = () => {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
} else if (typeof setImmediate !== “undefined”) {
// 如果前面都不支持 判断setImmediate
timerFunc = () => {
setImmediate(flushCallbacks);
};
} else {
// 最后降级采用setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
export function nextTick(cb) {
callbacks.push(cb);
if (!pending) {
pending = true;
timerFunc();
}
}
35. key有什么用?说说diff算法吧?
直接看这篇吧:为什么 Vue 中不要用 index 作为 key?(diff 算法详解)[8]
我讲的没他好
冷门的知识点
36. 如果子组件改变props里的数据会发生什么
- 改变的props数据是基本类型
如果修改的是基本类型,则会报错
props: {
num: Number,
}
created() {
this.num = 999
}
0458e2ff1538ee85d42953cec9a94ca.png
- 改变的props数据是引用类型
props: {
item: {
default: () => {},
}
}
created() {
// 不报错,并且父级数据会跟着变
this.item.name = ‘sanxin’;
// 会报错,跟基础类型报错一样
this.item = ‘sss’
},
37. props怎么自定义验证
props: {
num: {
default: 1,
validator: function (value) {
// 返回值为true则验证不通过,报错
return [
1, 2, 3, 4, 5
].indexOf(value) !== -1
}
}
}
38. watch的immediate属性有什么用?
比如平时created时要请求一次数据,并且当搜索值改变,也要请求数据,我们会这么写:
created(){
this.getList()
},
watch: {
searchInputValue(){
this.getList()
}
}
复制代码
使用
immediate
完全可以这么写,当它为true
时,会初始执行一次
watch: {
searchInputValue:{
handler: ‘getList’,
immediate: true
}
}
39. watch监听一个对象时,如何排除某些属性的监听
下面代码是,params发生改变就重新请求数据,无论是a,b,c,d属性改变
data() {
return {
params: {
a: 1,
b: 2,
c: 3,
d: 4
},
};
},
watch: {
params: {
deep: true,
handler() {
this.getList;
},
},
}
但是如果我只想要a,b改变时重新请求,c,d改变时不重新请求呢?
mounted() {
Object.keys(this.params)
.filter(() => ![“c”, “d”].includes()) // 排除对c,d属性的监听
.forEach((_) => {
this.$watch((vm) => vm.params[_], handler, {
deep: true,
});
});
},
data() {
return {
params: {
a: 1,
b: 2,
c: 3,
d: 4
},
};
},
watch: {
params: {
deep: true,
handler() {
this.getList;
},
},
}
40. 审查元素时发现data-v-xxxxx,这是啥?
image.png
这是在标记vue文件中css时使用scoped标记产生的,因为要保证各文件中的css不相互影响,给每个component都做了唯一的标记,所以每引入一个component就会出现一个新的’data-v-xxx’标记
41. computed如何实现传参?
// html
// js
computed: {
total() {
return function(n) {
return n * this.num
}
},
}
42. vue的hook的使用
- 同一组件中使用
这是我们常用的使用定时器的方式
export default{
data(){
timer:null
},
mounted(){
this.timer = setInterval(()=>{
//具体执行内容
console.log(‘1’);
},1000);
}
beforeDestory(){
clearInterval(this.timer);
this.timer = null;
}
}
上面做法不好的地方在于:得全局多定义一个timer变量,可以使用hook这么做:
export default{
methods:{
fn(){
const timer = setInterval(()=>{
//具体执行代码
console.log(‘1’);
},1000);
this.$once(‘hook:beforeDestroy’,()=>{
clearInterval(timer);
timer = null;
})
}
}
}
- 7.2 父子组件使用
如果子组件需要在mounted时触发父组件的某一个函数,平时都会这么写:
//父组件
<rl-child @childMounted=“childMountedHandle”
/>
method () {
childMountedHandle() {
// do something…
}
},
// 子组件
mounted () {
this.$emit(‘childMounted’)
},
使用hook的话可以更方便:
//父组件
<rl-child @hook:mounted=“childMountedHandle”
/>
method () {
childMountedHandle() {
// do something…
}
},
43. provide和inject是响应式的吗?
// 祖先组件
provide(){
return {
// keyName: { name: this.name }, // value 是对象才能实现响应式,也就是引用类型
keyName: this.changeValue // 通过函数的方式也可以[注意,这里是把函数作为value,而不是this.changeValue()]
// keyName: ‘test’ value 如果是基本类型,就无法实现响应式
}
},
data(){
return {
name:‘张三’
}
},
methods: {
changeValue(){
this.name = ‘改变后的名字-李四’
}
}
// 后代组件
inject:[‘keyName’]
create(){
console.log(this.keyName) // 改变后的名字-李四
}
44.Vue的el属性和$mount优先级?
比如下面这种情况,Vue会渲染到哪个节点上
new Vue({
router,
store,
el: ‘#app’,
render: h => h(App)
}).$mount(‘#ggg’)
这是官方的一张图,可以看出
el
和$mount
同时存在时,el优先级
>$mount
image.png
45. 动态指令和参数使用过吗?
…
<aButton @[someEvent]=“handleSomeEvent()” :[someProps]=“1000” />…
46. 相同的路由组件如何重新渲染?
开发人员经常遇到的情况是,多个路由解析为同一个Vue组件。问题是,Vue出于性能原因,默认情况下共享组件将不会重新渲染,如果你尝试在使用相同组件的路由之间进行切换,则不会发生任何变化。
const routes = [
{
path: “/a”,
component: MyComponent
},
{
path: “/b”,
component: MyComponent
},
];
如果依然想重新渲染,怎么办呢?可以使用
key
47. 自定义v-model
默认情况下,v-model 是 @input 事件侦听器和 :value 属性上的语法糖。但是,你可以在你的Vue组件中指定一个模型属性来定义使用什么事件和value属性——非常棒!
export default: {
model: {
event: ‘change’,
prop: ‘checked’
}
最后
喜欢的话别忘了关注、点赞哦~
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
这是官方的一张图,可以看出
el
和$mount
同时存在时,el优先级
>$mount
image.png
45. 动态指令和参数使用过吗?
…
<aButton @[someEvent]=“handleSomeEvent()” :[someProps]=“1000” />…
46. 相同的路由组件如何重新渲染?
开发人员经常遇到的情况是,多个路由解析为同一个Vue组件。问题是,Vue出于性能原因,默认情况下共享组件将不会重新渲染,如果你尝试在使用相同组件的路由之间进行切换,则不会发生任何变化。
const routes = [
{
path: “/a”,
component: MyComponent
},
{
path: “/b”,
component: MyComponent
},
];
如果依然想重新渲染,怎么办呢?可以使用
key
47. 自定义v-model
默认情况下,v-model 是 @input 事件侦听器和 :value 属性上的语法糖。但是,你可以在你的Vue组件中指定一个模型属性来定义使用什么事件和value属性——非常棒!
export default: {
model: {
event: ‘change’,
prop: ‘checked’
}
最后
喜欢的话别忘了关注、点赞哦~
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】