likes
comments
collection
share

第一次面试的深刻回忆

作者站长头像
站长
· 阅读数 15

前些天尝试着投了些简历,约到了场面试,回忆起学长学姐讲述面试官的可怕,面试前我内心十分害怕,紧张万分。说实话,总觉得面试是很遥远的事情,但不知不觉你已经走到这一步,感觉一切都始料不及。不过还好,我的面试跟我想象的不太一样,面试官其实并没有那么可怕,他根据你的项目来进行提问,以及一些遇到实际问题,以你现在的技能,你该如何处理。当然,有的基础的,常见的他也会问。

css 篇

1.用flex 有三个盒子 左右两个 宽度固定 实现中间盒子的宽度自适应

这个题就很简单了,我们只需要父容器 display: flex 中间的盒子 flex: 1即可。紧接着面试官就问:flex的属性有哪些?同样也简单

  1. display

display 属性必须设置为 flexinline-flex,才能开启 Flexbox 布局。

  1. flex-direction

flex-direction 属性控制主轴方向,默认值为 row(从左到右布局)。可以使用以下取值:

  • row: 主轴从左到右。
  • row-reverse: 主轴从右到左。
  • column: 主轴从上到下。
  • column-reverse: 主轴从下到上。
  1. flex-wrap

flex-wrap 属性控制是否换行,默认情况下项目都排在一条线上,可以使用以下取值:

  • nowrap: 不换行。
  • wrap: 换行,第一行在上方。
  • wrap-reverse: 换行,第一行在下方。
  1. justify-content

justify-content 属性控制项目在主轴上的对齐方式,默认为 flex-start,即左对齐。可以使用以下取值:

  • flex-start: 左对齐。
  • flex-end: 右对齐。
  • center: 居中对齐。
  • space-between: 两端对齐,项目之间的间隔相等。
  • space-around: 项目分布在轴线上,两侧间隔相等。
  1. align-items

align-items 属性控制项目在交叉轴上的对齐方式,默认为 stretch,即将项目拉伸至与容器一样高。可以使用以下取值:

  • stretch: 拉伸(默认值)。
  • flex-start: 交叉轴起点对齐。
  • flex-end: 交叉轴终点对齐。
  • center: 居中对齐。
  • baseline: 基线对齐。

还有一些其他的属性,例如 orderflex-growflex-shrink 等等,这里就不一一列举了。 当然我当时回答是时候并没有这么详细,有些当时记不太清了。

2. 盒子的横向排列有哪些方法

  1. 使用 float 属性

float 属性可以让元素浮动到容器的左侧或右侧,从而实现横向排列的效果。

.box {
  float: left;
}

需要注意的是,当使用 float 属性时,需要清除浮动以防止产生布局上的问题。

  1. 使用 display: inline-block

display: inline-block 属性可以让元素表现为块级元素和行内元素的混合形态,从而实现横向排列的效果。

.box {
  display: inline-block;
}

需要注意的是,由于 inline-block 元素会受到字体大小等因素的影响,可能会产生不必要的间距,可以通过调整 HTML 结构或者使用负的 margin 属性来解决。

  1. 使用 flexbox

flexbox 是一种强大的布局方式,可以轻松地实现盒子的横向排列。(flex默认是横向排列)

.container {
  display: flex;
}

.box {
  flex: 1; /* 让所有的盒子自适应宽度 */
}

需要注意的是,如果要控制每个盒子的宽度和间距,需要设置 flex-basisflex-growflex-shrink 等属性。

  1. 使用 grid

grid 是一种新的布局方式,可以通过网格状的列和行将页面划分为不同的区域,从而实现盒子的横向排列。

.container {
  display: grid;
  grid-template-columns: repeat(4, 1fr); /* 将容器分成四个等宽的列 */
}

.box {
  grid-column: span 1; /* 控制每个盒子的跨度 */
}

需要注意的是,由于 grid 的兼容性问题,需要添加浏览器前缀或者使用其他方式来解决。

3. 介绍lib-flexible,自己要实现怎么办

