Skip to content

一面 技术面-02

vue3 对比 vue2 做了哪些性能的提升

Vue 3 在性能方面进行了多项改进,主要体现在以下几个方面:

1、 响应式代理的改进:

  • Vue 3 中采用了 Proxy 作为响应式系统的实现机制,相比 Vue 2 中的 Object.defineProperty,Proxy 更加灵活且性能更高。
  • Proxy 可以直接代理整个对象,而 Object.defineProperty 需要逐个属性进行代理,这样在大型数据对象上 Proxy 的性能更优。

2、编译器的优化:

  • Vue 3 的编译器进行了重写,采用了更加高效的 AST 生成和优化算法,使得编译速度更快。
  • 新的编译器还允许更好的 Tree-shaking,能够更有效地剔除未使用的代码,减小打包体积。

3、虚拟 DOM 的优化:

  • Vue 3 中的虚拟 DOM 对比算法进行了改进,通过更精准的标记和 patch,减少了不必要的 DOM 操作,提高了性能。
  • 使用了 Fragments 和静态提升等技术,可以更好地处理组件的嵌套和静态内容,减少了渲染开销。

4、组件实例的改进:

  • Vue 3 中组件实例的创建和销毁过程进行了优化,减少了额外的内存分配和垃圾回收开销。
  • 改进了组件实例的缓存机制,减少了重复创建组件实例的情况,提高了性能和内存利用率。

5、Hooks 的引入:

  • Vue 3 引入了 Hooks API,使得组件的逻辑更加清晰和灵活,能够更好地复用和组合组件逻辑。
  • Hooks 可以让开发者更自由地管理组件状态和副作用,避免了传统的混入和高阶组件带来的复杂性和性能问题。

6、TypeScript 的原生支持:

  • Vue 3 原生支持 TypeScript,通过 TypeScript 的类型系统可以更好地捕获代码中的错误,并提供更好的编辑器支持和自动补全。

综上所述,Vue 3 在多个方面进行了性能的提升,包括响应式代理、编译器优化、虚拟 DOM、组件实例、Hooks 引入以及对 TypeScript 的原生支持,使得 Vue 3 在性能和开发体验上都有了明显的改进。

watch 和 watchEffect 有啥区别

watch和watchEffect都是用于观察和响应响应式数据的变化的工具,但它们之间存在一些关键的区别。

  1. 执行方式和依赖追踪
  • watchEffect()函数用于创建一个自动追踪依赖的响应式副作用。它会在初始化时立即执行一次,并自动追踪在回调函数中使用的所有响应式数据,在这些数据发生变化时重新运行回调函数。
  • watch函数则用于观察一个或多个响应式数据,并在这些数据发生变化时执行相应的回调函数。这意味着你需要明确指定要观察的数据源和回调函数。
  1. 手动与自动
  • 使用watchEffect时,你不需要显式地列出要观察的数据,它会自动收集依赖。这使得它在某些场景下更为简洁和方便。
  • 相比之下,watch则需要你手动指定要观察的数据和相应的回调函数。
  1. 深度监听
  • 当监视reactive定义的响应式数据中的某个属性,且该属性是一个对象时,watch的deep配置可以生效,实现深度监听。然而,watchEffect默认就是深度监听,不需要额外的配置。
  1. 使用场景
  • 如果你想要自动追踪和响应多个响应式数据的变化,并且不关心具体是哪些数据发生了变化,那么watchEffect可能是一个更好的选择。
  • 相反,如果你需要更精确地控制要观察哪些数据,并在这些数据发生变化时执行特定的回调函数,那么watch可能更适合你的需求。

总的来说,watch和watchEffect在Vue中都是强大的工具,用于观察和响应响应式数据的变化。它们的主要区别在于执行方式、依赖追踪、手动与自动以及使用场景。选择使用哪一个取决于你的具体需求和场景。

vuex和pina有啥区别

