两年前端小菜鸡深漂 30 天面试题记录

516 阅读30分钟

一、4.3 上午酷派大厦某公司线下面试

1、项目中有遇到什么难题吗?

信息发布平台的 排期 模块技术迭代,从原本的前端 jquery 改造为 vue 搭配二次封装 element ui组件,它分为日,月,周三个顶部 tab,可以相互切换,日,周都是手动用 表格标签拼的,月是二次封装的el日历组件。

2、浅拷贝、深拷贝

  • 浅拷贝只复制对象的第一层属性,如果属性值是基本类型,则复制基本类型的值,如果属性值是引用类型,则复制的是内存地址。即浅拷贝复制的是对象的引用。实现浅拷贝的方式:扩展运算符,Object.assign,Array.from,slice,concat。
  • 深拷贝会复制对象的所有层次属性,包括嵌套对象。深拷贝后,复制对象和原始对象之间没有任何引用关系,二者完全独立。实现深拷贝的方式:
    1. JSON.stringfy(JSON.parse(obj)): 这是一种简单的方法,但它不能处理函数、undefined、Symbol和循环引用,同时也会丢失对象的构造函数。
    2. 使用递归实现数组、对象的深拷贝: 使用递归手动实现深拷贝,这种方法可以处理大多数常见的数据类型,包括数组和对象。但是,它可能需要额外的代码来处理特殊对象(如Date、RegExp、Function、Symbol等)和循环引用。
function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return null; // 处理null值
  if (obj instanceof Date) return new Date(obj); // 处理日期对象
  if (obj instanceof RegExp) return new RegExp(obj); // 处理正则表达式
  if (typeof obj !== 'object') return obj; // 如果不是复杂数据类型,直接返回

  // 如果是对象或数组,先检查hash中是否克隆过,解决循环引用问题
  if (hash.has(obj)) {
    return hash.get(obj);
  }

  // 初始化返回结果,保证数组和对象的原型不丢失
  let cloneObj = Array.isArray(obj) ? [] : {};
  hash.set(obj, cloneObj);

  // 遍历对象的键值对进行克隆
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}

// 示例使用
const obj = {
  num: 0,
  str: '',
  boolean: true,
  unf: undefined,
  nul: null,
  obj: { name: '我是一个对象', id: 1 },
  arr: [0, 1, 2],
  func: function () { console.log('我是一个函数') },
  date: new Date(),
  reg: new RegExp('/我是一个正则/ig'),
};

const cloneObj = deepClone(obj);
console.log(cloneObj);

  1. 使用 ES 2021 提供的 structuredClone(): 它可以用于复制大多数结构化数据,包括循环引用和特殊对象(如Date、RegExp)。但是,它不支持函数和不可枚举的属性。
const cloneObj = structuredClone(obj);
  1. 使用 lodash 库提供的 _.cloneDeep(): 可以处理大多数数据类型,包括循环引用和特殊对象。
const _ = require('lodash');
const cloneObj = _.cloneDeep(obj);
  1. 使用 MessageChannel 提供的 postMessage: 可以处理循环引用和特殊对象。
function deepCloneMessageChannel(obj) {
 return new Promise(resolve => {
   const { port1, port2 } = new MessageChannel();
   port2.onmessage = ev => resolve(ev.data);
   port1.postMessage(obj);
 });
}
  1. 使用自定义实现结合Proxy:通过Proxy可以创建一个自定义的深拷贝实现,这样可以更精细地控制拷贝过程,处理特殊情况和性能优化。
function deepCloneProxy(obj, hash = new WeakMap()) {
 if (obj === null) return null;
 if (typeof obj !== 'object') return obj;
 if (hash.has(obj)) return hash.get(obj);

 let cloneObj;
 if (obj instanceof Date) {
   cloneObj = new Date(obj);
 } else if (obj instanceof RegExp) {
   cloneObj = new RegExp(obj);
 } else if (obj instanceof Map) {
   cloneObj = new Map();
   obj.forEach((value, key) => {
     cloneObj.set(key, deepCloneProxy(value, hash));
   });
 } else if (obj instanceof Set) {
   cloneObj = new Set();
   obj.forEach(value => {
     cloneObj.add(deepCloneProxy(value, hash));
   });
 } else {
   cloneObj = Array.isArray(obj) ? [] : {};
   hash.set(obj, cloneObj);
   for (let key in obj) {
     if (obj.hasOwnProperty(key)) {
       cloneObj[key] = deepCloneProxy(obj[key], hash);
     }
   }
 }
 return cloneObj;
}

3、this 指向问题

  • 普通函数中 this 指向 window, 严格模式下 this 值为 undefined。
  • 构造函数中 this 指向新创建的实例对象。
  • 对象或方法中 this 指向调用者本身。
  • 箭头函数无自己的 this,继承上一个作用域的 this,指向全局对象或者上一个函数。
  • call,bind,apply 可以改变 this 的指向。

4、pinia 保持持久化

  1. 自定义一个持久化插件。每次修改状态,会将 state 存储到 localStorage 中,初始化时从 localStorage 中恢复 state。
// pinia-persist-plugin.js

export default function piniaPersistPlugin(context) {
 const { store } = context;
 // 每次修改时,将state存储到localStorage
 store.$subscribe(() => {
 const state = store.$state;
 localStorage.setItem(store.$id, JSON.stringify(state));
 });
 // 初始化时,从localStorage恢复state
 const storedState = localStorage.getItem(store.$id);
 if (storedState) {
  store.$state = JSON.parse(storedState);
 }
}
  1. 安装 pinia-plugin-persist 进行更全面,可配置的持久化保存。

5、递归

在 Javascript 中,递归是一种强大的编程技巧,它允许函数调用自身。一般用来解决可分解为更小相似问题的问题,例如遍历树结构,计算阶乘等。递归函数通常有两个部分:基准情况(递归停止的条件)和递归步骤(函数调用自身的部分)。

//计算阶乘
function factorial(n) {
  // 基准情况:0! = 1
  if (n === 0) {
    return 1;
  }
  // 递归调用:n! = n * (n-1)!
  return n * factorial(n - 1);
}
console.log(factorial(5)); // 输出:120

递归虽然是一个强大的工具,但它在 js 中可能会导致性能问题,因为它会占用大量的调用栈空间。在某些情况下,可能需要使用迭代(循环)来替换递归以减少内存使用和提高性能。

6、前端项目鉴权

目前常见的四种前端鉴权方式:

  1. HTTP基本认证:这种认证方式是浏览器遵守[http协议]实现的基本授权方式, HTTP协议进行通信的过程中,HTTP协议定义了基本认证,允许HTTP服务器对客户端进行用户身份证的方法。目前基本没有再使用这种认证方式的, 一些老项目的内网认证可能还会有。
  2. Session-Cookie: 利用浏览器端的 session 和服务器端的 cookie 来实现前后端的认证。由于 http 请求时是无状态的,服务器正常情况下是不知道当前请求之前有没有来过,这个时候我们如果要记录状态,就需要在服务器端创建一个会话(session), 将同一个客户端的请求都维护在各自的会话中,每当请求到达服务器端的时候,先去查一下该客户端有没有在服务器端创建 session ,如果有则已经认证成功了,否则则没有认证。
  3. Token 令牌(包括 JWT, SSO):Token 是用户身份的验证方式,我们通常叫它:令牌。当用户第一次登录后,服务器生成一个 Token,并将此 Token 带给客户端,以后客户端只需要带上这个 Token 即可访问数据,无需带上用户名,密码。 (1)JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户.之后用户与服务器通信的时候.服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候, 会加上签名。jwt最大的特点就是: 服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。 (2)单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中, 用户只需要登录一次就可以访问所有相互信任的应用系统。
  4. OAuth(开放认证):OAuth 即开放授权,其实和 SSO 比较像。它允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容,为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向用户征求授权。我们常见的提供 OAuth 认证服务的厂商有 QQ,微信,微博等。

7、前端打包 src 文件夹和 public 文件夹的区别

  • src 文件夹中的文件是开发源代码,需要经过编译和构建过程才能用于生产环境。
  • public 文件夹中的文件通常是静态资源,通常不需要编译,直接用于生产环境。

8、vue 中 v-if 和 v-for 优先级

  • vue2 中 v-for 优先级比 v-if 高。
  • vue3 中 v-if 优先级比 v-for 高。

9、vue3 中 ref 和 reactive 区别

  1. 数据类型不同:ref用于包装JavaScript基本类型的数据(如字符串、数字、布尔值等),而reactive可以用于包装JavaScript对象和数组等复杂类型的数据。

  2. 使用方式不同:ref需要通过在模板中使用ref指令以及在JavaScript代码中使用ref函数进行创建和使用,而reactive则需要通过调用Vue.js提供的reactive函数进行包装和创建。

  3. 访问方式不同:对于通过ref函数创建的响应式数据,我们可以通过.value属性来访问其实际值;而对于通过reactive函数创建的响应式对象,我们可以直接访问其属性或调用其方法。

  4. 设计理念不同:ref主要是为了解决单一元素/数据的响应式问题,而reactive则是为了解决JavaScript对象和数组等复杂数据结构的响应式问题。

isReactive验证是否是reactive 类型响应式数据。

isRef验证是否是ref类型响应式数据。

二、4.7 下午南山科技园某公司线上二面

1、判断对象是否为空

  1. 使用Object.keys()
function isEmpty(obj) {
  return Object.keys(obj).length === 0;
}
const obj = {};
console.log(isEmpty(obj)); // 输出:true
  1. 使用Object.getOwnPropertyNames()方法
function isEmpty(obj) {
  return Object.getOwnPropertyNames(obj).length === 0;
}
const obj = {};
console.log(isEmpty(obj)); // 输出:true
  1. 使用for...in循环
function isEmpty(obj) {
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      return false;
    }
  }
  return true;
}
const obj = {};
console.log(isEmpty(obj)); // 输出:true

2、对象 A 赋值给对象 B,对象 B 改变属性,对象 A 也跟着改变,为什么

在JavaScript中,对象是按引用传递的。当你将一个对象赋值给另一个变量时,实际上是在将对象的引用复制给这个变量。因此,原始对象和新变量指向的是内存中的同一个对象。由于它们引用的是同一个对象,所以当你通过任何一个变量修改对象的属性时,这个修改会在两个变量中都体现出来。

3、深拷贝方法(参考前文)

三、4.8 上午龙华某公司线下初试

项目中有遇到困难的点吗

四、4.8 下午广州某公司电话面试

简历项目中什么让你觉得做得比较满意的地方

五、4.9 上午某广州公司驻中山大学线上面试

1、移动端适配方案

移动端适配是一个复杂的过程,涉及到多个方面的考虑,包括布局、字体大小、图片显示等。以下是一些常见的移动端适配方案:

  1. 响应式设计(Responsive Web Design, RWD)

   - 使用媒体查询(Media Queries)来根据不同设备的屏幕尺寸应用不同的 CSS 样式。

   - 流式布局(Fluid Grids)和弹性图片(Flexible Images)。

   - 使用相对单位(如%,em,rem)而不是绝对单位(如 px)。

  1. 百分比布局(流式布局)

   - 使用百分比来定义元素的宽度,使得元素宽度能够根据父容器宽度变化而变化。

  1. 使用 REM 单位

   - 将 HTML 的字体大小设置为视口宽度的某个百分比(例如,视口宽度的 10%)。

   - 其他所有尺寸都使用 REM 单位,从而实现字体大小和布局的响应式。

  1. 视口(Viewport)配置

   - 通过 标签设置视口属性,如宽度(width)、初始缩放比例(initial-scale)等。

  1. Flexbox布局

   - 使用 CSS Flexbox 布局模型,可以更轻松地实现各种布局效果,并且具有良好的响应式特性。

  1. Grid布局

   - 使用 CSS Grid 布局,可以创建复杂的网格设计,并且易于实现响应式布局。

  1. 图片适配

   - 使用响应式图片(元素或srcset属性)来提供不同分辨率的图片。

   - 使用矢量图形(如SVG)代替像素图形,以实现无损缩放。

  1. 移动端框架和库

   - 使用Bootstrap、Foundation、Framework7等移动端框架,它们提供了预定义的样式和组件,有助于快速开发响应式应用。

   - 使用jQuery Mobile、React Native、Ionic等库来构建跨平台的移动应用。

  1. 设计可触摸的元素

   - 确保按钮和链接的大小足够大,以便用户容易点击。

   - 使用触控事件(如touchstart、touchmove、touchend)来提高交互体验。

  1. 性能优化

    - 压缩图片和资源文件。

    - 使用CDN来加速资源加载。

    - 减少HTTP请求,合并CSS和JavaScript文件。

  1. 测试和调试

    - 使用浏览器的开发者工具模拟不同设备和屏幕尺寸。

    - 在真实设备上测试应用,以确保兼容性和性能。

选择合适的适配方案取决于项目的具体需求和目标设备。通常,结合多种方法可以达到最佳的适配效果。

2、Rem和px换算

在Web开发中,rempx是两种不同的长度单位,它们用于设置元素的大小。px是像素单位,它是固定的,不会因为父元素的字体大小而改变。而rem是相对单位,它是相对于根元素(即HTML元素)的字体大小的。

  1. px到rem的换算

   - 首先,你需要知道根元素(HTML)的字体大小。通常,根元素的字体大小被设置为16px

   - 然后,你可以将你想要的像素值除以根元素的字体大小,得到相应的rem值。

   例如,如果你想要将一个元素的宽度设置为32px,并且根元素的字体大小是16px,那么换算成rem就是:

   width: 32px / 16px = 2rem;
  1. rem到px的换算

   - 同样,你需要知道根元素的字体大小。

   - 然后,你可以将rem值乘以根元素的字体大小,得到相应的px值。

   例如,如果一个元素的字体大小被设置为1.5rem,并且根元素的字体大小是16px,那么换算成px就是:

   font-size: 1.5rem * 16px = 24px;

在实际开发中,为了方便计算,你可以将根元素的字体大小设置为100px,这样从pxrem的换算就非常简单,只需要将px值除以100即可得到rem值。例如,200px换算成rem就是2rem

请注意,虽然rem是一种相对单位,但是在不同的设备和视口中,根元素的默认字体大小可能不同。因此,在实际应用中,你可能需要通过CSS重置来确保根元素的字体大小是一致的。  

3、NextTick实际使用场景

  1. app.vue 初始化主题样式

  2. 初始化 echarts 图表

  3. 初始化百度地图  

4、vue插槽使用

插槽 总结 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。 分类:默认插槽、具名插槽、作用插槽 使用方式

  • 默认插槽

父组件中:

 <Category>
     <div>html结构1</div>
 </Category>

子组件中:

<template>
   <div>
       <!-- 定义插槽 -->
       <slot>插槽默认内容...</slot>
   </div>
</template>
  • 具名插槽

父组件中:

<Category>
    <template slot="center">
        <div>html结构1</div>
    </template>
    <template v-slot:footer>
        <div>html结构2</div>
    </template>
</Category>

子组件中:

<template>
    <div>
        <!-- 定义插槽 -->
        <slot name="center">插槽默认内容...</slot>
        <slot name="footer">插槽默认内容...</slot>
    </div>
</template>
  • 作用域插槽

理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

父组件中:

<Category>
    <template scope="scopeData">
        <!-- 生成的是ul列表 -->
        <ul>
            <li v-for="g in scopeData.games" :key="g">{{g}}</li>
        </ul>
    </template>
</Category>
<Category>
    <template slot-scope="scopeData">
        <!-- 生成的是h4标题 -->
        <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
    </template>
</Category>

子组件中:

<template>
    <div>
        <slot :games="games"></slot>
    </div>
</template>
<script>
export default {
    name:'Category',
    props:['title'],
    //数据在子组件自身
    data() {
        return {
            games:['红色警戒','穿越火线','劲舞团','超级玛丽']
        }
    },
}
</script>

六、4.9 下午龙华某做眼镜店相关系统公司笔试

1、写出下列对象的TypeScript类型

    Let a = {
        Aa: 1,
        Bb: “aaa”,
        Cc: ()=>{
            Return Promise.resolve(“res”)
        }
    }

答:

interface A { Aa: number; Bb: string; Cc: () => Promise<string>; }
let a: A = { Aa: 1, Bb: "aaa", Cc: () => Promise.resolve("res") };

