2024前端面试 -- JavaScript篇

378 阅读23分钟

【前端面试系列】:

1. JavaScript组成部分:

  • ECMAScriptJS 的核心内容。描述了语言的基础语法,比如var,for,数据类型(数组、字符串),
  • DOM(文档对象模型):DOM 把整个 HTML 页面规划为元素构成的文档
  • BOM(浏览器对象模型):对浏览器窗口进行访问和操作

2. 数据类型:

2.1. 内置对象:

JS几种基础数据类型?几种新增?

  • 8种基础数据类型:undefinednullbooleannumberstringobject | symbolbigInt
  • symbol:独一无二且不可变 => 全局变量冲突、内部变量覆盖
  • bigInt:任意精度正数 安全的存储和操作大数据,即使超出了number的安全整数范围

2.2. 内置对象:

  • String Boolean Number Array Object Function Math Date RegExp...
    • Math:abs() sqrt() max() min()
    • Date:new Data() getYear()
    • Array
    • String:concat() length slice() split()

3. 基础数据类型通常如何进行分类?使用起来有什么区别?使用过程中你是如何区分他们的?

(不同的存储形态) = 存储关系

可以分为:原始数据类型 + 引用数据类型

  • 原始数据类型:undefined、null、boolean、number、string
    • 使用效果:直接赋值后,不存在引用关系;
    • 存储位置:栈存储 => 先进后出栈维护结构 => 栈区由编译器自动分配释放 => 临时变量
    • 放置在栈中:空间小、大小固定、操作频繁
  • 引用数据类型:对象、数组、函数
    • 使用效果:属于引用关系;
    • 存储位置:堆存在 => 堆内存由开发展进行分配释放 => 长期变量 => 直到应用结束
    • 放置于堆中,引用类型数据量大、大小不固定、赋值给的是地址

4. 操作数组的方法有哪些?

push()pop()sort()splice()unshift()shift()reverse()concat()join()map()filter()ervery()some()reduce()isArray()findIndex()

哪些方法会改变原数组?

  • push()pop()unshift()shift()sort()reverse()splice()

5. 如何进行类型区分判断?

  • typeof:只能判断基本数据类型,不能判断引用数据类型
typeof 2 // number
typeof true // boolean

// 问题
typeof {}  // object
typeof []  // object
  • instanceof:只能判断引用数据类型,不能判断基础数据类型
2 instanceof Number // true
{} instanceof Object // true

*** 手写instanceof的原理实现?【通过翻户口本,查家庭信息】

function myInstance(left,right) {
    // 获取对象的原型
    let _proto = Object.getPrototypeOf(left);
    // 构造函数的prototype
    let _prototype = right.prototype;

    while(true) {
        if(!_proto) return false;

        if(_proto ===  _prototype) return true;

       _proto = Object.getPrototypeOf(_proto);
    }
}
  • constructor:可以判断基本类型和引用类型,但是如果声明了一个构造函数,并把他的原型指向了Array,就判断不出
(2).constructor === Number // true
([]).constructor === Array // true

隐患? constructor代表的是构造函数指向的类型,是可以被修改的。

function Fn() {};
Fn.prototype = new Array();

var f = new Fn();
  • Object.prototype.toString.call():全可以判断

6. 闭包:

什么是闭包?函数嵌套函数,内部函数被外部函数返回并保存下来时,就会产生闭包

  • 特点:可以重复利用变量,并且这个变量不会污染全局的一种机制;这个变量是一直保存在内存中,不会被垃圾回收机制回收
  • 缺点:闭包较多的时候,会消耗内存,导致页面的性能下降,在IE浏览器中才会导致内存泄漏
  • 使用场景:防抖,节流,函数嵌套函数避免全局污染的时候