Vuex和Pinia在Vue.js的状态管理领域中都是重要的工具,但它们之间存在一些明显的区别。

  1. 结构与设计
  • Vuex采用全局单例模式,通过一个store对象来管理所有的状态。组件通过store对象来获取和修改状态。这意味着在Vuex中,状态是集中管理的,所有的组件都可以访问和修改这些状态。
  • Pinia则采用了分离模式,每个组件都拥有自己的store实例。通过在组件中创建store实例来管理状态,这使得每个组件的状态管理更加独立和模块化。
  1. 核心概念
  • Vuex中有几个核心概念,包括State(用于提供唯一的公共数据源)、Mutation(用于同步修改State中的数据)、Getter(对State进行计算和操作)和Action(用于异步操作或提交Mutation)。
  • Pinia则简化了这些概念,它没有mutations,只有state、getters和actions。actions支持同步和异步操作,这使得Pinia在处理异步逻辑时更加灵活。
  1. 体积与性能
  • Pinia的体积相对较小,大约只有1KB,这使得它在应用中几乎感觉不到其存在。而Vuex由于其全局单例的设计,可能在大型应用中会稍显笨重。
  1. 语法与使用
  • Pinia的语法相对Vuex来说更为直观和灵活,更容易上手。此外,Pinia提供了完整的Typescript支持,这使得在类型安全的环境中开发变得更加容易。
  • Vuex虽然也支持Typescript,但在某些方面可能没有Pinia那么完善。
  1. 代码分割与模块化
  • Pinia通过设计提供了扁平结构,每个store都是互相独立的,没有命名空间,这使得代码分割更加清晰和易于管理。
  • Vuex则通过modules配置来实现模块化,虽然也能达到代码分割的目的,但可能在某些方面没有Pinia那么直观和简洁。

总的来说,Vuex和Pinia在Vue.js的状态管理中各有优势。Vuex适用于大型、复杂的应用,而Pinia则更适用于小型、简单的应用或需要更灵活状态管理的场景。选择使用哪一个取决于项目的具体需求和团队的偏好。

uniapp怎么区别开发测试生产环境

uni-app是一个使用Vue.js开发所有前端应用的框架,可以发布到iOS、Android、H5以及各种小程序等多个平台。在uni-app中,开发、测试和生产环境的区分对于确保应用在不同阶段的稳定性和正确性至关重要。

首先,关于uni-app如何区分这些环境,通常的做法是在项目的配置文件中定义不同的环境变量。例如,在HBuilderX中,可以通过修改manifest.jsonpages.json等文件,或者通过条件编译的方式,为不同的环境配置不同的参数。同时,uni-app也支持使用Vue的环境变量机制来判断当前环境,例如通过process.env.NODE_ENV来获取当前的环境信息,从而在不同的环境中执行不同的代码逻辑。

对于H5来说,其运行环境是浏览器,不依赖于特定的操作系统和平台。因此,H5的开发、测试和生产环境的区分主要依赖于前端的构建配置。通常,会在构建过程中定义不同的环境变量,以便在编译时注入不同的配置信息。例如,开发环境可能使用本地的API接口和调试工具,而生产环境则使用正式的API接口和优化的构建选项。

对于微信小程序,其运行环境是微信平台内部,具有特定的API和限制。微信小程序也区分开发、测试和生产环境,并提供了相应的API来区分这些环境。例如,可以使用wx.getAccountInfoSync()方法来获取当前小程序的环境信息,其中miniProgram.envVersion字段的值可以指示当前是开发版(develop)、体验版(trial)还是正式版(release)。这样,就可以根据环境信息来执行不同的代码逻辑,例如在不同的环境中调用不同的API接口或显示不同的调试信息。

总的来说,无论是H5还是微信小程序,uni-app都提供了灵活的方式来区分和管理不同的环境。通过合理地配置和使用这些环境变量和API,可以确保应用在开发、测试和生产各个阶段都能得到正确的处理和执行。

h5和原生端如何通信

H5页面(即HTML5页面,通常指运行在Web浏览器中的网页)与原生端(如Android或iOS的原生应用)之间的通信是实现混合应用(Hybrid App)时的重要一环。这种通信使得H5页面能够调用原生端的功能,如访问设备硬件、调用系统服务等,同时原生端也可以向H5页面传递数据或指令。

以下是H5与原生端通信的几种主要方法:

  1. WebView的JavaScriptInterface
  • 对于Android平台,可以通过WebView的addJavascriptInterface方法将原生类的实例暴露给JavaScript。这样,H5页面中的JavaScript代码就可以调用原生端的方法,实现通信。
  • 需要注意的是,出于安全考虑,Android 4.2及以上版本对JavaScriptInterface的访问进行了限制,因此需要特别注意安全性的处理。
  1. URL Scheme
  • 通过在H5页面中加载特定的URL,触发原生端的响应。原生端可以监听这些特定的URL,当它们被加载时执行相应的操作。
  • 这种方法简单直接,但可能不够灵活,且对于复杂的通信需求可能不太适用。
  1. postMessage与message事件
  • 在WebView中,可以通过postMessage方法向原生端发送消息。原生端可以监听WebView的message事件来接收这些消息。
  • 这种方法适用于需要频繁通信的场景,可以实现双向的、实时的通信。
  1. 第三方库
  • 使用一些第三方库(如JsBridge)可以简化H5与原生端的通信过程。这些库通常提供了更高级别的封装和更易于使用的API。
  1. iOS平台的特殊方法
  • 对于iOS平台,可以使用WKWebView或UIWebView的JavaScriptCore框架来实现JavaScript与原生Objective-C或Swift代码之间的通信。
  • 通过JavaScriptCore框架,可以在原生端和H5页面之间建立一个双向的通信通道。

