vue 面试题

# vue 面试题

# vue 响应式原理

# vue2 响应式原理

  • Object.defineProperty()来进行数据劫持,针对对象特定属性通过设置 getter 和 setter 方法来劫持数据,当读取属性时会触发 getter 函数;当 view 视图层发生变化时,会触发 Object.defineProperty()中的 setter 方法。

# vue3 响应式原理

  • Proxy 是针对 object 类型数据的所有属性都添加 get 和 set 方法进行监听,在实现嵌套数据时,更简单,原生支持数组类的监听。

# vue 生命周期

  • onErrorCapture 捕获后代组件传递的错误时调用。
  • onActivated 激活时(适用于 keep-alive 缓存的组件)
  • onDeactivated 解散时调用(适用于 keep-alive 缓存的组件)

# 父子组件生命周期执行顺序

  • 父组件 beforeCreated
  • 父组件 created
  • 父组件 beforeMounted
  • 子组件 beforeCreated
  • 子组件 created
  • 子组件 beforeMounted
  • 子组件 mounted
  • 父组件 mounted

# 当父组件引入了 mixin 之后

  • mixin beforeCreated
  • 父组件 beforeCreated
  • mixin created
  • 父组件 created
  • mixin beforeMounted
  • 父组件 beforeMounted
  • 子组件 beforeCreated
  • 子组件 created
  • 子组件 beforeMounted
  • 子组件 mounted
  • mixin mounted
  • 父组件 mounted

# 导航守卫

  • 全局守卫

    • beforeEach
    • beforeResolve
    • afterEach
  • 路由守卫

    • beforeEnter
  • 组件守卫

    • beforeRouteEnter
    • beforeRouteUpdate
    • beforeRouteLeave
  • router.beforeEach(to,from,next) 全局前置守卫,路由跳转前触发

  • router.beforeResolve() 全局解析守卫,在所有组件内守卫和异步组件被解析后触发

  • router.afterEach() 路由跳转完成后触发

  • beforeEnter 路由独享守卫

  • 参数

    • to 即将要进入的目标路由对象
    • from 即将要离开的路由对象
    • next 是否可以进入某个具体路由,或者是某个具体路由的路径

# 从 A 页面跳转 B 页面时的生命周期

  • A: beforeRouteLeave (组件守卫)
  • beforeEach (全局守卫)
  • beforeEnter (路由独享守卫)
  • beforeRouteEnter (组件守卫)
  • beforeResolve (全局解析守卫)
  • afterEach (全局守卫)
  • B: beforeCreate
  • B: created
  • B: beforeMount
  • A: beforeDestroy
  • A: destroyed
  • B: mounted

# vue 的传值方式

  • $emit
  • 路由传参
    • query 传参(地址栏显示参数)
      • name 和 path 都能用
      • 地址栏会显示参数
    • params 传参(地址栏不显示参数)
      • 跳转只能用 name ,不能用 path
      • 地址栏不显示参数
  • vuex
  • localStore
  • provide/inject
  • $ref
    • this.$refs.children 、 this.$refs.parents

# 路由的两种模式

  • hash 模式
  • history 模式
  • abstract 模式(用来在不支持浏览器 API 的环境中,充当 fallback)

# vue 的 history 和 hash 的实现原理

  • hash 是通过 window.onHashChange() 事件去监听的
  • history 是通过 window.history.pushState(null,null,path)(新增历史记录)、window.history.replaceState(null,null,path) (替换历史记录)在不进行刷新的情况下,操作浏览器的历史记录

# vue 优化方式

  1. 减少 v-if 的活用 v-show
  2. v-for 遍历添加 key 属性
  3. 将组件进行切割(vue 的更新是组件粒度的,将耗时任务单独拆分成一组件,父组件数据变化时只会重新渲染父组件,耗时组件并不会渲染,这样性能会更好)
  4. 使用 keep-alive 缓存组件
  5. 区分 computed 和 watch
  6. 图片资源懒加载
  7. 动态加载组件,异步加载组件
  8. 使用防抖和节流

# keep-alive 原理

  • 通过缓存 vnode 实现,在渲染的时候对比组件的 name(通过 include 和 exclude 控制),命中缓存时就从缓存中读取。
  • 问题: 使用 keep-alive 的时候数据传递过去不会更新
  • 会触发 activated deactivated
  • 内存超限时通过 LRU 算法来管理

# nextTick 原理

  • 在修改数据时,会开启一个异步队列,将 watcher 的 update 更新操作缓存在同一事件循环中,同一个 watcher 只会被推入队列一次(通过 watcherId 去重),然后再下一事件循环中刷新队列,执行代码,这样做能减少频繁不必要的更新,提升更新渲染效率。
  • 原理: nextTick 内部通过一系列异步 api 尝试将回调函数放在异步队列里, 包括 promise、mutationObserver、setImmediate、setTimeout。
  • 所有的回调方法都会放在 callbacks 数组里,最后通过 flushCallbacks 方法遍历 callbacks 数组来依次执行回调。
  • vue 的 dom 更新是异步的,本身也是使用的 nextTick。

# nextTick 使用场景

  • 获取数据更新后的 dom
  • 在 created 中进行 dom 操作
  • 获取元素的宽高

# vue 模板编译过程

  • 将模板编译为抽象语法树 AST(多叉树),将 AST 转化为渲染函数,执行渲染函数生成 VDOM,将 VDOM 映射为真实 DOM。

# Vue.extend()

  • 用于拓展组件生成一个构造器,通过 new 构造函数生成一个新的组件实例,类似组件继承。(在 vue3.0 里废弃了)。