因为面试官看到了我项目中使用了lib-flexible来实现移动端适配。

lib-flexible 是一款用于淘宝移动端适配的 JavaScript 库,能够根据屏幕大小和像素比例动态计算 <meta> 标签中 viewport 的宽度,并将其设置为 1/10 设备宽度的值。这样做可以让页面元素基于设计稿的实际尺寸进行布局,避免出现错位或者变形等问题。

使用 lib-flexible 可以方便地在不同分辨率的设备上提供良好的显示效果,并且能够自动适应新型设备的出现,同时也避免了手动计算和重新调整尺寸的繁琐过程。

自己实现:

如果需要自己实现一个类似 lib-flexible 的适配方案,可以参考以下步骤:

  1. 获取设备的宽度信息

可以通过 JavaScript 中的 window.screen.widthwindow.devicePixelRatio 属性来获取设备的宽度和像素比例信息。需要注意的是,由于浏览器可能会对这些属性进行缩放,因此可能需要使用一些诸如 window.innerWidth 等方法来获取准确的数值。

  1. 动态计算 viewport 的宽度

根据设计稿的尺寸和像素密度,可以通过公式 viewportWidth = designWidth * devicePixelRatio / 10 来动态计算出 <meta> 标签中 viewport 的宽度,并将其设置为文档头部的元数据。

<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<script>
  // 计算 viewport 宽度并设置 meta 标签
  let dpr = window.devicePixelRatio || 1;
  let vw = window.screen.width;
  document.querySelector('meta[name="viewport"]').setAttribute(
    'content',
    `width=${vw*dpr/10},initial-scale=${1/dpr},maximum-scale=${1/dpr},minimum-scale=${1/dpr},user-scalable=no`
  );
</script>

其中,initial-scale 表示初始缩放比例,maximum-scaleminimum-scale 都设置为 1/dp,即默认不允许用户手动缩放。

  1. 根据屏幕大小设置字体大小

<html> 元素上设置一个基础字体大小,然后配合 rem 单位来实现元素的自适应。

<style>
  /* 设置 <html> 的基础字体大小 */
  html {
    font-size: calc(100vw / (designWidth / 10));
  }

  /* 定义样式 */
  .box {
    width: 3.75rem;   /* 假设设计稿宽度为375px */
    height: 1.25rem;
  }
</style>

<body>
  <div class="box"></div>
</body>

其中,calc() 函数可以帮助计算得到基础字体大小,通过将设备宽度除以设计稿宽度再除以 10 得到,而元素的大小则使用 rem 单位设置,保证其相对于基础字体的正确性。

需要注意的是,以上只是基本实现方法,还有很多其他细节和效果需要根据具体需求来调整。同时也要注意可能存在的兼容性问题和不同浏览器的行为差异。

js 篇

1. js 的数据类型有哪些,类型判断的方法

  • 基本数据类型(值类型):NumberStringBooleanNullUndefinedSymbolBigInt。保存在栈内存中。

  • 复杂数据类型(引用类型):ObjectFunctionArrayRegExpDate 基本包装类型及单体内置对象(GlobalMath)等。

类型判断方法:

在 JavaScript 中,有多种方式可以判断数据类型,包括以下:

  1. typeof

typeof 操作符可以返回一个值的字符串表示形式,代表其对应的基本数据类型。但是 typeof 在某些情况下有一些奇怪的行为,例如将数组视为 object 类型,把 null 视为对象。使用时需要注意。

typeof 18     // 'number'
typeof 'hello'   // 'string'
typeof true    // 'boolean'
typeof undefined   // 'undefined'
typeof Symbol() // 'symbol'
typeof null    // 'object'
typeof []      // 'object'
typeof {}      // 'object'
typeof console // 'object' 
typeof console.log // 'function'
  1. instanceof

instanceof 操作符用于检查特定对象是否属于某个类或者构造函数的原型链上。它适用于自定义类型的检测,但不适用于基本类型和内置对象类型的检测。