无论使用哪种方法,都需要确保通信的安全性和稳定性。在实现通信功能时,应遵循最佳实践,避免潜在的安全风险,并确保在不同设备和浏览器上的兼容性。

总结来说,H5与原生端的通信是实现混合应用的关键环节,通过选择合适的方法和工具,可以实现高效、安全的通信,从而提升用户体验和应用功能。****

参考: js-bridge 原理

前端代码加密的方式

前端代码加密的方式多种多样,但需要注意的是,前端代码最终需要在用户的浏览器中运行,因此无法完全阻止用户查看或反编译。不过,通过一些加密和混淆技术,可以增加代码的安全性和保护知识产权。以下是一些常见的前端代码加密和混淆的方式:

  1. 代码混淆(Obfuscation)

    • 变量名替换:将变量名替换为无意义的字符或缩短的字符串。
    • 控制流平坦化:改变代码的控制流结构,使其难以理解和跟踪。
    • 字符串加密:对字符串进行编码或加密,使其在源代码中不易识别。
    • 代码拆分和动态加载:将代码拆分成多个部分,并动态加载和执行,增加分析的难度。
  2. 代码压缩(Minification)

    • 移除空格、换行和注释。
    • 缩短变量名和函数名。
    • 使用算法来替换常见的代码模式。 压缩后的代码更难阅读和理解,但通常不会改变代码的逻辑或功能。
  3. 代码加密(Encryption)

    • 对整个脚本或关键部分进行加密,然后在运行时解密并执行。
    • 使用Web Crypto API或其他加密库来实现。 加密可以提供更高的安全性,但也可能增加性能开销,并且需要确保解密密钥的安全。
  4. 闭源框架或库

    • 使用闭源的JavaScript框架或库,这些框架或库已经过加密或混淆处理。
    • 这种方式只能保护闭源部分的代码,而使用这些框架或库编写的代码仍然可以被分析和修改。
  5. 使用WebAssembly(WASM)

    • 将关键代码逻辑编译为WebAssembly字节码,这是一种二进制指令格式,可以在现代Web浏览器中安全、快速地运行。
    • WASM代码比JavaScript更难反编译和理解,提供了更高的安全性。
  6. 内容安全策略(Content Security Policy, CSP)

    • 虽然这不是直接对代码进行加密,但CSP可以通过限制可以执行的脚本的来源来增加安全性。
    • 通过CSP,可以限制只允许从特定来源加载和执行脚本,减少跨站脚本攻击(XSS)的风险。
  7. 代码分割和懒加载(Code Splitting and Lazy Loading)

    • 将大型代码库分割成较小的块,并按需加载。
    • 这不仅可以提高性能,还可以使攻击者更难获取完整的代码库。

需要注意的是,虽然这些技术可以增加攻击者分析和修改前端代码的难度,但它们并不能完全防止攻击。攻击者仍然可能通过各种手段来绕过这些保护措施。因此,除了前端加密和混淆外,还应结合后端验证、安全通信协议(如HTTPS)等其他安全措施来提供全面的保护。

前端怎么实现token的无感刷新。要是触发了很多请求呢?

答案一:

在前端实现 token 的无感刷新,主要的目标是在用户的 token 过期之前,自动地获取新的 token,同时不影响用户的正常操作。下面是一种可能的实现方式:

  1. 设置请求拦截器:在发送请求之前,先检查 token 是否有效。如果无效,则先尝试刷新 token。
  2. 刷新 token:当发现 token 无效时,前端向服务器发送一个请求以获取新的 token。这个请求通常包含一个旧的 token 或者其他形式的凭证,以便服务器能够验证用户的身份并颁发新的 token。
  3. 缓存新的 token:一旦收到新的 token,前端应该将其缓存起来,以便后续的请求使用。
  4. 重试原始请求:在获取新的 token 之后,前端应该重试原始的请求。