2、一个表格的数据如下,现在要提交数据,提交前需要验证商品id + 批号是否有重复,如有重复就不允许提交。问:如何通过for循环嵌套遍历或其他方法,高效识别数据是否有重复?(只需描述大概的思路,对tempArray进行for循环,2....)

let tempArray = [  
    { 
         //商品id
        commodityId: "com111",  
                 //批号
        batchNumber: "111222",  
    },  
     //...几十个不同的商品数据 
    {  
             //商品id
        commodityId: "com111",  
                 //批号 
        batchNumber: "111222",  
    }  
]

答: 为了高效地识别数据是否有重复,我们可以使用一个辅助的数据结构来存储已经遍历过的商品id和批号的组合。在JavaScript中,通常可以使用一个Set或者一个对象(Object)来作为这个辅助数据结构。 以下是使用Set的大致思路:

  1. 创建一个空的Set
  2. 遍历tempArray
  3. 对于每个元素,构造一个唯一的键,通常是商品id和批号的组合(例如,使用commodityId + batchNumber作为键)。
  4. 在每次遍历时,检查这个键是否已经存在于Set中。
  5. 如果键不存在于Set中,将其添加到Set中。
  6. 如果键已经存在于Set中,说明找到了重复的数据,可以停止遍历并返回重复的信息。
let tempArray = [
    {
        commodityId: "com111",
        batchNumber: "111222",
    },
    //...几十个不同的商品数据
    {
        commodityId: "com111",
        batchNumber: "111222",
    }
];
let uniqueSet = new Set();
let hasDuplicate = false;
let duplicateItem = null;
for (let item of tempArray) {
    let key = item.commodityId + item.batchNumber;
    if (uniqueSet.has(key)) {
        hasDuplicate = true;
        duplicateItem = item;
        break;
    } else {
        uniqueSet.add(key);
    }
}
if (hasDuplicate) {
    console.log("发现重复数据:", duplicateItem);
} else {
    console.log("没有重复数据,可以提交");
}

七、4.10 上午宝安区某风投公司线下面试

1、介绍下你最近一个项目

该项目是一个进行多媒体信息发布的后台管理系统,涵盖模块有节目管理,公告管理,账号管理,资源管理,付费服务,数据运维,系统管理,高级功能,任务通知等。该平台是高效、快捷、一体化的 LED 广告管理发布系统,具备强大的即编即播、所见即所得的广告编缉功能,集素材、屏幕和节目等所有资源统一管理和共享。

2、websocket

  • WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket在2011年被IETF定为标准RFC 6455,并且用于取代旧有的HTTP轮询(Polling)方式,来降低网络延迟,实现实时通信。
  • WebSocket使得客户端和服务器之间的数据交换变得更加快速,实时性更强。这是通过在客户端(浏览器)和服务器之间建立持久连接来实现的,这样它们就可以在任意时刻互相发送信息。WebSocket连接一旦建立,就可以送和接收消息,直到任意一端关闭连接。
  • WebSocket协议支持文本和二进制数据传输,这使得它非常适合于需要快速、实时通信的应用场景,如在线游戏、实时交易系统、实时聊天等。
  • WebSocket API也被广泛用于现代的Web应用开发中,允许开发者编写更少的代码来实现实时的功能。

3、项目优化

在Vue 3项目中,性能优化是一个重要的考虑因素。以下是一些常见的性能优化方法:

  1. 代码分割 (Code Splitting): 使用Vue Router或者异步组件,将代码分割成多个小块,只有在需要时才加载相应的代码。这可以减少初始加载时间。
  2. 动态导入 (Dynamic Imports): 使用动态导入语法import()来按需加载组件,而不是一次性导入整个组件库。
  3. 使用KeepAlive: 对于那些不需要频繁销毁和创建的组件,使用<keep-alive>来缓存组件状态,避免不必要的渲染。
  4. 虚拟滚动 (Virtual Scrolling): 对于长列表,使用虚拟滚动库(如vue-virtual-scroll-listvue3-virtual-scroller)来渲染可见的列表项,而不是渲染整个列表。
  5. 使用v-once: 对于不会改变的内容,使用v-once指令进行渲染,这样Vue就不会对这些内容进行更新。
  6. 使用requestAnimationFrame: 对于可能会引起性能问题的复杂计算或DOM操作,使用requestAnimationFrame来延迟这些操作到下一个动画帧。
  7. 使用异步组件: 对于一些不需要立即加载的组件,可以使用异步组件来延迟加载它们。
  8. 优化组件: 减少组件的复杂度,避免不必要的计算和渲染。例如,使用计算属性和侦听器时,确保它们是必要的。
  9. 使用SSR (Server-Side Rendering): 对于需要更快内容到达时间(Time-to-Content)的应用,使用服务器端渲染来预渲染应用程序的初始状态。
  10. 压缩资源: 使用Webpack的压缩插件(如compression-webpack-plugin)来压缩JavaScript和CSS文件,减少文件大小。
  11. 使用CDN和缓存: 将静态资源部署到CDN,并配置适当的缓存策略,以加快资源的加载速度。
  12. Tree Shaking: 确保你的构建工具(如Webpack)支持Tree Shaking,以移除未使用的代码。
  13. 使用Vue 3的Composition API: 利用Composition API来优化组件的性能,例如使用reactiverefcomputed来更高效地管理响应式数据。
  14. 使用路由和组件级别的代码分割: 在Vue Router中,可以为每个路由定义异步组件,这样只有在访问特定路由时才会加载相应的组件代码。
  15. 避免不必要的渲染: 使用shouldComponentUpdate生命周期钩子或者Vue 3的watchEffectwatchflush选项来控制组件的更新。
  16. 使用现代JavaScript特性: 使用现代JavaScript特性(如箭头函数、模板字符串、解构赋值等)来编写更简洁、更高效的代码。
  17. 使用性能分析工具: 使用Vue Devtools和浏览器的性能分析工具来识别性能瓶颈。 通过实施这些优化措施,你可以显著提高Vue 3应用程序的性能,提供更好的用户体验。

4、vue 动态组件

<template>
  <div>
    <!-- 动态组件 -->
    <component :is="currentComponent"></component>

    <!-- 切换按钮 -->
    <button @click="currentComponent = 'ComponentA'">切换到A</button>
    <button @click="currentComponent = 'ComponentB'">切换到B</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';

// 定义组件
const components = {
  ComponentA,
  ComponentB
};

// 当前组件
const currentComponent = ref('ComponentA');

// 注册组件
const componentKeys = Object.keys(components);
for (const key of componentKeys) {
  components[key].name = key;
  components[key].setup();
}
</script>

<script>
export default {
  components: {
    ComponentA,
    ComponentB
  }
}
</script>

在这个例子中,我们使用<component>元素和is属性来动态地切换不同的组件。currentComponent是一个响应式引用(由ref函数创建),它的值决定了哪个组件会被渲染到页面上。

如果你想要缓存动态组件的状态,可以使用<keep-alive>包裹<component>,就像在Vue 2中一样:

<keep-alive>
  <component :is="currentComponent"></component>
</keep-alive>

5、如果一次给你 10 万条数据,你前端怎么展示到页面上

如果需要在前端展示10万条数据,关键在于只渲染用户当前可以看到的数据,同时尽可能减少DOM操作和重绘重排。以下是一些实现这一目标的方法:

  1. 虚拟滚动(Virtual Scrolling): 虚拟滚动是处理大量数据展示的一种非常有效的方式。它只渲染视口(viewport)中可见的部分数据项,当用户滚动时,动态更新这些数据项。这种方法可以极大地减少DOM元素的数量,提高性能。 有许多库可以帮助实现虚拟滚动,例如vue-virtual-scroll-listvue3-virtual-scroller等。
  2. 分页(Pagination): 将数据分成多个页面,每次只加载和渲染一页的数据。用户可以通过分页控件来切换不同的页面。这种方法简单直观,但是用户在浏览大量数据时可能需要频繁切换页面。
  3. 无限滚动(Infinite Scrolling): 当用户滚动到页面底部时,加载并渲染更多的数据。这种方法可以提供无缝的用户体验,但是不适合需要精确分页的场景。
  4. 数据懒加载(Lazy Loading): 对于初始不在视口中的数据,可以延迟加载。例如,只有在用户滚动到某个区域时才加载和渲染相关的数据项。
  5. 数据预处理和索引: 对数据进行预处理,比如排序、过滤和索引,以便快速检索和渲染用户感兴趣的数据部分。
  6. 使用Web Workers: 对于数据处理密集型的操作,可以使用Web Workers在后台线程中处理,避免阻塞主线程。
  7. 优化渲染性能: 使用requestAnimationFrame来控制动画和重绘的时机,避免不必要的渲染。
  8. 使用高效的数据结构: 使用高效的数据结构来存储和访问数据,例如使用树状结构或者哈希表来快速定位数据。
  9. 使用骨架屏或加载指示器: 在数据加载期间,可以使用骨架屏或加载指示器来提供反馈,改善用户体验。 在实际应用中,通常会结合以上几种方法来处理大量数据。例如,可以使用虚拟滚动来渲染用户当前可见的数据,同时结合分页或无限滚动来管理数据的加载。通过这些方法,可以在用户体验和性能之间找到一个平衡点。

八、4.10 下午龙岗某公司线上面试

1、Js宏任务和微任务

宏任务与微任务都属于异步任务,微任务的优先级高于宏任务,因此每一次都会先执行完微任务再执行宏任务。宏任务有:定时器,dom事件,ajax事件。微任务有:promise的回调,MutationObserver的回调,process.nextTick回调。

2、浏览器事件循环机制(eventLoop)

Js是单线程的,主线程在执行时会不断循环往复地从同步队列中读取任务,执行任务,同步队列执行完毕后再依次执行异步队列中的任务。

3、diff算法

  • 出现:主流框架中多采用VNode虚拟节点进行数据更新,更新规则为diff算法。
  • 原理:diff算法会先将所有节点转换为VNode,数据变化后,diff算法会将VNode与OldNode进行对比,然后会在VNode的基础上,对OldeNode进行准确修改。

4、虚拟节点

在Vue.js框架中,虚拟节点(Virtual Node),通常简称为"VNode",是Vue的核心概念之一。Vue使用虚拟DOM(Virtual DOM)来优化DOM操作,而VNode就是虚拟DOM中的节点。

虚拟节点本质上就是一个普通的JS对象,用于描述视图的界面结构。

5、冒泡排序

function bubbleSort(arr) {
  var len = arr.length;
  for (var i = 0; i < len; i++) {
    for (var j = 0; j < len - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) { // 相邻元素两两对比
        var temp = arr[j + 1]; // 元素交换
        arr[j + 1] = arr[j];
        arr[j] = temp;
      }
    }
  }
  return arr;
}
// 使用示例
var arr = [64, 34, 25, 12, 22, 11, 90];
console.log(bubbleSort(arr)); // [11, 12, 22, 25, 34, 64, 90]。

项目中很少用,数组排序一般直接用 sort 方法

6、腾讯云IM

在created里先初始化SDK和登录逻辑,再注册腾讯云即时通信IM上传插件,最后获取登录腾讯云IM的信息凭证,在mounted里监听接收到的消息和会话列表变化。

7、国际化语言处理

VueI18n

8、vue表单校验插件

vee-validate 校验基本步骤

  1. 导入 Form Field 组件 将 form 和 input 进行替换,需要加上name用来指定将来的校验规则函数。
  2. Field 需要进行数据绑定,字段名称最好和后台接口需要的一致。
  3. 定义Field的name属性指定的校验规则函数,Form的validation-schema接受定义好的校验规则是对象。
  4. 自定义组件需要校验必须先支持v-model 然后Field使用as指定为组件名称。

9、flex:1代表什么?

当我们将一个项目的flex属性设置为1时,相当于将其分配了一个相对于其他项目相同的可伸缩空间。换句话说,flex: 1会使得该项目尽可能地占据父容器中的剩余空间,但不会影响到其他项目。

flex: 1; /* 等同于 flex: 1 1 0%; */

  • flex-grow 设置为 1,表示该项可以在父容器内扩展,占据所有可用的额外空间。

  • flex-shrink 设置为 1,表示该项可以在空间不足时收缩,以避免溢出。

  • flex-basis 设置为 0%,表示项目在分配额外空间之前不占用空间,会自动填充所有可用空间。