new Date() instanceof Date   // true
[] instanceof Array     // true
{} instanceof Object    // true
true instanceof Boolean; // false 
'str' instanceof String; // false 
function(){} instanceof Function; // true
'hello' instanceof String   // false
18 instanceof Number     // false
  1. Object.prototype.toString()

每个对象都有一个原型链,可以调用 Object.prototype.toString() 方法,获取对象的内部属性 [[Class]] 的值,从而判断其类型。

Object.prototype.toString.call(42)     // "[object Number]"
Object.prototype.toString.call('hello')   // "[object String]"
Object.prototype.toString.call(true)    // "[object Boolean]"
Object.prototype.toString.call(undefined)   // "[object Undefined]"
Object.prototype.toString.call(null)    // "[object Null]"
Object.prototype.toString.call([])      // "[object Array]"
Object.prototype.toString.call({})      // "[object Object]"

需要注意的是,使用 Object.prototype.toString() 的方式可以准确判断变量类型,但也存在一些小问题,例如无法准确判断某些对象是否为继承自某个内置类或构造函数的子类对象等情况。

  1. Array.isArray()

Array.isArray() 方法用于检测一个值是否为数组。它只能用于判断对象是否为数组,而不能用于其他类型。

Array.isArray([])   // true
Array.isArray({})   // false
Array.isArray(42)   // false

2. js 的数组遍历的方法

  1. for 循环

使用传统的 for 循环可以遍历数组中的所有元素。

const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
  1. forEach 方法

forEach() 方法用于遍历数组,接收一个回调函数作为参数。该回调函数接收当前元素、当前元素的索引和整个数组作为参数。

const arr = [1, 2, 3];
arr.forEach((item, index, array) => {
  console.log(item);
});
  1. map 方法

map() 方法返回一个新数组,其结果是对原始数组中每个元素进行指定函数的操作后得到的值。该函数接收三个参数:当前元素,当前元素索引和整个数组。

const arr = [1, 2, 3];
const newArr = arr.map((item, index, array) => {
  return item * 2;
});
console.log(newArr);
// output: [2, 4, 6]
  1. filter 方法

filter() 方法用于过滤数组中的元素,并返回符合条件的元素所组成的新数组。该函数接收三个参数:当前元素,当前元素索引和整个数组。

const arr = [1, 2, 3, 4, 5];
const newArr = arr.filter((item, index, array) => {
  return item > 2;
});
console.log(newArr);
// output: [3, 4, 5]
  1. reduce 方法

reduce() 方法依次对数组中的所有元素执行指定函数,并返回一个累加值。该函数接收两个参数:归并函数和初始值。

const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((acc, cur, index, array) => {
  return acc + cur;
}, 0);
console.log(sum);
// output: 15
  1. for...of 循环

使用 for...of 循环可以遍历数组中的所有元素,将当前元素赋值给一个变量。该方法不能获取当前元素的索引信息,适用于只需要访问元素值的场景。

const arr = [1, 2, 3];
for (const item of arr) {
  console.log(item);
}
  1. for...in 循环

for...in 循环遍历对象的属性名称或数组的索引。该方法也可以用于遍历数组,但是不是推荐的做法,因为它会遍历所有可枚举属性,包括原型链上的属性和一些非数值类型的属性。

const arr = [1, 2, 3];
for (const index in arr) {
  console.log(index, arr[index]);
}

3. 你对 js 的 this 关键字的理解以及 this 的指向问题

在 JavaScript 中,this 关键字指向当前执行代码的对象,其值在函数被调用时确定。具体表现如下:

  1. 全局环境中,this 指向 window 对象。
console.log(this === window); // output: true
  1. 函数内部,this 的值取决于函数的调用方式和上下文环境。

(1)作为普通函数调用,this 指向全局对象或 undefined(严格模式下)。

function test() {
  console.log(this);
}
test(); // output: window

"use strict";
function test() {
  console.log(this);
}
test(); // output: undefined

(2)作为对象的方法调用,this 指向调用该方法的对象。

