VuePressVuePress
vue2
vue3
React
css
javascript
实操题目
http
真题
事件循环
题目
vue2
vue3
React
css
javascript
实操题目
http
真题
事件循环
题目

react 中有对状态管理做进一步封装吗?

  • 在之前,我们使用 const { Provider, Consumer } = createContext()解构出了里面的 Provider 和 Consumer,如果采用封装就不需要了,而且一个项目中可以封装多个仓库(慎用),具体的方法如下
// store.js
import { createContext, useContext } from "react";

const MyContext = createContext();

const ContextStore = (props) => {
  // 相当于一个组件 所以这里使用了箭头
  // props用于接收子组件

  let obj = {
    name: "Mydata",
    count: 32,
    changeCount: (count) => {
      console.log("this.count");
      console.log(count++);
      console.log(count++);
      // count++需要useState的方法声明变量,
      // 然后再打包到obj,
      // 这里不再举例
    },
  };

  return <MyContext.Provider value={obj}>{props.children}</MyContext.Provider>;
};
export default ContextStore;
export { MyContext };
import ContextStore from "./contextStore/contextStore";
import PackageContext from './components/PackageContext'


   <h1>封装的状态管组件传参</h1>
            <ContextStore>
              <PackageContext/>
            </ContextStore>

import { createContext, useContext } from "react";
import { MyContext } from "../contextStore/contextStore";

const First = () => {
  // 申明 我要用MyContext这个仓库的数据
  const value = useContext(MyContext);
  console.log(value);
  return (
    <>
      <h2>第一层</h2>
      <h3>count:{value.count}</h3>
      <button
        onClick={() => {
          value.changeCount(1);
        }}
      >
        count++
      </button>
    </>
  );
};

export default First;

react 在父组件中获取子组件方法?

  • context
  • ref 获取子组件的引用
  • props 的回调函数

useCallback 使用没有?

import React, { useState, useCallback } from "react";

// 父组件
const ParentComponent = () => {
  const [count, setCount] = useState(0);

  const increment = useCallback(() => {
    setCount((count) => count + 1);
  }, []);

  console.log("父组件渲染");
  return (
    <div>
      <ChildComponent />
      <p>计数:{count}</p>
      <button onClick={increment}>点击我</button>
    </div>
  );
};

// 子组件
const ChildComponent = React.memo(() => {
  console.log("子组件渲染");
  return <div></div>;
});

export default ParentComponent;

函数组件和类组件处理重复渲染的区别?

  • 函数组件 React.memo/ React.pureComponent
  • 类组 SCU

封装的按钮权限组件这么是实现?

  • React Hooks 来简化权限按钮组件的实现。以下是一个使用 Hooks 的例子
import React, { useContext } from "react";

// 权限上下文
const AuthContext = React.createContext();

// 权限按钮组件
const AuthButton = ({ authKey, children, ...props }) => {
  // 从上下文中获取权限数据
  const authData = useContext(AuthContext);

  // 检查是否具有权限
  const hasAuth = authData.includes(authKey);

  // 如果具有权限,则渲染按钮;否则,不渲染任何内容
  return hasAuth ? <button {...props}>{children}</button> : null;
};

export default AuthButton;
import AuthButton from "./AuthButton";
import { AuthContext } from "./AuthContext";

// ...

<AuthContext.Provider value={userAuthData}>
  <AuthButton authKey="create">新建</AuthButton>
</AuthContext.Provider>;

请求的参数如何防止篡改?

  • https 安全通道, 对数据进行加密
  • 签名: 客户端用约定好的密钥加密参数

localstorage 如何跨域获取?

  • 使用 postMessag 跨域通讯
  • 使用服务器代理

如何写一个 split 方法并覆盖数组的原方法?

// 自定义的split方法
Array.prototype.split = function (separator) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    const element = this[i];
    if (element === separator) {
      continue;
    }
    result.push(element);
  }
  return result;
};

// 使用自定义split方法
const myArray = ["a", "b", "c", "d"];
const splitArray = myArray.split("b"); // 使用自定义split方法,去除'b'
console.log(splitArray); // 输出: ['a', 'c', 'd']

forEach 循环和 for 循环那个性能高? forEach 循环可以中断吗?

  • for > forEach
  • for continue 跳转本次循环 || break 跳出整个循环
  • forEach 需结合 try catch 操作,通过 return 跳出当次循环,通过抛出异常 throw Error()的方式跳出整个循环

