面试笔记

289 阅读21分钟

1、HTTPS的整个通信流程。

  • HTTPS的流程:
    • 建立连接:客户端向服务器发起HTTPS连接请求,连接的目标端口通常是443。服务器响应连接请求,开始SSL/TLS握手过程。

    • SSL/TLS握手:在握手过程中,客户端和服务器协商加密算法、生成会话密钥,并验证对方的身份。

      • 客户端Hello:客户端向服务器发送一个Hello消息,其中包含客户端支持的SSL/TLS版本加密算法压缩算法等信息。
      • 服务器Hello:服务器响应客户端的Hello消息,确认SSL/TLS版本,并发送自己的证书(包含公钥),同时也包含服务器支持的加密算法等信息。
      • 验证服务器证书:客户端验证服务器的证书是否合法是否由受信任的证书颁发机构签发,并检查证书中的域名是否与访问的域名匹配。
      • 生成会话密钥:客户端生成一个随机数,使用服务器的公钥加密后发送给服务器,服务器使用自己的私钥解密得到会话密钥
      • 密钥确认:客户端和服务器都已经拥有了同一个会话密钥,接下来的通信将使用这个密钥进行加密和解密
    • 加密通信:握手完成后,客户端和服务器开始使用会话密钥对通信数据进行加密和解密。通信的内容都是经过加密的,使得攻击者无法窃听篡改通信内容。

    • 数据传输客户端和服务器之间开始进行加密数据传输,包括请求响应HTTP报文

    • 终止连接:通信完成后,客户端和服务器可以选择关闭连接释放资源。通常情况下,HTTPS连接在一段时间内保持活动状态,以便在需要时进行更多的请求响应

  • HTTP的流程
    • 建立连接:客户端向服务器发起连接请求,连接的目标端口通常是80。服务器响应连接请求,建立TCP连接。

    • 发送请求:客户端向服务器发送HTTP请求,请求中包含请求方法(例如GET、POST)、URL、HTTP版本、请求头部(包括客户端的一些信息、所需的资源类型等)和可选的请求体(例如在POST请求中包含的数据)。

    • 处理请求:服务器收到请求后,根据请求的内容进行相应的处理。处理的内容可能包括查询数据库、执行程序、读取文件等操作。

    • 发送响应:服务器向客户端发送HTTP响应,响应中包含HTTP状态码、响应头部(包括服务器信息、内容类型、内容长度等)和响应体(实际的数据内容)。

    • 接收响应:客户端接收到服务器的响应后,根据响应的内容进行相应的处理,例如显示网页、下载文件等。

    • 终止连接:通信完成后,客户端和服务器可以选择关闭连接,释放资源。在HTTP/1.1中,连接默认是持久的,可以在一段时间内保持活动状态,以便在需要时进行更多的请求和响应。在HTTP/1.0中,默认情况下是非持久连接,每个请求/响应交换都会关闭连接。

2、http和https的区别

  • 安全性

    • HTTP是明文传输,数据以纯文本形式在网络上传输,容易被中间人窃听和篡改。
    • HTTPS通过使用SSL/TLS协议对数据进行加密,确保数据在传输过程中是加密的,从而提供更高的安全性,防止数据被窃听和篡改。
  • 传输方式

    • HTTP使用TCP作为传输协议,数据传输速度较快。
    • HTTPS在HTTP的基础上添加了SSL/TLS加密层,因此除了TCP协议外还需要进行SSL/TLS握手,数据传输速度相对慢一些。
  • 端口

    • HTTP默认使用端口80进行通信。
    • HTTPS默认使用端口443进行通信。
  • 证书

    • HTTPS通信需要使用SSL证书来验证服务器的身份。客户端会验证服务器的证书是否由受信任的证书颁发机构签发,并检查证书中的域名是否与访问的域名匹配。
    • HTTP通信不需要证书,因为数据传输是明文的,无需加密或认证。
  • SEO和搜索排名

    • 由于HTTPS提供了更高的安全性,搜索引擎(如Google)更倾向于将采用HTTPS协议的网站排名靠前。
    • 对SEO来说,使用HTTPS可以提高网站的可信度和可见性。

3、常见的性能优化问题

  • 减少HTTP请求

    • 合并和压缩CSS、JavaScript文件,减少文件数量和大小。
    • 使用CSS Sprites将多个图像合并成一个,减少HTTP请求次数。
    • 使用字体图标或SVG替代图片,减少图片请求次数。
    • 使用HTTP/2多路复用功能,减少请求延迟。
  • 优化资源加载

    • 将JavaScript脚本置于页面底部,以防止阻塞页面渲染。
    • 使用异步加载(async)或延迟加载(defer)JavaScript脚本,以提高页面加载速度。
    • 使用CDN加速静态资源的加载,提高资源获取速度。
    • 设置合适的缓存策略,利用浏览器缓存来减少资源请求次数。
  • 优化CSS和JavaScript

    • 避免使用复杂的CSS选择器,尽量减少页面重排和重绘。
    • 使用CSS缩写和压缩,减小CSS文件大小。
    • 避免在JavaScript中使用同步的AJAX请求,尽量使用异步请求。
    • 尽量减少全局变量的使用,优化JavaScript的内存占用和执行效率。
  • 图片优化

    • 使用适当的图片格式,如JPEG、PNG、WebP等。
    • 压缩图片大小,减少加载时间。可以使用工具如ImageOptim、TinyPNG等进行图片压缩。
    • 使用响应式图片,根据不同设备加载适当大小的图片,减少带宽占用。
  • 优化渲染性能

    • 使用CSS3动画代替JavaScript动画,以利用硬件加速。
    • 避免在页面加载时大量使用DOM操作,尽量减少重排和重绘。
    • 使用虚拟列表(Virtual List)或无限滚动(Infinite Scroll)技术,优化大量数据的渲染性能。
  • 移动端优化

    • 使用响应式设计或移动优先设计,适配不同尺寸的设备。
    • 使用CSS3媒体查询和Viewport设置,优化移动端页面布局和显示效果。
    • 使用移动端专用的手势操作和触摸事件,提升用户体验。
  • 性能监控和优化

    • 使用性能分析工具(如Chrome DevTools、Lighthouse等)进行页面性能分析和优化。
    • 监控页面加载速度、资源加载情况、错误信息等,及时发现并解决性能问题。

4、减少首屏加载白屏时间

  • 优化关键资源加载
    • 减少首屏所需加载的关键资源,包括HTML、CSS和JavaScript文件等。
    • 使用HTTP/2协议多路复用特性,同时请求多个资源,减少请求延迟。
    • 压缩和合并CSS、JavaScript文件,减少文件大小和请求次数。
  • 延迟加载非关键资源
    • 将非关键资源(如图片、视频、广告等)的加载时机延迟到页面主要内容加载完毕后再进行加载,以减少对首屏加载的影响。
    • 使用懒加载技术(Lazy Loading),只有当用户滚动到可见区域时才加载图片资源。
  • 使用服务端渲染(SSR)或预渲染
    • 对于SPA(Single Page Application),使用服务端渲染(SSR)或预渲染技术,在服务器端生成首屏内容,减少客户端加载和渲染时间。
  • 优化图片加载
    • 使用合适的图片格式和大小,尽量减少图片资源的加载时间。
    • 使用图片压缩工具进行优化,减小图片文件大小。
  • 启用浏览器缓存
    • 设置合适的缓存策略,利用浏览器缓存来加速资源加载。
    • 使用资源文件的缓存指纹(Cache Busting),以确保浏览器能够获取最新版本的资源。