(参考:juejin.cn/post/735730…

九、4.11 下午腾讯外包线下面试

1、字符串转换为数组方法

  1. Split

  2. Array.from

  3. 扩展运算符

2、从字符串中截取指定字符替换

Replace

3、防抖和节流

  • 防抖:防抖技术确保函数不会在短时间内被频繁调用。其基本思想是,只有在最后一次触发事件之后的一段时间内没有再次触发该事件,才执行函数。这样可以减少不必要的函数调用,提高性能。

  • 节流:节流技术确保函数在指定的时间间隔内只执行一次,无论该事件被触发多少次。这样可以保证函数在一定时间内的执行频率是可控的,避免函数在短时间内被过度调用。

4、怎么判断一个对象是一个响应式对象

isReactive

isProxy

5、vue3 中的 hooks

组合式 api 中的函数

ref, reactive, watch, watchEffect

6、vue3中 watch 和 watchEffect 区别?

  • watch需要明确指定追踪的数据源,可以访问旧值和新值,并且可以监听多个数据源。

  • watchEffect不需要指定数据源,它会自动追踪回调函数中访问的所有响应式数据,并且无法访问旧值。

  • watchEffect在组件初始化时会立即执行一次,以收集依赖,而watch不会。

  • watchEffect更加适合于自动追踪的场景,而watch则更加灵活,适合于需要精确控制依赖追踪的场景。

7、axios中断请求

在使用 Axios 进行网络请求时,有时可能需要中断一个已经发出的请求。例如,用户可能离开了页面,或者新的数据已经覆盖了旧的数据,导致请求不再必要。Axios 库本身并不直接提供中断请求的方法,但是可以通过几种方式实现这一功能。

  1. 使用 CancelToken

Axios 提供了 CancelToken 来取消请求。首先,你需要创建一个 CancelToken 源,然后在请求配置中引用它。当你想要取消请求时,调用 cancel 方法即可。

const axios = require('axios');

const CancelToken = axios.CancelToken;

let cancel;

axios.get('/user/12345', {

  cancelToken: new CancelToken(function executor(c) {

    // executor 函数接收一个取消函数作为参数

    cancel = c;

  })

});

// 调用取消函数取消请求

cancel('Operation canceled by the user.');

// 当你调用 `cancel` 函数时,Axios 会在内部抛出一个 `Cancel` 对象。你可以通过捕获异常来处理取消事件。

axios.get('/user/12345', {

  cancelToken: new CancelToken(function executor(c) {

    // executor 函数接收一个取消函数作为参数

    cancel = c;

  })

}).catch(function(thrown) {

  if (axios.isCancel(thrown)) {

    console.log('Request canceled', thrown.message);

  } else {

    // 处理错误

  }

});
  1. 使用AbortController(适用于 Axios v0.22.0+)

如果你使用的是较新的 Axios 版本(v0.22.0+),可以使用 AbortController 来取消请求。AbortController 是一个浏览器的原生 API,也被 Axios 支持。

const controller = new AbortController();
axios.get('/user/12345', {
  signal: controller.signal
}).then(response => {
  // 处理响应数据
}).catch(error => {
  if (axios.isCancel(error)) {
    console.log('Request canceled', error.message);
  } else {
    // 处理错误
  }
});
// 当需要取消请求时
controller.abort();
  1. 在 Vue 或 React 等框架中的应用

在 Vue 或 React 等框架中,通常需要在组件销毁时取消 Axios 请求,以避免内存泄漏或状态不一致的问题。可以在组件的 beforeDestroycomponentWillUnmount 生命周期函数中调用取消请求的函数。

以上就是在 Axios 中中断请求的一些方法。根据你的使用场景和 Axios 版本,你可以选择适合你的方法来实现中断请求的功能。

十、4.17 上午上海某公司线上初面

1、样式选择器优先级

  !Important > 行内样式 > id选择器 > class选择器 > ::before伪元素选择器 > *通配符。

2、ES6新特性

箭头函数,模版字符串,解构赋值,扩展运算符,let和const,promise,set和map,symbol类型。

3、防抖,节流应用场景

防抖:防抖技术确保函数不会在短时间内被频繁调用。

应用场景:搜索框输入,窗口大小调整,按钮点击防止重复提交,文本编辑器的实时保存。

节流:节流技术确保函数在指定的时间间隔内只执行一次 ****

应用场景:滚动加载,窗口调整大小,鼠标移动和拖拽,键盘事件。

4、computed, watch应用场景

Computed: 模版内的复杂表达式,数据格式化,过滤数据

Watch: 数据变化后的异步操作, 深度监听和立即执行, 复杂的逻辑处理

5、在线地图

在Vue 3项目中使用在线地图,你可以选择使用各种地图服务提供商,如Google Maps、OpenStreetMap、Bing Maps等。以下是一个使用Vue 3和OpenStreetMap的例子,我们将使用vue3-leaflet库来集成地图。 首先,你需要安装vue3-leafletleaflet库:

npm install vue3-leaflet leaflet

然后,你可以在Vue组件中这样使用:

<template>
  <div class="map-container" style="height: 400px;">
    <l-map ref="map" :zoom="zoom" :center="center">
      <l-tile-layer :url="url" :attribution="attribution"></l-tile-layer>
      <!-- 其他地图标记和图层 -->
    </l-map>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import { LMap, LTileLayer } from 'vue3-leaflet';
import 'leaflet/dist/leaflet.css';
const zoom = ref(13);
const center = ref([47.41322, -1.219482]);
const url = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
const attribution = '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors';
</script>
<style>
.map-container {
  /* 样式根据需要调整 */
}
</style>
  • 在上面的例子中,我们创建了一个简单的地图组件,它使用了OpenStreetMap的tile图层。你可以通过调整zoomcenter来设置地图的初始缩放级别和中心点。url是OpenStreetMap的tile图层URL,attribution是版权信息。
  • vue3-leaflet是一个Vue 3组件库,它封装了Leaflet地图库的功能,使得在Vue中集成地图变得非常简单。你可以添加标记、矢量图层、弹出框等,以丰富地图的功能。 如果你想要使用Google Maps,你需要安装vue3-google-map或者类似的库,并且需要配置Google Maps API密钥。使用Google Maps时,你需要遵守Google Maps API的使用条款,并确保你的应用符合Google的政策。
  • 请注意,使用在线地图服务可能需要考虑隐私和安全性问题,尤其是在处理用户数据和遵守特定地区的法律法规时。此外,一些地图服务可能是付费的,因此在使用之前,请确保了解相关的费用和条款。

十一、4.18 傍晚上海某公司线上初面

4.18傍晚上海某公司线上面试

某地主雇佣一位农民收割麦子,一共干 7 天,农民的薪酬为每日一根金条地主有七根连在一起的金条。只能掰开两次的情况下,地主每日如何付款,才能做到每日完工后既不赊账也不欠账。

这个问题实际上是一个有趣的数学智力题,要求地主每天都要支付一根金条给农民,但一开始只有一根连续的由七节组成的金条,并且只能折断两次。为了解决此问题,地主需要通过两次折断金条使得他能够得到长度分别为1、2、4三段的金条。 下面是地主折断金条的方法:

  1. 首先,地主把这根由7节相连的金条从中间折断,这样就得到了两部分,一部分是3节,另一部分是4节。

  2. 然后,地主再把那根4节的部分从中点再次折断,这样就得到了长度分别为2节和2节的两段金条。 现在地主有了三段金条,分别是1节(原本的单节),2节和4节。他可以按以下方式每天支付给农民:

  • 第1天:支付1节的金条
  • 第2天:支付2节的金条
  • 第3天:收回第一天的1节金条,并支付剩下的2节金条
  • 第4天:支付4节的金条
  • 第5天:收回第2天的2节金条,并支付剩余的2节金条
  • 第6天:同样支付1节的金条(之前收回的)
  • 第7天:同样支付2节的金条(之前收回的)

这样,在整个7天的过程中,地主每天都按时支付了1根金条给农民,并且没有超出规定的只能折断两次的限制。

十二、4.19 上午北京某公司线上面试

HR 面,没问啥技术问题

十三、4.19上午上海某公司线上二面(2道)

1、说下 pinia

pinia 是一个由 vue.js 核心团队维护的轻量级状态管理库,它是 vuex 的官方替代品,专为 vue3 应用程序设计。pinia 提供了一个更简单,更直观的 api,同时保持了 vue3 组合式 api 的优势。
以下是 pinia 的一些关键特点:

  1. vue3 支持:Pinia 与 Vue 3 的现代特性和组合式 API 完美集成。
  2. 类型支持:Pinia 提供了出色的 TypeScript 支持,使得状态管理更加可靠。
  3. 去除了 mutations:与 Vuex 不同,Pinia 不需要定义 mutations,直接在 actions 中进行状态修改。
  4. 模块化设计:Pinia 允许开发者创建多个 store,这些 store 可以相互导入,便于代码组织和分割。
  5. 插件扩展:Pinia 允许开发者通过插件扩展其功能,例如添加日志、持久化等功能。
  6. 轻量级:Pinia 的设计目标是简单易用,因此它的体积比 Vuex 小,更容易上手。

2、有使用过哪些 vite 插件吗

vite 是一个由 vue 作者尤雨溪开发的现代前端构建工具,它利用原生 ES 模块导入实现快速的开发服务器启动和热更新。Vite 提供了一套插件机制,使得开发者可以根据需要为项目添加额外的功能。以下是一些推荐的 vite 插件:

  1. @vitejs/plugin-vue:官方提供的 vue3 单文件组件支持插件。
  2. @vitejs/plugin-vue-jsx: 官方提供的 vue3 jsx 支持。
  3. @vueuse/core: 一组实用的 vue3 组合式 api,可以与 vite 和 vue3 配合使用,提供许多有用的功能。
  4. @intlify/vite-plugin-vue-i18n: vuei18n 的 vite 插件,用于国际化。
  5. vite-plugin-mock: 用于创建模拟数据的 vite 插件,便于前后端分离开发。
  6. vite-plugin-windicss: Windi CSS 的 Vite 插件,提供原子 CSS 的支持,用于快速开发响应式和可配置的组件。
  7. unplugin-vue-components: Vue 3 组件自动导入插件,支持 Vite、Webpack、Rollup 等。
  8. unplugin-icons: 图标自动导入插件,支持许多图标集,如 Font Awesome、Material Icons 等。
  9. vite-plugin-pages: 自动生成 Vue 路由的 Vite 插件,基于文件系统。

安装和使用这些插件通常很简单,只需使用 npm 或 yarn 安装对应的包,然后在 Vite 配置文件(通常是 vite.config.jsvite.config.ts)中引入并配置即可。例如,使用 @vitejs/plugin-vue 插件的配置如下:

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
  plugins: [vue()]
})

请注意,插件的选择应根据项目的具体需求来确定,不必要的插件会增加项目的复杂性和构建时间。

十四、4.19 下午福田某移动外包线上一面(5道题)

1、什么是js

javaScript是直接运行在浏览器上的,动态性,解释性,弱类型,基于对象和事件驱动的客户端脚本语言,能够运行在所有浏览器中,增强了用户与 Web 网页和 Web 应用程序之间的交互。

2、说下js面向对象

JavaScript 是一种基于原型的面向对象编程(OOP)语言。在 JavaScript 中,几乎所有的东西都是对象,包括函数、数组、字符串等。JavaScript 的面向对象特性使得它能够支持一些核心的面向对象概念,如封装、继承和多态。

下面是 JavaScript 面向对象编程的一些基本概念:

  1. 对象(Objects):对象是 JavaScript 的基础,是一组无序的相关属性和方法的集合。对象可以通过字面量或构造函数来创建。
  2. 构造函数(Constructor Functions):构造函数是一种特殊类型的函数,用于创建和初始化对象。在 JavaScript 中,构造函数通常以大写字母开头。
  3. 原型(Prototypes):每个 JavaScript 对象都有一个内置的 [[Prototype]] 属性,这个属性是一个链接到另一个对象的引用,这个对象被称为“原型”。原型对象可以包含共享的方法和属性。
  4. 原型链(Prototype Chain):当访问一个对象的属性或方法时,如果这个对象本身没有这个属性或方法,解释器会沿着原型链向上查找,直到找到为止。这是 JavaScript 实现继承的一种方式。
  5. 继承(Inheritance):在 JavaScript 中,继承是通过原型链实现的。一个对象可以继承另一个对象的属性和方法。
  6. 类(Classes):ES6 引入了类的概念,它是对构造函数和原型继承模式的语法糖。类提供了一种更清晰、更简洁的方式来创建对象和实现继承。
  7. 封装(Encapsulation):封装是指将数据(属性)和操作数据的方法(函数)捆绑在一起,对外界隐藏其内部实现。JavaScript 使用闭包来实现封装。
  8. 多态(Polymorphism):多态是指同一个行为具有多个不同表现形式或形态的能力。在 JavaScript 中,多态通常通过继承和重写方法来实现。

3、函数柯里化的好处是什么

  1. 参数复用:柯里化可以将一个函数拆分成多个小函数,每个小函数负责处理单一参数。这样,在多次调用中可以复用某些参数,减少代码冗余。
  2. 动态函数创建:通过柯里化,可以根据不同的参数创建特定的函数,这样可以实现更加灵活的函数组合。
  3. 延迟执行:柯里化允许我们传递部分参数后,返回一个记住了这些参数的函数。这样我们可以在需要的时候再传递剩下的参数,从而实现延迟执行。
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      // 如果传入的参数数量大于等于原始函数的参数数量,直接调用原始函数
      return fn.apply(this, args);
    } else {
      // 如果传入的参数数量不足,返回一个新的函数,等待剩余的参数
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  };
}

// 示例使用
function add(a, b, c) {
  return a + b + c;
}

const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3));  // 输出:6
console.log(curriedAdd(1, 2)(3));  // 输出:6
console.log(curriedAdd(1)(2, 3));  // 输出:6

4、什么是原型,原型链

  1. 在 JavaScript 中,原型(prototype)是一个对象,其他对象可以通过它继承属性和方法。JavaScript 中的对象有一个内置的 proto 属性,指向其原型对象。当访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,解释器就会去该对象的原型中查找。如果原型中也没有,就会一直查找到 object.prototype.proto, 即原型链的顶端。这个过程构成了原型链。
  2. 原型链是 JavaScript 实现继承的一种机制,它允许对象继承另一个对象的属性和方法。这种机制是动态的,如果原型对象的属性和方法被修改,所有基于该原型创建的对象都会被影响。

5、vue 中 nextTick 使用场景

  1. 更新数据后获取最新的 dom 结构:当你设置了 vue 实例的数据,vue 将开启一个队列,并缓冲在同一个事件循环中发生的所有数据变动。如果你想要基于更新后的 dom 状态进行某些操作,就需要在 nextTick 回调函数中执行。
new Vue({
  methods: {
    updateMessage() {
      this.message = '更新后的信息'
      this.$nextTick(function () {
        // DOM 更新完毕后执行
        console.log('DOM 已更新');
      })
    }
  }
})
  1. 组件生命周期内部:在某些 vue 生命周期钩子函数中,如果你需要在 dom 真正更新后再执行某些操作,也可以用到 nextTick.
mounted() {
  this.$nextTick(function () {
    // 挂载后执行的代码
  })
}
  1. 第三方库集成:当使用第三方库时,可以使用 nextTick 确保在 dom 更新之后再初始化插件。

十五、4.22 晚长沙某公司线上面试

1、流式布局

流式布局(Fluid Layout)是一种网页设计布局方式,它能够根据浏览器窗口的大小自动调整页面元素的宽度。在这种布局中,元素的宽度通常以百分比表示,而不是固定的像素值。当浏览器窗口大小变化时,元素宽度也会相应地变化,从而保持元素之间的相对位置和比例关系。 流式布局的主要特点包括:

  1. 响应性:流式布局能够很好地适应不同尺寸的屏幕,提供良好的用户体验。
  2. 灵活性:元素宽度可以随着窗口大小的变化而变化,这为设计提供了很大的灵活性。
  3. 易于实现:与固定布局或弹性布局相比,流式布局通常更简单,更容易实现。
  4. 兼容性:流式布局在各种浏览器上都有较好的兼容性,能够适应不同的设备和浏览器环境。 流式布局通常与媒体查询(Media Queries)结合使用,以便在不同的屏幕尺寸或设备上提供更好的布局效果。通过媒体查询,可以针对不同的屏幕尺寸设置不同的样式规则,从而实现更加精细的响应式设计。 流式布局的示例代码如下:
.container {
  width: 80%;
  margin: 0 auto;
}
.column {
  width: 31%;
  float: left;
  margin: 1%;
}
/* 使用媒体查询针对不同屏幕尺寸进行调整 */
@media (max-width: 600px) {
  .column {
    width: 100%;
  }
}

在这个示例中,.container 容器的宽度设置为80%,使其在大多数屏幕上都能够有适当的空间。.column 类表示容器中的列,宽度设置为31%,并且浮动使其并排显示。每个列之间有1%的边距。当屏幕尺寸小于600px时,媒体查询会调整列的宽度为100%,使其堆叠显示。 总的来说,流式布局是一种简单而有效的响应式设计方法,能够使网页布局在不同设备上都能够保持良好的可读性和功能性。

2、移动端适配

移动端适配是确保网页内容在不同移动设备上能够正确显示和良好运行的过程。由于移动设备的屏幕尺寸、分辨率、像素密度和浏览器特性各异,因此移动端适配是一个复杂且必要的过程。以下是一些移动端适配的策略和技术:

  1. 响应式网页设计(Responsive Web Design, RWD):
    • 使用媒体查询(Media Queries)来应用不同的CSS样式规则,以适应不同的屏幕尺寸和设备特性。
    • 使用流动布局(Fluid Layouts)和弹性网格(Flexible Grids)来设计可伸缩的布局。
    • 使用可伸缩的图片和媒体,确保它们能够适应不同的屏幕尺寸。
  2. 视口(Viewport)配置:
    • 在HTML中设置视口标签(<meta name="viewport" content="...">),控制页面在移动设备上的布局和缩放行为。
  3. 使用百分比和相对单位:
    • 使用百分比来定义元素的宽度,而不是固定的像素值。
    • 使用相对单位(如em、rem)来定义字体大小,以便在不同设备上保持一致的可读性。
  4. 移动端优先(Mobile First):
    • 采用移动端优先的设计理念,先为移动设备设计页面,然后逐步增加复杂性和功能,以适应更大的屏幕。
  5. 触摸和手势事件:
    • 确保网页能够响应触摸事件,如点击、滑动、拖动等。
    • 使用JavaScript库或框架(如Hammer.js)来简化手势事件的实现。
  6. 性能优化:
    • 优化图片和资源加载,使用懒加载等技术减少初始加载时间。
    • 压缩CSS和JavaScript文件,减少代码体积。
    • 使用CDN分发内容,加快加载速度。
  7. 测试和调试:
    • 使用真实的移动设备进行测试,或者使用模拟器/emulators和开发者工具来模拟不同的设备和屏幕尺寸。
    • 使用浏览器的开发者工具来调试CSS和JavaScript。
  8. 使用框架和库:
    • 使用Bootstrap、Foundation等前端框架,它们提供了预定义的响应式设计组件和网格系统。
    • 使用Vue.js、React等前端库或框架,它们提供了更高效的状态管理和组件化开发方式。
  9. 图标和字体:
    • 使用SVG图标,它们可以缩放而不失真。
    • 使用Web字体(如Google Fonts)来确保字体在不同设备上的显示效果一致。
  10. 应用程序壳(App Shell)架构:
    • 使用Service Workers和缓存技术来实现离线功能和快速加载的界面。 通过上述策略和技术,可以确保网页在移动设备上提供良好的用户体验,同时保持内容和功能的完整性。

3、promise

  • 概念:异步编程的一种解决方案,解决了回调地狱的问题。
  • 使用方法:New Promise(resolve();reject());如果有多个resole,reject的话,仅执行第一个,如果第一个是resolve的话,后面可以接then,查看成功消息;如果第一个是reject的话,后面可以接catch,查看错误消息。

十六、4.23 下午上海虹口某做算法交易系统公司线上面试

1、说下同步和异步及应用场景

同步(Synchronous)和异步(Asynchronous)是两种不同的编程模式,它们在处理任务和操作时有着根本的区别。

同步

同步操作是按顺序执行的,每个操作必须在前一个操作完成后才能开始。在同步模式下,程序可能会阻塞,即当前操作没有完成时,后续操作将无法执行。