# Vue.use()

  • 实际调用了该插件的 install 方法,当引入的插件有 install 时需要调用。

# Suspense 组件

  • 作用: 可以在组件树上层等待下层的多个嵌套异步依赖项时,渲染一个加载状态 通过 #fallback 的形式。

# teleport

  • 作用: 将组件内部的一部分模板“传送”到该组件 dom 结构的最外层。

# vue 中的插槽

  • 具名插槽
// 组件
<slot name="default" />

<template v-slot:default> </template>
// 或者
<template #default> </template>
1
2
3
4
5
6
  • 作用域插槽
<!-- <MyComponent> 的模板 -->
<div>
  <slot :text="greetingMessage" :count="1"></slot>
</div>

<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
1
2
3
4
5
6
7
8

# $attrs 和 $listeners

  • 透传 Attributes 是指由父组件传入,且没有被子组件声明为 props 或是组件自定义事件的 attributes 和事件处理函数。
  • 你可以通过 inheritAttrs: false 来禁用这个默认行为。

# watch 和 watchEffect 的区别

  • watch 需要传入侦听的数据源。而 watchEffect 是自动收集数据源作为依赖
  • watchEffect 在初始化时就会执行一次。而 watch 的话,只有设置了 immediate: true 时,才会在初始化时监听。

# watchEffect

  • 立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行
  • 设置 flush: post 将会使侦听器延迟到组件渲染后执行。
  • 返回值是一个用来停止该副作用的函数。
// 副作用清除
watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(id.value);
  // `cancel` 会在 `id` 更改时调用
  // 以便取消之前
  // 未完成的请求
  onCleanup(cancel);
  data.value = await response;
});

// 停止侦听器
const stop = watchEffect(() => {});

//当不需要此侦听器时:
stop();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 怎么去做按钮权限的处理?

  • 菜单栏权限怎么做?
  • 按钮权限怎么做?

# vue2 跟 vue3 的区别

# 小程序面试题

# 打开一个小程序到页面展示,经历了什么

  1. 启动小程序
  2. 运行环境准备
  3. 下载代码包
  4. 缓存代码包
  5. 代码注入
  6. 逻辑层(jsCore)与渲染层(webview)双线程并行
  7. 渲染页面

# 微信小程序原理

  • 数据驱动的架构模式,UI 和数据是分离的,所有页面的更新都需要通过数据的更改来实现。

# 小程序的 v-model 和 vue 的有什么不一样

  • 小程序设置 this.data 不会被同步到视图,必须要调用
this.setData({});
1

# 小程序的优化

  • 控制图片大小以及比例
  • 避免 wxml 节点数过大
  • 滚动区域设置惯性滚动。-webkit-overflow-scrolling: touch
  • 避免短时间内发起太多请求
  • 合理的使用分包
  • 减少 data 的大小,非必要不 setData
  • 组件化
  • 按需注入

# bindtap 与 bindcatch 区别

  • bindtap 不会阻止事件冒泡,bindcatch会阻止事件冒泡

# webview 中的页面怎么跳回小程序中?

  • wx.miniProgram.navigateTo({url:'/pages/index/index'})

# webview 的页面怎么跳转到小程序导航的页面?

  • 通过 switchTab 跳转,但是默认情况下不会重新加载数据,若需要重新加载数据,则需要在 success 属性中手动调用
// 在 webview中的success属性中添加以下代码

success:function (e) {
  var page = getCurrentPages().pop()
  if(page === undefined || page === null) return

  page.onLoad()
}

1
2
3
4
5
6
7
8
9

# webview 怎么跟小程序通信?

# 微信小程序和 h5 的区别

  • 运行环境不同

    • 小程序运行在 webview 内核中
    • h5 运行环境是浏览器
  • 开发成本不同

    • 只在微信中运行,不用顾虑浏览器兼容问题

# 小程序设置 less

  • easy less 插件,配置一下 settings.json 就可以了

# 小程序的生命周期

  • App

    • onLanch
    • onShow
  • Page

    • onLoad
    • onShow
    • onReady
    • onHide
    • onUnload
    • onShareMessage
    • onPageScroll
    • onPullDownRefresh
  • Component

    • created
    • attached
    • ready
    • moved
    • detached
    • error

# 关于无痛刷新 token 的问题

# 封装的 地理位置授权这个问题

# 封装的 request 请求

  • 通过事件队列的形式去封装的。
  1. 判断是否是登录接口
  2. 登录接口放行。非登录接口,判断是否有 token
  3. 有 token,放行
  4. 没有 token,调取刷新 token 的接口,并将请求的接口缓存在一个任务队列里。
  5. 等 token 请求完成之后,在遍历队列,执行里面的代码

# 如果在页面还没加载出来,就跳到其他页面,将任务队列清空。

# 多个品牌你是写的一套代码吗?还是说写的 7 套代码?

  • 回答:h5 活动写的一套代码,小程序维护的是七套代码。

  • 我是通过配置文件的形式,根据小程序的 appid 来进行区分,一套代码,将配置文件传进去。

# h5 页面你是怎么去做授权的?

  • 答:h5 页面获取的用户信息是通过,小程序往 h5 页面传 token,然后 h5 调取后台接口,拿到用户信息。

# 要是在 h5 中浏览之后,token 过期了,怎么处理的?

  • 回答:从 h5 页面直接跳到小程序页面的登录页,然后去做登录。

# 小程序绘制海报会出现的问题

  • 图片显示不全,或者图片模糊
  • 原因: 你设置的图片宽高例如为 750 x 1200 ,但实际在 canvas 画布上画出的尺寸为 375 x 600,图片会显示不全。

