Skip to content
On this page

参加【成电智创】公司面试,主要使用Vue2框架,招的也是高级工程师,整个面试过程经历如下:

  • 首先到前台报告,然后要求填写相关个人信息(基本信息、工作经历、家庭情况)等
  • 然后就是技术面试

技术面试大致问了如下一系列问题,其中问题包括基础、进阶、高级、实际应用几大类(记不清哪些属于什么类了):

技术面试题

箭头函数和普通函数的区别?

  • 箭头函数的this指向,是定义时所在的对象,而不是使用时所在的对象;
  • 箭头函数不能使用callapplybind来改变this指向;
  • 箭头函数不能当做构造函数,即不能使用new命令;
  • 箭头函数不能使用arguments对象,可以使用rest参数代替;
  • 箭头函数不能用作Generator函数,故不可以使用yield命令;
  • 箭头函数没有原型对象prototype

Map和WeakMap的区别?

  • Map和WeakMap都是ES6中新增的数据类型,用于存储键值对;
  • Map是一个普通的键值对集合,可以使用任何类型的值作为键或值,包括基本数据类型和引用数据类型;
  • 而WeakMap的键则必须是对象,值可以是任何类型;
  • Map中的键值对是强引用关系,即只要Map的键或者值存在,Map对象就会一直保留这个键值对,不会被垃圾回收机制回收;
  • 而WeakMap中的键是弱引用,所以只要WeakMap中的键不再被引用,那这个键值对就会被自动删除;
  • 由于WeakMap的键值对是弱引用,所以WeakMap对象的size属性无法访问,也无法使用forEachkeysvaluesentries以及clear方法;
    • 如果在WeakMao中使用某个对象的引用作为键时,那如果这个对象被回收后,对应的键值对也会被自动删除。
  • WeakMap对象的主要使用场景是需要与另外一个对象关联,并且不影响到这个对象的垃圾回收机制,以避免内存泄漏。
    • 例如: 将对象存储在WeakMap中,可以确保这个对象只在其他地方仍然被引用时才被保留,否则会自动删除,以此避免内存泄漏。

总结: WeakMap适用于需要存储私有数据或者需要临时存储数据的场景,而Map则适用于需要存储和访问多个数据的场景

Vue3中的响应式数据依赖收集,将依赖存储在依赖列表中,这个依赖列表就是WeakMap集合,WeakMap的键是当前的响应式对象,值为一个Map集合,该Map集合中的键位当前响应式对象的属性,值为Set集合,而Set中存储的优势当前响应式对象属性的所有副作用函数。

Set和WeakSet的区别?

  • Set和WeakSet都是ES6中新增的数据类型,是一种叫做集合的数据结构;
  • Set类似于数组,但成员是唯一且无序的,没有重复的值,可以存储任何类型的值,包括基本数据类型和引用数据类型;
  • 而WeakSet则是只能存储引用数据类型的值,不能存储其他类型;
  • WeakSet是弱引用,在WeakSet中引用了对象,垃圾回收机制不会考虑该对象,若外面已经没有地方引用该对象,则该对象会被垃圾回收机制回收,进而WeakSet内部引用的该对象也会自动消失;
  • WeakSet在初始化时是不能赋值的,必须通过add方法来添加值;
  • Set拥有addhasdeleteclearsizekeysvaluesentriesforEach方法和属性。
  • 而SeakSet只有addhasdelete方法,没有sizekeysvalues等方法和属性;

Set和Map主要的应用场景在于数据重组数据储存Set是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构

说说你对闭包的理解?

闭包就是能够读取其他函数内部变量的函数,起作用包括如下:

  • 能够读取函数内部的变量;
  • 让这些变量的值始终保持在内存中;

命名空间是闭包吗?有什么缺陷?

命名空间是一种组织和管理代码的方式,可以避免不同模块之间的命名冲突,提高代码的可维护性和可读性。在JavaScript中,实现命名空间可以通过对象字面量和模块化的方式来完成。

  • 使用对象字面量创建命名空间
js
var MyNamespace = {
  // 在命名空间中定义变量
  myVariable: 123,
 
  // 在命名空间中定义函数
  myFunction: function() {
    // 函数实现
  }
};
  • 使用模块化方式创建命名空间