应用场景
  1. 简单的顺序操作:当操作简单且需要按照特定顺序执行时,同步模式是合适的。
  2. 需要严格保证数据一致性的情况:例如,数据库事务处理,需要确保一系列操作要么全部完成,要么全部不发生。
  3. 程序逻辑简单,易于理解和调试

异步

异步操作允许程序在等待一个操作完成的同时继续执行其他操作。这种方式可以提高程序的效率和响应性,因为它不会因为一个耗时的操作而阻塞整个程序的执行。

应用场景
  1. IO密集型操作:如文件读写、网络请求等,这些操作通常需要等待外部系统响应。
  2. UI应用程序:异步操作可以避免界面冻结,保持应用程序的响应性。
  3. 多任务处理:在多线程或多进程环境中,异步模式可以有效地管理多个任务的执行。
  4. 耗时的计算任务:如视频渲染、大数据处理等,异步执行这些任务可以让程序继续处理其他事情。

实际例子

同步

在JavaScript中,普通的函数调用是同步的。例如:

function syncFunction() {
  // 假设这个操作耗时1秒
  console.log("同步操作完成");
}
console.log("开始同步操作");
syncFunction();
console.log("同步操作之后的操作");

输出将是:

开始同步操作
同步操作完成
同步操作之后的操作
异步

在JavaScript中,可以使用setTimeout来实现异步操作。例如:

function asyncFunction() {
  setTimeout(() => {
    console.log("异步操作完成");
  }, 1000);
}
console.log("开始异步操作");
asyncFunction();
console.log("异步操作之后的操作");

输出将是:

开始异步操作
异步操作之后的操作
异步操作完成

注意,异步操作的完成是在最后打印的,这是因为程序在等待异步操作完成的同时继续执行了后续的代码。 在实际开发中,选择同步还是异步取决于具体的应用场景和需求。在现代编程中,异步编程模式越来越受欢迎,特别是在处理IO密集型任务和构建响应式应用程序时。

十七、4.24 下午两点上海宝山某公司线上面试

1、下面代码会造成哪些问题,如何解决?(回流)

const el = document.querySelector('.box')
el.style.margin = '5px'
el.style.borderRadius = '12px'
el.style.boxShadow = '1px 3px 4px #ccc'

这段代码会导致浏览器进行不必要的回流和重绘。每次设置元素的样式属性都会触发浏览器的布局过程,即回流(reflow),以确保元素的正确显示。回流是一个相对昂贵的操作,因为它需要浏览器重新计算元素的几何位置和大小,以及所有子元素的位置和大小。 在上述代码中,每次设置el.style属性时,都会触发一次回流。为了减少回流的次数,可以将所有样式修改合并在一起,然后一次性应用。 解决方法是将样式修改合并到el.style对象中,或者使用cssText属性一次设置所有样式,或者使用classList添加预定义的样式类。下面是两种解决方法的示例:

  1. 方法一:使用CSS类
/* 在CSS中预定义样式 */
.my-style {
  margin: 5px;
  border-radius: 12px;
  box-shadow: 1px 3px 4px #ccc;
}
// 在JavaScript中添加类名
const el = document.querySelector('.box');
el.classList.add('my-style');
  1. 方法二:使用cssText
const el = document.querySelector('.box');
el.style.cssText += 'margin: 5px; border-radius: 12px; box-shadow: 1px 3px 4px #ccc;';
  1. 方法三:一次性修改style对象
const el = document.querySelector('.box');
el.style.margin = '5px';
el.style.borderRadius = '12px';
el.style.boxShadow = '1px 3px 4px #ccc';

在这种方法中,虽然我们还是在JavaScript中分别设置了三个样式属性,但是这些修改会在下一次回流时一次性应用,从而减少了回流的次数。 总之,为了避免不必要的回流和重绘,应该尽量减少对DOM样式的直接修改,并且在必要时将样式修改合并在一起。

2、哪些手段或方法可以减小回流造成的性能损耗?

回流是浏览器更新页面布局和重新绘制元素的过程,它可能会对性能造成较大影响。为了减小回流造成的性能损耗,可以采取以下手段或方法:

  1. 减少DOM操作:尽量减少对DOM的访问和修改次数,例如,可以使用documentFragment来批量操作DOM。
  2. 合并样式修改:如前所述,尽量一次性修改元素的样式,而不是多次单独修改。可以使用cssText属性或者修改类名来应用多个样式。
  3. 避免不必要的样式计算:减少使用offsetWidthoffsetHeightclientWidthclientHeight等会触发回流的方法。
  4. 使用缓存:对于需要多次读取的样式值,可以将其缓存到变量中,避免多次查询。
  5. 使用requestAnimationFrame:将DOM操作放在requestAnimationFrame回调中,这样可以保证在下次重绘之前执行DOM更新。
  6. 使用Flexbox布局:相比于传统的布局模型(如浮动和定位),Flexbox可以更高效地处理布局,减少回流的次数。
  7. 提升动画元素的图层:对于经常变动或者动画效果的元素,可以使用will-change属性或者transform属性将其提升到单独的图层,这样它们的变动就不会影响到其他元素的布局。
  8. 避免使用复杂的CSS选择器:复杂的CSS选择器会增加浏览器的渲染时间,尤其是在回流时。
  9. 使用虚拟DOM库:在现代前端框架中,如React、Vue等,虚拟DOM可以帮助优化DOM操作,减少实际的DOM更新次数。
  10. 优化资源加载:延迟加载图片和非关键的资源,可以减少页面加载时的回流次数。 通过上述方法,可以显著减少回流和重绘的次数,从而提高页面的性能。

3、如果一个页面非常卡顿,你可以借助哪些工具来查找到造成卡顿的源文件或文件夹?

如果一个页面非常卡顿,你可以使用以下工具来帮助查找造成卡顿的源文件或文件夹:

  1. Chrome DevTools:
    • Performance 面板: 记录页面加载过程中的所有活动,包括网络请求、JavaScript 执行、渲染和布局等,可以帮助你分析页面性能瓶颈。
    • Memory 面板: 分析页面的内存使用情况,帮助你识别内存泄漏或其他内存问题。
    • Layer 面板: 查看页面上的图层,帮助确定是否有不必要的图层或复杂的绘制操作导致性能问题。
  2. Lighthouse:
    • 这是一个开源的自动化工具,可以帮你检查页面性能、可访问性、SEO 和最佳实践。它可以在 Chrome DevTools 中运行,也可以作为 Node.js 模块使用。
  3. WebPageTest:
    • 这个在线工具可以模拟不同网络条件和设备上的页面加载情况,并提供详细的性能分析报告。
  4. Profiling in Visual Studio Code:
    • 如果你使用 VS Code 作为你的代码编辑器,它可以提供 JavaScript 代码的性能分析工具。
  5. Node.js 性能监控工具:
    • 如果页面是服务器端渲染的(如使用 Next.js 或 Nuxt.js),可以使用 Node.js 的性能监控工具,如 clinic0x,来分析服务端的性能问题。
  6. 第三方性能监控服务:
    • 使用第三方服务,如 New Relic、Dynatrace 或 GTmetrix,可以提供实时的性能监控和报警,帮助你及时发现和诊断性能问题。 使用这些工具时,你应该关注以下几个方面:
  • 加载时间:页面加载过程中的每个阶段所需的时间。
  • 资源大小和加载时间:脚本、样式表、图片等资源的大小和加载时间。
  • JavaScript 执行时间:脚本解析、执行和事件处理的时间。
  • 渲染和布局:页面的渲染和布局时间,以及是否有过多的重绘和回流。
  • 内存使用:页面的内存使用情况,是否有内存泄漏。 通过这些工具和分析,你可以识别出导致页面卡顿的具体原因,并针对性地进行优化。

3、下面代码会造成哪些问题?(浏览器垃圾回收机制)

<div id="root">
<div class="child">我是子元素</div>
<button>移除</button>
</div>
<script>
 let btn = document.querySelector('button')
 let child = document.querySelector('.child')
 let root = document.querySelector('#root')
  
 btn.addEventListener('click', function() {
  	root.removeChild(child)
 })
</script>
  • 这段代码本身在功能上是没有问题的:它会在按钮被点击时移除idrootdiv元素下的classchild的子元素。然而,从内存管理和垃圾回收的角度来看,这里有一个潜在的问题。 在JavaScript中,给元素添加事件监听器会导致元素与事件处理函数之间建立一个引用。即使元素从DOM中被移除,如果事件监听器没有被正确清理,这个引用仍然存在。在这种情况下,由于事件监听器仍然持有对child元素的引用,垃圾回收机制(Garbage Collection)可能不会释放child元素占用的内存,从而导致内存泄漏。
  • 为了解决这个问题,你应该在不再需要child元素时移除事件监听器,或者使用现代JavaScript框架(如React、Vue等),这些框架通常会自动处理这些内存管理问题。如果你需要手动处理这个问题,可以在移除子元素之前移除事件监听器,或者使用弱引用(如WeakMapWeakSet),这样垃圾回收器就可以在元素不再被引用时回收其内存。
  • 在这个例子中,由于child元素是通过querySelector获取的,并且没有其他引用,所以当removeChild被调用时,child元素应该会被垃圾回收器正确回收。但是,这是一个好的实践,即在不需要元素时移除事件监听器,以避免潜在的内存泄漏问题。

4、续写下面代码

function test () {
	console.log(1);
    
    var flag = false;
    
    setTimeout(() =>{
        console.log(2);
        flag = true;
    }, 5000)

    // 上方定时器的时间不固定, 写一些代码,判断什么时候flag 为 true, 才执行下面代码,否则下方代码一直等待
    // .....
	console.log(3);
}

	test()

要实现这个功能,你可以使用Promiseasync/await来等待flag变为true。下面是一个修改后的test函数,它将等待flag变为true后继续执行:

function test() {
    console.log(1);

    let flag = false;

    const waitUntilTrue = () => {
        return new Promise((resolve) => {
            const interval = setInterval(() => {
                if (flag) {
                    clearInterval(interval);
                    resolve();
                }
            }, 100); // 检查间隔时间,这里设置为100毫秒
        });
    };

    setTimeout(() => {
        console.log(2);
        flag = true;
    }, 5000);

    // 使用async函数来等待flag变为true
    (async () => {
        await waitUntilTrue();
        console.log(3);
    })();
}

test();

十八、4.24 下午三点广州海珠区某公司线上面试

1、浏览器输入 url 到页面加载完成的全过程

当你在浏览器中输入一个URL并按下回车键时,浏览器会经历一系列复杂的步骤来加载并显示该URL对应的网页。以下是这一过程的详细描述:

  1. 域名解析(DNS查询)
    • 浏览器首先检查域名是否在本地缓存中。
    • 如果不在缓存中,浏览器会向网络上的DNS服务器发送请求,将域名解析为一个IP地址。
  2. 建立连接(TCP连接)
    • 浏览器使用解析得到的IP地址,通过TCP协议与目标服务器建立连接。
    • 这通常涉及到三次握手过程,以确保双方都准备好进行数据传输。
  3. 发送HTTP请求
    • 浏览器发送一个HTTP请求到服务器,请求可以是GET或POST,取决于所需的操作。
    • 如果是HTTPS网址,还会在TCP连接之后建立TLS连接,以确保数据加密和安全。
  4. 服务器处理请求
    • 服务器接收到请求后,根据请求类型进行处理。
    • 这可能包括读取数据库、执行服务器端代码、调用其他服务或返回静态文件。
  5. 服务器响应
    • 服务器将处理结果作为HTTP响应返回给浏览器。
    • 响应通常包括状态码(如200表示成功),以及响应体(如HTML内容)。
  6. 浏览器处理响应
    • 浏览器接收到服务器返回的HTML内容后,开始解析HTML文档并构建DOM树。
    • 如果HTML中包含CSS文件、JavaScript文件等外部资源,浏览器会额外发送请求来获取这些资源。
  7. 构建DOM树和渲染
    • 浏览器解析HTML,并根据解析结果构建DOM树。
    • 浏览器根据CSS样式信息和DOM树来构建渲染树。
    • 浏览器进行布局和绘制,将页面内容显示在屏幕上。
  8. 执行JavaScript
    • 如果页面包含JavaScript,浏览器会执行它,这可能会导致页面内容的改变或额外的网络请求。
  9. 页面加载完成
    • 当所有资源都被加载和渲染完成后,浏览器会触发onload事件,标志着页面加载过程的结束。 这个过程可能会因为各种因素(如网络速度、服务器响应时间、页面复杂性等)而有所不同。浏览器提供了开发者工具,允许开发者查看这个过程的所有细节,以便于优化页面加载性能。

2、浏览器怎么解析 html 的

浏览器解析HTML的过程可以分为几个主要步骤:

  1. 解析HTML文档
    • 浏览器从网络或本地缓存中获取HTML文档的内容。
    • 解析器开始从文档的开始部分逐字符地读取内容。
    • 解析器识别标签、属性和文本内容,并将它们转换为解析树中的节点。
  2. 构建DOM树
    • 解析器按照HTML文档的结构构建一个树状结构,这个树被称为文档对象模型(DOM)树。
    • 每个HTML标签都成为DOM树中的一个节点,文本内容也被创建为文本节点。
  3. 处理脚本
    • 如果解析器遇到<script>标签,它会暂停构建DOM树,并执行脚本。
    • 如果脚本是同步的,解析器会等待脚本执行完毕后再继续构建DOM树。
    • 如果脚本是异步的(asyncdefer),解析器会继续构建DOM树,而不等待脚本执行。
  4. 触发DOMContentLoaded事件
    • 一旦所有的同步脚本都执行完毕,浏览器会触发DOMContentLoaded事件。
    • 这表示HTML文档已经被完全加载和解析,但是像图片、样式表等外部资源可能还没有加载完成。
  5. 加载外部资源
    • 在构建DOM树的过程中,如果遇到外部资源(如CSS文件、JavaScript文件、图片等),浏览器会发起额外的网络请求来获取这些资源。
    • CSS文件被下载后,浏览器会解析它们并构建CSSOM(CSS对象模型)树。
  6. 构建渲染树
    • 结合DOM树和CSSOM树,浏览器构建渲染树。
    • 渲染树只包含需要显示的节点和这些节点的样式信息。
  7. 布局和绘制
    • 浏览器计算渲染树中每个节点的布局位置,这个过程称为布局或重排。
    • 然后浏览器将布局后的节点绘制到屏幕上,这个过程称为绘制或重绘。 整个解析过程是逐步进行的,浏览器可以在接收到HTML文档的部分内容后就开始构建DOM树,而不必等到整个文档下载完成。这种流式解析方式可以显著提高页面加载速度。

3、首页性能优化的手段

首页性能优化是提高用户体验和搜索引擎排名的关键。以下是一些优化首页性能的主要手段:

  1. 优化资源加载
    • 压缩图片和其他媒体文件。
    • 使用现代图像格式,如WebP。
    • 通过CDN(内容分发网络)分发静态资源。
    • 减少HTTP请求,合并CSS和JavaScript文件。
  2. 利用浏览器缓存
    • 设置合理的缓存策略,使资源在用户的浏览器中缓存更长时间。
  3. 代码优化
    • 压缩CSS和JavaScript文件。
    • 使用代码拆分和懒加载,只加载用户当前需要的资源。
    • 移除未使用的CSS和JavaScript代码。
    • 避免使用CSS表达式和复杂的CSS选择器。
  4. 提高服务器响应速度
    • 优化服务器配置,使用高效的服务器软件。
    • 实施服务器端渲染(SSR)以减少首屏时间。
    • 使用Etags减少不必要的请求。
  5. 优化HTML和CSS
    • 优化HTML结构,确保关键内容优先加载。
    • 使用CSS Sprites减少图片请求。
    • 使用Flexbox或Grid布局代替传统的浮动布局。
  6. 减少重排和重绘
    • 避免在布局和绘制过程中频繁更改样式。
    • 使用transformopacity属性进行动画,这些属性不会触发重排。
  7. 使用Web字体优化
    • 限制使用的字体数量和样式。
    • 使用本地字体或子集化字体。
  8. 优化JavaScript执行
    • 避免长时间运行的JavaScript任务。
    • 使用异步脚本(asyncdefer)避免阻塞DOM构建。
  9. 使用HTTP/2
    • 如果可能,升级到HTTP/2,它提供了更好的性能特性,如多路复用和服务器推送。
  10. 监控和分析
    • 使用工具如Google Lighthouse、WebPageTest和Chrome DevTools来分析页面性能。
    • 监控真实的用户性能数据,以便于持续优化。
  11. 移动端优化
    • 使用响应式设计确保页面在移动设备上的性能。
    • 优化触摸事件的处理。
  12. SEO优化
    • 优化页面元数据,如标题、描述和关键词。
    • 使用结构化数据(如JSON-LD)提高搜索引擎的解析效率。 通过这些手段,可以提高首页的加载速度,提升用户体验,并可能提高在搜索引擎中的排名。