const obj = {
  name: "John",
  sayName: function () {
    console.log(this.name);
  },
};
obj.sayName(); // output: John

(3)作为构造函数调用,this 指向新创建的实例对象。

function Person(name) {
  this.name = name;
  this.sayName = function () {
    console.log(this.name);
  };
}
const john = new Person("John");
john.sayName(); // output: John

(4)使用 call()apply() 方法调用,this 指向第一个参数所指定的对象。

const obj = {
  name: "John",
};
function test() {
  console.log(this.name);
}
test.call(obj); // output: John

(5)使用 bind() 方法返回一个新函数,其 this 值被绑定为第一个参数所指定的对象。

const obj = {
  name: "John",
};
function test() {
  console.log(this.name);
}
const boundTest = test.bind(obj);
boundTest(); // output: John

4. js 中获取元素的方法,和原生js 的增删改查

(一)、获取元素

  1. 通过 id 属性获取元素:使用 document.getElementById(id) 方法可以获得拥有指定 id 属性的元素对象。
const myElement = document.getElementById("my-element");
  1. 通过标签名获取元素:使用 document.getElementsByTagName(tagname) 方法可以获取所有指定标签名的元素集合。
const myElements = document.getElementsByTagName("div");
  1. 通过类名获取元素:使用 document.getElementsByClassName(classname) 方法可以获取所有指定类名的元素集合。
const myElements = document.getElementsByClassName("my-class");
  1. 通过选择器获取元素:通过使用 document.querySelector(selector)document.querySelectorAll(selector) 方法,可以根据 CSS 选择器获取符合条件的第一个元素或所有元素。
const myElement1 = document.querySelector("#my-div");
const myElements2 = document.querySelectorAll(".my-class");
  1. 通过父元素和位置关系获取子元素:使用 parentElement.childNodes[index]parentElement.firstChildparentElement.lastChild 可以获取指定父元素下的指定位置的子元素或第一个/最后一个子元素。
const parent = document.getElementById("parent");
const child1 = parent.childNodes[0];
const child2 = parent.firstChild;
const child3 = parent.lastChild;

需要注意的是,某些方法返回的是类数组的实例,需要通过 Array.from()Array.prototype.slice.call() 方法将其转换成数组才能使用数组的方法。

(二)、增删改查

  1. 增加新元素:使用 document.createElement(tagname) 方法创建新的元素节点,并使用 element.appendChild(newNode)element.insertBefore(newNode, refNode) 方法将其添加到页面中。

  2. 删除元素:使用 element.removeChild(child) 方法移除指定元素的子节点或 element.remove() 方法直接移除该元素本身。

  3. 修改元素内容和属性:使用 element.innerHTMLelement.innerTextelement.setAttribute(attr, value) 分别修改元素的 HTML 内容、纯文本内容和指定属性的值。

  4. 查询元素信息:使用 document.getElementById(id)document.getElementsByTagName(name)document.getElementsByClassName(name)document.querySelector(selector)document.querySelectorAll(selector) 等方法查询元素信息,并使用元素的属性和方法获取其更多信息。

具体的例子这里就不写了,(问这个我是没想到的)但也确实回答的不是很全面,只记得常见的一些。

vue 篇

1. v-for和v-if优先级的问题,在vue2 , vue3 中(这里面试官没问为什么v-for 和 v-if 不建议一起使用)

Vue 2 中,v-for的优先级比v-if高,这意味着v-if将分别重复运行于每一个v-for循环中。如果要遍历的数组很大,而真正要展示的数据很少时,将造成很大的性能浪费。

Vue 3 中,则完全相反,v-if的优先级高于v-for,所以v-if执行时,它调用的变量还不存在,会导致异常。