5、web应用在iOS和安卓的区别,有哪些兼容性?

  • 浏览器差异
    • iOS平台上的主要浏览器是Safari,而安卓平台上则有多种浏览器,如Chrome、Firefox、Opera等。
    • 不同浏览器对于HTML、CSS和JavaScript的支持程度可能会有所不同,需要进行兼容性测试。
  • 屏幕尺寸和分辨率
    • iOS设备的屏幕尺寸和分辨率相对较小且统一,而安卓设备则存在多种不同的屏幕尺寸和分辨率。
    • 需要确保Web应用能够适应不同尺寸和分辨率的设备,采用响应式设计或者适配不同设备的布局
  • 触摸事件和手势支持:
    • iOS设备使用Safari浏览器时,通常对触摸事件和手势有更好的支持,如触摸滑动、双指缩放等。
    • 在安卓设备上不同浏览器可能会存在触摸事件和手势支持方面的差异,需要进行相应的测试和调整。

6、常见的http请求头有哪些

  • Accept: 指定客户端可以接受的响应内容类型,如 text/html、application/json 等。

  • Content-Type: 指定发送给服务器的实体主体的数据类型,如 application/json、application/x-www-form-urlencoded、multipart/form-data 等。

  • User-Agent: 包含了发起请求的用户代理软件的信息,通常是浏览器的名称和版本号。

  • Referer: 表示当前请求的来源页面的 URL,用于指示服务器当前请求是从哪个页面跳转而来的。

  • Host: 指定请求的目标服务器的主机名和端口号。

  • Authorization: 包含了客户端向服务器发送的验证信息,通常用于进行身份验证,如基本认证、Bearer 令牌等。

  • Cookie: 包含了客户端的 Cookie 信息,用于在客户端和服务器之间传递会话状态信息。

  • Cache-Control: 指定请求或响应的缓存行为,用于控制缓存策略。

  • Content-Length: 指定发送给服务器的实体主体的长度,以字节为单位。

  • Accept-Language: 指定客户端接受的语言类型,用于服务器返回内容的国际化。

  • Accept-Encoding: 指定客户端接受的内容编码类型,如 gzip、deflate 等,用于压缩传输的内容。

  • Connection: 指定与服务器建立的持久连接是否应该保持打开状态,或者在请求完成后是否应该关闭连接。

7、vue和react组件传值

  • Vue 组件传值(Props)父组件向子组件传值
    // 父组件
    <template>
      <child-component :prop-name="data"></child-component>
    </template>
    
    <script>
    import ChildComponent from './ChildComponent.vue';
    
    export default {
      components: {
        ChildComponent
      },
      data() {
        return {
          data: 'Value to pass'
        };
      }
    }
    </script>
    
    // 子组件
    <template>
      <div>{{ propName }}</div>
    </template>
    
    <script>
    export default {
      props: ['propName']
    }
    </script> 
    
  • React 组件传值(Props)父组件向子组件传值
    // 父组件
    import React from 'react';
    import ChildComponent from './ChildComponent'; 
    
    class ParentComponent extends React.Component {
      render() {
        return <ChildComponent propName="Value to pass" />;
      }
    } 
    
    export default ParentComponent;
    
    
    // 子组件
    import React from 'react';
    
    class ChildComponent extends React.Component {
      render() {
        return <div>{this.props.propName}</div>;
      }
    }
    
    export default ChildComponent;
    
    
  • React 函数式组件
    import React from 'react';
    
    // 子组件
    function ChildComponent(props) {
      return <div>{props.propName}</div>;
    }
    
    // 父组件
    function ParentComponent() {
      const dataToPass = 'Value to pass';
    
      return <ChildComponent propName={dataToPass} />;
    }
    
    export default ParentComponent;
    
    

8、常见的 HTTP 方法

  • GET:
    • 用于请求指定资源。
    • 只从服务器获取数据,不对服务器资源进行任何修改。
    • 可以将参数添加到 URL 的查询字符串中,发送给服务器。
  • POST:
    • 用于向服务器提交数据,用于创建新资源。
    • 可以发送大量数据,且不受 URL 长度限制。
    • 可以发送表单数据、JSON 数据等。
  • PUT:
    • 用于向服务器发送数据,用于更新指定资源。
    • 对于指定资源的全部内容进行替换。
  • PATCH:
    • 用于向服务器发送数据,用于更新部分资源。
    • 通常用于更新资源的一部分,而不是整个资源。
  • DELETE:
    • 用于请求删除指定资源。
    • 从服务器删除指定资源。
  • OPTIONS:
    • 用于请求服务器支持的 HTTP 方法。
    • 在 CORS(跨域资源共享)中,客户端发送 OPTIONS 请求以确定是否可以安全地发送其他请求。
  • HEAD:
    • 与 GET 类似,但服务器只返回响应头部,不返回实际内容。
    • 主要用于获取资源的元数据,例如文件大小、类型等。
  • TRACE:
    • 用于在目的服务器的请求-响应链上执行请求消息的回显测试。
    • 主要用于诊断。
  • CONNECT:
    • 用于建立客户端到代理服务器的隧道。
    • 主要用于代理服务器

9、页面置灰

  • 样式控制, CSS 的滤镜(filter)属性来实现。具体来说,可以使用 grayscale 配合 brightness 来调整页面的亮度和灰度。下面是一个示例
    .grayed-out {
      filter: grayscale(100%) brightness(50%);
    } 
    
  • react 全局置灰
    export const SilenceInterface = (show: boolean) => {
    if (!show || document == null) {
      return "";
    }
    const d = document.createElement("style");
    d.setAttribute("type", "text/css");
    d.innerHTML = `html{
          filter: grayscale(100%);
          -webkit-filter: grayscale(100%);
          -moz-filter: grayscale(100%);
          -ms-filter: grayscale(100%);
          -o-filter: grayscale(100%);
        }`;
    document?.querySelector?.("head")?.appendChild(d);
    };
    
    

10、什么是闭包

  • 闭包(Closure)是指在一个函数内部定义的函数可以访问该函数作用域中的变量,即使在其定义之后该函数被调用时,依然能够访问到这些变量。换句话说,闭包可以捕获并保存了所在函数的局部变量,使得这些局部变量在函数外部仍然可以被访问和操作。

  • 它可以用于实现函数工厂、私有变量、模块化等多种设计模式。同时,使用闭包也需要注意内存泄漏的问题,因为闭包会持有对外部函数作用域的引用,导致外部函数的变量无法被垃圾回收机制释放。因此,在使用闭包时,需要合理管理内存,避免造成不必要的内存泄漏。

    // 函数内部定义了另一个函数。
    // 内部函数引用了外部函数的局部变量。
    function outerFunction() {
    var outerVariable = 'I am from outer function';
    
    function innerFunction() {
        // 内部函数引用了外部函数的局部变量
        console.log(outerVariable);
    }
    // 返回内部函数
    return innerFunction;
    }
    
    // 调用外部函数,返回内部函数
    var innerFunc = outerFunction();
    // 调用内部函数
    innerFunc();
    
    