4、vue3 中异步组件怎么实现

在Vue 3中,你可以使用异步组件来优化你的应用程序的性能,特别是在你需要懒加载或者代码分割的时候。Vue 3提供了一种新的异步组件的写法,使用defineAsyncComponent函数来定义异步组件。 以下是如何在Vue 3中使用异步组件的步骤:

  1. 首先,确保你有一个Vue 3项目。如果没有,你可以使用Vue CLI或者其他工具来创建一个。
  2. 使用defineAsyncComponent函数定义一个异步组件。这个函数接受一个返回Promise的工厂函数,Promise应该解析为组件定义。 下面是一个简单的例子:
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
  import('./components/AsyncComponent.vue')
);
app.component('async-component', AsyncComponent);

在上面的例子中,defineAsyncComponent函数接收一个返回import调用的函数,这个import调用返回一个Promise,当Promise解析时,它会返回AsyncComponent.vue组件的定义。 3. 在你的Vue组件中,你可以像使用普通组件一样使用这个异步组件:

<template>
  <div>
    <async-component />
  </div>
</template>
<script>
import { defineAsyncComponent } from 'vue';
export default {
  components: {
    AsyncComponent: defineAsyncComponent(() =>
      import('./components/AsyncComponent.vue')
    ),
  },
};
</script>
  1. 你还可以为异步组件提供加载中、错误和超时情况下的回退选项:
const AsyncComponent = defineAsyncComponent({
  loader: () => import('./components/AsyncComponent.vue'),
  loadingComponent: LoadingComponent, // 加载中显示的组件
  errorComponent: ErrorComponent, // 发生错误时显示的组件
  delay: 200, // 在显示加载中组件之前的延迟时间(毫秒)
  timeout: 3000, // 如果加载组件的时间超过了这个值,将显示错误组件
});

使用defineAsyncComponent可以让你的应用程序更灵活地管理组件的加载,提高性能和用户体验。

5、js 代码分割

JavaScript(JS)代码分割是一种优化技术,旨在将代码拆分成多个小块,然后按需加载,从而提高网页加载速度。这种方法也被称为“懒加载”或“代码分割”。 在JavaScript中,有多种方法可以实现代码分割,其中最常用的是使用模块化的打包工具,如Webpack、Rollup或Parcel。 以下是一个简单的例子,展示了如何使用Webpack进行代码分割:

  1. 首先,安装Webpack和Webpack CLI:
npm install --save-dev webpack webpack-cli
  1. 在项目根目录下创建一个webpack.config.js文件,并添加以下配置:
const path = require('path');
module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },
};
  1. src目录下创建一个index.js文件,并添加以下代码:
import('./math').then((math) => {
  console.log(math.add(16, 26));
});
  1. src目录下创建一个math.js文件,并添加以下代码:
export const add = (a, b) => a + b;
  1. package.json文件中添加一个脚本,用于构建项目:
"scripts": {
  "build": "webpack"
}
  1. 运行以下命令构建项目:
npm run build

这将生成两个JavaScript文件:main.bundle.jssrc_math.bundle.jsmain.bundle.js包含入口文件index.js的代码,而src_math.bundle.js包含按需加载的math.js模块的代码。 在浏览器中加载main.bundle.js时,它将异步加载src_math.bundle.js,从而实现代码分割。这种方法可以提高网页加载速度,因为它可以减少初始加载的JavaScript文件大小。

6、vue3 中实现代码分割

在Vue 3中,代码分割可以通过多种方式实现,其中最常见的是使用动态导入(Dynamic Imports)和Vue的异步组件特性。代码分割允许你将应用程序分割成多个小块,然后按需加载,这样可以减少应用程序的初始加载时间,提高性能。 以下是使用Vue 3进行代码分割的几种方法:

  1. 使用异步组件和defineAsyncComponent Vue 3提供了一个defineAsyncComponent函数,它可以用来定义一个异步组件。这个函数返回一个异步组件,当组件被渲染时,它会在背后自动导入相关的模块。
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
  import('./components/AsyncComponent.vue')
);
export default {
  components: {
    AsyncComponent
  }
}

在模板中,你可以像使用普通组件一样使用AsyncComponent。 2. 使用动态导入语法 你还可以直接在组件内部使用ES2020的动态导入语法来实现代码分割。

export default {
  components: {
    AsyncComponent: () => import('./components/AsyncComponent.vue')
  }
}
  1. 使用路由懒加载 如果你在使用Vue Router,你可以通过路由懒加载来实现代码分割。这需要在定义路由时使用动态导入。
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/async',
      component: () => import('./components/AsyncComponent.vue')
    }
  ]
});
export default router;
  1. 使用构建工具 除了Vue提供的特性外,现代前端构建工具如Webpack、Rollup和Vite也支持代码分割。在使用这些工具时,你可以通过配置它们来实现代码分割。 例如,在使用Webpack时,你可以通过配置optimization.splitChunks来实现代码分割。在使用Vite时,它默认支持动态导入,因此你只需要使用动态导入语法,Vite就会自动处理代码分割。

注意事项

  • 确保你的构建工具支持代码分割,并且正确配置。
  • 使用代码分割时,考虑用户体验,确保按需加载的组件不会导致页面内容的跳跃或不流畅。
  • 测试你的应用程序,确保分割后的代码能够正确加载和执行。 通过这些方法,你可以有效地减少应用程序的初始加载时间,提高用户体验。

7、js 实现 md5 加密

在JavaScript中实现MD5加密参数传递,你可以使用现成的MD5库,比如crypto-js。以下是如何在浏览器中使用crypto-js库来加密参数的步骤:

  1. 首先,你需要将crypto-js库添加到你的项目中。如果你正在使用npm,可以通过以下命令安装:
npm install crypto-js

或者,如果你想在浏览器中使用,可以直接在HTML中引入CDN脚本:

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
  1. 然后,你可以使用crypto-jsMD5函数来加密你的参数。 以下是一个示例代码,展示了如何在JavaScript中使用MD5加密参数:
// 假设这是你要传递的参数
var params = {
  name: 'John Doe',
  age: '30',
  country: 'USA'
};
// 将参数对象转换为字符串
var paramsString = Object.keys(params).sort().map(function(key) {
  return key + '=' + params[key];
}).join('&');
// 使用crypto-js的MD5函数进行加密
var md5Hash = CryptoJS.MD5(paramsString).toString();
// 将MD5散列值作为参数的一部分
params.signature = md5Hash;
// 现在,你可以将params对象作为请求发送到服务器
console.log(params);

在这个例子中,我们首先将参数对象转换为排序后的字符串,然后使用crypto-jsMD5函数来生成MD5散列值,并将这个散列值作为signature参数添加到原始参数中。最后,你可以将这个包含签名的参数对象作为请求发送到服务器。 服务器端也应该使用相同的过程来验证接收到的参数,以确保参数在传输过程中没有被篡改。

十九、4.25 下午三点半上海静安区某公司腾讯天美工作室外包线上面试

1、call, apply, bind 区别

  1. fn.call (newThis,params) call函数的第一个参数是this的新指向,后面依次传入函数fn要用到的参数。会立即执行fn函数。

  2. fn.apply (newThis,paramsArr) apply函数的第一个参数是this的新指向,第二个参数是fn要用到的参数数组,会立即执行fn函数。

  3. fn.bind (newThis,params) bind函数的第一个参数是this的新指向,后面的参数可以直接传递,也可以按数组的形式传入。不会立即执行fn函数,且只能改变一次fn函数的指向,后续再用bind更改无效。返回的是已经更改this指向的新fn

2、ts 数据类型区别

TypeScript 是 JavaScript 的一个超集,它添加了静态类型检查。TypeScript 提供了多种数据类型,这些数据类型可以帮助开发者更好地理解和维护代码。以下是一些基本的数据类型及其区别:

基本类型

  1. 布尔值 (boolean)
    • 表示逻辑值 true 或 false。
  2. 数字 (number)
    • 表示整数或浮点数。
  3. 字符串 (string)
    • 表示文本数据。
  4. 符号 (symbol)
    • 表示唯一的、不可变的数据类型,常用于对象属性的唯一标识符。

对象类型

  1. 数组 (Array)
    • 表示一系列有序的元素。
    • 可以是任意类型的元素。
    • 例如:number[]Array<number>
  2. 元组 (Tuple)
    • 表示一个已知数量和类型的元素组合。
    • 元组中的元素类型不必相同。
    • 例如:[string, number]
  3. 枚举 (enum)
    • 用于创建一个命名的常量集合。
    • 枚举可以是有序或无序的。
  4. 对象 (Object)
    • 表示非原始数据类型。
    • 可以包含多个键值对。
    • 例如:{ name: string, age: number }

其他类型

  1. 任意 (any)
    • 表示任意类型。
    • 当不希望类型检查器对这些值进行检查时使用。
  2. 未知 (unknown)
    • 表示任意类型,但比 any 类型更安全。
    • 在对值进行操作之前,必须进行类型检查。
  3. 未定义 (undefined)
    • 表示未初始化的变量。
  4. 空 (void)
    • 表示没有返回值的函数。
  5. 永不 (never)
    • 表示不应该出现的值。
    • 例如,一个总是会抛出异常或永远不会有返回值的函数表达式。
  6. 可选 (?:)
    • 表示属性或参数可能是 undefinednull
  7. 联合 (|)
    • 表示变量可能是多个类型中的一个。
    • 例如:string | number
  8. 交集 (&)
    • 表示变量同时满足多个类型的要求。
    • 例如:{ name: string } & { age: number }。 理解这些数据类型及其区别对于编写类型安全的 TypeScript 代码至关重要。正确的类型注解可以帮助编译器在开发阶段捕捉错误,并提高代码的可维护性。

3、vue3 获取 dom 结构

ref

4、5个表单放进一个数组,怎么统一校验

forEach 遍历校验

5、vue3 v-if 和 v-for 一起用会导致什么问题

性能问题:循环体内的条件语句会被重复执行,导致性能下降

6、ECharts 图表有哪些类型

柱状图,饼状图,折线图,甘特图,散点图等

7、ECharts 甘特图

在 Vue 3 中创建 ECharts 甘特图的步骤大致如下:

  1. 安装 ECharts:首先,你需要安装 ECharts 库。这可以通过 npm 命令完成:
    npm install echarts --save
    
  2. 引入 ECharts:在 Vue 组件中,你需要引入 ECharts 库。例如,你可以在 utils 文件中引入 ECharts,然后创建一个工具函数来初始化 ECharts 实例。
  3. 准备数据:根据你的需求,准备相应的数据。甘特图的数据通常包括项目名称、开始时间、结束时间等信息。
  4. 创建 ECharts 实例:在 Vue 组件的 mounted 钩子函数中,创建 ECharts 实例,并将其绑定到一个 HTML 元素上。
  5. 配置图表:使用 ECharts 的 API 配置图表的选项,包括标题、坐标轴、数据系列等。对于甘特图,你需要设置时间轴类型、Y轴数据、工具提示等。
  6. 设置数据系列:根据你的数据,定义不同的数据系列。每个系列可以有不同的类型(如柱状图、折线图等),以及相应的数据和样式。
  7. 添加样式和交互:根据需要,可以为图表添加不同的样式和交互功能,如图例的显示位置、工具提示的内容格式等。
  8. 完整示例代码:你可以参考 CSDN 博客上的示例代码,其中提供了使用 ECharts 库创建甘特图的详细步骤和代码。
  9. Vue 3 中使用 ECharts:如果你在 Vue 3 项目中使用 ECharts,可以参考知乎上的文章,其中提供了在 Vue 3 中使用 ECharts 的详细步骤和代码示例。 通过这些步骤和示例代码,你可以创建一个基本的甘特图。当然,具体实现可能会根据你的需求和数据结构有所不同。你可以参考 CSDN 博客和知乎上的相关文章,获取更详细的实现方法和示例代码。

二十、4.25 下午五点杭州某公司线上面试

1、字符串转数组 split

2、数组转字符串 join

3、scss 中 & 是干嘛的

在 SCSS 中,& 是一个特殊的符号,它代表当前选择器的父选择器。当你在一个选择器中使用 & 时,它会引用包含该 & 的选择器的父选择器。 这里有一个例子来解释 & 的用法:

.parent {
  color: red;
  &:hover {
    color: blue;
  }
}

在这个例子中,.parent是当前的选择器,而&引用的是.parent的父选择器。所以,.parent:hover实际上是.parent的父选择器在鼠标悬停时的样式。 另一个例子,如果你有嵌套的选择器:

.container {
  .item {
    color: red;
    &:hover {
      color: blue;
    }
  }
}

在这个例子中,.item:hover实际上是.container .item在鼠标悬停时的样式。 &是一个非常有用的符号,它可以帮助你创建更加简洁和可维护的 SCSS 代码。

建议:

简历还要再改写下,最新公司放前面

八股文还要背

mdn字符串数组方法敲敲

leecode至少刷200道

自我介绍别是背诵,自信大方展示自己即可,别说太快

二十一、4.26 上午南山某公司线下面试

1、vue3 中路由守卫 router.beforeEach 有哪些参数,各是什么含义

在 Vue 3 中,使用 Vue Router 时,router.beforeEach 是一个全局前置守卫,它在每个导航触发时被调用。这个守卫可以用来检查路由是否允许访问,或者执行一些其他逻辑,如修改请求、重定向等。 router.beforeEach 接受一个回调函数作为参数,该回调函数有三个参数:

  1. to: 即将要进入的目标路由对象。
  2. from: 当前导航正要离开的路由。
  3. next: 是一个函数,必须调用这个函数来解析这个钩子。它可以接受以下参数:
    • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed(确认的)。
    • next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
    • next('/')next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
    • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。 下面是一个使用 router.beforeEach 的例子:
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
  history: createWebHistory(),
  routes: [
    // ... 路由配置
  ],
});
router.beforeEach((to, from, next) => {
  // 可以根据 to 和 from 的信息来执行逻辑
  if (to.meta.requiresAuth && !auth.isLoggedIn()) {
    // 如果用户未登录且路由需要认证,则重定向到登录页面
    next({ name: 'login' });
  } else {
    // 否则继续导航
    next();
  }
});
// 导入 router 并在 Vue 应用中使用

在这个例子中,我们检查了即将进入的路由是否需要认证(to.meta.requiresAuth),如果需要认证且用户未登录(!auth.isLoggedIn()),则我们将用户重定向到登录页面。如果不需要认证或者用户已经登录,则调用 next() 继续导航。

2、H5 端横屏怎么适配

对于Web前端开发来说,适配横屏显示是一个重要的议题。以下是一些基本的方法和步骤来帮助你适配横屏:

  1. 媒体查询(CSS) 使用CSS的媒体查询可以检测屏幕的方向,并为不同的屏幕方向应用不同的样式。例如:
/* 竖屏样式 */
@media screen and (orientation: portrait) {
  /* 竖屏时应用的CSS样式 */
}
/* 横屏样式 */
@media screen and (orientation: landscape) {
  /* 横屏时应用的CSS样式 */
}
  1. 使用JavaScript检测屏幕方向 除了CSS,你还可以使用JavaScript来检测屏幕的方向,并根据方向做出相应的调整。
window.addEventListener("resize", function() {
  if (window.orientation === 90 || window.orientation === -90) {
    // 横屏
  } else {
    // 竖屏
  }
});
  1. 适配不同的分辨率和设备像素比 不同的设备有不同的分辨率和像素比,因此在适配横屏时,你可能还需要考虑这些因素。
@media screen and (min-resolution: 192dpi) and (orientation: landscape) {
  /* 高分辨率横屏样式 */
}
  1. 使用百分比和flexbox布局 使用百分比和flexbox布局可以帮助你的页面在不同尺寸的屏幕上更好地适配。