js
var MyNamespace = (function() {
  // 在命名空间中定义私有变量和函数
  var privateVariable = 456;
 
  function privateFunction() {
    // 私有函数实现
  }
 
  // 返回公共接口
  return {
    // 在命名空间中暴露的公共变量
    publicVariable: 789,
 
    // 在命名空间中暴露的公共函数
    publicFunction: function() {
      // 公共函数实现
    }
  };
})();

Object.create(null)会怎样?

  • 使用Object.create(null)产生的对象是一个没有原型的对象,相对于直接使用{}产生的对象,后者会产生一个原型链接到Object.prototype

前者相当于一个纯净的map不会有Object.prototype的属性或者方法污染,自然前者遍历的时候也不需要使用hasOwnProperty来判断当前属性是否为自有的。

  • Object.create函数的作用就是创建一个对象,并且让对象的原型为函数提供的参数。

null作为一个对象,它是Object.prototype的原型,通过下面的代码可以验证,而现有的很多方法例如toStringvalueOf等都是来自于Object.prototype,因此如果对象的原型时null,那么这个对象将不包含任何Object.prototype的函数和变量。

手写实现:

js
// create并不是在原型上的,直接判断是否有这个方法
if (typeof Object.create !== "function") {
  Object.create = function(proto, properties) {
    // 简单的实现的过程,忽略了properties
    var func = function() {};
    func.prototype = proto; // 将fn的原型指向传入的proto
    return new func();  // 返回创建的新对象
  };
}

setTimeout(1000)真就会在一秒后执行吗,执行时间是大于1s还是怎样?

讲讲什么是跨域,如何解决跨域?小程序是否会跨域?

跨域就需要先了解同源策略....

小程序会跨域,可以在小程序管理端进行配置,进入【小程序后台 -> 开发 -> 服务器域名】中进行配置。

rem的计算规则?

rem是相对于根节点来计算的,例如在html标签中设置了font-size: 16px,那么1rem = 16px

讲讲原型和原型链?

原型 在JS中,我们所创建的每一个函数都自带一个属性prototype,它是一个对象。通过该函数实例化出来的对象都可以继承得到原型上的所有属性和方法,这里的prototype就是原型。

原型链 当访问一个对象的属性时,会现在对象本身上查找,如果这个对象本身找不到这个属性,就会通过对象__proto__属性指向函数的原型对象(函数.prototype)一层一层往上找,直到找到Object的原型对象(Object.prototype)为止,层层继承的链接结构叫做原型链。

js事件循环机制?

JavaScript的运行机制基于事件循环(Event Loop),这是一个持续运行的过程,负责处理和执行JavaScript代码。主线程从任务队列中读取事件,这个过程是循环不断地,所以整个的这种机制又称为Event Loop(事件循环),只要主线程空了,就会读取任务队列,这个过程会循环反复。

微任务和宏任务的区别?

微任务 微任务是指那些在当前任务执行结束后立即执行的任务,它们可以被看作是在当前任务的“尾巴”上添加的任务。微任务的执行优先级高于宏任务,即在一个宏任务结束后,会立即执行所有的微任务,然后再开始下一个宏任务。

宏任务 宏任务是指那些需要在JavaScript引擎的空闲时间才能执行的任务,每当一个宏任务开始时,它都会执行宏任务队列中的一个任务。当宏任务执行完毕后,会查看微任务队列是否有任务,如果有,就会执行所有的微任务。

promise.resolve是宏任务还是微任务?

Promise.resolve属于宏任务。

有哪些操作会导致内存溢出?

  • 意外全局变量
  • 闭包引起的内存泄漏
  • 被遗忘的计时器 / 回调函数
  • 没有清理的Dom元素引用
  • 无限的循环和递归,或者叫死循环

vue2数据响应式实现原理?

Vuejs的响应式设计是基于数据劫持+发布订阅模式来实现的,通过遍历data中的属性,并通过Object.defineProperty()方法来劫持各个属性的gettersetter,并在其内部追踪相关依赖,当属性被访问或修改时发布消息给所有依赖重新计算或更新。