11、什么是递归

  • 递归函数通常包含两个部分:
    • 基本情况(Base Case): 定义了递归的终止条件,当满足终止条件时,递归函数将不再调用自身,直接返回结果。
    • 递归步骤(Recursive Step): 在函数内部调用自身,用于处理子问题,并逐步向基本情况靠近。

12、如何判断数据类型

  • typeof 操作符
    • typeof 操作符返回一个表示数据类型的字符串。
    • 例如,typeof 42 返回 'number',typeof 'hello' 返回 'string',typeof true 返回 'boolean',typeof [] 返回 'object'。
  • Object.prototype.toString 方法
    • 使用 Object.prototype.toString 方法可以更精确地判断复杂数据类型,例如数组、对象等。
    • 通过调用 Object.prototype.toString 方法并传入要检测的值作为参数,可以得到一个表示数据类型的字符串。
  • Array.isArray 方法
    • 使用 Array.isArray 方法来判断一个值是否是数组。
    • 如果是数组,则返回 true,否则返回 false。

13、什么是原型、原型链

  • 原型(Prototype)

    • 原型是每个对象都拥有的一个属性,它指向另一个对象,这个对象就是该对象的原型对象。原型对象本身也可以拥有原型,因此形成了一个原型链。
  • 原型链(Prototype Chain)

    • 原型链是由一系列对象组成的链表结构,它连接了每个对象与它的原型对象,直到到达原型链的末端。当试图访问一个对象的属性时,JavaScript 引擎会首先在该对象本身中查找是否存在这个属性,如果不存在,则沿着原型链向上查找,直到找到该属性或者到达原型链的末尾。

14、什么是多态

  • 在 JavaScript 中,多态(Polymorphism)是一种面向对象编程的特性,它允许不同的对象对同一个方法做出不同的响应,具体的行为取决于对象的类型。JavaScript 的多态性主要是通过原型链实现的,由于 JavaScript 是一种动态类型语言,因此多态性更加灵活。

  • 在 JavaScript 中,多态性通常通过对象的方法来实现。不同类型的对象可以拥有相同名称的方法,但实际执行的代码可能会有所不同,这就是多态的体现。JavaScript 中的多态性可以在运行时动态确定,而不需要事先指定对象的类型。

      // 定义一个动物类
      function Animal() {}
    
      // 为动物类添加一个 makeSound 方法
      Animal.prototype.makeSound = function() {
      console.log('The animal makes a sound');
      };
    
      // 定义狗类,继承自动物类
      function Dog() {}
      Dog.prototype = Object.create(Animal.prototype);
    
      // 重写 makeSound 方法
      Dog.prototype.makeSound = function() {
      console.log('The dog barks');
      };
    
      // 定义猫类,继承自动物类
      function Cat() {}
      Cat.prototype = Object.create(Animal.prototype);
    
      // 重写 makeSound 方法
      Cat.prototype.makeSound = function() {
      console.log('The cat meows');
      };
    
      // 创建动物实例
      var animal = new Animal();
      // 创建狗实例
      var dog = new Dog();
      // 创建猫实例
      var cat = new Cat();
    
      // 调用各个对象的 makeSound 方法,实现多态性
      animal.makeSound(); // 输出 'The animal makes a sound'
      dog.makeSound(); // 输出 'The dog barks'
      cat.makeSound(); // 输出 'The cat meows'
    
    

15、什么是继承

  • 继承是面向对象编程中的一个重要概念,它允许一个对象(子类)可以使用另一个对象(父类)的属性和方法。在继承关系中,子类会自动拥有父类的属性和方法,同时可以定义自己特有的属性和方法。

  • 在 JavaScript 中,继承可以通过原型链来实现。基本的继承关系可以分为以下几种:

    • 原型链继承(Prototype Chain Inheritance)

      • 原型链继承是最基本的继承方式,它利用原型链的特性实现继承关系。
      • 子类的原型对象指向父类的实例,从而使得子类可以访问到父类的属性和方法。
    • 构造函数继承(Constructor Inheritance)

      • 构造函数继承通过在子类中调用父类的构造函数来实现继承。
      • 子类中可以使用 call 或者 apply 方法调用父类的构造函数,并传入子类的实例作为上下文。

16、如果后端返回一个长整型的数据类型,怎么处理

  • 对于 JavaScript 中的数据类型,通常情况下,JavaScript 使用 Number 类型来表示数字,但是 JavaScript 中的 Number 类型是双精度浮点型(64 位),所以它有一定的范围限制,超出范围的整数会出现精度丢失的问题。在处理后端返回的长整型数据时,可能会遇到这样的问题。

  • 一种处理方法是使用 JavaScript 的字符串类型来存储长整型数据,因为字符串类型可以表示任意长度的数字。当接收到后端返回的长整型数据时,可以将其作为字符串处理,然后根据具体需求进行后续操作,例如进行数值计算、格式化等。

    // 假设后端返回的长整型数据为字符串类型
    var longIntString = "12345678901234567890";
    
    // 使用字符串的方法进行操作,例如转换成数值类型或者进行数值计算
    var longIntNumber = parseInt(longIntString); // 将字符串转换成整数
    
    console.log(longIntNumber); // 输出 12345678901234567890
    
    // 如果需要进行加法计算,可以直接使用字符串进行拼接
    var result = longIntString + "123"; // 在字符串后面追加另一个数字字符串
    
    console.log(result); // 输出 "12345678901234567890123"
    
    

17、es6的新特性

  • let 和 const 声明
    • let 和 const 关键字用于声明变量,取代了 var 关键字。
    • let 声明的变量具有块级作用域,而 const 声明的变量是常量,值不能被修改。
  • 箭头函数(Arrow Functions)
    • 箭头函数提供了更简洁的语法来声明函数。
    • 箭头函数的语法形式为:(parameters) => { statements }。
  • 模板字符串(Template Literals)
    • 模板字符串允许在字符串中插入变量和表达式,使用反引号(``)包裹字符串。
    • 可以通过 ${} 插入变量和表达式。
  • 解构赋值(Destructuring Assignment)
    • 解构赋值允许从数组和对象中提取值,然后将其赋给变量。
    • 使用花括号 {} 提取对象的属性,使用方括号 [] 提取数组的元素。
  • 默认参数(Default Parameters)
    • 默认参数允许在函数声明中为参数设置默认值。
    • 当调用函数时没有提供参数或者参数为 undefined 时,将使用默认值。
  • 扩展运算符(Spread Operator)
    • 扩展运算符用于展开数组、对象等可迭代对象。
    • 可以在函数调用和数组字面量中使用扩展运算符。
  • 类和继承(Classes and Inheritance)
    • ES6 提供了类(Class)语法,使得 JavaScript 的面向对象编程更加直观和易用。
    • 使用 class 关键字定义类,支持构造函数、继承、静态方法等特性。
  • Promise 对象
    • Promise 对象用于异步编程,表示一个异步操作的最终完成或失败。
    • 可以通过 new Promise() 构造函数创建 Promise 实例,使用 then() 和 catch() 方法处理异步操作的结果。
  • 模块化(Modules)
    • ES6 引入了模块化的语法,使用 import 和 export 关键字实现模块之间的依赖管理。
    • 每个模块都有自己的作用域,可以将变量、函数和类封装在模块中。
  • 迭代器和生成器(Iterators and Generators)
    • 迭代器用于遍历数据集合,可以通过 Symbol.iterator 方法创建可迭代对象。
    • 生成器是一种特殊的函数,可以通过 function* 关键字定义,可以暂停和恢复函数的执行。