.container {
  display: flex;
  flex-direction: row; /* 或者 column,取决于你需要的效果 */
}
  1. 测试和优化 最后,不要忘记在各种设备和浏览器上测试你的横屏适配,并根据测试结果进行优化。 通过以上这些方法,你可以更好地为横屏设备适配你的Web应用。希望这些信息能够帮助你!

3、前端 vite 项目怎么打包到不同环境,比如测试服,正式服

在使用 Vite 进行前端项目开发时,打包到不同环境(如测试服和正式服)通常涉及到环境变量的管理和构建命令的配置。以下是一些基本步骤:

1. 安装和配置环境变量管理工具

Vite 本身支持基于 .env 文件的环境变量管理。你不需要安装额外的工具。

2. 创建不同环境的环境变量文件

在你的项目根目录下创建以下文件:

  • .env.development:开发环境
  • .env.staging:测试环境
  • .env.production:正式环境 在每个文件中,定义对应环境所需的环境变量,例如: .env.development
VITE_API_BASE_URL=/api

.env.staging

VITE_API_BASE_URL=https://staging-api.example.com

.env.production

VITE_API_BASE_URL=https://api.example.com

3. 配置 vite.config.js

在 Vite 项目中,vite.config.js 是配置 Vite 构建和开发服务器的主要文件。在这个文件中,你可以根据不同的环境(开发、测试、生产等)来设置不同的构建配置。

环境变量

Vite 使用 process.env.NODE_ENV 来确定当前的环境。默认情况下,这个变量的值由运行的命令决定:

  • npm run devvite:开发环境(development
  • npm run build:生产环境(production) 你可以通过在命令行中添加 --mode 参数来覆盖这个值,例如:
npm run build --mode staging

这会将 process.env.NODE_ENV 设置为 staging

vite.config.js 配置

vite.config.js 文件中,你可以使用 defineConfig 帮助函数来定义配置。这个函数提供了类型提示和自动完成,使得配置 Vite 更加方便。 以下是一个根据不同环境配置 vite.config.js 的示例:

import { defineConfig } from 'vite'
export default defineConfig(({ command, mode }) => {
  // command 可以是 'build' 或 'serve'
  // mode 可以是 'development', 'staging', 或 'production'
  if (mode === 'development') {
    // 开发环境配置
    return {
      // 开发服务器配置...
    }
  } else if (mode === 'staging') {
    // 测试环境配置
    return {
      build: {
        outDir: 'dist-staging', // 构建输出目录
        // 其他测试环境构建配置...
      }
    }
  } else if (mode === 'production') {
    // 生产环境配置
    return {
      build: {
        outDir: 'dist-production', // 构建输出目录
        // 其他生产环境构建配置...
      }
    }
  } else {
    // 其他自定义模式
    return {
      // 自定义配置...
    }
  }
})

在这个配置中,我们根据 mode 参数的值来返回不同的配置对象。这样,你就可以为不同的环境设置不同的 outDir(输出目录),以及其他构建配置选项,如 rollupOptionsminifycssCodeSplit 等。

注意事项

  • defineConfig 是 Vite 提供的一个工具函数,用于提供类型提示和自动完成。如果你不需要这些功能,可以直接返回一个配置对象。
  • 在使用 defineConfig 时,你可以传入一个配置对象或一个返回配置对象的函数。在上面的示例中,我们传入了 一个函数,这样可以根据命令和模式动态地返回配置。
  • 确保在部署到不同环境时,使用了正确的 NODE_ENV 值,以便 Vite 能够应用正确的配置。 通过这种方式,你可以轻松地为不同的环境配置 Vite,以确保你的应用在不同的部署环境中都能正确地运行。

4. 打包命令

Vite 提供了以下命令来构建不同环境的应用:

# 打包开发环境
npm run build --mode development
# 打包测试环境
npm run build --mode staging
# 打包正式环境
npm run build --mode production

默认情况下,npm run build 会使用 production 模式。你可以通过 --mode 参数来指定不同的模式。

5. 部署

将打包后的文件部署到对应的服务器或服务。你可以使用 FTP、SCP、RSYNC 或其他传输工具手动上传,或者配置 CI/CD 工具(如 Jenkins、GitLab CI/CD、GitHub Actions)来自动化部署。

6. 确认部署

部署完成后,访问对应的域名或IP地址,确认应用是否正常运行。

注意事项

  • 确保在部署前进行了充分的测试。
  • 使用版本控制来管理不同环境的部署,以便于追踪和回滚。
  • 使用HTTPS和其他安全措施来保护用户数据。 通过以上步骤,你可以将 Vite 项目打包并部署到不同的环境。记得根据实际情况调整和优化部署流程,以确保高效和稳定的应用部署。

二十二、4.28 下午某广州公司(angular)线上面试

1、防抖和节流怎么实现的

  1. 防抖
/**
 * @desc 防抖函数:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
 * @param {Function} func 函数
 * @param {Number} wait 延迟执行毫秒数
 * @param {Boolean} immediate true 表示立即执行,false 表示非立即执行
 */
let timeout;
export function debounce(func, wait = 500, immediate = false) {
    // 清除定时器
    if (timeout) clearTimeout(timeout)
    // 立即执行,此类情况一般用不到
    if (immediate) {
        let callNow = !timeout
        timeout = setTimeout(() => {
            timeout = null
        }, wait)
        if (callNow) typeof func === 'function' && func()
    } else {
        // 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
        timeout = setTimeout(() => {
            typeof func === 'function' && func()
        }, wait)
    }
}
//使用示例:debounce(() => req(e.target.value), 500);
  1. 节流
/**
 * @desc 节流函数:在一定时间内,只能触发一次
 * @param {Function} func 函数
 * @param {Number} wait 延迟执行毫秒数
 */
let previous = 0;
export function throttle(func, wait = 500) {
    let now = Date.now()
    if (now - previous > wait) {
        typeof func === 'function' && func()
        previous = now
    }
}
//使用示例:throttle(() => req(), 1000)

二十三、4.29 下午南山西丽某电商公司线下面试

二十四、5.7 上午广州某腾讯外包线上面试

前端笔试题

1.  浏览器缓存规则是怎样的?(5) 

浏览器缓存规则是浏览器用来决定是否使用缓存中的资源而不是重新从服务器下载的机制。这些规则主要基于HTTP响应头中的信息,包括Cache-ControlExpiresLast-ModifiedEtag等。以下是浏览器缓存的基本规则:

  1. Cache-Control:这是HTTP/1.1中定义的通用缓存响应头,它包含了多个指令,如max-ages-maxagepublicprivateno-storeno-cache等。max-age指定资源被视为新鲜的时间(秒数),在这段时间内,浏览器会使用缓存中的资源。s-maxagemax-age类似,但是它只适用于共享缓存(如CDN)。public表示响应可以被任何缓存存储,而private表示响应只能被用户的浏览器缓存。no-store表示不允许缓存响应,而no-cache表示可以缓存,但在使用之前必须重新验证。
  2. Expires:这是一个HTTP/1.0的响应头,它指定了一个日期/时间,在这个时间之前,浏览器会认为缓存资源是新鲜的。如果同时存在Cache-ControlExpiresCache-Control优先级更高。
  3. Last-Modified / If-Modified-SinceLast-Modified是服务器响应头,表示资源的最后修改时间。浏览器在请求资源时,会在请求头中包含If-Modified-Since,它的值是上次从服务器接收到资源的Last-Modified值。如果服务器发现资源自那以后没有修改,它会返回一个304 Not Modified状态码,而不是资源的内容,浏览器可以使用缓存的版本。
  4. Etag / If-None-MatchEtag是资源的唯一标识符,由服务器生成。浏览器在请求资源时,会在请求头中包含If-None-Match,它的值是上次从服务器接收到资源的Etag值。服务器会检查当前的Etag是否与浏览器发送的If-None-Match相同,如果相同,则返回304 Not Modified状态码。
  5. Pragma:这是一个HTTP/1.0的响应头,用于向后兼容只理解HTTP/1.0的缓存。它的值no-cacheCache-Control中的no-cache类似,表示在发送给客户端之前,必须重新验证缓存。
  6. Vary:这个响应头告诉缓存机制,如何根据请求头来确定是否可以使用缓存的响应。例如,如果Vary: Accept-Encoding,缓存需要根据Accept-Encoding请求头的内容来决定是否可以使用缓存的响应。 浏览器在请求资源时,会根据上述规则检查缓存,并决定是否需要重新从服务器获取资源。这些规则可以确保用户在获取到最新内容的同时,也能享受到缓存带来的性能优势。

2. 怎么删除一个字符串的左边空白字符? 怎么获取函数的所有参数?(5)

要删除一个字符串的左边空白字符,可以使用 JavaScript 中的 trimStart() 方法(也称为 trimLeft()),这个方法会创建一个新字符串,删除原字符串开头的空白字符。例如:

const str = "   Hello World!";
const trimmedStr = str.trimStart();
console.log(trimmedStr); // 输出 "Hello World!"

在获取函数的所有参数方面,可以使用 arguments 对象,这是一个类数组对象,包含了函数接收到的所有参数。例如:

function getAllArgs() {
  console.log(arguments);
}
getAllArgs(1, 2, 3, "a", "b", "c"); // 输出 { '0': 1, '1': 2, '2': 3, '3': 'a', '4': 'b', '5': 'c' }

需要注意的是,arguments对象不是一个真正的数组,它没有数组的方法,比如 mapfilter 等。如果你需要将 arguments 转换为一个真正的数组,可以使用 Array.from() 或者扩展运算符 ...,例如:

function getAllArgs() {
  const argsArray = Array.from(arguments);
  // 或者使用扩展运算符
  // const argsArray = [...arguments];
  console.log(argsArray);
}
getAllArgs(1, 2, 3, "a", "b", "c"); // 输出 [ 1, 2, 3, 'a', 'b', 'c' ]

这样就可以使用数组的所有方法来操作函数的参数了。在现代 JavaScript 中,推荐使用剩余参数(rest parameters)来获取函数的所有参数,这样可以得到一个真正的数组,并且代码更加清晰。例如:

function getAllArgs(...args) {
  console.log(args);
}
getAllArgs(1, 2, 3, "a", "b", "c"); // 输出 [ 1, 2, 3, 'a', 'b', 'c' ]

使用剩余参数是 ES6 中的特性,它提供了一种更简洁的方式来处理不定数量的函数参数。

3.  什么是原型链?原型链的是用来干啥的?es6的类extends怎么实现继承?和原型链继承有什么区别?(10)

原型链是什么

在 JavaScript 中,几乎所有的对象都有一个prototype属性,这个属性是一个指针,指向另一个对象。这个原型对象也可能有自己的原型,形成一个原型链。当访问一个对象的属性或方法时,如果这个对象本身没有这个属性或方法,解释器会沿着原型链向上查找,直到找到为止。如果整个原型链上都找不到,则返回undefined

原型链的作用

原型链的主要作用是实现继承。在 JavaScript 中,通过原型链,一个对象可以继承另一个对象的属性和方法。这样,我们就可以创建一个基对象(通常是构造函数的prototype对象),然后创建其他对象来继承这个基对象的所有属性和方法。这样,我们就可以实现代码的复用和模块化。

ES6 类的 extends 继承

在 ES6 中,引入了class关键字,使得 JavaScript 的面向对象编程更加清晰和方便。使用class关键字,我们可以更直观地创建类和实现继承。extends关键字用于指定一个类继承另一个类。

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}
class Dog extends Animal {
  constructor(name) {
    super(name); // 调用父类的constructor(name)
  }
  speak() {
    console.log(`${this.name} barks.`);
  }
}
const dog = new Dog('Mitzie');
dog.speak(); // Mitzie barks.

在上面的例子中,Dog类通过extends关键字继承了Animal类。super关键字用于调用父类的constructor

ES6 类继承与原型链继承的区别

ES6 的类继承本质上是基于原型链的继承,但是提供了更清晰的语法和更多的功能。ES6 类继承允许我们使用super来调用父类的构造函数,同时也支持静态方法的继承。此外,ES6 类还提供了对constructorstaticgetset等属性的更好支持。 原型链继承是 ES6 类继承的基础,但是 ES6 类继承提供了更多的语法糖和功能,使得面向对象编程更加直观和易于理解。

4.  promise.all 和 promise.race有什么区别?(5)

Promise.allPromise.race 是 JavaScript 中处理多个 Promise 的两个非常有用的方法。它们都接受一个包含多个 Promise 实例的数组作为输入,但它们的行为和用途有所不同。

Promise.all

Promise.all 方法用于并行执行多个 Promise,并且等待所有 Promise 都成功解决(resolved)后,按照原始数组顺序返回一个包含所有结果的数组。如果数组中的任何一个 Promise 被拒绝(rejected),Promise.all 会立即返回一个被拒绝的 Promise,并且不会等待其他 Promise 完成。 示例:

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(values => {
  console.log(values); // [3, 42, 'foo']
});

Promise.race

Promise.race 方法同样用于并行执行多个 Promise,但它只等待第一个完成的 Promise,无论是解决还是拒绝。一旦有一个 Promise 解决或拒绝,Promise.race 就会返回那个 Promise 的结果。其他未完成的 Promise 会被忽略。 示例:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then(value => {
  console.log(value); // 'two'
});

区别

  • Promise.all 等待所有 Promise 完成,而 Promise.race 只等待第一个完成的 Promise。
  • Promise.all 返回一个包含所有结果的数组,而 Promise.race 只返回第一个完成的结果。
  • 如果 Promise.all 中的任何一个 Promise 被拒绝,整个 Promise.all 会立即被拒绝,而 Promise.race 只关心第一个完成的 Promise,无论它是解决还是拒绝。 总结来说,Promise.all 用于确保所有异步操作都成功完成,而 Promise.race 用于返回第一个完成的异步操作的结果,不管它是成功还是失败。

5.  webpack常⻅的有哪些plugin loader,可否举例一些plugin和loader,并说明 他们是用来干什么的?(10)

Webpack 的插件(Plugins)和加载器(Loaders)是构建过程中的两个关键概念,它们扩展了 Webpack 的功能。

常见的 Webpack 插件(Plugins)

  1. HtmlWebpackPlugin
    • 作用:生成一个 HTML 文件,自动将所有生成的 bundle 插入到 HTML 中。
    • 示例:
      const HtmlWebpackPlugin = require('html-webpack-plugin');
      module.exports = {
        plugins: [
          new HtmlWebpackPlugin({
            template: 'src/index.html'
          })
        ]
      };
      
  2. CleanWebpackPlugin
    • 作用:在构建之前清理输出目录中的文件。
    • 示例:
      const { CleanWebpackPlugin } = require('clean-webpack-plugin');
      module.exports = {
        plugins: [
          new CleanWebpackPlugin()
        ]
      };
      
  3. MiniCssExtractPlugin
    • 作用:将 CSS 提取到单独的文件中,而不是内联在 JavaScript 中。
    • 示例:
      const MiniCssExtractPlugin = require('mini-css-extract-plugin');
      module.exports = {
        plugins: [
          new MiniCssExtractPlugin({
            filename: '[name].css',
            chunkFilename: '[id].css'
          })
        ],
        module: {
          rules: [
            {
              test: /\.css$/,
              use: [MiniCssExtractPlugin.loader, 'css-loader']
            }
          ]
        }
      };
      
  4. SplitChunksPlugin
    • 作用:用于代码分割,可以将公共的依赖模块提取到单独的 chunk 中。
    • 示例:
      module.exports = {
        optimization: {
          splitChunks: {
            chunks: 'all'
          }
        }
      };
      
  5. DefinePlugin
    • 作用:允许在编译时创建全局变量。
    • 示例:
      const { DefinePlugin } = require('webpack');
      module.exports = {
        plugins: [
          new DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify('production')
          })
        ]
      };
      