function outer() {
    var a = '变量1';
    var inner = function () {
        console.info(a);
    }
    return inner; // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}

var inner = outer(); // 获得inner闭包函数
inner(); // 输出: "变量1"

7. 防抖和节流

都是应对页面中频繁发事件的优化方法。

  • 防抖:避免发生重复触发。
  • 【使用场景】:
  1. 频繁和服务端交互
  2. 输入框的自动保存时间。
  • 优点:
    • 减少不必要的操作:触发连续操作时,只执行最后一次操作,避免了频繁执行操作带来的性能浪费。
    • 优化用户体验:在一些需要用户等待的操作中,防抖可以避免频繁触发操作导致的界面卡顿,提高用户体验。
  • 缺点:
    • 可能会错过一些操作:如果事件触发频率过高,防抖可能会忽略掉一些操作。
    • 不适合所有场景:只适合连续触发事件时使用,不适合非连续触发的事件。
function debounce(func, wait) {  
    let timeout;  
    eturn function() {  
        const context = this;  
        const args = arguments;  
        clearTimeout(timeout);  
        timeout = setTimeout(function() {  
            func.apply(context, args);  
        }, wait);  
    };  
};

// 使用示例
const input = document.querySelector('input');  
input.addEventListener('input', debounce(function() {  
    console.log('Input value:', input.value);  
}, 200));
  • 节流:把频繁触发的事件减少,每隔一段时间执行。

【使用场景】:scroll 事件

  • 优点:
    • 控制操作频率:能够确保在一定时间内执行一次操作,避免了频繁执行操作带来的性能浪费。
    • 适合各种场景:无论是连续触发的事件还是非连续触发的事件,都可以是节流来优化性能。
  • 缺点:
    • 可能会漏掉一些操作:如果事件触发频率过高,防抖可能会漏掉一些操作。
    • 需要合理设置时间间隔:节流的时间间隔需要根据实际情况调整,如果时间间隔过长,可能会导致性能问题;如果时间间隔设置过短,则可能会导致频繁触发事件时无法及时处理。
function throttle(func, limit) {  
    let inThrottle;  
    return function() {  
        const context = this;  
        const args = arguments;  
        if (!inThrottle) {  
            func.apply(context, args);  
            inThrottle = true;  
            setTimeout(function() {  
                inThrottle = false;  
            }, limit);  
        }  
    };  
}

// 使用示例
const input = document.querySelector('input');  
input.addEventListener('input', throttle(function() {  
    console.log('Input value:', input.value);  
}, 200));

8. 前端的内存泄露怎么理解?

JS里已经分配内存地址的对象,但是由于长时间没有释放或者没办法清除,造成长期占用内存的现象,会让内存资源大幅浪费,最终导致运行速度慢,甚至崩溃的情况。

  • 垃圾回收机制(因素):一些为生命直接赋值的变量;一些未清空的定时器;过渡的闭包;一些引用元素没有被清除。

9. 事件委托

又叫事件代理,原理就是利用了事件冒泡的机制来实现,也就是说把子元素的事件绑定到了父元素的身上。如果子元素阻止了事件冒泡,那么委托也就不成立。

  • 组织事件冒泡:event.stopPropagation() addEventListener('click',函数名,true/false) 默认是false(事件冒泡),true(事件捕获)
  • 好处:提高性能,减少事件的绑定,也就减少了内存的占用。

10. 原型链

  • 原型就是一个普通对象,它是为构造函数的实例共享属性和方法;所有实例中引用的原型都是同一个对象。
  • 使用 prototype 可以把方法挂在原型上,内存在保存一份
  • __proto__ 可以理解为指针,实例对象中的属性,指向了构造函数的原型(prototype

11. new操作符具体做了什么?

  1. 先创建一个空对象
  2. 把空对象和构造函数通过原型链进行链接
  3. 把构造函数的this绑定到新的空对象身上
  4. 根据构造函数分会的类型判断,如果是值类型,则返回对象;如果是引用类型,就要返回这个引用类型。

12. JS是如何实现继承的?

  1. 原型链继承
  • 特点:继承父类原型上的属性和方法。
  • 缺点:创建子类实例时,不能向父类构造函数中传参数,无法实现多继承
// 父类
function Person(name) {
    this.name = name;
    this.wait = functon() {
        console.log(this.name + "正在休息")
    }
}
Person.prototype.change = "修改";
Person.prototype.hello = function() {
    console.log("Hello!")
}
// 子类
function Child(age) {
    this.age = age;
}
Child.prototype = new Person();
Child.prototype.constructor = Child;
let child = new Child(10);
console.dir(child);
  1. 借用构造函数继承
  • 特点:解决了子类构造函数向父类构造函数中传递参数,可以实现多继承(call或者apply多个父类)
  • 缺点:方法都在构造函数中定义,无法复用,不能继承原型上的属性和方法。
// 父类
function Person(name) {
    this.name = name;
    this.wait = function() {
        console.log(this.name + "等等")
    }
}
Person.prototype.change = "父类 change";
Person.prototype.hello = function() {
    console.log("Hello!")
}
// 子类
function Child(name, age) {
    Person.call(this, name);
    this.age = age;
}
let child = new Child("张三", 19)
console.dir(child);
  1. 组合式继承
  • 特点:把12的方法相结合,函数可以复用,可以继承属性和方法,并且可以继承原型的属性和方法
  • 缺点:会挂载两次父类的属性和方法(有两个namewait),产生小bug
// 父类
function Person(name) {
    this.name = name;
    this.wait = function() {
        console.log(this.name + "等等")
    }
}
Person.prototype.change = "父类 change";
Person.prototype.hello = function() {
    console.log("Hello!")
}
// 子类
function Child(name, age) {
    Person.call(this, name);
    // Person.apply(this, [name]) // => apply后面的参数是个数组
    this.age = age;
}
Child.prototype = new Person();
Child.prototype.constructor = Child;
let child = new Child("熊大", 14);
console.dir(child);
  1. ES6class 类继承
// class 相当于es5中的构造函数
// class中定义方法时,前后不能加function,全部定义在class的prototype属性中
// class中定义的所有方法时不可枚举的
// class中只能定义方法,不能定义对象,变量等
// class和方法内默认都是严格模式
// es5中constructor为隐性属性
class People {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    
    wait() {
        console.log(`${this.name} ${this.age} 正在等待`)
    }
}

// 继承父类
class Child extends People {
    constructor(name, age) {
        // 继承父类属性
        super(name, age)
    }
    
    wait() {
        console.log(this);
        // 继承父类方法
        super.wait();
    }
}

let child = new Child("小王",22)
console.log(child);
child.wait();
  1. 寄生组合继承
  • 特点:通过寄生的方式来修复组合式继承的不足,完美的实现继承。
  • Object.create()方法创建一个空对象,让属性和方法继承到对象__proto__
// 父类
function Person(name) {
    this.name = name;
    this.wait = function() {
        console.log(this.name + "等等")
    }
}
Person.prototype.change = "父类 change";
Person.prototype.hello = function() {
    console.log("Hello!")
}
// 子类
function Child(name, age) {
    Person.call(this, name);
    // Person.apply(this, [name]) // => apply后面的参数是个数组
    this.age = age;
}
Child.prototype = Object.create(Person.prototype);
Child.prototype.constructor = Child;
let child = new Child("熊二", 13);
console.dir(child);

13. JS设计原理:

JS引擎、运行上下文、调用栈、事件循环、回调

14. JS中的this指向问题

  1. 全局对象中的this指向:指向的是window;
  2. 全局作用域或者普通函数中的this:指向全局window;
  3. this永远指向最后调用它的那个对象:在不是箭头函数的情况下;
  4. new关键词改变了 this 的指向;
  5. apply,call,bind:可以改变 this 指向,不是箭头函数;
  6. 箭头函数中的this:它的指向在定义的时候就已经确定了。箭头函数它没有this,看外层是否有函数,有就是外层城函数的this,没有就是window
  7. 匿名函数中的this:永远指向了window,匿名函数的执行环境具有全局性,因此this指向window

15. script 标签里的 asyncdefer 有什么区别?

  • 当没有async和defer这两个属性的时候,
    • 浏览器会立刻加载并执行指定的脚本
  • 有async
    • 加载和渲染后面元素的过程将和script的加载和执行并行进行(异步)
  • 有defer
    • 加载和渲染后面元素的过程将和script的加载并行进行(异步),但是它的执行事件要等
    • 所有元素解析完成之后才会执行

16. setTimeout最小执行时间是多少?

HTML5规定的内容:

  • setTimeout最小执行时间是4ms
  • setInterval最小执行时间是10ms

17. ES6的新特性有哪些?

  1. 新增块级作用域(let, const
    • 不存在变量提升
    • 存在暂时性死去的问题
    • 块级作用域的内容
    • 不能在同一个作用域内重复声明
  2. 新增了定义类的语法糖(class
  3. 新增了一种基本数据类型(symbol
  4. 新增了解构赋值
    • 从数组或者对象中取值,然后给变量赋值
  5. 新增了函数参数的默认值
  6. 给数组新增了API
  7. 对象和数组新增了扩展运算符
  8. Promise
    • 解决回调地狱的问题。
    • 自身有all, reject, resolve, race方法
    • 原型上有then, catch
    • 把异步操作队列化
      • 三种状态:pending初始状态, fulfilled操作成功, rejected操作失败
      • 状态:pending -> fulfilled; pending -> rejected一旦发生,状态就会凝固,不会再变
    • asyncawait
      • 同步代码做异步的操作,两者必须搭配使用
      • async 表明函数内有异步操作,调用函数会返回 promise
      • await 是组成 async 的表达式,结果是取决于它等待的内容,如果是 promise 那就是promise 的结果,如果是普通函数就进行链式调用
      • await后的promise如果是reject状态,那么整个async函数都会中断,后面的代码不执行
  9. 新增了模块化(import, export
  10. 新增了setmap数据结构
    • set就是不重复
    • mapkey 的类型不受限制
  11. 新增了generator
  12. 新增了箭头函数
    • 不能作为构造函数使用,不能用new
    • 箭头函数就没有原型
    • 箭头函数没有arguments
    • 箭头函数不能用call, apply, bind 去改变 this 的执行
    • this指向外层第一个函数的this

18. call,aply,bind三者有什么区别?

  • 都是改变this指向和函数的调用,callapply 的功能类似,只是传参的方法不同
  • call 方法传的是一个参数列表
  • apply传递的是一个数组
  • bind传参后不会立刻执行,会返回一个改变了this指向的函数,这个函数还是可以传参的,bind()()
  • call方法的性能要比apply好一些,所以call用的更多一点

19. 深拷贝和浅拷贝

  • 浅拷贝:
    • 是指在复制对象或数组时,只复制一层对象的引用而不是实际的对象本身。也就是说,新对象与原对象共享同一个内存地址,其中一个对象的修改会影响到另一个对象。
    • 使用场景:只需要复制一层对象引用的情况,比如克隆一个对象用于修改而不影响原始对象
// 对象的赋值运算
Object.assign(target,key,value);
Object.created();
// 数组的解构
Array.concat();
Array.slice();
Array.toReversed();
Array.reverse();

let a = {age:"2"};
let b = a;
// b.age会随着a.age的改变而改变,这就叫浅拷贝
b.age = 14;
console.log(b.age); // 14 
  • 深拷贝:
    • 是指在复制对象或数组时,不仅复制对象本身,还要递归地复制对象的所有子对象,使得新对象与原对象完全对立,互不影响。
    • 使用场景:适用于完全独立的对象副本的情况,比如避免修改原始对象或者将对象传递给其他函数时产生副作用。
let obj = {
    name: "张三",
    age: "18"
}

let newObj = JSON.parse(JSON.stringify(obj));
// 缺点:
// 会忽略 undefined、Symbol 和函数等特殊值,导致无法拷贝。
// 若尝试拷贝 bigInt 类型的值或者是循环引用的数据,会直接报错。

let obj = { 
    name: '你好啊', 
    like: { 
        type: 'coding', 
        a: undefined, 
        b: null, 
        c: function () { 
            console.log('hello'); 
        }, 
        e: Symbol('hello'), \
        // acef都拷贝不了 
    } 
} 

let newObj= JSON.parse(JSON.stringify(obj)) console.log(newObj);// {name:'你好啊',like:{ type: 'coding', b: null}}

20. 并发模型与事件循环(EventLoop

名称内容优点缺点拓展/备注
进程(操作系统中的基本概念之一)指的是一个正在运行中的程序,包括了程序的执行代码、数据、资源等。操作系统为每个进程分配一定的系统资源,例如内存空间、文件和设备等,以便进程能够正常运行。进程是自愿分配和管理的最小单位
线程(进程中的一个执行流程)可以看做是进程中的一个独立执行单元。每个进程中可以包含多个线程,这些线程共享进程的资源线程通过利用进程的资源来执行任务,并可以在进程内进行切换线程是无法独立存在的,必须依存于进程而存在线程是程序执行的最小单位。
单线程指的是程序只有一个执行线程,只能串行执行任务,即每个任务都得等前一个任务执行完毕后才能进行下一个任务。这种模式的好处是逻辑简单,开发维护成本低,但执行效率较低特别是处理大量数据时容易出现阻塞等问题适用于简单的应用程序
多线程指的是程序同时拥有多个执行线程,每个线程可以独立运行,可以同时处理多个任务可以大大提高程序的执行效率和响应能力,特别是在需要同时处理多个任务或大量数据时非常有用复杂度高,容易出现并发问题,需要耗费更多的系统资源和开发成本适用于复杂的应用程序

在多任务环境下,多个进程可以同时执行,同一个进程中的多个线程也可以同时执行。线程的切换比进程的切换开销小,因此多线程的程序更加高效。

  • 渲染线程:指用于渲染图形的计算机进程线程。
    • 渲染线程负责将HTMLCSSJavaScript代码转化为网页上的可视化内容
    • 主要任务:解析HTML文档,确定渲染树和绘制图像。还负责检查和处理JavaScript代码,以便在网页上进行交互式操作。
  • 微任务:指的是一些比较短且需要立即执行的任务,例如Promisethen方法,MutationObserver等等。这些任务执行时,会在当前宏任务执行完毕后立即执行。
  • 宏任务:包括一些比较耗时的任务,例如setTimeoutsetIntervalAjax请求、DOM事件等等。
    • 先选取队列中最先进入队列的任务执行,直到队列为空。

微任务具有更高的优先级,会比宏任务优先执行。因此在编写异步代码时,应当注意使用合适的任务类型,以保证代码的正确性和效率。

  • 同步任务:代码按照顺序执行,每完成一项任务后再进行下一项任务。
  • 异步任务:代码在执行过程中,不必等待前一个任务完成后再执行下一个任务。
  • 调用栈:用来跟踪程序执行顺序的一种内存结构。
  • 事件循环
    1. 先执行所有的微任务队列中的任务,直到队列为空。
    2. 然后再执行宏任务队列中的任务。
    3. 然后再执行微任务队列中的任务,直到队列为空。
    4. 这个过程一直重复下去,直到任务队列中和微任务队列中同时为空。

21. ajax是什么?怎么实现的?

创建交互式网页应用的网页开发技术:在不重新加载整个网页的前提下,与服务器交换数据并更新部分内容

通过 XMLHttpRequest 对象向服务器发送异步请求,然后从服务器拿到数据,最后通过JS操作DOM更新页面

  1. 创建XMLHttpRequest对象 xmh
  2. 通过 xmh 对象里的open()方法和服务器建立连接
  3. 构建请求所需的数据,并通过 xmh 对象的 send() 发送给服务器
  4. 通过 xmh 对象的 onreadystate change 事件监听服务器和你的通信状态
  5. 接收并处理服务器响应的数据结果
  6. 把处理的数据更新到 HTML 页面上

21.1. getpost有什么区别?

  1. get一般是获取数据,post一般是提交数据
  2. get参数会放在url上,所以安全性比较差,post是放在body中
  3. get请求刷新服务器或退回是没有影响的,post请求退回时会重新提交数据
  4. get请求时会被缓存,post请求不会被缓存
  5. get请求会被保存在浏览器历史记录中,post不会
  6. get请求只能进行url编码,post请求支持很多种

22. 浏览器的存储方式有哪些?

  1. cookies
    • H5标准前的本地存储方式
    • 兼容性好,请求头自带cookie
    • 存储量小,资源浪费,使用麻烦(封装)
  2. localstorage
    • H5加入的以键值对为标准的方式
    • 操作方便,永久存储,兼容性较好
    • 保存值的类型被限定,浏览器在隐私模式下不可读取,不能被爬虫
  3. sessionstorage
    • 当前页面关闭后就会立刻清理,会话级别的存储方式
  4. indexedDB
    • H5标准的存储方式,,他是以键值对进行存储,可以快速读取,适合WEB场景
  • token存在sessionstorage还是loaclstorage?
    • token:验证身份的令牌,一般就是用户通过账号密码登录后,服务端把这些凭证通过加密等一系列操作后得到的字符串
    1. 存loaclstorage里,后期每次请求接口都需要把它当作一个字段传给后台
    2. 存cookie中,会自动发送,缺点就是不能跨域
    • 如果存在localstorage中,容易被XSS攻击,但是如果做好了对应的措施,那么是利大于弊
    • 如果存在cookie中会有CSRF攻击

23. token的登录流程。

  1. 客户端用账号密码请求登录
  2. 服务端收到请求后,需要去验证账号密码
  3. 验证成功之后,服务端会签发一个token,把这个token发送给客户端
  4. 客户端收到token后保存起来,可以放在cookie也可以是localstorage
  5. 客户端每次向服务端发送请求资源的时候,都需要携带这个token
  6. 服务端收到请求,接着去验证客户端里的token,验证成功才会返回客户端请求的数据

24. 页面渲染的过程是怎样的?

  • DNS解析
  • 建立TCP连接
  • 发送HTTP请求
  • 服务器处理请求
  • 渲染页面
    • 浏览器会获取HTML和CSS的资源,然后把HTML解析成DOM树
    • 再把CSS解析成CSSOM
    • 把DOM和CSSOM合并为渲染树
    • 布局
    • 把渲染树的每个节点渲染到屏幕上(绘制)
  • 断开TCP连接

25. 了解过JWT吗?

JSON Web Token:通过 JSON 形式作为在 web 应用中的令牌,可以在各方之间安全的把信息作为 JSON 对象传输。在数据传输过程中还可以完成数据加密,签名等相关处理。

  • 信息传输、授权

25.1. 基于传统的 Session 认证

http 协议是一种无状态的协议,也就意味着当用户第一次通过用户名和密码登录成功以后,下一次再请求的时候,用户还需要再进行登录才行,因为根据 http 协议,我们不知道是哪个用户发出的请求。

  • 弊端:
    1. 占用大量服务器内存:每个用户经过认证之后都会在服务器的内存中记录一次,随着用户的增加,服务器的内存开销户明显增加。
    2. 限制分布式架构的应用:用户认证之后会将 sessionId 保存到内存中,这就意味着用户下次请求必须要请求到这台服务器上,才完成能授权,在分布式系统中,一定程度上增加了配置负载均衡的复杂度(指定请求打到记录 sessionId 的不同服务器)。
    3. 如果后端需要实现 session 共享机制,还需要进行 redis 集群的部署,加大了部署难度。
    4. 因为是基于 cookie 来进行用户识别的(set-cookie),cookie 如果被捕获,用户很容易受到 CSRF(跨网站请求伪造的攻击)。

25.2. JWT 的认证流程:

image.png

  1. 前端把账号密码发送给后端的接口
  2. 后端核对账号密码成功后,把用户id等其他信息作为JWT 负载,把它和头部分别进行base64编码拼接后签名,形成一个JWTtoken)。
  3. 前端每日请求时都会把JWT放在HTTP请求头的Authorization字段内
  4. 后端检查是否存在,如果存在就验证JWT的有效性(签名是否正确,token是否过期)
  5. 验证通过后后端使用JWT中包含的用户信息进行其他的操作,并返回对应结果

简洁、包含性、因为TokenJSON加密的形式保存在客户端,所以JWT是跨语言的,原则上是任何web形式都支持。

25.3. JWT 的优势:

  1. 不占用服务器资源:服务器只需要通过 JWT 工具进行校验即可;
  2. 适用于分布式微服务:不需要通过负载均衡找到指定服务器上的会话,只需要在网关中对请求头中的 JWT 令牌进行统一校验即可;
  3. 信息安全:由于签名是使用标头有效负载计算的,因此还可以验证内容是否遭到篡改;
  4. 支持多种语言: Token 时以 JSON 加密的形式保存在客户端,原则上任何 web 都支持。

25.4. JWT的结构:

JWT令牌由三个部分组成,分别是标头(Header)、有效载荷(Payload)、签名(Signature),并有“.”分割。即:Header.Payload.Signature.

  • 第一部分:标头(Header):

通常由两个部分组成,分别是令牌的类型(例如:JWT)和所使用的签名算法(例如:HMAC、SHA256、RSA,一般是用HS256即可)。

{
    "alg": "HS256",
    "typ": "JWT"
}
// 最后,使用 Base64 编码构造出 JWT 结构中的第一部分(Header)。
  • 第二部分:有效载荷(Payload):

自定义传输的信息,通常是一些用户信息。

// 例如:
{
    "id": "11",
    "username": "wang",
    "admin": true,
}
// 最后,使用 Base64 编码构造出 JWT 结构中的第二部分(Payload)
  • 第三部分:签名(Signature):防篡改

需要使用Base64编码后的headerpayload以及提供的密钥(私钥),然后使用header中指定的签名算法(HS256)构建一个签名,保证JWT,没有被篡改过。

26. HTTP协议规定的协议头和请求头有什么?

  1. 请求头信息:
    1. Accept:浏览器告诉服务器所支持的数据类型
    2. Host:浏览器告诉服务器我想访问服务器的哪台主机
    3. Referer:浏览器告诉服务器我是从哪里来的(防盗链)
    4. User-Agent:浏览器类型、版本信息
    5. Date:浏览器告诉服务器我是什么时候访问的
    6. Connection:连接方式
    7. Cookie
    8. X-Request-With:请求方式
  2. 响应头信息:
    1. Location:告诉浏览器你要去找谁

    2. Server:告诉浏览器,服务器的类型

    3. Content-Type:告诉浏览器,返回的数据类型

    4. Refresh:控制了的定时刷新