浅拷贝和深拷贝, 如何实现一个深拷贝?

  • 浅拷贝只是复制了对象的引用(地址),而不是对象本身 1。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址 1。因此,如果其中一个对象改变了这个地址,就会影响到另一个对象

  • 深拷贝则是将一个对象从内存中完整地拷贝一份出来,新对象跟原对象不共享内存,修改新对象不会改到原对象 1。深拷贝会递归复制整个对象结构,包括对象内部的对象,确保新对象和原对象之间的所有关系都是独立的 1。

实现:

  1. JSON.parse +JSON.stringfly
  2. 递归调用
function cloneDeep(obj) {
  if (typeof obj !== "object" || obj === null) return obj;

  let result;
  if (obj instanceof Array) {
    result = [];
  } else {
    result = {};
  }

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = cloneDeep(obj[key]);
    }
  }

  return result;
}
  1. 借助第三方工具库_.cloneDeep

如何实现一个 new ?

function myNew(constructor, ...args) {
  // 创建一个新的空对象
  const obj = {};
  // 将新对象的 __proto__ 属性指向构造函数的 prototype 对象
  obj.__proto__ = constructor.prototype;
  // 将 this 指向新对象,并执行构造函数
  const result = constructor.apply(obj, args);
  // 如果构造函数返回的是一个对象,就返回该对象,否则返回新创建的对象
  return typeof result === "object" ? result : obj;
}

// 测试
function Person(name, age) {
  this.name = name;
  this.age = age;
}

const person = myNew(Person, "Tom", 20);
console.log(person.name); // 输出:Tom
console.log(person.age); // 输出:20

怎么理解回流和重绘?

  • 重绘:当渲染树中的一部分元素需要更新属性,如改变元素的外观、风格,而不影响布局的重新渲染的过程叫重绘。例如,颜色的修改,文本方向的修改,阴影的修改等,都会触发重绘

  • 回流:当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建的过程叫回流。这就意味着元素的几何属性(如尺寸、位置等)发生了变化。例如,添加或删除可见的 DOM 元素,元素位置改变,元素尺寸改变(包括外边距、内边框、边框大小、宽度和高度等),内容改变(比如文本改变或者图片大小改变而引起的计算值宽度和高度改变),页面渲染初始化,浏览器窗口尺寸改变等,都会触发回流

了解 axios 的原理吗? 怎么实现?

  • 原理:Axios 的核心是基于 Promise,使得异步 HTTP 请求变得简单而直观。Axios 底层实现使用了 XMLHttpRequest 对象(浏览器环境)和 http 模块(Node.js 环境)来发送请求。Axios 还提供了一个拦截器(interceptor),允许在请求或响应被处理前、或者响应被 reject 前截获它们

js 有哪些数据类型?

  1. 基本类型:String、Number、Boolean、Symbol、Undefined、Null
  2. 引用类型:Object

ES6 新增哪些新特性?

  1. let const
  2. 模版字符串
  3. class
  4. 解构
  5. 箭头函数 ...

图片懒加载和预加载 ?

  • 懒加载
// 获取所有的 img 元素
let imgs = document.querySelectorAll("img");

// 监听滚动事件
window.addEventListener("scroll", function () {
  // 遍历所有的 img 元素
  for (let img of imgs) {
    // 如果 img 元素进入可视区域
    if (img.getBoundingClientRect().top < window.innerHeight) {
      // 将 img 元素的 data-src 属性值赋给 src 属性,开始加载图片
      img.src = img.getAttribute("data-src");
    }
  }
});
  • 预加载
// 需要预加载的图片 URL 列表
let urls = ["url1", "url2", "url3"];

// 遍历所有的 URL
for (let url of urls) {
  // 创建一个新的 Image 对象
  let img = new Image();
  // 设置 Image 对象的 src 属性,开始加载图片
  img.src = url;
}

单点登录?

https 为什么比 http 安全 ?

  1. 数据加密
  2. 防篡改

为什么 data 属性是一个函数而不是一个对象?

在 Vue.js 中,组件的 data 属性需要是一个函数,而不是一个对象,主要是为了防止数据在组件之间共享。 当我们定义一个组件时,这个组件可能会被用来创建多个实例。如果 data 是一个对象,那么所有的组件实例将共享同一个 data 对象。这可能会导致一个组件实例修改了 data,其他的组件实例的 data 也会被改变。 而当 data 是一个函数时,每个组件实例都会调用这个函数,返回一个全新的 data 对象。这样,每个组件实例都有自己的 data 对象,修改这个对象不会影响到其他的组件实例。