18、vue和react的 虚拟dom有什么区别?

  • 更新策略:
    • Vue 使用了一种称为“响应式数据绑定”的机制,当数据发生变化时,Vue 会通过依赖追踪来自动更新相关的组件。Vue 会对整个组件树进行 diff 操作,找出需要更新的部分,并更新到真实的 DOM 上。
    • React 采用了一种称为“单向数据流”的模型,当数据发生变化时,React 会重新渲染整个虚拟 DOM 树,并通过 diff 算法找出需要更新的部分,然后更新到真实的 DOM 上。
  • 实现方式:
    • Vue 的虚拟 DOM 实现比较轻量,它是 Vue 框架的一部分,与 Vue 组件紧密集成。
    • React 的虚拟 DOM 实现相对独立,它是 React 库的核心之一,可以与其他库或框架配合使用。
  • 语法差异:
    • Vue 的模板语法中可以直接写 HTML 标签和 Vue 指令,较为直观和简洁。
    • React 使用 JSX 语法来描述虚拟 DOM,它是一种 JavaScript 语法扩展,可以在 JavaScript 代码中直接编写 HTML 结构,需要经过编译才能生成真实的 JavaScript 代码。
  • 组件化方式:
    • Vue 的组件化开发更加自然,组件可以包含模板、样式和逻辑,较为简单易用。
    • React 也支持组件化开发,但它更倾向于函数式编程风格,组件以纯函数的形式进行定义,较为灵活和可复用。

19、协商缓存和强缓存

  • 强缓存(Strong Cache):
    • 强缓存是浏览器在发送网络请求之前,直接从本地缓存读取资源的一种缓存策略
    • 当浏览器发送请求时,会先检查该资源的缓存控制头(例如 Cache-Control 和 Expires),如果符合强缓存的条件,即资源未过期,浏览器将直接从本地缓存中读取资源,而不发送请求到服务器。
    • 强缓存的优点是速度快,因为不需要经过网络请求服务器响应的过程,直接从本地缓存获取资源,节省了网络带宽和服务器资源。
    • 常见的缓存控制头有 Cache-ControlExpires。Cache-Control 是 HTTP/1.1 引入的缓存控制头,Expires 是 HTTP/1.0 引入的缓存控制头。
  • 协商缓存(Conditional Cache):
    • 协商缓存是浏览器在强缓存失效时,通过与服务器进行通信,协商是否使用缓存的一种缓存策略
    • 资源过期或者缓存控制头中指定了不使用强缓存时,浏览器会发送一个请求到服务器,服务器会根据请求头中的条件判断(如 If-Modified-SinceIf-None-Match),来决定是否使用缓存。
    • 如果资源在服务器上没有发生变化,服务器将返回一个 304 Not Modified 状态码,告诉浏览器可以使用缓存;如果资源发生了变化,服务器将返回最新的资源

20、var、let和const有什么区别?

  • var:
    • var 是在ES5中引入的声明变量的关键字。
    • 它的作用域是函数作用域或全局作用域,而不是块级作用域。
    • 如果在同一函数内部多次声明同名的var变量,后面的声明会覆盖前面的声明。
    • 在变量声明之前使用变量会导致变量的值为undefined。
    • var 声明的变量可以被重新赋值。
  • let:
    • let 是在ES6中引入的声明变量的关键字。
    • 它的作用域是块级作用域,例如if语句块、循环块等。
    • 在同一作用域内不能重复声明同名的let变量。
    • 在变量声明之前使用变量会导致暂时性死区(Temporal Dead Zone),即在声明之前访问变量会抛出ReferenceError。
    • let 声明的变量可以被重新赋值。
  • const:
    • const 也是在ES6中引入的声明变量的关键字,但它声明的是一个常量,即其值在声明后不能再被修改。
    • const 也具有块级作用域。
    • 声明时必须进行初始化赋值,且之后不能再重新赋值。
    • 对于复合类型(例如数组、对象),虽然不能修改变量指向的内存地址,但可以修改其内部的属性或元素。

21、var 变量提升的危害,let和const会有变量提升?

var的变量提升:

  • var 声明的变量会被提升(hoisting)到其作用域的顶部。这意味着变量声明会在代码执行前被处理,但赋值不会提升。

    • 未定义行为:
      • 变量可以在声明之前访问,并且其值为 undefined。这可能导致意外的行为和错误。
    console.log(foo); // undefined
    var foo = 42; 
    
    • 变量覆盖:
      • 由于提升,变量声明会覆盖前面相同名称的变量声明,即使它们在不同的作用域中。
    var foo = 1;
    function bar() {
      if (!foo) {
        var foo = 10;
      }
      console.log(foo); // 10
    }
    bar(); 
    
    • 全局污染:
      • var 声明的变量会在函数外部作用域或全局作用域中创建,容易造成命名冲突和全局变量污染。
    var foo = 'global';
    function test() {
      var foo = 'local';
      console.log(foo); // local
    }
    test();
    console.log(foo); // global 
    

let、const的变量提升:

  • letconst 也存在变量提升,但它们的提升与 var 不同。它们的变量提升发生在块级作用域中,但在提升期间不会初始化。这意味着在变量声明之前访问它们会导致引用错误(ReferenceError),这一特性被称为“暂时性死区”(Temporal Dead Zone,TDZ)。

  • letconst 的优势:

    • 块级作用域:
      • let 和 const 声明的变量具有块级作用域,仅在其所在的块内有效,避免了变量提升带来的问题。
    if (true) {
      let foo = 42;
      console.log(foo); // 42
    }
    console.log(foo); // ReferenceError: foo is not defined
    
    • 避免重复声明:
      • let 和 const 在同一作用域内不能重复声明相同名称的变量,减少了变量覆盖的风险。
    let foo = 42;
    let foo = 24; // SyntaxError: Identifier 'foo' has already been declared 
    
    • 暂时性死区:
      • TDZ 使得在变量声明之前访问变量会抛出错误,避免了未定义行为。
    console.log(foo); // ReferenceError: Cannot access 'foo' before initialization
    let foo = 42; 
    
    • 常量声明:
      • const 用于声明常量,其值在初始化后不能修改,提高了代码的可读性和安全性。
    const bar = 42;
    bar = 24; // TypeError: Assignment to constant variable.
    

22、常见的 HTTP 状态码

  • 1xx 信息性状态码:表示请求已接收,继续处理。

    • 100 Continue:服务器已接收到请求的初始部分,客户端应该继续发送剩余的请求。
    • 101 Switching Protocols:服务器正在根据客户端的请求切换协议。
  • 2xx 成功状态码:表示请求已成功被服务器接收、理解、并接受。

    • 200 OK:请求成功。
    • 201 Created:请求已经被实现,而且有一个新的资源已经依据请求的需要而建立。
  • 3xx 重定向状态码:表示需要客户端采取进一步的操作才能完成请求。

    • 301 Moved Permanently:请求的资源已被永久移动到新 URI。
    • 302 Found:请求的资源临时从不同的 URI 响应请求。
  • 4xx 客户端错误状态码:表示客户端发送的请求有误。

    • 400 Bad Request:服务器无法理解请求的语法。
    • 403 Forbidden:服务器拒绝请求,没有权限访问。
    • 404 Not Found:服务器找不到请求的资源。
  • 5xx 服务器错误状态码:表示服务器在尝试处理请求时发生了错误。

    • 500 Internal Server Error:服务器内部错误。
    • 503 Service Unavailable:服务器当前无法处理请求,通常是由于维护或过载。