在 Vue 中,v-ifv-for 是两个常用的指令,但不建议将它们一起使用,主要有以下几点原因:

  1. 逻辑混乱:将 v-ifv-for 放在一起使用时,很容易造成代码逻辑的混乱。因为 v-if 的作用是根据条件控制元素是否渲染,并且它可以存在于同一个元素上或其父元素上;而 v-for 的作用是遍历数据并渲染元素,并且它只能存在于元素上。将两者一起使用时,会让代码更难以理解和调试。

  2. 效率低下:将 v-ifv-for 放在一起使用时,可能会导致不必要的计算开销和性能问题。因为当 v-if 条件不成立时,会在渲染时直接跳过整个列表遍历循环。然而,如果在每次循环中都要计算一次 v-if 表达式的值,就会加重计算的负担,降低性能。

  3. 可读性差:使用 v-ifv-for 描述复杂嵌套关系时,代码可读性会变得很差。尤其是针对初学者来说,一旦代码中存在复杂的嵌套和逻辑,就会容易出现错误,从而导致代码难以维护。

因此,在 Vue 的开发中,我们应该尽量避免在同一元素中同时使用 v-ifv-for 指令。如果确实需要使用这两个指令,则应尽可能将它们拆分成不同的元素,并使用计算属性或过滤器来处理数据,以提高代码效率和可读性。

2. vue 组件之间数据传递的方法

这里面试官只问了我,父子,爷孙之间的传递方法。

  1. props 和 $emit

父组件向子组件传递数据可以使用 props,子组件向父组件传递数据可以使用 $emit 发送事件。通过这两个基本的 API 可以搭建起一个完整的组件通信机制。

  1. provide 和 inject

provide 和 inject 是用于祖先组件向后代组件传递数据的高级特性。祖先组件通过调用 provide 来定义一个变量,然后在后代组件中通过 inject 来注入该变量来传递数据。它可以将一些共享的状态跨过多层组件传递给各层子组件。

  1. Vuex 状态管理

Vuex 是 Vue 的官方状态管理库,用于集中管理应用程序中的所有组件的状态。Vuex 的特点是将全部组件的状态存储在一个全局 store 对象中,从而使得任何组件都能够访问这个对象中的状态,并且在其中更改状态后,其他组件也能够立即获知。

  1. event bus

event bus 是指创建一个新的 Vue 实例,在其中利用 emit和emit 和 emiton 方法来进行组件之间的通信。通过将一个实例作为公共事件中心,所有组件都可以来监听、广播、接收和发送信息,从而实现了组件之间的解耦。

  1. 插槽

插槽是一种特殊的组件内容分发方式,可以允许父组件向子组件传递任意模板内容。在使用时,需要在父组件中将要分发的内容,用 slot 标签包装起来,然后在子组件中利用 $slot 对它进行挂载和渲染。

3. 你对vue3 setup 的理解 (两个参数)

setup 是 Vue 3 中的一个新特性,它是用于编写组件逻辑的新的选项。相比于 Vue 2.x 中的 datacomputedmethods 等选项,setup 更加灵活和简洁。

在使用 setup 时,需要将组件选项中的 datacomputedmethods 等属性全部移除,同时将它们的内容放到 setup 函数中。

setup 函数接收两个参数:props 和 contextprops 是一个对象,包含了从父组件传递下来的所有属性,context 是一个对象,包含了一些常用的组件上下文,例如 attrsslotsemit 等。

setup 函数需要返回一个对象,该对象包含了组件中需要用到的数据、方法和内部状态等。这个返回对象会被 Vue 3 编译器处理成一个渲染函数的上下文,从而实现了组件逻辑的编写。

使用 setup 的好处在于它可以更好地实现逻辑的复用和组合。可以将组件中的逻辑封装成一个独立的函数,然后在多个组件中共用,从而提高了代码的可维护性和复用性。

在 Vue 3 中,setup 函数接收两个参数:propscontext

  1. props

props 是一个对象,包含了从父组件传递下来的所有属性。在 setup 函数中,可以通过 props 对象来访问这些属性。例如:

setup(props) {
  console.log(props.title) // 访问父组件传递的 title 属性
}

需要注意的是,在 setup 函数中,props 对象是只读的,不能直接修改其中的属性值。

  1. context