常见的 Webpack 加载器(Loaders)

  1. BabelLoader
    • 作用:使用 Babel 转换 ES2015+ 代码为 ES5。
    • 示例:
      module.exports = {
        module: {
          rules: [
            {
              test: /\.js$/,
              exclude: /node_modules/,
              use: {
                loader: 'babel-loader',
                options: {
                  presets: ['@babel/preset-env']
                }
              }
            }
          ]
        }
      };
      
  2. CssLoader
    • 作用:加载 CSS 文件,并将 CSS 内容转换为 JavaScript 模块。
    • 示例:
      module.exports = {
        module: {
          rules: [
            {
              test: /\.css$/,
              use: ['style-loader', 'css-loader']
            }
          ]
        }
      };
      
  3. FileLoader
    • 作用:将文件发送到输出目录,并返回文件的 public URL。
    • 示例:
      module.exports = {
        module: {
          rules: [
            {
              test: /\.(png|jpg|gif)$/,
              use: [
                {
                  loader: 'file-loader',
                  options: {}
                }
              ]
            }
          ]
        }
      };
      
  4. UrlLoader
    • 作用:像 file-loader 一样工作,但如果文件小于指定的限制,则可以返回一个 DataURL。
    • 示例:
      module.exports = {
        module: {
          rules: [
            {
              test: /\.(png|jpg|gif)$/,
              use: [
                {
                  loader: 'url-loader',
                  options: {
                    limit: 8192
                  }
                }
              ]
            }
          ]
        }
      };
      
  5. VueLoader
    • 作用:加载 Vue 单文件组件(SFCs)。
    • 示例:
      const VueLoaderPlugin = require('vue-loader/lib/plugin');
      module.exports = {
        module: {
          rules: [
            {
              test: /\.vue$/,
              use: 'vue-loader'
            }
          ]
        },
        plugins: [
          new VueLoaderPlugin()
        ]
      };
      

这些插件和加载器只是 Webpack 生态中的一小部分,它们可以根据项目的需求进行组合和配置,以实现各种构建目标。

6.  vue-router 跳转到下一个路由的方法是什么?取代当前路由的方法又是什么?如何获取 url 中的 query 参数?(5)

在 Vue Router 中,跳转到下一个路由和取代当前路由的方法如下:

跳转到下一个路由

要跳转到下一个路由,可以使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,通常用于导航到不同的页面。

// 字符串路径
this.$router.push('home')
// 对象
this.$router.push({ path: 'home' })
// 命名路由,params 不能与 path 一起使用
this.$router.push({ name: 'user', params: { userId: 123 } })
// 带查询参数,结果是 /register?plan=private
this.$router.push({ path: 'register', query: { plan: 'private' } })

取代当前路由

要取代当前路由,可以使用 router.replace 方法。这个方法不会向 history 添加新记录,而是替换当前的 history 记录。

// 字符串路径
this.$router.replace('home')
// 对象
this.$router.replace({ path: 'home' })
// 命名路由,params 不能与 path 一起使用
this.$router.replace({ name: 'user', params: { userId: 123 } })
// 带查询参数,结果是 /register?plan=private
this.$router.replace({ path: 'register', query: { plan: 'private' } })

获取 URL 中的 Query 参数

在 Vue Router 中,可以通过 this.$route.query 对象来访问 URL 中的查询参数。例如,如果一个路由的 URL 是 /search?keyword=vue&author=evan,你可以这样获取查询参数:

this.$route.query.keyword // vue
this.$route.query.author  // evan

在 Vue 组件中,this.$route 用于访问当前路由信息,而 this.$router 用于执行路由导航操作。

7.  js数组中不会改变原有数组的方法是

A. push

  B. concat

  C. sort

  D. shift

8.  执行以下代码,错误的输出结果是

A. 输入:typeof {"x":1} 输出:"object" 

B. 输入:typeof 1 输出:"number" 

C. 输入:typeof [{x:1}] 输出:"array" 

 D. 输入:typeof NaN 输出:"number"

9.  编程题:(40) 实现⼀个登录弹窗,输⼊⽤户名,密码,验证码后,⽆刷新登录

● 需列出html,css,javascript核心代码,并附上完整运行示例

● 弹窗大小350px*350px,相对于浏览器垂直居中,外层是5%#000000黑色蒙层,点击蒙层,可以关闭弹窗

● 支持3种登录方式:手机短信登录,帐号密码登录,扫码登录

● 手机短信登录:手机号,短信验证码(60秒只能点击一次),4位随机数验证码(点击刷新验证码)

● 帐号密码:帐号,密码,4位随机数验证码(点击刷新验证码)

10、补充:vite 常⻅的有哪些 plugin,loader,可否举例一些 plugin 和 loader,并说明 他们是用来干什么的?

Vite 是一个由原生 ES 模块提供的现代前端开发与构建工具,它利用浏览器的模块加载特性,实现了快速的开发服务器启动时间和热模块替换(HMR)。Vite 提供了丰富的插件(Plugins)和加载器(Loaders)生态系统,可以用来扩展其功能。

常见的 Vite 插件(Plugins)

  1. Vite Plugin CSS
    • 作用:自动处理 CSS 文件,支持 CSS Modules 和预处理器(如 SCSS)。
    • 示例:
      import vitePluginCss from 'vite-plugin-css';
      module.exports = {
        plugins: [vitePluginCss({})]
      };
      
  2. Vite Plugin ESLint
    • 作用:在开发过程中进行 ESLint 检查。
    • 示例:
      import { defineConfig } from 'vite';
      import vitePluginESLint from 'vite-plugin-eslint';
      module.exports = defineConfig({
        plugins: [vitePluginESLint()]
      });
      
  3. Vite Plugin React
    • 作用:为 React 项目提供额外的功能,如预编译和配置。
    • 示例:
      import { defineConfig } from 'vite';
      import vitePluginReact from '@vitejs/plugin-react';
      module.exports = defineConfig({
        plugins: [vitePluginReact()]
      });
      

常见的 Vite 加载器(Loaders)

  1. Vite Loader Babel
    • 作用:使用 Babel 转换 ES2015+ 代码为 ES5。
    • 示例:
      import { defineConfig } from 'vite';
      import vitePluginBabel from '@vitejs/plugin-babel';
      module.exports = defineConfig({
        plugins: [vitePluginBabel({})]
      });
      
  2. Vite Loader Sass
    • 作用:编译 SCSS 文件。
    • 示例:
      import { defineConfig } from 'vite';
      import vitePluginSass from 'vite-plugin-sass';
      module.exports = defineConfig({
        plugins: [vitePluginSass()]
      });
      
  3. Vite Loader PostCSS
    • 作用:使用 PostCSS 处理 CSS 文件,包括自动前缀、插件等。
    • 示例:
      import { defineConfig } from 'vite';
      import vitePluginPostCSS from 'vite-plugin-postcss';
      module.exports = defineConfig({
        plugins: [vitePluginPostCSS({})]
      });
      

这些插件和加载器只是 Vite 生态中的一小部分,它们可以根据项目的需求进行组合和配置,以实现各种构建目标。

前端面试题

1、vite 为什么比 webpack 性能更快

Vite(法语单词,意为“快速”)是一个由原生 ES 模块提供的现代前端开发与构建工具,它利用浏览器的模块加载特性,实现了快速的开发服务器启动时间和热模块替换(HMR)。相比之下,Webpack 是一个模块打包器,它将各种资源(如 JavaScript、CSS、图片等)打包成一个或多个 bundle。Vite 比 Webpack 性能更快的原因主要有以下几点:

  1. 原生 ES 模块支持:Vite 直接利用浏览器的 ES 模块支持,不需要像 Webpack 那样先打包再加载,因此可以实现更快的冷启动和模块更新。
  2. 按需编译:Vite 在开发模式下按需编译,只有当浏览器请求对应的模块时,Vite 才会编译那个模块,这样可以避免编译未使用的模块,大大减少了编译所需的时间。
  3. 预编译依赖:对于库和工具这类不太会改变的模块,Vite 在启动开发服务器时预先编译它们,这样在后续的开发过程中可以复用这些编译结果,提高性能。
  4. 插件和加载器:Vite 使用 Rollup 和 esbuild 这样的高性能工具来处理模块,它们通常比 Webpack 的加载器和插件更快。
  5. 文件系统缓存:Vite 利用文件系统的缓存来缓存编译结果,这意味着在重复构建时,Vite 可以跳过已经编译的模块,进一步减少编译时间。
  6. 多线程打包:Vite 在构建阶段使用了多线程打包,提高了构建的速度。
  7. 去重和树摇(Tree Shaking):Vite 通过去重和树摇来减少最终打包文件的体积,减少加载时间。
  8. 更快的HMR:Vite 的热模块替换(HMR)机制更加高效,因为它直接更新模块,而不是像 Webpack 那样需要替换整个 bundle。 由于这些设计上的优化,Vite 在许多场景下提供了比 Webpack 更快的性能,尤其是在开发阶段。然而,Webpack 依然是一个非常强大和灵活的工具,适用于各种复杂的项目和需求。开发团队在选择构建工具时,应该根据项目特点和团队偏好来决定使用哪一个。

2、用户因为网络问题导致超时前端 vue3 项目怎么处理

处理网络超时问题,可以通过以下几种方式在 Vue 3 项目中增强用户体验:

  1. 显示加载状态:在网络请求开始时,显示一个加载指示器,这样用户就知道系统正在处理。
  2. 超时重试:设置一个超时时间,如果在这个时间内没有收到响应,可以提示用户当前网络状态不佳,并给用户提供一个重试按钮。
  3. 乐观更新:如果可能,可以先在本地应用状态上模拟更改,这样即使服务器响应超时,用户也会感觉到操作是即时的。一旦网络恢复,再与服务器同步状态。
  4. 提供反馈:当请求超时,可以通过弹窗、提示栏或者页面上其他显眼的地方给用户一个明确的错误提示。
  5. 使用骨架屏:在数据加载时,可以使用骨架屏占位,这样即使超时,用户也不会面对一个空白的页面。
  6. 缓存数据:如果可能,可以在本地缓存一些数据,这样即使网络超时,用户也可以查看缓存的内容。
  7. 异步组件和代码分割:使用 Vue 3 的异步组件和模块联邦等特性,可以减少初始加载时间,提高用户体验。
  8. 监控网络状态:可以使用浏览器的 navigator.onLine 属性和 online/offline 事件来监控网络状态,根据网络状态来调整行为。
  9. 服务端渲染(SSR)或静态生成:对于静态内容,可以使用服务端渲染或静态生成,这样即使网络超时,用户也能看到一个完整的页面。 下面是一个简单的超时重试机制的例子:
import { ref } from 'vue';
const data = ref(null);
const isLoading = ref(false);
const error = ref(null);
async function fetchData() {
  isLoading.value = true;
  try {
    const response = await Promise.race([
      fetchYourData(),
      new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000))
    ]);
    data.value = await response.json();
  } catch (e) {
    error.value = e;
  } finally {
    isLoading.value = false;
  }
}
function retry() {
  error.value = null;
  fetchData();
}
// 在组件中
fetchData();

在这个例子中,fetchYourData 是你用来请求数据的函数。我们使用 Promise.race 来设置一个 5 秒的超时。如果请求在 5 秒内没有完成,将会抛出一个超时错误。用户可以通过调用 retry 函数来重试请求。 记住,处理网络问题时,最重要的是提供清晰的用户反馈,并尽可能提供简单的重试机制,以保持良好的用户体验。

二十五、5.7 下午广州黄埔区某医疗公司线上面试

1、前端性能优化指标和方法有哪些

前端性能优化是一个持续的过程,涉及多个方面。以下是一些关键的性能优化指标和方法:

性能优化指标:

  1. 加载时间
    • 首次内容绘制(FCP):页面开始加载到浏览器首次绘制任何内容的时间。
    • DOMContentLoaded:HTML 文档被完全加载和解析的时间。
    • 完全加载时间:页面所有资源(包括图片、脚本、样式等)都被加载完成的时间。
  2. 交互性
    • 第一次有意义的绘制(FMP):页面上主要内容的元素被绘制的时间。
    • 首次输入延迟(FID):用户首次与页面交互到浏览器实际能够响应这个交互的时间。
  3. 响应速度
    • CPU 占用率:页面在交互时 CPU 的占用情况。
    • 执行时间:脚本执行的时间。
    • 每秒帧数(FPS):页面在动画和交互时的平滑度。
  4. 资源效率: -传输大小:页面所有资源传输到浏览器的大小。 -缓存利用率:浏览器缓存被有效利用的程度。

性能优化方法:

  1. 代码优化
    • 压缩和混淆:使用工具如 TerserPlugin、UglifyJS 等,压缩和混淆 JavaScript 代码。
    • tree shaking:移除未使用的代码,减少打包体积。
    • 代码分割:将代码分割成多个小块,按需加载。
  2. 资源优化
    • 图片优化:使用适当格式的图片(如 WebP),压缩图片大小。
    • 字体优化:子集化字体,只包含需要的字符。
    • 懒加载:延迟加载非首屏资源,如图片、视频、脚本等。
  3. 网络优化
    • 使用 CDN:内容分发网络(CDN)可以加速资源的全球分发。
    • 预加载和预连接:通过 <link rel="preload"><link rel="preconnect"> 提前加载资源和建立连接。
    • HTTP/2:使用 HTTP/2 多路复用特性,减少请求延迟。
  4. 构建优化
    • 使用现代构建工具:如 Vite、Webpack 等,它们提供了许多性能优化的内置功能。
    • 缓存策略:利用浏览器的缓存机制,减少重复下载。
  5. 用户体验优化
    • 骨架屏:在内容加载前显示一个占位布局。
    • 渐进式 Web 应用(PWA):使用 Service Worker 来提供离线体验和加速重复访问。
  6. 监控和分析
    • 使用性能分析工具:如 Lighthouse、WebPageTest、Chrome DevTools 等,来识别性能瓶颈。
    • 真实用户监控(RUM):收集真实用户访问页面的性能数据。 性能优化是一个多方面的任务,需要根据具体情况来制定策略。通常,优化应该从监测和分析开始,识别最关键的问题,然后逐步实施上述方法来改善性能。

2、css 盒模型

CSS 盒模型(Box Model)是 CSS 核心概念之一,它描述了如何计算一个元素的总宽度和高度。每个 HTML 元素都可以看作是一个盒子,这个盒子由以下几个部分组成:

  1. 内容区域(Content Area):包含元素的内容(如文本、图片等)的区域。内容的宽度和高度可以通过 widthheight 属性来设置。
  2. 内边距(Padding):围绕内容区域的空白区域。内边距的宽度可以通过 padding-toppadding-rightpadding-bottompadding-left 属性来设置,或者使用 padding 简写属性一次性设置。
  3. 边框(Border):围绕内边距和内容的线。边框的样式、宽度和颜色可以通过 border-topborder-rightborder-bottomborder-left 属性来设置,或者使用 border 简写属性一次性设置。
  4. 外边距(Margin):围绕边框的空白区域。外边距的宽度可以通过 margin-topmargin-rightmargin-bottommargin-left 属性来设置,或者使用 margin 简写属性一次性设置。 盒模型有两种类型:
  5. 标准盒模型(W3C盒模型):在这种盒模型中,widthheight 属性仅包含内容区域的宽度和高度,不包括内边距和边框。这意味着,如果一个元素有内边距或边框,它的总宽度和高度会比设置的 widthheight 大。
  6. 怪异盒模型(IE盒模型):在这种盒模型中,widthheight 属性包括内容区域的宽度和高度、内边距和边框。这种模型可以通过设置元素的 box-sizing 属性为 border-box 来实现。 以下是一个示例,展示了盒模型的各个部分:
.box {
  width: 200px;        /* 内容区域的宽度 */
  height: 100px;       /* 内容区域的高度 */
  padding: 20px;       /* 内边距 */
  border: 5px solid red; /* 边框 */
  margin: 10px;        /* 外边距 */
  box-sizing: border-box; /* 使用怪异盒模型 */
}

在这个示例中,如果一个元素应用了 .box 类,它的总宽度将是 200px(内容宽度)+ 2 * 20px(左右内边距)+ 2 * 5px(左右边框)+ 2 * 10px(左右外边距)= 270px,总高度将是 100px(内容高度)+ 2 * 20px(上下内边距)+ 2 * 5px(上下边框)+ 2 * 10px(上下外边距)= 170px。如果 box-sizing 属性是 content-box(默认值),则总宽度和高度会更大,因为还要加上内边距和边框的尺寸。

3、vue3 和 vue2 的区别?

Vue 3 和 Vue 2 是两个不同的 Vue.js 版本,它们之间存在一些重要的区别,这些区别影响了开发体验、性能和代码结构。以下是一些主要区别:

性能

  • 响应式系统:Vue 3 使用 Proxy 实现响应式系统,而 Vue 2 使用 Object.defineProperty。Proxy 提供了更加强大的功能和性能优势。
  • 组合式 API:Vue 3 引入了 Composition API,它提供了一种更灵活、更强大的方式来组织逻辑,而 Vue 2 主要使用 Options API。
  • Tree Shaking:Vue 3 支持更严格的 Tree Shaking,这意味着未被使用的代码不会被打包,从而减少了最终的文件大小。