http 缓存有哪些?

  • 强缓存 1、Expires 2、Cache-control

  • 协商缓存 1、Last-Modified/If-Modified-Since 2、ETag/If-Not-Match

vue2/3 区别,底层双向绑定区别

Vue 2 和 Vue 3 在设计和功能上有许多区别,其中最显著的是它们各自的双向绑定实现方式。

Vue 2 的双向绑定是通过 Object.defineProperty 实现的。这个方法允许你创建一个对象的属性,并为这个属性提供 getter 和 setter,从而可以在属性被访问或修改时执行特定的逻辑。Vue 2 使用这个方法来监听数据对象的属性变化,当属性值发生变化时,Vue 会自动更新 DOM。

Vue 3 则采用了 Proxy 对象来实现双向绑定。Proxy 是 ES6 中引入的新特性,它可以拦截对象的各种操作,如属性读取、设置和删除等。Vue 3 通过 Proxy 可以更灵活地监听数据变化,它不仅可以监听属性的变化,还可以监听属性的添加和删除,这是 Object.defineProperty 所不能做到的

这两种方法的主要区别在于:

性能:Proxy 可以提供更好的性能,因为它不需要像 Object.defineProperty 那样遍历对象的每个属性来设置 getter 和 setter。 灵活性:Proxy 可以拦截更多种类的操作,包括属性的添加和删除,这使得 Vue 3 可以更灵活地处理数据变化。

特性Vue 2Vue 3
核心 APIObject.definePropertyProxy
数据劫持方式递归遍历对象属性逐个劫持直接代理整个对象
数组监听需重写数组方法(push/pop 等)原生支持数组索引修改和长度变化
新增/删除属性需通过 Vue.set/Vue.delete自动检测

正对 vue3 中 composition api 写法和普通写法,比如有个方法绑定在全局中,怎么去兼容这个写法?

实则考的 this

const app = getCurrentInstance()

全局的 provider 和 inject 用过吗

在 Vue 中,provide和inject是一对用于依赖注入的 API,允许开发者在组件树中跨级传递数据。这对于避免 prop 的逐级传递非常有用,尤其是在深层次的组件树中。

全局的 provide 和 inject:

  • provide: 在 Vue 应用的根组件或任何祖先组件中,你可以使用provide来定义要传递的数据或方法。这样,无论组件嵌套有多深,所有的子孙组件都可以通过inject来接收这些数据 ¹².
  • inject: 子孙组件使用inject来接收祖先组件通过provide提供的数据。如果提供的值是响应式的,注入的值也将保持响应性 ¹².

在 Vue 3 中,provide和inject通常在组合式 API 的setup函数中使用,而在 Vue 2 中,它们可以直接在组件选项中使用。Vue 3 的provide和inject也支持 TypeScript 的类型推断,这使得在使用 TypeScript 开发时更加方便。

例如,在 Vue 3 中,你可以在根组件中这样使用provide和inject:

// 根组件
import { provide, reactive } from 'vue';

export default {
  setup() {
    const state = reactive({
      user: 'Copilot',
      theme: 'dark'
    });

    provide('appState', state);
  }
}

// 子组件
import { inject } from 'vue';

export default {
  setup() {
    const appState = inject('appState');

    return {
      user: appState.user,
      theme: appState.theme
    };
  }
}

在这个例子中,根组件提供了一个响应式的状态对象state,任何子孙组件都可以通过inject来访问这个状态对象。这样,你就可以在整个应用中共享全局状态,而不需要使用 Vuex 等状态管理库。

vue3 一个 list 有 5 个 item,每个绑定不一样的 ref,怎么写

<template>
  <div v-for="(item, index) in list" :key="index">
    <YourComponent :ref="setRefs(index)" />
  </div>
</template>

<script setup>
import { ref } from 'vue';

const list = ref([/* 列表的初始值 */]);
const itemRefs = ref([]);

const setRefs = index => {
  return el => {
    itemRefs.value[index] = el;
  };
};
</script>

react 和 vue 说一下 vdom 的区别,createElement 做了什么事情?渲染是真实 dom 还是 vdom?

在 React 和 Vue 中,虚拟 DOM(VDOM)都是用来提高应用性能的关键概念。它们通过在内存中模拟 DOM 树的结构来实现这一点。尽管 React 和 Vue 都使用虚拟 DOM,但它们在实现细节上有所不同。