23、js自带的一些函数

  • String 类型的方法:

    • charAt():返回指定位置的字符。

    • concat():连接两个或多个字符串,并返回一个新的字符串。

    • indexOf():返回字符串中第一个指定字符的位置。

    • slice():提取字符串的一部分,并返回一个新的字符串。

  • Array 类型的方法:

    • push():向数组的末尾添加一个或多个元素,并返回新的长度。

    • pop():删除并返回数组的最后一个元素。

    • forEach():对数组的每个元素执行指定的函数。

    • map():对数组的每个元素执行指定的函数,并返回一个新数组。

  • Math 类型的方法:

    • Math.random():返回一个介于 0(包括)到 1(不包括)之间的随机数。

    • Math.floor():返回小于或等于一个给定数字的最大整数。

    • Math.max():返回一组数字中的最大值。

    • Math.min():返回一组数字中的最小值。

  • Date 类型的方法:

    • getDate():返回一个月中的某一天。

    • getMonth():返回日期的月份(0-11)。

    • getFullYear():返回一个表示年份的 4 位数字。

  • 全局函数:

    • setTimeout():在指定的毫秒数后执行一个函数。
    • setInterval():在指定的毫秒数间隔执行一个函数。

24、数组去重

  • 使用 Set 对象:Set 对象是一种集合数据类型,其中的值是唯一的,可以用来快速去重数组。
    // 单数组
    let array = [1, 2, 2, 3, 4, 4, 5];
    let uniqueArray = [...new Set(array)];
    console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
    
    // 双数组
    let array1 = [1, 2, 2, 3, 4, 4, 5];
    let array2 = [4, 5, 6, 6, 7, 8];
    
    // 将两个数组合并,并利用 Set 去重
    let mergedArray = [...new Set([...array1, ...array2])];
    console.log(mergedArray); // 输出: [1, 2, 3, 4, 5, 6, 7, 8]
    
  • 使用 Array.filter() 和 indexOf():利用 Array.filter() 方法和 indexOf() 方法来过滤出数组中的唯一值。
    let array = [1, 2, 2, 3, 4, 4, 5];
    let uniqueArray = array.filter((value, index, self) => {
        return self.indexOf(value) === index;
    });
    console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
    
  • 使用 Array.reduce():利用 Array.reduce() 方法和一个临时对象来实现去重。
    let array = [1, 2, 2, 3, 4, 4, 5];
    let uniqueArray = array.reduce((accumulator, currentValue) => {
        if (!accumulator.includes(currentValue)) {
            accumulator.push(currentValue);
        }
        return accumulator;
    }, []);
    console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
    

25、this的指向

  • 它通常指向当前执行上下文中的对象。this 的指向取决于函数被调用的方式,以下是几种常见情况:
  • 全局作用域:
    • 在全局作用域中,this 指向全局对象,在浏览器中通常是 window 对象,在 Node.js 中是 global 对象。
  • 函数中:
    • 在函数中,this 的指向取决于函数的调用方式。
    • 作为函数调用:当函数作为普通函数调用时,this 指向全局对象(非严格模式下)或 undefined(严格模式下)。
    • 作为方法调用:当函数作为对象的方法调用时,this 指向调用该方法的对象。
    • 构造函数中:在构造函数中,this 指向新创建的实例对象。
    • 箭头函数中:箭头函数没有自己的 this 绑定,它会捕获外层作用域的 this 值。

26、箭头函数的常见问题

  • 简要介绍一下箭头函数的语法
    • 箭头函数是一种简化函数声明的语法,它使用箭头 (=>) 来定义函数,省略了 function 关键字和花括号,如果只有一条语句,还可以省略 return 关键字。
  • 箭头函数与普通函数的区别:
    • 箭头函数没有自己的 this 绑定,它会捕获外层作用域的 this 值。
    • 箭头函数不能用作构造函数,不能通过 new 关键字调用。
    • 箭头函数没有 arguments 对象,可以使用剩余参数语法 ...args 来代替。
  • 箭头函数的适用场景:
    • 箭头函数适合于简单的函数表达式,特别是当函数不需要自己的 this 值,或者需要访问外层作用域的 this 值时。它们通常用于回调函数、数组方法、简单的计算或者简洁的语法书写。
  • 箭头函数的注意事项
    • 箭头函数没有自己的 this,它会捕获外层作用域的 this,因此要谨慎使用,避免出现意外行为。
    • 箭头函数不能用作构造函数,不能通过 new 关键字调用,否则会抛出错误。
    • 箭头函数没有 arguments 对象,如果需要访问传入的参数,可以使用剩余参数语法 ...args。

27、浏览器本地存储

  • 浏览器的本地存储是指在浏览器中保存数据的机制,允许网页在用户的计算机上存储数据以供以后使用。主要有两种本地存储技术:Web StorageIndexedDB
  • Web Storage有俩个:
    • LocalStorage:用于存储字符串键值对持久化数据。数据保存在浏览器关闭后仍然存在,并且可以跨会话访问。它拥有较大的存储空间,通常可以存储几兆字节的数据。可以通过 localStorage 对象进行访问。
    • SessionStorage:类似于 localStorage,但是存储的数据仅在浏览器会话期间存在。当用户关闭浏览器窗口或标签页后,存储的数据就会被清除。可以通过 sessionStorage 对象进行访问。
  • IndexedDB:
    • IndexedDB 是一种更为强大的浏览器本地数据库,允许存储结构化数据,并支持事务操作和索引。它适用于需要大量数据存储、复杂查询和高级数据管理的场景。IndexedDB 是异步的,使用 JavaScript API 进行操作。

LocalStorage、SessionStorage 和 cookie 谁会被同源策略影响

  • LocalStorageSessionStorage严格遵循同源策略。同源策略要求协议主机端口号相同。如果有任何一个不同,数据将无法共享

  • Cookie 同样遵循同源策略,但稍微宽松一些。Cookie 主要是通过 domainpath 属性来控制其访问范围,而不是协议、主机和端口的严格匹配

    • 可以通过 domain 属性设置允许访问 Cookie 的域,通常用于跨子域共享 Cookie
    • 可以通过 path 属性设置允许访问 Cookie 的路径,控制访问的 URL 范围