代码结构

  • 组合式 API:Vue 3 引入了 Composition API,它提供了一种更灵活、更强大的方式来组织逻辑。
  • 选项合并:Vue 3 进行了选项合并的优化,使得组件的选项合并更加高效。
  • 插槽(Slots):Vue 3 改进了插槽系统,使得插槽的使用更加直观和强大。

组件系统

  • Composition API:Vue 3 的 Composition API 提供了一种更灵活的方式来组织逻辑,可以更方便地复用逻辑。
  • Teleport:Vue 3 引入了 Teleport 组件,它可以将内容移动到页面的任何位置。
  • Suspense:Vue 3 引入了 Suspense 组件,它可以用来包裹等待的异步组件。

其他改进

  • 错误处理:Vue 3 改进了错误处理机制,提供了更详细的错误信息。
  • 异步组件:Vue 3 支持更强大的异步组件,包括动态导入和懒加载。
  • 类型系统:Vue 3 支持 TypeScript,并且类型系统得到了加强。

兼容性

  • 向下兼容:Vue 3 仍然与 Vue 2 向下兼容,但两者的 API 有所不同。 总的来说,Vue 3 带来了许多新特性和改进,使得开发体验更加现代化和高效。然而,迁移到 Vue 3 需要适应新的 API 和模式,这可能需要一些时间和学习。

二十六、5.8 下午浙江绍兴某公司线上面试

1、强缓存和协商缓存

在 HTTP 协议中,缓存控制可以通过多种方式实现,其中最主要的两种方式是强缓存(Strict Caching)和协商缓存(Fallback Caching)。

强缓存(Strict Caching)

强缓存是通过 HTTP 响应头中的 Cache-Control 字段来控制的。这个字段可以指定客户端在缓存资源时应遵循的缓存策略。常见的强缓存策略包括:

  • public:资源可以被任何缓存(如代理服务器、浏览器缓存等)缓存。
  • private:资源只能被单个用户缓存,不能被共享。
  • no-store:禁止缓存存储资源,即一旦请求成功,服务器和缓存都不保存资源。
  • no-cache:允许缓存存储资源,但在使用之前必须先向服务器验证。
  • max-age=<seconds>:指定资源在缓存中可以存储的最长时间(秒)。
  • s-maxage=<seconds>:指定资源在共享缓存(如 CDN)中可以存储的最长时间(秒),这个值必须与 Cache-Control 字段中的 publicprivate 配合使用。 例如,以下是一个设置强缓存的例子:
HTTP/1.1 200 OK
Content-Type: text/html
Cache-Control: max-age=3600

协商缓存(Fallback Caching)

协商缓存是通过 HTTP 响应头中的 Last-ModifiedETag 字段来控制的。当客户端请求资源时,服务器会检查这两个字段,并与客户端发送的 If-Modified-SinceIf-None-Match 字段进行比较。如果服务器发现资源自上次更新以来没有变化,它会返回一个 304 Not Modified 状态码,而不是资源的内容,从而减少不必要的数据传输。 例如,以下是一个设置协商缓存的例子:

HTTP/1.1 200 OK
Last-Modified: Sat, 29 Oct 2022 18:45:00 GMT
ETag: "2f80f-5678-4j6k7l8m"

客户端在请求资源时可能会发送:

If-Modified-Since: Sat, 29 Oct 2022 18:45:00 GMT
If-None-Match: "2f80f-5678-4j6k7l8m"

如果服务器确定资源未更改,它会返回 304 Not Modified,客户端可以继续使用缓存中的资源。 强缓存和协商缓存可以结合起来使用,以实现更有效的缓存策略。通过设置适当的 Cache-Control 字段,可以控制资源在缓存中的存活时间;通过设置 Last-ModifiedETag 字段,可以控制资源的缓存验证。

4、vue 路由懒加载

Vue Router 支持懒加载(lazy loading)和分割(code splitting)路由组件。这意味着你可以将你的路由组件分割成不同的块,只有当路由被访问时,对应的组件才会被动态地加载。 懒加载路由组件的一个好处是,它可以减少初始加载时间,因为不需要加载整个应用的所有路由组件。只有当用户访问特定的路由时,对应的组件才会被加载,这可以显著提高应用的性能。 在 Vue Router 4 中,你可以使用 import() 函数动态导入路由组件,这会返回一个 Promise。Vue Router 会自动处理这些 Promise,确保只有在需要时才加载对应的组件。 下面是一个简单的例子,展示了如何使用懒加载来分割路由组件:

const router = createRouter({
  routes: [
    {
      path: '/home',
      name: 'home',
      component: () => import('./components/Home.vue')
    },
    {
      path: '/about',
      name: 'about',
      component: () => import('./components/About.vue')
    }
  ]
});

在上面的代码中,当用户访问 /home/about 路由时,相应的组件 Home.vueAbout.vue 才会被动态地加载。 如果你使用的是 Vue Router 3,你可以在路由定义中使用 component 属性来指定一个返回组件的函数,例如:

const router = new VueRouter({
  routes: [
    {
      path: '/home',
      name: 'home',
      component: () => import('./components/Home.vue')
    },
    {
      path: '/about',
      name: 'about',
      component: () => import('./components/About.vue')
    }
  ]
});

使用懒加载和路由分割是一种有效的技术,可以帮助你构建高性能的单页面应用程序(SPA)。

二十六、5.8 下午上海某公司华为外包电话面试

1、promise状态

在 JavaScript 中,Promise 是一种用于异步编程的构造函数,它代表了未来将要完成(或失败)的操作的结果。Promise 有三种状态:

  1. pending:初始状态,既不是成功(fulfilled)也不是失败(rejected)。
  2. fulfilled:操作成功完成的状态。一旦状态变为 fulfilled,Promise 对象就不再改变。
  3. rejected:操作失败的状态。一旦状态变为 rejected,Promise 对象就不再改变。 状态只能由 pending 变为 fulfilled 或 rejected,并且状态一旦改变,就不能再变回 pending。 Promise 对象有以下几个特点:
  • 不可变性:一旦状态改变,就不能再改变。
  • 无状态:Promise 对象本身不存储值,而是通过 then() 方法访问值。
  • 不可取消:一旦创建了一个 Promise,就不能取消它。
  • 链式调用:Promise 可以采用链式调用的方式,即一个 then() 方法可以返回一个新的 Promise。 Promise 的这些特点使得它在处理异步操作时非常强大和灵活。

2、同步任务和异步任务:

(1)同步任务

    同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务,当我们打开网站时,网站的渲染过程,比如元素的渲染,其实就是一个同步任务。

(2)异步任务

    异步任务是指不进入主线程,而进入任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程,当我们打开网站时,像图片的加载,音乐的加载,其实就是一个异步任务。

二十七、5.9 下午浙江某银行外派公司线上面试

1、vue keep-alive

在Vue.js中,<keep-alive> 是一个内置组件,它能够使被包含的组件在它们被切换时保持状态,避免重新渲染。这意味着当组件在<keep-alive>内切换时,它们的创建和销毁生命周期钩子不会被调用,而是会调用activateddeactivated钩子。 <keep-alive> 主要用于保留组件状态,避免重复渲染一些开销较大的组件,或者在用户从一个视图导航到另一个视图时保持用户的工作进度(例如,在表单输入中)。

基本用法

<keep-alive>
  <component :is="currentComponent"></component>
</keep-alive>

在这个例子中,currentComponent 是一个响应式数据属性,它决定了哪个组件被渲染。当currentComponent改变时,新的组件会被渲染,并且如果之前的组件被<keep-alive>包裹,它的状态会被保留。

生命周期钩子

对于被<keep-alive>包含的组件,它们会有额外的生命周期钩子:

  • activated:当组件被激活时调用。
  • deactivated:当组件被停用时调用。

缓存策略

<keep-alive>提供了两个属性来控制缓存策略:

  • max: 保留的最大组件实例数,超过这个数字的已缓存组件会被销毁。
  • include: 只有匹配的组件会被缓存。
  • exclude: 匹配的组件不会被缓存。 例如:
<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>

在这个例子中,只有组件名为ab的组件会被缓存。

注意事项

  • <keep-alive>不会在函数式组件中工作,因为它们没有缓存的概念。
  • 使用<keep-alive>时,被包裹组件的mounted钩子只会在第一次渲染时调用,之后不会再被调用。 <keep-alive>是Vue.js中实现组件缓存和状态保持的一个非常有用的工具,特别是在开发单页面应用(SPA)时,可以帮助提高用户体验和性能。

2、说一说 vue 列表为什么加 key?

vue 列表加 key 的原因是为了给 diff 算法添加标识,因为 diff 算法判断新旧 VDOM 是否相同的依据是节点的 tag 和 key。如果 tag 和 key相同的话则会进一步比较,使尽可能多的节点进行复用。此外,key 绑定的值一般是唯一的一个值,比如 id。如果绑定数组的索引index,则起不到优化 diff 算法的作用,因为数组内元素一但进行增删,后续节点的绑定 key也会发生变化,导致 diff 算法进行多余的更新操作。

3、说一说虚拟 dom

Vue.js 也使用虚拟 DOM(Virtual DOM)来提高性能。Vue 的虚拟 DOM 算法借鉴了 React 的虚拟 DOM,但是做了一些优化,以适应 Vue.js 的具体需求。在 Vue 中,虚拟 DOM 被称为“VNode”,表示虚拟节点。

Vue.js 中的虚拟 DOM

  1. VNode 创建:当你在 Vue 中创建一个组件或元素时,Vue 会生成一个对应的 VNode 对象。这个对象包含了组件或元素的所有必要信息,如标签名、属性、子节点等。
  2. 差异比较(Diffing):当组件的状态发生变化时,Vue 会生成一个新的 VNode 树,并与旧的 VNode 树进行比较。Vue 使用一个高效的差异比较算法来找出新旧 VNode 树之间的差异。
  3. 更新:一旦差异被识别,Vue 将只对实际 DOM 中变化的部分进行更新,这个过程称为“修补”(patching)。这样可以避免不必要的 DOM 操作,提高性能。

Vue.js 中的优化

Vue.js 在虚拟 DOM 方面做了一些优化,以提高性能:

  1. 静态节点优化:Vue.js 会静态地分析模板,找出那些在多次渲染中不会变化的节点。这些节点在差异比较时会被跳过,从而减少了比较的开销。
  2. 编译时优化:Vue.js 的模板在编译时会进行静态分析,将模板编译成渲染函数。在这个过程中,Vue 会尽可能地优化渲染函数,以提高虚拟 DOM 的性能。
  3. 组件级更新:Vue.js 的更新是组件级别的。当组件的状态发生变化时,只有该组件及其子组件会被重新渲染,而不是整个应用。

Vue.js 虚拟 DOM 的使用

在 Vue.js 中,开发者通常不需要直接与虚拟 DOM 交互。Vue 的响应式系统会自动处理状态的更新,并触发虚拟 DOM 的差异比较和修补过程。开发者只需关注组件的状态和模板的编写。

总结

Vue.js 的虚拟 DOM 实现是框架性能优化的关键部分。它允许 Vue.js 高效地更新 UI,同时提供了声明式的组件开发体验。通过一系列的优化,Vue.js 能够在保持易用性的同时提供出色的性能。

二十八、5.10 下午常熟某房地产公司线上初面

1、防抖和节流

在JavaScript中,防抖(Debouncing)和节流(Throttling)是两种用于控制函数执行频率的技术,常用于优化性能和提升用户体验,尤其是在处理频繁触发的事件时,如窗口大小调整(resize)、滚动(scroll)、键盘事件(keypress)等。

防抖(Debouncing)

防抖技术确保函数不会在短时间内被频繁触发,而是在事件触发后经过一段特定的时间间隔再次执行。如果在时间间隔结束前再次触发了事件,则重新开始计时。这样可以减少不必要的函数执行次数,提高性能。 应用场景:搜索框输入、窗口大小调整 示例代码

function debounce(func, wait) {
  let timeout;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
  };
}
// 使用
const debouncedResize = debounce(function() {
  console.log('Window resized');
}, 250);
window.addEventListener('resize', debouncedResize);

节流(Throttling)

节流技术确保函数在特定时间间隔内只执行一次,无论该事件触发多少次。这样可以限制函数在短时间内被频繁调用的次数。 应用场景:滚动加载更多、高频点击事件 示例代码

function throttle(func, limit) {
  let inThrottle;
  return function() {
    const args = arguments;
    const context = this;
    if (!inThrottle) {
      func.apply(context, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}
// 使用
const throttledScroll = throttle(function() {
  console.log('Window scrolled');
}, 250);
window.addEventListener('scroll', throttledScroll);

防抖和节流都是高级技术,能够有效提高Web应用的性能和响应能力,特别是在处理用户交互和动画时。正确使用这些技术可以使应用程序运行得更加平滑,提升用户体验。

2、cookie 的安全隐患

Cookie在Web开发中起着重要作用,用于存储用户信息和跟踪用户状态。然而,它们也存在一些安全隐患,主要包括:

  1. 敏感信息泄露:如果Cookie中存储了敏感信息,如用户身份验证令牌,一旦被截获,攻击者可能利用这些信息进行未授权访问。
  2. 跨站脚本攻击(XSS):攻击者可以注入恶意脚本,窃取用户的Cookie信息。
  3. 跨站请求伪造(CSRF):攻击者利用用户的Cookie信息,在用户不知情的情况下,以用户的名义执行非用户本意的操作。
  4. 会话劫持:攻击者通过获取用户的Cookie信息,模仿用户身份进行操作。
  5. 中间人攻击:在未加密的连接中,攻击者可以截获传输中的Cookie数据。 为了减少这些风险,可以采取以下措施:
  • 使用HTTPS协议传输Cookie,防止中间人攻击。
  • 为Cookie设置HttpOnly属性,防止客户端脚本访问Cookie,降低XSS攻击风险。
  • 为Cookie设置Secure属性,确保Cookie只在HTTPS请求中被发送。
  • 为Cookie设置SameSite属性,限制Cookie在跨站请求中的使用,减少CSRF攻击风险。
  • 对敏感信息进行加密存储。
  • 设置适当的过期时间,减少Cookie的有效期。 通过这些措施,可以在一定程度上降低Cookie的安全隐患。

3、vue 插槽

在Vue.js中,插槽(Slots)是一种内容分发的API,它允许你将组件的模板的一部分定义为可填充的“插槽”,这样父组件就可以在这些插槽中插入内容。插槽提供了一种方式,使得组件可以更加灵活和复用。 Vue.js中有几种不同类型的插槽:

  1. 匿名插槽:这是最基本的插槽类型,它没有名字,每个组件只有一个匿名插槽。 父组件
    <my-component>
      <p>这是匿名插槽的内容</p>
    </my-component>
    
    子组件
    <div>
      <slot>如果没有提供内容,这里的内容会显示</slot>
    </div>
    
  2. 具名插槽:具名插槽允许你有多个插槽,并且可以通过名字来区分它们。 父组件
    <my-component>
      <template v-slot:header>
        <h1>这是头部内容</h1>
      </template>
      <p>这是匿名插槽的内容</p>
      <template v-slot:footer>
        <p>这是底部内容</p>
      </template>
    </my-component>
    
    子组件
    <div>
      <slot name="header">如果没有提供头部内容,这里的内容会显示</slot>
      <slot>如果没有提供内容,这里的内容会显示</slot>
      <slot name="footer">如果没有提供底部内容,这里的内容会显示</slot>
    </div>
    
  3. 作用域插槽:作用域插槽是一种特殊类型的插槽,它可以提供数据给父组件,使得父组件可以决定如何渲染这些数据。 子组件
    <div>
      <slot :user="user">
        {{ user.lastName }}
      </slot>
    </div>
    
    父组件
    <my-component>
      <template v-slot:default="slotProps">
        {{ slotProps.user.firstName }}
      </template>
    </my-component>
    

在Vue 2.6.0及以后的版本中,推荐使用新的v-slot指令来定义插槽,取代了之前的slotslot-scope属性。在Vue 3中,v-slot是定义插槽的标准方式。 插槽是Vue组件设计中非常强大和灵活的特性,它允许组件作者提供可定制的模板部分,同时让使用组件的开发者能够根据需要填充内容。

持续更新中ing 祝我早日拿下 offer!!!