React 的虚拟 DOM:

  • React 使用虚拟 DOM 来优化 UI 的渲染过程。当组件的状态发生变化时,React 会首先在虚拟 DOM 上进行变更,然后通过比较新旧虚拟 DOM 的差异,来决定是否以及如何更新真实的 DOM¹¹。
  • createElement函数在 React 中被用来创建虚拟 DOM 元素。它接受类型(type)、属性(props)和子元素(children)作为参数,并返回一个 React 元素。这个元素是虚拟 DOM 树中的一个节点 ¹。

Vue 的虚拟 DOM:

  • Vue 的虚拟 DOM 同样是为了提高性能,它允许开发者编写声明式的代码,框架负责将这些声明转换成实际的 DOM 更新 ¹²。
  • Vue 中的createElement函数(通常在render函数中使用)用于创建 VNode,或者说是虚拟节点。这些节点代表了真实 DOM 的结构,但并不直接与之相对应。Vue 会处理这些 VNode,并在必要时将它们映射到真实的 DOM 上 ³。

至于渲染,无论是 React 还是 Vue,最终用户看到的都是真实的 DOM。虚拟 DOM 只是一个内存中的表示,用于优化性能,减少对真实 DOM 的操作次数。当数据变化时,框架会首先在虚拟 DOM 上应用这些变化,然后计算出需要对真实 DOM 做出的最小更新,最后将这些更新应用到真实 DOM 上,用户看到的界面也随之更新 ⁸⁹。

简而言之,虚拟 DOM 是性能优化的一种手段,而真实 DOM 是用户实际看到的内容。createElement函数在两个框架中都是用来创建虚拟 DOM 节点的,但具体的实现和优化策略各有不同。

react setState 会触发 diff 算法吗? 它是异步还是同步

在 React 中,setState 方法确实会触发 diff 算法。当你调用 setState 时,React 会更新组件的状态,并且只有当状态真正发生变化时,才会通过 diff 算法来确定哪些部分的虚拟 DOM 需要更新 1。这意味着,如果状态更新后的结果与之前相同,React 不会对真实 DOM 进行不必要的操作。

至于 setState 是异步还是同步,通常情况下,setState 是异步的 2。React 可能会将多个 setState 调用批量处理,以优化性能。因此,调用 setState 后立即读取 this.state 可能得到的是更新前的状态。如果你需要在状态更新后立即执行某些操作,可以在 setState 的回调函数中进行,这样可以确保状态已经更新完成。

react 17/18 的区别,fiber 变更了哪些东西

时间切片 并发模式 批量更新

说一说 vite 和 webpack 的本质区别?

Vite 和 Webpack 是两种常用的前端构建工具,它们在设计理念和实现方式上有一些本质区别。以下是它们的主要区别:

1. 构建速度

  • Webpack:Webpack 是一个通用的构建工具,需要对整个项目进行分析和打包,因此在启动和构建时间上可能比较慢,尤其是对于大型项目和复杂的构建配置而言。
  • Vite:Vite 利用浏览器原生的 ES 模块支持,将构建过程延迟到开发环境的运行时。这种方式使得 Vite 具有非常快的冷启动速度和即时热更新 ¹²。

2. 开发模式

  • Webpack:在开发阶段,Webpack 需要将所有代码打包成一个或多个 bundle,然后在浏览器中动态加载。这种模式需要使用热加载或者手动刷新浏览器才能看到更新的效果。
  • Vite:Vite 采用 ES 模块原生的开发模式,不需要将所有代码打包成一个 bundle,而是直接在浏览器中加载和运行文件。这使得 Vite 能够实现更快的冷启动和热更新 ²³。

3. 生产构建

  • Webpack:在生产环境下,Webpack 会将所有代码打包成一个或多个 bundle,以便进行优化、压缩和代码拆分等操作,提高性能和加载速度。
  • Vite:Vite 在生产环境下仍然保持开发时的原生 ES 模块导入方式,不会将所有代码打包成一个 bundle,而是保留第三方依赖的单独引用,以便在浏览器端实现更快的加载速度 ²。

4. 插件生态系统

  • Webpack:Webpack 拥有广泛的插件生态系统,有大量的插件可以满足不同的构建需求,并能与其他工具和框架良好地集成。
  • Vite:Vite 的插件生态系统相对较小,但依然可以满足常见的构建需求,并且在逐渐增长 ²。