简单来说就是当响应式对象被访问时就会触发getter,然后在getter中记录下哪些地方使用了该数据,并将这些使用者存放进依赖列表中,当数据发生变更时,就会触发setter,在setter中将更新操作消息发布依赖列表中的所有依赖项重新计算或更新。

vue2中如何判断一个对象是否为响应式?

可以通过__ob__来判断。

js
import Vue from 'vue'
export default {
  data() {
    return {
      obj: {
        name: '张十八'
      }
    };
  },
  created () {
    console.log(this.obj.__ob__) // 输出的东西就是Vue.observe
  }
};

vue3响应式实现原理

vue3中主要是采取的ES6中的Proxy来进行数据的代理,通过reflect反射来实现动态的数据响应式。

  • 通过Proxy(代理):拦截对象中任意属性的变化,属性值的读写,属性的增加,属性的删除等。
  • 通过Reffect(反射):对源对象的属性进行操作。

如何将一个list数据处理为不是响应式的?

  • 方法一:将数据定义在data中
js
data () {
    this.list1 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list2 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list3 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list4 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list5 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    return {}
 }
  • 方法二:使用Object.freeze()冻结对象,禁止对该对象的属性进行修改
js
data () {
    return {
        list1: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list2: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list3: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list4: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list5: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
    }
 }
  • 方法3:数据放在vue实例外(该方式在template中访问不到)
js
const test = ['1', '2', '3']
export default {
// options
}
  • 方式4:在createdmounted钩子函数中定义
js
created() {
    // 注意data中不要声明该变量名
    this.testData = 'testData'
}
  • 方式5:自定义Options
html
<template>
  <div>{{ $options.myOptions.test }}</div>
</template>
<script>
export default {
    data() {
    },
    // 自定义options和data同级
    // 取值时: this.$options.myOptions.test
    myOptions: {
        test: '111'
    }
}
<script>

虚拟列表的理解?

虚拟列表(Virtual List)是一种用于优化长列表渲染性能的技术。在Web应用或移动应用中,当需要展示大量数据时,如果一次性渲染所有数据,可能会导致性能问题,如页面加载缓慢、滚动不流畅等。虚拟列表通过只渲染当前可见区域的数据,而不是一次性渲染所有数据,从而大大提高了性能。

虚拟列表的工作原理:它根据当前滚动位置计算可见区域的范围,然后只渲染这个范围内的数据,当滚动发生时,虚拟列表会更新可见区域的范围,并重新渲染新的数据。这样只有少数数据元素在任何给定时间都是“真实的”或“活动的”,而其他元素都是“虚拟的”,只存在于计算中,并不真正渲染到DOM中。

虚拟列表的优点包括:

  • 提高性能:通过减少同时渲染的数据量,虚拟列表可以显著提高页面加载速度和滚动流畅性。
  • 优化内存使用:由于只渲染可见区域的数据,虚拟列表可以降低内存使用量,这对于处理大量数据或移动设备上的应用尤为重要。
  • 支持无限滚动:虚拟列表可以很容易地实现无限滚动功能,因为它只需要根据滚动位置动态加载和渲染数据。

虚拟列表的实现通常涉及以下几个步骤:

  • 计算可见区域:根据滚动位置和列表高度计算当前可见区域的范围。
  • 渲染可见数据:只渲染可见区域内的数据,通常使用虚拟DOM技术来提高渲染性能。
  • 监听滚动事件:监听滚动事件,当滚动发生时更新可见区域的范围,并重新渲染新的数据。

nextTick实现原理?是否支持await语法?底层使用的setTimeout来兜底之类的?

Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。Vue在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。

所以如果你用一个for循环来动态改变数据100次,实际上它只会应用最后一次改变,如果没有这种机制,DOM就进行 100 次的重绘,这固然是一个很大的开销。

nextTick官方的定义 在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,获取更新后的DOM。我们可以理解成Vue在更新DOM时是异步执行的,当数据发生变化,Vue将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新。 https://blog.csdn.net/weixin_45948566/article/details/112849435

支持await语法:需要将nextTick调用放在一个async函数中。

html
<template>
  <div>
    <p ref="myParagraph">这是一个段落。</p>
    <button @click="updateParagraph">更新段落</button>
  </div>