28、浏览器渲染过程

  • 浏览器的渲染过程涉及多个步骤,包括从服务器获取资源、解析 HTML、构建 DOM 树、构建 CSSOM 树、合并成 Render 树、布局和绘制等。以下是浏览器渲染过程的基本步骤:
    • 1、获取资源:
      • 浏览器首先通过 URL 请求 HTML 文件。HTML 文件中可能包含对其他资源的引用,如样式表(CSS)、JavaScript 文件、图像等。浏览器会逐一获取这些资源。
    • 2、解析 HTML:
      • 浏览器开始解析 HTML 文件,并构建 DOM(文档对象模型)树。DOM 树是由 HTML 元素及其关系组成的树形结构,表示文档的层次结构。
    • 3、获取和解析 CSS:
      • 浏览器在解析 HTML 的同时,会逐步获取外部样式表和内联样式,并构建 CSSOM(CSS 对象模型)树。CSSOM 树表示样式表的层次结构和样式规则的关系。
    • 4、合并成 Render 树:
      • 浏览器将 DOM 树和 CSSOM 树合并成 Render 树(渲染树),渲染树中包含了需要显示在页面上的所有节点和其样式信息。但是一些不可见的元素(如 <head>、<script>、display: none 等)不会被添加到渲染树中。
    • 5、布局(回流):
      • 浏览器根据 Render 树中的节点计算它们在页面中的位置和大小,这个过程称为布局或回流。布局过程会根据 DOM 元素的盒模型、浮动、定位等属性计算元素的位置和大小。
    • 6、绘制(重绘):
      • 浏览器根据 Render 树和布局信息将页面内容绘制到屏幕上。这个过程称为绘制或重绘。浏览器会根据每个元素的样式信息和位置信息,使用图形库将页面内容绘制到屏幕上的相应位置。

29、用户输入URL到跳转发生了什么

  • 当用户输入 URL 并按下回车键时,浏览器会执行一系列步骤来解析并加载该 URL 所对应的网页。以下是用户输入 URL 到跳转发生的主要过程:
    • 1、URL 解析:
      • 浏览器首先解析用户输入的 URL。如果用户输入的是一个完整的 URL(包括协议、主机、路径等),则直接进行解析;如果用户输入的是一个相对 URL,浏览器会将其解析为当前页面的基础 URL。
    • 2、DNS 解析:
      • 浏览器通过 DNS(Domain Name System)解析器将主机名解析为 IP 地址。如果浏览器已经缓存了该主机的 IP 地址,则直接使用缓存的结果;否则,浏览器将向本地 DNS 解析器发送 DNS 请求,解析器将根据域名的 DNS 记录返回对应的 IP 地址。
    • 3、建立 TCP 连接:
      • 浏览器通过 TCP(Transmission Control Protocol)建立与服务器的连接。浏览器首先会进行三次握手,即发送 SYN,服务器返回 SYN-ACK,然后浏览器发送 ACK,完成连接建立。
    • 4、发起 HTTP 请求:
      • 浏览器通过 TCP 连接向服务器发起 HTTP 请求。请求包括请求方法(如 GET、POST)、URL、HTTP 版本、请求头等信息。如果请求的是一个 HTTPS 网址,还会进行 SSL/TLS 握手过程。
    • 5、服务器处理请求:
      • 服务器接收到请求后,会根据请求的 URL 和其他信息执行相应的处理。服务器可能会查询数据库、调用后端程序、读取文件等,然后生成 HTTP 响应并返回给浏览器。
    • 6、接收响应:
      • 浏览器接收到服务器返回的 HTTP 响应。响应包括状态码、响应头和响应体。状态码告诉浏览器请求的结果(如 200 表示成功,404 表示未找到,500 表示服务器错误等)。
    • 7、渲染页面:
      • 浏览器根据接收到的 HTML、CSS 和 JavaScript 等资源开始渲染页面。浏览器会构建 DOM 树、CSSOM 树和 Render 树,并进行布局和绘制,最终将页面内容显示在屏幕上。
    • 8、显示页面:
      • 浏览器将渲染好的页面内容显示给用户。用户可以与页面交互,点击链接、填写表单等。

30、postMessage的用法

  • postMessage() 方法是 HTML5 中的一项功能,用于在不同的窗口或 iframe 之间进行跨域通信。它允许发送消息并在目标窗口或 iframe 中异步接收消息。
  • 发送消息:
    • 调用 postMessage() 方法向目标窗口发送消息。消息可以是字符串、对象或数组。
      var targetWindow = document.getElementById('targetWindow').contentWindow;
      var message = 'Hello, world!';
      targetWindow.postMessage(message, 'https://example.com');
      
      
  • 接收消息:
    • 在目标窗口或 iframe 中监听 message 事件来接收消息。当接收到消息时,浏览器会触发 message 事件,开发者可以通过事件处理程序来处理接收到的消息。
      window.addEventListener('message', function(event) {
        if (event.origin === 'https://example.com') {
            console.log('Received message:', event.data);
        }
      });
      
      

31、commonjs和esm的区别

CommonJS (主要用于 Node.js),ES Modules (ESM) (现代 JavaScript 标准,支持浏览器Node.js)

导入

  • CommonJS:使用 require 函数。

  • ESM:使用 import 语句。

导出

  • CommonJS:使用 module.exports 或 exports 对象。

  • ESM:使用 export 语句。

加载方式

  • CommonJS:同步加载模块。这意味着 require 调用会阻塞代码执行,直到模块加载完成。这适合于服务器端环境(如 Node.js),因为服务器端代码通常在启动时加载所有必要的模块。

  • ESM:异步加载模块。import 语句可以在编译时静态解析,并且浏览器和 Node.js 都支持异步加载。这种方式更适合于客户端环境,因为客户端应用程序通常需要按需加载模块以减少初始加载时间。

执行时机

  • CommonJS:在代码运行时加载和解析模块。每次调用 require 时,模块会在运行时同步加载并执行。

  • ESM:在代码解析时静态解析模块依赖。这使得编译器和构建工具可以进行优化,如代码拆分和静态分析。

模块作用域

  • CommonJS:每个文件都是一个模块,具有自己的作用域。导出的对象是模块的 module.exports。

  • ESM:每个文件也是一个模块,具有自己的作用域。导出的内容通过 export 显式指定,导入的内容通过 import 显式引入。

兼容性

  • CommonJS:主要用于 Node.js 环境,适用于服务器端开发。浏览器不原生支持 CommonJS 语法,需要通过打包工具(如 Browserify、Webpack)转换。

  • ESM:是 JavaScript 的官方模块规范,得到现代浏览器和 Node.js 的原生支持。适用于客户端和服务器端开发。

32、js的垃圾回收机制

  • JavaScript 的垃圾回收机制主要通过标记清除(Mark-and-Sweep)算法实现。这个算法分为两个阶段:
    • 标记阶段: 从根对象(如全局对象、局部变量等)开始,递归遍历所有可以访问到的对象,并将其标记为“可达”。

    • 清除阶段: 遍历堆中的所有对象,如果某个对象没有被标记为“可达”,则认为它是不可达的,可以回收其占用的内存。

33、new操作符具体干了什么?

  • 创建一个新对象: 首先,创建一个空对象,这个对象会成为新对象的实例。

  • 链接原型链: 将新对象的 [[Prototype]] 指针(内部属性,也称为原型)连接到构造函数的 prototype 属性上。这样新对象就可以访问构造函数原型上的属性和方法。

  • 将构造函数的作用域赋给新对象: 将构造函数内部的 this 关键字指向新创建的对象,这样构造函数内部的代码就可以操作新对象。

  • 执行构造函数内部的代码: 执行构造函数内部的代码,初始化新对象的属性,方法等。

  • 返回新对象: 如果构造函数没有显式返回一个对象,则返回新创建的对象;否则,返回构造函数显式返回的对象。