对于触发了很多请求的情况,需要注意以下几点:

  • 并发控制:如果多个请求同时发现 token 无效,并且都尝试去刷新 token,那么可能会产生并发问题。为了避免这种情况,可以使用一个锁或者队列来确保只有一个请求在尝试刷新 token。
  • 请求优先级:如果某些请求比其他请求更重要(例如,用户正在等待的某个关键操作),那么可能需要设置请求的优先级,确保这些请求能够优先得到处理。
  • 错误处理:如果刷新 token 的请求失败(例如,因为网络问题或者用户已经登出),那么需要有一种机制来处理这种情况。这可能包括显示一个错误消息给用户,或者将用户重定向到登录页面。

此外,还需要注意以下几点:

  • token 过期时间:为了避免频繁地刷新 token,应该设置一个合理的 token 过期时间。这个时间应该根据应用的具体需求来设定。
  • 安全性:在处理 token 时,需要注意安全性问题。例如,不应该在前端代码中硬编码任何敏感信息(如密钥或密码),也不应该在不安全的地方存储 token。
  • 用户体验:无感刷新 token 的目标是提高用户体验,因此应该尽量减少对用户的干扰。例如,在刷新 token 时,不应该让用户等待太长时间,也不应该显示过多的提示或错误消息。

答案二:

在前端实现 token 的无感刷新,主要思路是:在每次请求发起之前,检查当前 token 是否有效,如果无效则先刷新 token,然后再使用新的 token 发起请求。这个过程需要做到对使用者透明,即用户不需要感知到 token 的刷新过程。

当触发了很多请求时,你需要确保 token 的刷新只进行一次,并且在所有请求都等待这个刷新完成后,再使用新的 token 重新发起请求。这可以通过使用 Promise 和 async/await 来实现。

以下是一个使用 axios 的示例代码片段:

javascript
import axios from 'axios';

// 假设你有一个获取新 token 的 API
const refreshTokenApi = '/api/refresh-token';

// 假设你有一个存储 token 的地方,这里简化为一个全局变量
let currentToken = localStorage.getItem('token');

// 用于存储是否正在刷新 token 的标志
let isRefreshing = false;
let refreshTokenPromise = null;

// 封装 axios 请求
function request(url, data) {
  // 如果正在刷新 token,则等待刷新完成
  if (isRefreshing) {
    return refreshTokenPromise.then(token => {
      currentToken = token;
      localStorage.setItem('token', token);
      return axios({ url, data, headers: { Authorization: `Bearer ${token}` } });
    });
  }

  // 如果 token 无效(例如过期),则刷新 token
  if (!currentToken || isTokenExpired(currentToken)) {
    isRefreshing = true;
    refreshTokenPromise = refreshToken().then(token => {
      currentToken = token;
      localStorage.setItem('token', token);
      isRefreshing = false;
      refreshTokenPromise = null;
      return axios({ url, data, headers: { Authorization: `Bearer ${token}` } });
    }).catch(error => {
      // 处理刷新 token 失败的情况,例如跳转到登录页等
      isRefreshing = false;
      refreshTokenPromise = null;
      throw error;
    });
    return refreshTokenPromise;
  }

  // token 有效,直接发起请求
  return axios({ url, data, headers: { Authorization: `Bearer ${currentToken}` } });
}

// 刷新 token 的函数
function refreshToken() {
  return axios.post(refreshTokenApi, { /* 需要的参数 */ })
    .then(response => response.data.token);
}

// 检查 token 是否过期的函数,这里需要你自己实现具体的逻辑
function isTokenExpired(token) {
  // ...
  return true; // 或者 false
}

在这个示例中,我们封装了一个 request 函数来处理所有的 axios 请求。在每次请求之前,我们都会检查当前的 token 是否有效。如果无效,我们会先调用 refreshToken 函数来刷新 token,并将刷新 token 的 Promise 存储在 refreshTokenPromise 中。然后,所有正在等待的请求都会使用这个 Promise,并在 token 刷新完成后重新发起请求。这样就实现了 token 的无感刷新,并且避免了在刷新 token 时发起多余的请求。

答案三:

在前端实现无感刷新 Token 的方法通常涉及拦截 HTTP 请求,检查 Token 的过期时间,如果 Token 即将过期或已过期,则自动请求新的 Token,并在拿到新 Token 后重新发送之前被中断的请求。 下面是使用 Axios 库实现这一功能的代码片段:

javascript

import axios from 'axios';

// 创建 Axios 实例
const instance = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000, // 请求超时时间
});

// 设置请求拦截器
instance.interceptors.request.use(
  config => {
    // 在发送请求之前检查是否存在 Token,如果存在,则在请求头中添加 Token
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

// 定义一个变量来保存是否正在刷新 Token
let isRefreshing = false;
// 保存被挂起的请求
let pendingRequests = [];

// 设置响应拦截器
instance.interceptors.response.use(
  response => {
    return response;
  },
  async error => {
    const originalRequest = error.config;

    // 如果响应状态码为 401,表示 Token 过期
    if (error.response.status === 401 && !originalRequest._retry) {
      if (isRefreshing) {
        // 如果正在刷新 Token,则将当前请求添加到挂起列表中
        return new Promise(resolve => {
          pendingRequests.push(() => {
            resolve(instance(originalRequest));
          });
        });
      }

      // 设置标志位表示正在刷新 Token
      isRefreshing = true;

      // 发起刷新 Token 的请求
      const refreshToken = localStorage.getItem('refreshToken');
      try {
        // 这里假设刷新 Token 的接口为 /auth/refresh,并且返回新的 Token
        const response = await axios.post('/auth/refresh', { refreshToken });

        // 更新本地存储的 Token
        localStorage.setItem('token', response.data.token);

        // 设置标志位表示刷新 Token 完成
        isRefreshing = false;

        // 重新发送被挂起的请求
        pendingRequests.forEach(callback => callback());
        pendingRequests = [];

        // 返回重新发送的请求
        return instance(originalRequest);
      } catch (refreshError) {
        // 刷新 Token 失败,跳转到登录页面
        console.error('Refresh token failed:', refreshError);
        // 清除本地存储的 Token
        localStorage.removeItem('token');
        localStorage.removeItem('refreshToken');
        // 跳转到登录页面
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }

    return Promise.reject(error);
  }
);

export default instance;

前端接口加密的方式有哪些

前端接口加密的主要目的是为了增加数据传输的安全性,防止敏感信息在传输过程中被窃取或篡改。以下是一些常见的前端接口加密方式:

  1. HTTPS

    • HTTPS 是 HTTP 的安全版本,它通过 SSL/TLS 协议在 HTTP 层提供加密。当浏览器通过 HTTPS 与服务器通信时,所有的数据(包括请求和响应)都会被加密。
    • 优点:提供了端到端的加密,且由浏览器和服务器自动处理,无需前端开发者额外编码。
    • 缺点:需要服务器支持 SSL/TLS,并配置相应的证书。
  2. 对称加密

    • 使用相同的密钥进行加密和解密。常见的对称加密算法有 AES、DES 等。
    • 优点:加密和解密速度快。
    • 缺点:密钥的分发和管理是一个问题,如果密钥被泄露,加密的数据就可能被解密。
  3. 非对称加密

    • 使用一对密钥(公钥和私钥)进行加密和解密。公钥用于加密数据,私钥用于解密数据。常见的非对称加密算法有 RSA、ECC 等。
    • 优点:安全性高,因为即使公钥被泄露,也无法用其解密数据。
    • 缺点:加密和解密速度相对较慢,且密钥管理复杂。
  4. 混合加密

    • 结合对称加密和非对称加密的方式。通常,先使用非对称加密安全地传输对称加密的密钥,然后使用对称加密对大量数据进行加密和解密。
    • 优点:结合了对称加密的速度优势和非对称加密的安全性优势。
  5. 数字签名

    • 用于验证数据的完整性和来源。发送方使用私钥对数据进行签名,接收方使用公钥验证签名。
    • 优点:确保数据在传输过程中未被篡改,且可以验证数据的来源。
  6. OAuth、OpenID Connect 等身份验证协议

    • 这些协议允许用户在不共享密码的情况下授权第三方应用访问其资源。它们通常使用令牌(token)作为访问资源的凭证,并可能涉及加密和签名操作。
  7. 前端混淆与代码加密

    • 这并非针对数据传输的加密,而是针对前端代码本身的安全措施。例如,可以使用工具或技术对前端代码进行混淆、压缩或加密,以增加攻击者理解和篡改代码的难度。

在选择加密方式时,需要根据具体的应用场景、安全性需求、性能要求以及实施成本进行综合考虑。同时,还应注意遵守相关的法律法规和标准,确保加密措施的合规性。