context 是一个对象,包含了一些常用的组件上下文,例如 attrsslotsemit 等。其中,attrs 包含了组件接收到的所有非 props 属性,slots 包含了组件插槽的内容,emit 是一个函数,用于触发自定义事件。

setup(props, context) {
  console.log(context.attrs) // 访问所有非 props 属性
  console.log(context.slots) // 访问插槽内容
  context.emit('custom-event') // 触发自定义事件
}

需要注意的是,在 setup 函数中,context 对象也是只读的,不能直接修改其中的属性值。

通过 propscontext 这两个参数,我们可以在 setup 函数中访问组件的属性和上下文,并根据需要进行处理和操作,从而实现组件的逻辑编写。

面试官还问我 props 参数是响应式的吗? 在 Vue 3 中,setup 函数的参数 props 是响应式的,但是它是一个只读的响应式对象。 当父组件的 props 发生变化时,子组件的 props 对象也会跟着更新。

es6的解构props出来的是响应式的吗?

在 Vue 3 中,使用 ES6 的解构语法解构 props 对象中的属性时,得到的结果并不是响应式的。

要把它变成响应式的怎么办?

在 Vue 3 中,如果需要将 ES6 解构出来的变量变成响应式,可以使用 toRef 或 toRefs 函数将其转换成响应式对象。

toRef 函数的作用是将一个简单的值或对象的属性转换成一个响应式的 Ref 对象。

toRefs 函数可以将一个对象的所有属性转换成响应式的 Ref 对象,并返回一个新的对象,其中每个属性都是一个响应式的 Ref 对象。

需要注意的是,使用 toRefs 函数将对象的所有属性转换成响应式的 Ref 对象时,如果对象中有嵌套对象,嵌套对象的属性并不会转换成响应式的 Ref 对象。这时可以使用递归的方式,将嵌套对象中的属性也转换成响应式的 Ref 对象。

ref 函数的作用是将一个简单的值转换成一个响应式的 Ref 对象。

需要注意的是,ref 函数只能将一个简单的值转换成响应式的 Ref 对象,而无法将对象本身转换成响应式对象。如果需要将对象变成响应式对象,可以使用 reactive 函数。

reactive 函数的作用是将一个对象转换成一个响应式的 Proxy 对象。

在 Vue 3 中,reactive 函数默认就会递归地将对象转换成响应式代理对象。这意味着如果对象中包含嵌套对象,那么这些嵌套对象也会被自动转换成响应式代理对象,并且当任何一个嵌套对象发生变化时,都会触发相关依赖更新。

举个例子,假设我们有以下对象:

const obj = {
  foo: 'bar',
  nested: {
    a: 1,
    b: 2
  }
};

我们可以通过以下代码使用 reactive 函数将其转换为响应式代理对象:

import { reactive } from 'vue';

const reactiveObj = reactive(obj);

此时,reactiveObj 对象中的 foo 属性和 nested 属性都会被转换为响应式数据,并且 nested 属性中的 a 属性和 b 属性也都会被自动转换为响应式数据。换句话说,当我们修改 reactiveObj.nested.a 的值时,就会触发与该属性相关联的依赖更新。

所以,Vue 3 中的 reactive 函数默认会递归地将所有嵌套对象转换成响应式代理对象,使得整个对象都成为响应式数据。

总结

以上就是我这一次面试的全部题目(应该是大部分都记录下来了)。可以看到其实问题都没有特别难,甚至可以说是相当基础,但是我当时面试时其实回答的没有特别全面和清晰。这也反映了自己的问题,知识掌握的不够透彻,理解不够深入。 当然,紧张、心态不好、经验不足等因素都可能影响发挥。我在面试时候其实还好,没有特别紧张,甚至和面试官有说有笑。我觉得这是十分重要的,不要胆怯,不要害怕,脸皮厚点。就算是遇到不会的,或者是不清楚的也不必慌张。 面试结束后的总结和复盘也是十分重要的,我一般面试结束后会立刻回忆面试遇到的题目,记录下来,回想自己当时的回答,看看是否有不对的或不全面的,总结问题,改正问题,吸取教训。