34、Generator 是怎么样使用的以及各个阶段的变化如何?

  • JavaScript 的 Generator 是一种可以退出和重新进入的函数。它的上下文(变量绑定)会在每次执行后保存,可以在之后恢复执行。生成器函数用 function* 语法定义,调用生成器函数会返回一个生成器对象。这个生成器对象符合迭代器协议,也符合可迭代协议。

  • 定义生成器函数

    • 生成器函数的定义方式与普通函数类似,但需要在 function 关键字后加上 * 号。
    function* myGenerator() {
      yield 1;
      yield 2;
      yield 3;
    } 
    
  • 调用生成器函数

    • 调用生成器函数不会立即执行其内部代码,而是返回一个生成器对象。
    const gen = myGenerator();
    
  • 生成器对象的方法

    • 生成器对象有一个主要方法 next(),每次调用 next() 方法都会执行生成器函数的代码,直到遇到 yield 关键字。next() 返回一个对象,包含两个属性:
      • value:当前 yield 表达式的值
      • done:一个布尔值,表示生成器函数是否已执行完毕
    console.log(gen.next()); // { value: 1, done: false }
    console.log(gen.next()); // { value: 2, done: false }
    console.log(gen.next()); // { value: 3, done: false }
    console.log(gen.next()); // { value: undefined, done: true }
    
  • 生成器的状态变化

  • 每次调用 next() 方法,生成器会从上一次停下来的地方继续执行。以下是生成器不同阶段的变化:

    • 初始状态:生成器函数未开始执行。

    • 执行至第一个 yield:执行生成器函数,遇到第一个 yield,暂停执行,并返回 yield 的值。

    • 中间状态:每次调用 next() 方法时,从上一次暂停的地方继续执行,直到遇到下一个 yield,再次暂停并返回 yield 的值。

    • 完成状态:生成器函数执行完所有代码,返回 { value: undefined, done: true }

35、Tailwind CSS 简单介绍一下

  • Tailwind CSS 是一个功能类优先的 CSS 框架,专注于实用工具(utility-first)的设计方法。与传统的 CSS 框架不同,Tailwind 不提供预定义的组件(如按钮、导航栏),而是提供了一系列低级别实用工具类,这些类可以组合起来构建复杂的用户界面。

    • 快速开发:通过实用工具类,可以在 HTML 中快速实现复杂的设计,而无需频繁切换到 CSS 文件。

    • 一致性:使用统一的实用工具类可以确保设计的一致性和可维护性。

    • 减少 CSS 冲突:由于大部分样式都在 HTML 中定义,减少了传统 CSS 中选择器冲突的问题。

1. 实用工具优先

  • Tailwind 提供数百个低级实用工具类,这些类通过组合使用可以构建几乎任何设计。这种方法鼓励开发者直接在 HTML 中使用这些类来定义样式,而不是编写自定义的 CSS 规则。

    • 例如,一个按钮的样式可以这样写:
    <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
      Button
    </button> 
    

2. 可定制性

  • Tailwind 提供了高度的可定制性。通过配置文件(tailwind.config.js),你可以自定义颜色、间距、字体、断点等几乎所有的设计系统参数。
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        'custom-blue': '#1e40af',
      },
    },
  },
}

3. 响应式设计

  • Tailwind 内置了响应式设计支持。通过使用类似 md:、lg: 等前缀,可以轻松地定义不同屏幕尺寸下的样式。
<div class="text-base md:text-lg lg:text-xl">
  Responsive text size
</div> 

4. 暗模式

  • Tailwind 支持暗模式,可以根据用户的系统设置或自定义设置应用暗模式样式。
<div class="bg-white dark:bg-gray-800">
  Dark mode example
</div>

5. JIT 编译

  • Tailwind 的 Just-In-Time (JIT) 模式可以按需生成 CSS 类,从而显著减少生成的 CSS 文件大小,并提高开发效率。
// 启用 JIT 模式
module.exports = {
  mode: 'jit',
  purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
  // 其他配置...
}

微前端技术知识点

微前端简单来说就是将一个大应用拆分多个独立可部署的子应用。这些子应用可以独立开发测试部署,并且可以使用不同的框架来编写。主应用作为基座,将各个子应用整合在一起。

1. 微前端概述

  • 定义:微前端是一种架构风格,将前端应用拆分多个独立的、松耦合的子应用,这些子应用可以独立开发测试部署

  • 目标:解决大型前端应用复杂性问题,提高开发效率和应用的可维护性

2. 微前端的核心理念

  • 独立开发:每个子应用可以由不同的团队独立开发,不受其他团队的干扰。

  • 独立部署:每个子应用可以独立部署不需要重新部署整个应用

  • 技术栈无关:子应用可以使用不同的技术栈,只要能在同一个页面中协同工作

3. 微前端的实现方式

  • iframe:通过 iframe 将子应用嵌入到主应用中,适用于早期的微前端实现方式,但会带来一些性能和用户体验问题。

  • Web Components:使用 Web 组件(如 Custom Elements)封装子应用,确保每个子应用具有独立的 DOM 结构和样式

  • JavaScript 插入:通过 JS 动态加载子应用的代码,实现子应用嵌入运行。 前端框架:使用如 Single-SPAqiankun 等框架,实现子应用注册加载通信

4. 微前端的关键技术点

  • 4.1 应用注册与路由

    • 主应用:负责注册加载子应用,根据路由配置决定加载哪个子应用

    • 子应用独立前端应用,有自己的路由状态管理

  • 4.2 通信机制

    • 全局事件总线:通过发布-订阅模式主应用子应用之间传递消息

    • 共享状态管理:使用全局状态管理工具(如 Redux、Vuex)在主应用子应用之间共享状态。

    • 自定义事件:通过浏览器的自定义事件机制实现子应用之间的通信。

  • 4.3 样式隔离

    • CSS Modules:通过 CSS Modules 实现样式的局部作用域防止样式冲突

    • Shadow DOM:使用 Shadow DOM 隔离子应用的样式,确保每个子应用的样式不影响其他应用。

  • 4.4 依赖管理

    • 单例模式:确保某些全局依赖(如 React、Vue)在整个微前端架构中只加载一次避免重复加载

    • 模块联邦:使用 Webpack 5Module Federation 特性,实现应用之间的模块共享

5. 微前端的框架和工具

  • 5.1 Single-SPA

    • 简介:一个微前端框架,用于管理多个子应用的加载卸载通信

    • 特点:支持多种前端框架(如 React、Vue、Angular),提供灵活的路由应用生命周期管理。

  • 5.2 qiankun

    • 简介基于Single-SPA 的微前端框架,提供更加友好的 API扩展能力

    • 特点:支持子应用的沙箱隔离样式隔离简化了子应用的注册加载过程。

  • 5.3 Module Federation

    • 简介Webpack 5新特性,允许多个独立构建的应用共享模块

    • 特点:实现应用之间的模块共享减少重复代码,提高加载速度

6. 微前端的优势与挑战

  • 6.1 优势

    • 提高开发效率:各个子应用可以独立开发测试部署减少相互依赖

    • 技术栈多样化不同的子应用可以使用不同的技术栈,满足不同团队的需求。

    • 渐进迁移:可以逐步将传统的单体应用迁移到微前端架构,降低迁移风险

  • 6.2 挑战

    • 性能问题多个子应用可能带来额外性能开销需要进行优化

    • 样式冲突不同子应用的样式可能会相互影响需要进行样式隔离

    • 依赖管理:确保全局依赖(如框架、库)的一致性,避免重复加载