# wx.canvasToTempFilePath() 将画布指定区域导出生成指定大小的图片

  • 解决
    • 图片居中问题
    • 生成图模糊问题
wx.canvasToTempFilePath({
  x: 0,
  y: 0,
  width: 50,
  height: 50,
  destWidth: 100,
  destHeight: 100,
  canvasId: "myCanvas",
  success(res) {
    console.log(res.tempFilePath);
  },
});
1
2
3
4
5
6
7
8
9
10
11
12

# wx.onAppRoute 获取路由回调设置全局分享

// wx.onAppRoute 可以获取到当前页面的信息
wx.onAppRoute(() => {
  const page = getCurrentPages();
  let view = pages[pages.length - 1];
  let data;
  view.onShareAppMessage = () => {
    return {
      imageUrl:
        "https://img.carlsberg.asia/container-reference/img/somersby/2021-10-28/efe0c7ff61a5462babb1b28fc5dd856c.jpg",
      title: "和我一起,在SOMERSBY夏日纷阳光乐园玩趣吧!",
      path: `/pages/index/index`,
    };
  };
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# React

# 组件通信

  • 父传子: props;
  • 子传父: 子调用父组件中的函数并传参;
  • 兄弟: 利用 redux 实现和利用父组件。

# diff 算法

  • diff 算法的本质: 就是找出两个对象之间的差异,目的是尽可能做到节点复用。
  • 上述中的对象:指的其实就是 vue 中的 virtual dom(虚拟 dom 树),即使用 js 对象来表示页面中的 dom 结构。

三个层级策略: 1、tree 层级:dom 节点跨层级的移动操作特别少,可以将其忽略不计。 2、component 层级:拥有相同类的两个组件会生成类似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。 3、element 层级:对于同一层级的一组子节点,可以通过唯一 key 进行区分。

# 高阶组件

  • 高阶组件就是一个函数,且该函数接收一个组件作为参数,并返回一个新的组件。

# 常用的 hooks

  • seState: 设置和改变 state,代替原来的 state 和 setState
  • useEffect: 代替原来的生命周期,componentDidMount,componentDidUpdate 和 componentWillUnmount 的合并版
  • useLayoutEffect: 与 useEffect 作用相同,但它会同步调用 effect
  • useMemo: 控制组件更新条件,可根据状态变化控制方法执行,优化传值
  • useCallback: useMemo 优化传值,usecallback 优化传的方法,是否更新
  • useRef: 跟以前的 ref,一样,只是更简洁了

# useCallback 和 useMemo 的区别

  • useMemo 计算结果是 return 出来的值。使用场景: 需要计算的状态。
  • useCallback 计算结果是函数,主要用于缓存函数。使用场景:需要缓存的函数,因为函数式组件每次任何一个 state 的变化 整个组件都会被重新刷新,一些函数是没有必要重新刷新的,此时就应该缓存起来,提高性能和减少资源浪费。(利用缓存函数,使子组件不会重新渲染)

# hooks 优势

  1. 函数组件无 this 问题
  2. 自定义 Hooks 方便状态复用
  3. 副作用关注点分离。

# redux

核心概念: store、action、reducer

关键函数: getState() :用于获取当前最新的状态 subscribe() :用于订阅监听当前状态的变化,然后促使页面重新渲染 dispatch() :用于发布最新的状态

执行流程: (1)用户通过事件触发 ActionCreator 制造 action (2)同时,用户触发的事件内调用 dispatch 来派发 action (3)reducer 接收 action,并处理 state 返回 newState (4)View 层通过 getState() 来接收 newState 并重新渲染视图层

# JS 基础

# var, let , const 的区别

  • 变量提升、块级作用域、重复声明同名变量、重新赋值

# 面向对象

运用类、继承、封装等进行程序设计的一种思想。

  • 能够将复杂问题和复杂逻辑简单化
  • 方便扩展
  • 方便维护

# 防抖和节流

防抖(debounce):触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间 节流(throttle):高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率

区别:防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。

# Object.is() 对比两个参数是否相等

Object.is(NaN, NaN); // true

Object.is(+0, -0); // false

// 自己实现一个Object.is
if (!Object.is) {
  Object.is = function (x, y) {
    if (x === y) {
      // Steps 1-5, 7-10
      // Steps 6.b-6.e: +0 != -0
      return x !== 0 || 1 / x === 1 / y;
    } else {
      // Step 6.a: NaN == NaN
      return x !== x && y !== y;
    }
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 什么是纯函数

  • 没有副作用的函数

# 什么是函数副作用

  • 函数在正常工作任务之外对外部环境所施加的影响。

# 怎么在不使用变量的情况下 交换两个变量的值

let a = 1;
let b = 2;
[b, a] = [a, b];
1
2
3

# [b, a] = [a, b] 的原理是什么,它是怎么交换两个变量的值的?

  • [a, b] = [b=a, a=3]

  • 解构的过程为:

    1. 以从左到右的顺序计算出右侧数组的值,得到数组
    2. 以从左到右的顺序,将右侧数组的值赋值给左侧
  • 可以看到解构赋值的过程中会有一个包含两个元素的的临时数组,并没有比传统方法节省空间,甚至空间会比传统方法多一个 int 值大小的空间

# 使用 DocumentFragment 创建并组成一个 dom 子树

  • 目的: 减少重绘重排的次数
<ul id="list"></ul>;

const list = document.querySelector("#list");
const fruits = ["Apple", "Orange", "Banana", "Melon"];

const fragment = new DocumentFragment();

fruits.forEach((fruit) => {
  const li = document.createElement("li");
  li.textContent = fruit;
  fragment.appendChild(li);
});

list.appendChild(fragment);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 自己实现一个 promise.all

Promise.prototype.all = function (promises) {
  let results = [];
  let promiseCount = 0;
  let promisesLength = promises.length;

  return new Promise((resolve, reject) => {
    for (let val of promises) {
      Promise.resolve(val).then(
        function (res) {
          promiseCount++;
          results[i] = res;
          if (promiseCount === promisesLength) {
            return resolve(results); // 可以不用 return 出去
          }
        },
        function (err) {
          return reject(err); // 可以不用 return 出去
        }
      );
    }
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 数组对象去重

  1. 使用 filter 和 map
function uniqueFunc(arr, key) {
  const res = new Map();
  return arr.filter((item) => !res.has(item[key]) && res.set(item[key], 1));
}
1
2
3
4
  1. 使用 reduce
function uniqueFunc(arr, key) {
  let hash = {};
  return arr.reduce((prevValue, currentValue) => {
    hash[currentValue[key]]
      ? ""
      : (hash[currentValue[key]] = true && prevValue.push(currentValue));
    return prevValue;
  }, []);
}
1
2
3
4
5
6
7
8
9

# nginx 怎么配置负载均衡?

# meta 标签有多少属性(重要)

  • name
    • name 属性的几个值
      • keywords 关键字
      • description 描述
      • viewport 视口
      • robots 搜索引擎的索引方式
      • renderfer 搜索引擎的渲染方式
      • revisit-after 搜索引擎的重访时间
  • http-equiv
    • http-equiv 属性的几个值
      • content-Type 设定网页字符集
      • X-UA-Compatible 告知浏览器以何种方式渲染页面
      • cache-control 请求和响应的缓存机制
      • expires 网站过期时间
      • refresh 自动刷新并重新跳转指定页面
      • set-Cookie 设置 cookie
<meta name="xxx" content="xxxx" />

<!-- 相当于设置http -->
<meta http-equiv="参数" content="具体的描述" />
1
2
3
4

# 跨终端解决方案

  • flutter
  • Taro
  • React Native
  • H5 Hybrid
  • 桌面端对应的方案就是 Electron

# jsBridge

  • jsBridge (opens new window)

  • Web 端和 Native 可以类比于 Client/Server 模式,Web 端调用原生接口时就如同 Client 向 Server 端发送一个请求类似,JSB 在此充当类似于 HTTP 协议的角色,实现 JSBridge 主要是两点:

    • 将 native 端的原生接口封装为 JavaScript 接口
    • 将 JavaScript 接口 封装为 原生接口

# 开源的 jsBridge

  • DSBridge,主要通过注入 API 的形式。DSBridge for AndroidDSBridge for IOS
  • JSBridge, 主要通过拦截 URL Schema 的形式, JsBridge

# 跨终端 web 适配方式

  • 可以写多套代码,输入网址时判断 pc 或 mobile,返回对应的内容。
  • 响应式设计 bootstrap

# less 是怎么编译成 css 的

  • 通过 less 和 less-loader
  • less 使用 less 语言的特性,嵌套语法,变量,函数,常量,map
  • less-loader 将 less 编译成 css 文件
  • css-loader 解析 css 文件里面的 css 代码(默认 webpack 只解析 js 代码的)
  • style-loader 将 css-loader 解析的内容挂载到 html 页面中

# require 和 import 的区别

  • require 是运行时加载;import 是编译时输出接口。
  • require 是同步加载模块; import 是异步加载模块(有一个独立模块依赖的解析阶段)。
  • require 输出的是一个值的(浅)拷贝;import 输出的是值的引用。

# js 基础数据类型和引用数据类型存在哪

  • 基础数据类型存在栈(stack)(特点:先进后出)中。
  • 引用数据类型存在堆(heap)(特点:数据任意存放)中。

# new 一个构造函数会发生什么

  • 创建了一个空对象
  • 让 this 指向了该对象
  • 执行构造函数并给对象的属性赋值,继承构造函数的原型
  • 判断构造函数是否有返回值,且根据返回值的类型进行返回,引用类型,返回引用地址,值类型返回值。

# 频繁的请求后台,不会有什么问题吗?用户量不大吗?

# dispatchEvent 点击和真实点击的区别

  • 没啥大区别,最终调用的都是 fireEventListener
  • 其中的 microtask 执行条件是必须在 host 包装嵌套深度为 0 时才跑
  • 用户点击时候,microtask 执行时,是回调完成后,此时包装深度已经是 0 了
  • 而 dispatchEvent 时,由于 dispatchEvent 本身就是个层包装,需要它执行完成后包装深度才能是 0,才能 runMicrotask。
<!-- 可以通过 event.isTrusted 判断是否由用户触发 true 用户触发;false 代码触发-->
<button class="btn">测试dispatchEvent()方法</button>

<script>
  let btn = document.querySelector(".btn");

  btn.addEventListener("click", function () {
    alert("点击了");
  });

  let clickEvent = new Event("click");
  btn.dispatchEvent(clickEvent);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13

# 深拷贝

function clone(target, map = new Map()) {
  if (typeof target === "object") {
    let cloneTarget = Array.isArray(target) ? [] : {};
    if (map.get(target)) {
      return map.get(target);
    }
    map.set(target, cloneTarget);
    for (const key in target) {
      cloneTarget[key] = clone(target[key], map);
    }
    return cloneTarget;
  } else {
    return target;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 自动化测试有没有了解过? 自动化测试有什么好处?

# 自动化测试的流程

  • 输入 - 断言 - 验证

  • 不管是单测还是 E2E 都可以遵循这个原则,如果不知道如何开始,可以考虑我们的预期结果是什么,以终为始,再去慢慢实现函数或组件的具体功能,这就是 TDD 的一种模式

  • 自动化这种方案基本上能做到不漏测一个功能,而且不需要我们手动去输入数据和点击页面去验证了,提升了代码的质量

  • 人工测试:通过开发人员或测试人员与程序的交互来完成,即手动操作验证。

  • 自动化测试:通过自动化脚本与程序的交互来完成,除了刚开始编写的自动化脚本时间,基本上无需手动操作

# 自动化测试解决了什么问题?

  • 解决了耗时时间和人力成本

# 在使用新技术的时候,是怎么去做 新旧技术的融合的?

# fetch 和 pull 的区别

  1. 对远端跟踪分支操作的权限不同

    1. fetch: fetch 能够直接更改远端跟踪分支。
    2. pull: pull 无法直接对远程跟踪分支操作,我们必须先切回本地分支然后创建一个新的 commit 提交。
  2. 拉取后的操作不同

    1. fetch:fetch 会将数据拉取到本地仓库 - 它并不会自动合并或修改当前的工作。
    2. pull: pull 是从远程获取最新版本并 merge 到本地,会自动合并或修改当前的工作
  3. 使用后 commitID 不同

    1. fetch:使用 fetch 更新代码,本地的库中 master 的 commitID 不变,还是等于 1
    2. pull:使用 pull 更新代码,本地的库中 master 的 commitID 发生改变,变成了 2。

# 数据结构与算法

# 你知道哪些算法

  • 递归
  • 冒泡排序
  • 插入排序
  • 选择排序
  • 快速排序
  • 堆排序

# 链表

  • 线性递归结构,通过指针相连
  • 可以用 git 仓库的储存理解,不同的分支和节点连接在一起

# 什么是单链表

  • 线性递归结构,通过指针相连,单链表只有 next 指针

# 判断一个单链表中是否有环?

// 判断一个链表中是否有环

// 例如:A->B->C->D->B->C->D

// D指向B形成环

// 要求:在空间复杂度O(1)的情况下,时间复杂度最小

// 思路: 做一个快指针,做一个慢指针,快指针每次走两步,慢指针每次走一步;如果有环的话,就慢指针就会追上快指针。
function ListNode(x) {
  this.val = x;
  this.next = null;
}
function EntryNodeOfLoop(pHead) {
  if (pHead === null) return null;
  var fast = pHead;
  var slow = pHead;

  while (fast.next !== null && fast.next.next !== null) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) break;
  }

  if (fast === null || fast.next === null) return null;
  // 有环,slow重新回到链表头
  slow = pHead;

  // slow和fast重新相遇时,相遇节点就是入环节点
  while (slow !== fast) {
    slow = slow.next;
    fast = fast.next;
  }

  return slow;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 有一个 LinkedList 类,实现一个单链表中的 insert、find、append 方法。

class LinkedList {
  constructor() {
    this.size = 0; // 单链表的长度
    this.head = new Node("head"); // 表头节点
    this.currNode = ""; // 当前节点的指向
  }

  insert(item, element) {
    let itemNode = this.find(item);

    if (!itemNode) {
      // 如果item元素不存在
      return;
    }

    let newNode = new Node(element);

    newNode.next = itemNode.next; // 若currNode为最后一个节点,则currNode.next为空
    itemNode.next = newNode;

    this.size++;
  }

  isEmpty() {
    return this.size === 0;
  }

  findLast() {
    let currNode = this.head;

    while (currNode.next) {
      currNode = currNode.next;
    }

    return currNode;
  }

  find(item) {
    let currNode = this.head;

    while (currNode && currNode.data !== item) {
      currNode = currNode.next;
    }

    return currNode;
  }
  delete(item) {
    if (!this.find(item)) {
      // item元素在单链表中不存在时
      return;
    }

    // 企图删除头结点
    if (item === "head") {
      if (!this.isEmpty()) {
        return;
      } else {
        this.head.next = null;
        return;
      }
    }

    let currNode = this.head;

    while (currNode.next.data !== item) {
      // 企图删除不存在的节点
      if (!currNode.next) {
        return;
      }
      currNode = currNode.next;
    }

    currNode.next = currNode.next.next;
    this.size--;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

# 斐波拉契数列数组

const map = new Map();
const list = [];
function fn(num) {
  if (map.has(num)) {
    return map.get(num);
  }

  if (num === 1 || num === 2) {
    if (!map.has(num)) {
      list.push(1);
      map.set(num, 1);
    }
    return 1;
  }

  const result = fn(num - 1) + fn(num - 2);
  map.set(num, result);
  list.push(result);
  return result;
}

console.log("list", list);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 斐波那契数列 二叉树分布,寻找最左边节点值组成的集合

二叉树找最左边节点

// [1,1,2,3,5,8,13,21,34,....,n-2,n-1]
let index = 1;
const newList = [list[0]];
list.forEach((item, i) => {
  if (index > i) {
    return;
  }
  if (i === index) {
    newList.push(item);
    index = 2 * index + 1;
  }
});
1
2
3
4
5
6
7
8
9
10
11
12

# 安全方面

# 黑客上传了一个文件到我们服务器之后,是怎么黑进我们系统的?

# 页面常见攻击方式

  • xss 攻击
  • crsf 攻击
  • sql 注入

# liunx 修改文件读取权限

  • chmod -R 777

    • 表示的意思:
      • 第一个 7 表示:文件所有者访问权限
      • 第二个 7 表示:群组访问权限
      • 第三个 7 表示:其他人访问权限
  • r(read)w(write)x(eXecute) 读、写、执行

    • 权限值:4
    • 权限值:2
    • 权限值:1

# 你还知道哪些加密方法?

  • aes/des 加密
  • base64 加密(不可逆)
  • MD5 加密
  • sha512 加密(不可逆)

# 为什么要使用 aes(对称加密)进行加密?

  • 优点: 算法公开、计算量小、加密速度快、加密效率高
  • 缺点:
    • 交易双方都使用同样钥匙,安全性得不到保证
    • 密钥管理困难,使用成本较高

# 前后端交互如何保证数据的安全性?

  • 用 aes(对称加密)和 rsa(非对称加密) 配合使用。
  1. 客户端启动,发送请求到服务端,服务端用 rsa 算法生成一个公钥(public1)和私钥(prikey1)。将公钥返回给客户端。
  2. 客户端拿到服务端返回的 公钥(public1)之后,自己在前端生成一个公钥(public2)和私钥(prikey2),并将 public2 通过 public1 进行加密,并传输给服务端。
  3. 服务端接收后通过私钥(prikey1)进行解密,拿到了客户端的公钥(public2)。
  4. 服务端生成一个 aes 的 16 位的 key,并使用公钥(public2)加密传输给客户端。
  5. 客户端获取到 key 后,通过私钥(prikey2)进行解密。拿到 aes 加密的 key。
  6. 然后客户端就用加密的 key 进行数据传输,整个流程结束。

# 前端怎么去防止 sql 注入

  • 防止 SQL 注入唯一可靠的方式是验证输入参数化查询
  • 开发者必须检查所有用户输入,而不是仅检查网页表单中的输入,比如登录表单。他们必须移除潜在的恶意代码因素,比如单引号。在你的线上环境中屏蔽数据库错误也是个好主意。因为结合数据库错误,SQL 注入攻击将获得更多数据库相关信息

# sql 注入的危害

  • 通过 sql 注入可以添加、编辑、删除数据库中的数据
  • 可以拿到其他用户的凭证。伪装成这些用户。
  • 甚至可以拿到数据库管理员权限。窃取数据。
  • 影响应用的可用性。
  • 可以通过 sql 注入作为初始手段,攻击防火墙背后的内网。

# sql 注入攻击的过程及原因

  • 为了发起 SQL 注入攻击,攻击者首先需要在网站或应用程序中找到那些易受攻击的用户输入。这些用户输入被有漏洞的网站或应用程序直接用于 SQL 查询语句中。攻击者可创建这些输入内容。这些内容往往被称为恶意载体,它们是攻击过程中的关键部分。随后攻击者将内容发送出去,恶意的 SQL 语句便会在数据库中被执行。
    • 攻击者可利用 SQL 注入,从数据库中得到其他用户的用户凭证。之后他们便能伪装成这些用户。这些用户中甚至有可能包括有所有数据库权限的数据库管理员。
    • SQL 可用于从数据库中选择并输出数据。SQL 注入漏洞允许攻击者访问数据库服务中的所有数据。
    • SQL 也可用于修改数据库中数据,或者添加新数据。例如,在金融产品中,攻击者能利用 SQL 注入修改余额,取消交易记录或给他们的账户转账。
    • SQL 可用于从数据库中删除记录,甚至删除数据表。即使管理员做了数据库备份,在数据库中数据恢复之前,被删除的数据仍然会影响应用的可用性。而且,备份很可能没有覆盖最近的数据。
    • 在某些数据库服务中,可通过数据库服务访问操作系统。这种设计可能是有意的,也可能是无意的。在这种情况下,攻击者将 SQL 注入作为初始手段,进而攻击防火墙背后的内网

# 预防 sql 注入

  • 培养并保持安全意识
  • 不要信任任何用户输入
  • 使用白名单,而不是黑名单: 不要基于黑名单过滤用户输入。因为聪明的攻击者总是能找到绕过黑名单的方法,所以应尽可能只使用严格的白名单,对用户输入进行验证和过滤。
  • 采用最新的技术
  • 采用经过验证的机制
  • 周期性扫描: SQL 注入漏洞可能被开发者引入,也可能被外部库、模块或软件引入。你应该使用网站漏洞扫描器(比如 Acunetix)周期性扫描你的网站。如果你使用 Jenkins,你可以安装 Acunetix 插件,实现每次构建时进行自动扫描。

# typescript

# type 和 interface 的区别

  1. type 后面有 = ,interface 没有。
  2. type 可以描述任何类型组合,interface 只能描述对象结构
  3. interface 可以继承自(extends)interface 或对象结构的 type。type 也可以通过 & 做对象结构的继承。
  4. 多次声明的同名 interface 会进行声明合并,type 则不允许多次声明。
  5. type 可以使用 in 关键字 生成映射类型,interface 不行。
  • 用来描述对象或函数类型
  • 可以通过交叉实现继承
    • interface 可以扩展,type 可以通过交叉实现 interface 的 extends 行为
    • interface 可以 extends type,同时 type 也可以与 interface 类型交叉
  • type 可以使用 in 关键字 生成映射类型,interface 不行。
type Keys = "name" | "sex"
type DuKey = {
  [Key in Keys]: string //类似 for ... in
}
let stu: Dukey = {
  name: 'wang'sex: 'man'
}
1
2
3
4
5
6
7
8

# type 怎么继承

type Name = {
  name: string,
};

type Person = Name & {
  age: number,
};

const people: Person = {
  name: "ren",
  age: 20,
};

// interface 可以通过 extends 进行继承 ; type不能通过 extends进行继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 计算机基础

# 常见 code 码

  • 200 - 请求成功
  • 301 - 永久重定向
  • 302 - 临时重定向
  • 304 - 自从上次请求后,请求网页未修改过,服务器返回响应,不返回内容 ()
  • 400 - 服务器不理解请求的语法
  • 403 - Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求
  • 404 - 请求的资源(网页等)不存在
  • 413 - 请求体过大,超出服务器的处理能力上限
  • 414 - 请求 url 太长,服务器无法处理
  • 500 - 内部服务器错误
  • 502 - Bad Gateway 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应

# 浏览器缓存(重要)

# 浏览器缓存的意义

  • 避免了冗余的数据传输,节省流量
  • 加快了用户访问速度
  • 减小了服务器的压力

# 浏览器缓存策略

  • 第一次请求数据时,浏览器没有对应的缓存数据,此时需要请求服务器,服务器返回数据后,会把请求数据缓存。
  • 当浏览器有缓存数据后,可以根据是否向服务器发送请求,将缓存类型分为: 协商缓存与强缓存

# 强缓存

  • 命中缓存后,不会向服务器发送请求,直接从本地获取。返回的状态码为 200(from disk cache) 或 200(from memory cache)

# 协商缓存

  • 缓存过期,浏览器会向服务器发送请求,协商对比服务端和本地的资源,验证本地资源是否失效。若服务器资源与本地资源相同,则命中缓存,返回 304,否则直接请求服务器资源。
  • 协商缓存的响应结果,不仅验证资源的有效性,同时还会更新浏览器缓存
  • Age:0 表示命中了代理服务器的缓存, age 值为 0 表示代理服务器刚刚刷新了一次缓存.

# 强缓存与协商缓存的关系

  • 相同点: 强缓存与协商缓存在命中缓存资源后都是从本地读取资源。
  • 不同点:
    • 如果强缓存生效,不需要再向服务端发起请求。
    • 协商缓存,不管是否使用缓存,必须向服务器发送一个请求来协商。

# 优先级

  • 两类缓存规则同时存在的情况下, 强缓存 > 协商缓存
  • 当执行强缓存的规则时,如果缓存生效,直接使用缓存,不再执行协商缓存规则。如果强制缓存规则不生效,则需要进行协商缓存判断

# 总结

  • 浏览器第一次请求数据时,浏览器缓存中没有对应的缓存数据,此时需要请求服务器,浏览器返回数据后,会把请求数据缓存。
  • 浏览器再次访问同一 url,则会根据请求资源是否过期向服务器发送不同请求,此时分为强缓存和协商缓存。
  • 若资源未过期(expires 和 cache-control)时,命中缓存,不会向服务器发送请求,直接从缓存中读取。
  • 若资源显示过期(if-modified-since/Last-Modified)和(if-none-match/Etag),则会向服务器发送请求,协商对比服务器资源,验证是否失效。若相同,命中协商缓存,返回 304,否则直接请求服务器。

# 清除浏览器缓存

  • meta 标签中令 cache-control 为 no-cache 或者 expires 为 0
//不缓存
<meta http-equiv="Cache-Control" content="no-cache, must-revalidate">
<meta http-equiv="expires" content="0">
1
2
3

# 哪些不能被浏览器缓存

  1. http 头中 cache-control 为 no-cache 或者为 max-age: 0
  2. 需要根据 cookie,和输入内容的动态请求
  3. 经过 https 加密的请求
  4. post 请求不可以被缓存
  5. http 响应头中不含 Last-Modified/Etag,也不包含 Cache-Control/Expires 的请求无法被缓存

# 在浏览器中输入一个网址到页面展示,会经历什么?(重要)

传送门 (opens new window)

  1. 浏览器输入 url
  2. 查找缓存: 浏览器先查看浏览器缓存-系统缓存-路由缓存中是否有该地址页面,如果有,则显示该页面,没有则进行下一步.
  3. DNS 域名解析: 浏览器向 dns 服务器发送请求,解析该 URL 域名中对应的 ip 地址。(dns 服务器是基于 UDP 的,因此会用到 UDP 协议)
  4. 建立 TCP 连接:解析出 IP 地址后,根据 IP 地址和默认的端口,和服务器建立 TCP 连接。
  5. 发起 http 请求: 浏览器发起读取文件的 http 请求,改请求作为 TCP 三次握手的第三次数据发送给服务器
  6. 服务器响应并返回结果: 服务器对浏览器请求做出响应,并把对应的 html 文件发送给浏览器
  7. 关闭 TCP 连接: 通过四次挥手释放 TCP 连接
  8. 浏览器渲染: 客户端解析 html 内容并渲染出来
    1. 构建 dom 树:词法分析然后解析成 dom 树(dom tree),是由 dom 元素及属性节点组成,树的跟是 document 对象
    2. 构建 css 规则树: 生成 css 规则树(CSS Rule Tree)
    3. 构建 render 树: 浏览器将 dom 和 CSSOM 结合,并构建出渲染树(render Tree)
    4. 布局(Layout):计算出每个节点在屏幕中的位置
    5. 绘制(Painting):遍历 render 树,并使用 UI 后端层绘制每个节点

# TCP 的三次握手和四次挥手

  • 三次握手

    • 客户端向服务端发送 syn 请求包,询问服务端能不能收到请求
    • 服务端接收后向客户端发送 ack 确认包,告诉客户端能收到
    • 客户端向服务端发送 ack 确认包,告诉服务端我也能收到,然后客户端进入准备状态,服务器接收后也进入准备状态。
  • 四次挥手

    • 客户端向服务端发送 fin 请求包,告诉服务端要断开连接
    • 服务端收到后发送 ack 确认包告诉客户端收到了断开请求
    • 服务端有未传完的数据会继续传完,然后向客户端发送 fin 请求包,告诉客户端可以断开连接了
    • 客户端收到后向服务端发送 ack 确认包,然后客户单断开连接,服务端收到后也断开连接

# 浏览器的重绘、重排/回流

  • 重排/回流(Reflow): 当 dom 变化影响元素的几何信息,浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。表现为:重新生成布局,重新排列元素。
  • 重绘(Repaint): 当一个元素外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫重绘。表现为某些元素的外观被改变

# 如何触发重排和重绘?

  • 添加、删除、更新 DOM 节点
  • 通过 display: none 隐藏一个 DOM 节点-触发重排和重绘
  • 通过 visibility: hidden 隐藏一个 DOM 节点-只触发重绘,因为没有几何变化
  • 移动或者给页面中的 DOM 节点添加动画
  • 添加一个样式表,调整样式属性
  • 用户行为,例如调整窗口大小,改变字号,或者滚动。

# 如何避免重排重绘?

  • 集中改变样式,不要一条一条地修改 DOM 的样式
  • 不要把 DOM 结点的属性值放在循环里当成循环里的变量。
  • 为动画的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他们的 CSS 是不会 reflow 的
  • 不使用 table 布局
  • 尽量只修改 position:absolute 或 fixed 元素,对其他元素影响不大
  • 动画开始 GPU 加速,translate 使用 3D 变化
  • 提升为合成层
    • 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
    • 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
    • 对于 transform 和 opacity 效果,不会触发 layout 和 paint

# OSI 七层模型

  • (1)应用层 (为计算机用户提供应用接口或网络服务。网络协议: HTTP,HTTPS,FTP,POP3,SMTP)
  • (2)表示层 (提供应用层数据的加密例如 base64、ase 加密
  • (3)会话层 (负责建立、管理、终止表示层之间的通信
  • (4)传输层 (为上层协议提供端到端的数据传输服务
  • (5)网络层 (通过 IP 寻址建立两个节点之间的连接
  • (6)数据链路层 (将比特组合成字节,再将字节组合成帧,使用链路层地址访问介质,并进行差错检测。)
  • (7)物理层 (最终信号的传输。通过物理介质传输比特流网线、中继器

# https 和 http 的区别

  • 安全方面:http 使用的是无状态和明文传输。而 https 实际上是http+ssl协议组成的加密传输协议
  • 响应速度: 理论上 http 响应速度更快,只需要三次握手;而 https 则在三次握手的情况下,还需要一次 ssl 握手
  • 端口: http 默认是80端口,而 https 默认端口是443
  • 消耗资源方面:https 是构建在 ssl 之上的协议,所以 https 会消耗更多的服务资源。
  • 展示方面: https 会在浏览器上面加上一个绿色的锁。

# https 的加密是在 ISO 七层模型中的哪一层?

  • https 的加密在 ISO 七层模型中的表示层

# 网络安全

# 前端秘钥怎么存储

  1. 前端需要传输时,需要先向服务器获取一个加密公钥(加密密钥对由后端生成,以公钥为 key,私钥为 value 存储在 redis 中)
  2. 获取到数据后,将数据进行加密并将加密后的数据和公钥一起传给后端。
  3. 后端通过公钥从 redis 中得到配对的秘钥,然后对密码解密

# 添加加密后接口访问速度比之前快还是慢,慢多少?

  • 没有影响

# 有没有自己写过爬虫脚本?

# 怎么让自己的网站不被爬虫爬?

爬虫 (opens new window)

  • 限制 User-Agent 字段:User-Agent 字段能识别用户所使用的操作系统、版本、CPU、浏览器等信息,如果请求来自非浏览器,就能识别其为爬虫,阻止爬虫抓取网站信息
  • 限制 IP:限制 IP 是最常见的手段之一,为了效率,恶意爬虫的请求频率往往比正常流量高,找出这些 IP 并限制其访问,可以有效降低恶意爬虫造成的危害。
  • 添加验证码:在登录页等页面,添加验证码,以识别是正常流量还是恶意爬虫,也是一种基本的操作。不过如今爬虫技术,早已能解决验证码的问题,例如二值化、中值滤波去噪等等。
  • Cookies 限制: 根据业务需求,可对 Cookies 进行限制,要求用户登录后才能使用某些功能或权限,并针对同一账号的访问速度进行限制。
  • 使用爬虫管理产品: 可以使用防火墙产品,能够有效防范爬虫,比如阿里云、腾讯云

# 性能优化

# 前端怎么做性能优化?

  • cdn 服务加速网络访问
  • 浏览器缓存
  • gzip 压缩
  • 图片优化:压缩、base64、svg、字体图标、懒加载
  • 首屏优化:路由懒加载、图片懒加载、dom 懒加载、loading 效果、骨架屏
  • dns 预解析(dns-prefetch)、js 和 css 的预解析 preload(当前页资源)和 prefetch(其他页资源)
  • script 标签放底部,适当地加 async 和 defer 异步属性
  • 减少重排重绘
  • 节流和防抖

# 首页白屏怎么优化

  • ssr 渲染(后端渲染)
  • 预渲染(prerender-spa-plugin)
  • 使用骨架屏

# fetch 和 ajax 的区别是什么

  • ajax 是利用 XMLHttpRequest 对象来请求数据的,而 fetch 是 window 的一个方法
  • ajax 基于原生的 XHR 开发,XHR 本身的架构不清晰,已经有了 fetch 的替代方案
  • fetch 比较与 ajax 有着更好更方便的写法
  • fetch 只对网络请求报错,对 400,500 都当做成功的请求,需要封装去处理
  • fetch 没有办法原生监测请求的进度,而 XHR 可以
最后更新时间: 2024/1/4 01:21:36