</template>
 
<script>
export default {
  methods: {
    async updateParagraph() {
      this.$refs.myParagraph.textContent = '正在更新...';
      await this.$nextTick(); // 等待DOM更新完成
      this.$refs.myParagraph.textContent = '更新完毕。';
    }
  }
}
</script>

vue3之Teleport的好处?

Teleport是一个内置组件,它的作用是将组件的内容渲染到 DOM 中的任何位置。使用teleport可以轻松实现在组件内部定义的内容在 DOM 树的其他位置动态渲染,而不受组件嵌套的限制。

使用场景如处理弹出框、模态框、通知栏等需要将组件内容挂载到 DOM 结构中其他位置。

html
<teleport to="目标选择器">
  <!-- 要移动的内容 -->
</teleport>

teleport组件的简化实现原理:

  • 在teleport组件的mounted钩子中,会使用createTeleport函数创建一个 Teleport实例,并将目标选择器传递给它。
  • 在createTeleport函数内部,它会通过querySelector查找目标选择器对应的 DOM 元素。
  • Teleport实例会监听到内容的变化,一旦内容发生变化,它就会将内容移动到目标位置。
  • 当teleport组件销毁时,它会清理相应的资源,确保不会造成内存泄漏。

keep-alive是什么?有什么作用?使用场景有哪些?底层实现原理?对生命周期的影响?

keep-alive组件是Vue的一个内置组件,它可以将其包裹的组件进行缓存,提高组件的性能,同时也可以节省服务器资源的消耗。

作用 keep-alive组件的主要作用就是将需要缓存的组件进行缓存,当组件被切换时,它会将之前缓存的组件重新渲染到页面上,而不会再重新创建新的组件实例,这种缓存机制可以极大地提高页面的加载速度和响应速度。

使用场景

  • 缓存组件
  • 缓存路由
  • 缓存表单数据

实现原理 keep-alive组件利用了其中的两个生命周期钩子函数:activateddeactivated

  • activated函数会在组件被渲染到页面上之后调用,而deactivated函数会在组件被从页面上移除之后调用。当我们将组件包裹在keep-alive组件中时,这两个生命周期钩子函数就会被触发。
  • activated函数中,keep-alive组件会将之前缓存的组件重新渲染到页面上,而不会重新创建实例。

    这是因为keep-alive组件使用了LRU(Least Recently Used)算法来管理缓存的组件实例,当缓存的组件数量超过一定的阈值时,较早使用的组件会被销毁,释放内存空间。

  • deactivated函数中,keep-alive组件会将当前的组件实例保存到缓存中,不会被销毁。这样当组件再次被激活时,可以直接从缓存中取出组件实例,而不需要重新创建。

LRU最近最少使用算法?

LRU最近最少会用算法是一种典型的内存管理算法,又被成为淘汰算法。 在缓存满时,将最近最久未使用的数据淘汰出缓存,以便给新的数据留出空间。

  • 最近被使用或访问的数据放置在最前面;
  • 每当缓存命中(即缓存数据被访问)则将数据移到头部;
  • 当缓存数量达到最大值时,将最近最少访问的数据剔除;

vue-loader是如何将vue组件加载的?实现原理?

https://juejin.cn/post/7038959467408392205

  1. 解析.vue文件:vue-loader首先会解析.vue文件,提取出其中的模板、脚本和样式信息。
  2. 模板编译:如果有模板,它会用vue-template-compiler编译成JavaScript渲染函数。
  3. 脚本编译:如果有脚本部分,它会用Webpack配置中指定的加载器(如 babel-loader)来编译JavaScript代码。
  4. 样式编译:如果有样式,它会用相应的加载器(如css-loader和sass-loader)来编译样式代码。
  5. 生成CommonJS模块:最后vue-loader会将编译后的模板、编译过的脚本和编译过的样式组合成一个JS模块,这个模块可以被Webpack进一步处理和打包。

讲讲monorepo模式?

Monorepo 是一种项目代码管理方式,指单个仓库中管理多个项目,有助于简化代码共享、版本控制、构建和部署等方面的复杂性,并提供更好的可重用性和协作性。

Released under the MIT License.