7. 案例

  • 主应用:
import { registerMicroApps, start } from 'qiankun';

// 注册子应用
registerMicroApps([
  {
    name: 'app1',
    entry: '//localhost:7100',
    container: '#container',
    activeRule: '/app1',
  },
  {
    name: 'app2',
    entry: '//localhost:7200',
    container: '#container',
    activeRule: '/app2',
  },
]);

// 启动微前端
start();
  • 子应用:
// main.js
import { createApp } from 'vue';
import App from './App.vue';

let app = null;

function render(props) {
  const { container } = props;
  app = createApp(App);
  app.mount(container ? container.querySelector('#app') : '#app');
}

if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}

export async function bootstrap() {}

export async function mount(props) {
  render(props);
}

export async function unmount() {
  app.unmount();
  app._container.innerHTML = '';
  app = null;
}

JSONP的原理是什么?

  • 定义一个回调函数客户端定义一个全局的回调函数,这个函数用来处理服务器返回的数据

  • 创建 <script> 标签:客户端创建一个 <script> 标签,将其 src 属性设置为服务器URL,同时传递一个查询参数,这个参数是前面定义的回调函数名字

  • 服务器响应:服务器接收到请求后,生成一段 JS 代码,这段代码调用客户端传递的回调函数,并将数据作为参数传递给这个函数。

  • 执行回调:当 <script> 标签加载完服务器返回的 JS 代码后,这段代码被执行,调用客户端的回调函数,并传入数据

什么是单点登陆?

  • 单点登录(Single Sign-On,SSO)是一种用户认证过程,允许用户在多个独立的软件系统中使用同一个登录凭据(例如用户名和密码)进行身份验证SSO 的主要目的是简化用户的登录流程增强用户体验,同时提高安全性管理效率

正则的基础知识

  • 字符匹配:

    • . :匹配任意单个字符(换行符除外)。

    • \d :匹配一个数字(等同于 [0-9])。

    • \w :匹配一个字母、数字或下划线(等同于 [a-zA-Z0-9_])。

    • \s :匹配一个空白字符(空格、制表符、换页符等)。

    • \D :匹配一个非数字字符。

    • \W :匹配一个非字母、数字或下划线的字符。

    • \S :匹配一个非空白字符。

  • 边界匹配:

    • ^ :匹配输入的开始。

    • $ :匹配输入的结束。

  • 量词:

    • * : 匹配前面的表达式 0 次或多次。

    • + : 匹配前面的表达式 1 次或多次。

    • ? : 匹配前面的表达式 0 次或 1 次。

    • {n} : 精确匹配 n 次。

    • {n,} : 至少匹配 n 次。

    • {n,m} : 匹配 n 到 m 次。

  • 使用正则表达式的常用方法

    • RegExp.prototype.test:测试一个字符串是否匹配一个正则表达式。
    • RegExp.prototype.exec:在一个字符串中执行匹配搜索,并返回结果数组。
    • String.prototype.match:检索一个字符串匹配正则表达式的结果。
    • String.prototype.replace:替换与正则表达式匹配的子字符串。
    • String.prototype.search:测试正则表达式匹配,并返回匹配的开始位置。
    • String.prototype.split:使用正则表达式拆分字符串。
  • 正则表达式的修饰符

    • g:全局匹配。

    • i:忽略大小写匹配。

    • m:多行匹配。

    • u:启用 Unicode 匹配。

    • y:粘性匹配。

前端倒计时秒杀中出现时间不准确的现象

引起的原因

  • JavaScript 的定时器精度问题:

    • JavaScript 中使用 setTimeoutsetInterval 进行倒计时,这些方法在长时间运行时可能会受到浏览器的调度、页面性能、系统时间变更等因素的影响,导致计时不准确。
  • 浏览器节能策略:

    • 现代浏览器在标签页不处于激活状态时,会减少对定时器的调用频率,以节省资源。这也会导致倒计时变得不准确。
  • 网络延迟:

    • 如果倒计时依赖于服务器时间,每次请求服务器时间都可能会有网络延迟,导致时间误差。

解决方法

  • 使用精确的时间差:

    • 通过计算预期结束时间与当前时间的差值来更新倒计时,而不是依赖于定时器的累加。这可以显著提高精度。
    const endTime = new Date().getTime() + countdownDuration; // 预期结束时间
    const interval = 1000; // 每秒更新一次
    
    const countdown = setInterval(() => {
        const now = new Date().getTime();
        const remainingTime = endTime - now;
        
        if (remainingTime <= 0) {
            clearInterval(countdown);
            // 倒计时结束,执行相应逻辑
        } else {
            // 更新倒计时显示
            console.log(Math.floor(remainingTime / 1000));
        }
    }, interval); 
    
  • 使用 requestAnimationFrame

    • requestAnimationFramesetInterval 和 setTimeout 更加精准,因为它会在下一次页面重绘之前执行回调。
    let start = null;
    const duration = countdownDuration;
    const endTime = new Date().getTime() + duration;
    
    function countdown(timestamp) {
        if (!start) start = timestamp;
        const now = new Date().getTime();
        const remainingTime = endTime - now;
    
        if (remainingTime <= 0) {
            // 倒计时结束,执行相应逻辑
        } else {
            // 更新倒计时显示
            console.log(Math.floor(remainingTime / 1000));
            requestAnimationFrame(countdown);
        }
    }
    
    requestAnimationFrame(countdown);
    
  • 使用服务器时间

    • 每次请求服务器时间进行倒计时,可以避免本地时间不准导致的问题。结合前面的方法计算时间差进行倒计时更新。
    async function fetchServerTime() {
      const response = await fetch('/server-time'); // 获取服务器时间的接口
      const data = await response.json();
      return new Date(data.serverTime).getTime();
    }
    
    async function startCountdown() {
        const serverTime = await fetchServerTime();
        const endTime = serverTime + countdownDuration;
    
        const countdown = setInterval(() => {
            const now = new Date().getTime();
            const remainingTime = endTime - now;
    
            if (remainingTime <= 0) {
                clearInterval(countdown);
                // 倒计时结束,执行相应逻辑
            } else {
                // 更新倒计时显示
                console.log(Math.floor(remainingTime / 1000));
            }
        }, 1000);
    }
    
    startCountdown();
    

优化页面中的图片展示,将图片从模糊到清晰展示出来

  • 使用图片格式中的渐进解码功能,可以实现页面上的图片从模糊到清晰展示。渐进式图片(如渐进式 JPEG 或逐行扫描 PNG)是一种图像编码方式,允许图片在下载时逐渐变得清晰浏览器支持这种格式时,会在图片还未完全下载完成时显示一个较模糊的版本,随着更多数据的到达,图片逐渐变得清晰

  • 渐进式 JPEG

    • 渐进式 JPEG 是一种可以在下载过程中逐渐显示的 JPEG 格式。它通过多次扫描逐步细化图像,每次扫描都会提供更多的细节。
  • 如何创建渐进式 JPEG

    • 可以使用图像编辑工具(如 Photoshop)或在线工具将普通的 JPEG 图片保存为渐进式 JPEG。