5. 热模块替换(HMR)

  • Webpack:Webpack 的 HMR 通常会在代码发生变化时替换整个模块,然后通过 HMR runtime 来触发页面级别的刷新或重新渲染,这可能导致部分页面状态的丢失。
  • Vite:Vite 的 HMR 实现了更细粒度的模块更新,可以实现局部模块的更新而无需刷新整个页面,从而减少页面状态的丢失 ¹。

6. 底层实现

  • Webpack:Webpack 是用 Node.js 实现的,支持丰富的生态库,前后端一致性较高,适合异步编程和跨平台运行。
  • Vite:Vite 使用 esbuild(用 Go 语言编写)进行预构建,专注于快速的构建和打包,能够充分利用多核处理器和高效的算法,实现极快的构建速度 ¹²。

总结

  • Webpack:适用于处理各种复杂的构建需求和场景,具有强大的功能和灵活性。
  • Vite:专注于开发体验和构建速度,适用于快速原型开发和轻量级项目。

选择使用哪个工具应该根据具体项目的需求和优先级来决定。如果你有更多问题或需要进一步的解释,请告诉我!

¹: 面试官:【webpack 和 vite 的区别?vite 一定比 webpack 快吗 ... ²: 前端构建工具 | Vite 与 webpack 的主要区别 - CSDN 博客 ³: Vite 和 Webpack 的区别 (我总结了 12 点【全】) - CSDN 博客

开发一个统计的 SDK, 你该如何设计?

  1. 访问量 PV
  2. 自定义事件 event
  3. 性能, 错误
const PV_URL_SET = new Set();

class MyStatistic {
  constructor(productId) {
    this.productId = productId;

    this.initPerformance(); // 性能统计
    this.initError(); // 错误监控
  }

  // 发送统计数据
  send(url, params = {}) {
    this.productId = params.productId;

    const paramsArr = [];
    for (let key in params) {
      const val = params[key];
      paramsArr.push(`${key}=${val}`);
    }

    const newUrl = `${url}?${paramsArr.join("&")}`;

    // 用<img/>发送 1. 可跨域 2.兼容性极好
    const img = document.createElement("img");
    img.src = newUrl; // get
  }

  // 初始化性能统计
  initPerformance() {
    const url = "yyy";
    this.send(url, performance.timing);

    // 加载时机:dom节点加载完毕后触发, 需要在业务中 判断
  }

  // 初始化错误监控
  initError() {
    window.addEventListener("error", (event) => {
      const { error, lineno, colno } = event;
      this.error(error, { lineno, colno });

      // Promise 未 catch 的错误
      window.addEventListener("onunhandledrejection", (event) => {
        this.error(new Error(event.reason), { type: "onunhandledrejection" });
      });
    });
  }

  // 用户pv(特殊的event, 不能重复发送)
  pv() {
    const href = location.href;
    if (PV_URL_SET.get(href)) return;
    this.event("pv");
    PV_URL_SET.add(href);
  }

  // 自定义事件(可重复发送)
  event(key, val) {
    const url = "xxx";
    this.send(url, { key, val });
  }

  // 错误信息
  error(err, info = {}) {
    const url = "xxx";
    const { message, stack } = err;
    this.send(url, { message, stack, ...info });
  }
}

设计一个 图片懒加载的 SDK

  1. <img src="loading.png" data-src="xxxxxx"/>
  2. 元素的位置:elem.getBoundingClientRect()
// 加载函数

function mapImagesAndTryLoad() {
  const images = document.querySelectorAll("img[data-src]");
  if (images.length === 0) return;

  images.forEach((img) => {
    const rect = img.getBoundingClientRect();

    // 进入可视区域
    if (rect.top < window.innerHeight) {
      img.src = img.dataset.src;
      img.removeAttribute("data-src"); // 移除data-src属性, 为了下次执行减少
    }
  });
}

// 业务使用
window.addEventListener("scroll", _.throttle(mapImagesAndTryLoad, 100));

// 初始化 加载一次
mapImagesAndTryLoad();

数组去重复, undefined 和 null 可以放在 new Set 去重?[...new Set([1,2,3,1,3,2,1, null, undefined])]

Set 是 ES6 引入的数据结构,它允许存储任何类型的唯一值,无论是原始值还是对象引用。对于原始值,Set 使用严格相等(===)来判断是否重复

Last Updated:
Contributors: zhanghusheng