1. JS的数据类型有哪些
- 基本数据类型
- String/Number/Boolean/Null/Undefined/Symbol/BigInt
- 复杂数据类型
- Object/Array/Function
2. JavaScript代码是如何被执行的
- 预处理
- JavaScript引擎会对代码进行词法分析和语法分析,生成抽象语法树(AST),同时会进行字符串化处理
- 词法分析
- JavaScript引擎将源代码转换为一系列的词法单元
- 语法分析
- 引擎会将词法单元转化为一个抽象语法树
- 语义化
- 生成AST之后,引擎会进行语义化过程,检查源代码是否有语义错误
- 执行环境
- JavaScript引擎会创建一个执行环境,这里包括全局执行环境
- 变量与作用域
- 引擎会确定变量和作用域,进行声明
- 执行代码
- 一旦所有的声明完成,代码就会按照顺序执行
- 事件循环
- JavaScript是单线程的,当执行完同步代码后,它就会进入一个事件循环,等待和处理异步事件
3. JavaScript引擎
- 什么是JavaScript引擎
- JavaScript是一种脚本语言,计算机无法直接理解,但浏览器内置了JavaScript引擎,用于执行和转化JavaScript代码为计算机可理解的语言
- 常见的JavaScript引擎有哪些
- Google浏览器 --- V8
- Edge浏览器 --- Chakra
- Firefox浏览器 --- SpiderMonkey
- Safari浏览器 --- JavaScriptCore
4. V8引擎是如何执行JavaScript代码的
- 词法分析
- 首先,V8引擎将JavaScript代码分成一个个标记或词法单元,这些标记是程序语法的最小单元
- 例如,变量名、关键字、运算符等都是词法单元
- V8引擎使用词法分析器来完成这个任务
- 语法分析
- 在将代码分成标记或词法单元之后,V8引擎将使用语法分析器将这些标记转换为抽象语法树(AST)
- 语法树是代码的抽象表达,它捕捉了代码中的结构和关系
- V8引擎会检查代码是否符合JavaScript语言规范,并将其转换为抽象语法树
- 字节码生成
- 接下来,V8引擎将从语法树生成字节码
- 字节码是一种中间代码,它包含了执行代码所需的指令序列
- 字节码是一种抽象的机器代码,它比源代码更接近机器语言,但也仍需进一步翻译成机器指令
- 机器码生成
- 最后,V8引擎将生成机器码,这是一种计算机可以执行的二进制代码
- V8引擎使用即时编译器(JIT)来将字节码编译成机器码
- JIT编译器将字节码分析为代码的热点部分,并生成高效的机器码,以提高代码的性能
5. V8引擎的架构的组成和作用
- Parse模块会将JavaScript代码转换成AST(抽象语法树),这是因为解释器并不直接认识JavaScript代码
- Ignition是一个解释器,会将AST转换成ByteCode(字节码)
- 字节码某种程度上可以做到跨平台,并且实现JS的动态性
- Ignition会收集TurboFan优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算)
- 如果函数只调用一次,Ignition会解释执行ByteCode
- TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码
- 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能
- 但是,机器码实际上也会被还原成ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如sum函数原来执行的是number类型,后来执行变成了string类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码
6. 变量提升
- 什么是变量提升
- 变量提升即将变量声明提升到它所在的作用域最开始的部分;通过var定义变量,在定义语句之前就可以访问该变量
- 原因
- 提升性能
- 通过将变量和函数的声明提前到作用域顶部,避免了在执行过程中重复解析和查找的开销
- 预先分配栈空间也提高了函数执行的效率
- 容错性更好
- 变量提升使得可以在变量声明之前就使用它们
- 即使在代码书写上存在疏忽,也不会报错
- 这种容错性可以避免一些不规范的代码导致的问题
- 提升性能
- 问题
- 由于变量声明被提升到作用域顶部,如果在变量声明之前就使用该变量,其值会是undefined,可能导致意外的行为和错误
7. 作用域和作用域链
- 什么是作用域
- 表示变量和函数起作用的区域,指代了它们在什么样的上下文中执行,亦即上下文执行环境
- 分类
- 全局作用域
- 局部(函数)作用域
- 块级作用域
- 什么是作用域链
- 当在函数内部访问一个变量的时候,JS引擎会先在当前作用域中查找(是否存在该变量),如果找到了就直接使用
- 否则就会向上层作用域逐级查找,直到找到全局作用域为止
- 这个查找的过程形成了作用域链
- 作用
- 变量查找:当在函数内部引用一个变量时,JavaScript引擎会在当前函数的作用域中查找该变量,如果找不到,就会沿着作用域链
- 闭包实现:通过作用域链,内部函数可以访问外部函数的变量,从而实现闭包,使得外部函数的作用域在内部函数执行完毕后仍然可以被引用
8. 原型和原型链
- 原型
- 是一个对象,每一个函数都有一个prototype属性,有一个属性__proto__,值为Object.prototype
- 通过该函数实例化出来的对象都可以继承得到原型对象上的所有属性和方法
- 原型链
- 访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会沿着它的__proto__属性所指向的那个对象里找,直到null为止,这样一层一层的就构成了原型链
- 设计的目的
- JavaScript中需要原型和原型链的原因是为了实现继承和共享属性方法的目的
- 通过原型链的机制,JavaScript可以在对象之间共享属性和方法,提高代码的重用性和效率
- 代码实现
function Person(name) { this.name = name; } var p = new Person("John"); function printPrototypeChain(obj) { console.log(obj); var proto = Object.getPrototypeOf(obj) if (proto !== null) { printPrototypeChain(proto) } else { console.log('End of prototype chain') } } printPrototypeChain(p)
9. this指向
- this绑定规则
- 默认绑定 --- 独立调用函数,this指向window
- 隐式绑定 --- 通过某个对象发起的函数调用,this指向该对象
- 显示绑定
- apply --- 第一个参数绑定this,第二个参数是传入额外的实参,以数组的形式
- call --- 第一个参数绑定this,后续的参数以多参数的形式来传递,会作为实参
- bind --- 总是显示的绑定到一个对象上(bind生成的this指向改变函数需要手动调用)
- new绑定 --- 使用new关键字来调用函数
- 创建了一个空对象
- 将this指向这个空对象
- 执行函数体代码
- 没有显示返回非空对象,则默认返回这个对象
- this绑定优先级
- new > bind >apply/call > 隐式绑定 > 默认绑定
- this指向与函数定义位置无关,与函数调用的方式和位置有关
10. 立即执行函数
- 什么是立即执行函数
- 创建一个匿名函数,然后立马执行这个匿名函数
- 作用
- 创建一个独立的作用域,这个作用域里面的变量,外面访问不到
11. 闭包
- 什么是闭包
- 闭包是指有权访问到另外一个函数作用域中变量的函数,创建闭包最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问当前函数的局部变量
- 怎么用
- 立即执行函数
- 防抖
// 当事件触发时,会执行这个事件的响应函数 // 如果这个事件被频繁触发,那么节流函数会按照一定的频率来执行函数 // 不管在这个中间有多少次触发这个事件,执行的频率总是固定的 function debounce(fn, delay) { let timer = null; const _debounce = function () { if (timer) clearTimeout(timer) setTimeout(() => { fn(); timer = null; }, delay) } return _debounce }- 节流
function throttle(fn, interval) { let startTime = 0; const _throttle = function() { const nowTime = new Date().getTime(); const waitTime = interval - (nowTime - startTime); if (waitTime <= 0) { fn(); startTime = nowTime; } } return _throttle }
5. 浅拷贝和深拷贝
- 浅拷贝
- 将原对象/数组的引用赋值给新的对象/数组,新的对象/数组获取的只是原对象/数组的引用
- { ...obj }
- Object.assign({}, obj)
- 深拷贝
- 创建一个新的对象/数组,将原对象/数组各属性值拷贝给新的对象/数组
- JSON.parse(JSON.stringify(obj))
- 手写拷贝函数
function deepClone(obj) { if (typeof obj != "object") return const newObj = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { if (obj[key] && typeof obj[key] == "object") { newObj[key] = deepClone(obj[key]) } else { newObj[key] = obj[key] } } } return newObj }
6. ES6~ES13新增的知识点
- ES6
- 使用class来定义类
- constructor构造器/extends实现继承/super关键字代表继承的父类
- let/const
- 不能重复声明变量/不存在作用域提升/存在暂时性死区/不添加window/存在块级作用域
- 对象字面量的增强
- 属性的简写/方法的简介/计算属性名
- 解构
- 展开语法
- 字符串模版
- 函数的默认参数
- 箭头函数
- 没有显式原型prototype/不绑定this、arguments、super参数
- 规范了二进制和八进制的写法
- 新增Symbol
- Set、WeakSet、Map、WeakMap
- 使用class来定义类
- ES7
- Array Includes
- 指数exponentiation运算符
- ES8
- Object values
- Object entries
- String Padding(padStart/padEnd)
- Trailing Commas
- Object.getOwnPropertyDescriptors
- ES9
- 构建字面量时,可以用展开运算符
- ES10
- flat
- flatMap
- Object fromEntries
- trimStart trimEnd
- ES11
- BigInt
- 空值合并操作符
- Optional Chaining
- Global This
- for in
7. var、const、let
- var
- var可以在全局/局部范围声明变量/函数
- var变量可以重新声明和修改
- 存在变量提升
- const
- const声明的变量是块级作用域
- const不能被修改且不可以重新声明
- const存在变量提升,但是不会初始化
- let
- let是块级作用域
- let可以被修改但是不可以重新声明
- let存在变量提升,但是不会初始化
8. 常见的内置类
- 数字类型Number
- 属性:MAX_SAFE_INTEGER/MIN_SAFE_INTEGER
- 实例方法:toString/toFixed
- 类方法:Number.parseInt/Number.parseFloat
- 数学对象Math
- 属性:PI
- 方法:floor/ceil/round/random/pow
- 字符串类型
- 属性:length
- 方法:
- charAt/chartCodeAt/indexOf/includes/substring/substr/slice/splice/replact/startWith/endWith/trim/contact/split/toUpperCase/toLowerCase
- 数组类型Array
- 属性:length
- 方法
- pop/push/shift/unshift/sort/join/reverse/slice/splice/indexOf/includes
- map/forEach/filter/some/every/find/findIndex/reduce
- 日期类型Date
- 类方法
- Date.now --- 返回自 1970 年 1 月 1 日以来的毫秒数
- Date.parse --- 将日期/时间转换成毫秒数
- Date.UTC --- 根据世界时间来创建日期
- 实例方法
- getDate/getDay/getMonth/getFullYear/getHours/getMinutes/getSeconds/getMilliseconds
- getTime/getUTCDate/getUTCDay/getUTCMonth/getUTCFullYear/getUTCHours/getUTCSeconds/getUTCMilliseconds
- 类方法
9. Proxy
- Proxy对象是什么
- Proxy用于创建一个对象的代理,从而实现基本操作的拦截和自定义
- 用法
- Proxy为构造函数,用来生成Proxy实例
- var proxy = new Proxy(target, handler)
- 参数
- target表示所要拦截的目标对象
- handler通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作代理p的行为
- 使用场景
- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
10. Map、WeakMap
- Map类似于对象,但是键可以是任意类型的,不仅仅是字符串类型,也不限于引用类型
- 常见属性:size/set/get/has/delete/clear
- 遍历方法:keys/values/entries/forEach
- WeakMap类似于Map,但是键只能是对象类型,且键名所指向的对象是弱引用,这意味着这个对象在其他地方没有被引用,那么它将会被垃圾回收,这也是WeakMap的主要应用场景
- API方法:has/get/set/delete
- 区别
- Map的键可以是任意类型,WeakMap只能接受对象作为键,不接受其他类型的值作为键
- Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键;WeakMap的键是弱引用,键所指向的对象可以被垃圾回收,此时键是无效的
- Map可以被遍历,WeakMap不能被遍历
- WeakMap使用场景(键名所指向的对象是弱引用,这意味着这个对象在其他地方没有被引用,那么它将会被垃圾回收)
- DOM节点元数据
- 部署私有属性
- 数据缓存
11. 垃圾回收
- 什么是垃圾回收
- JavaScript使用自动垃圾回收机制,意味着不需要手动释放内存。垃圾收集器会自动找出那些不再继续使用的变量,并在运行时释放其内存
- 垃圾回收集的策略
- 标记清除
- 引用计数
- 内存泄漏
- JS定期会进行垃圾回收,回收掉未被引用的变量,但是由于一些原因,程序中的内存没有不再需要却没有被释放掉,就会阻止垃圾回收,造成内存泄漏
- 循环引用/定时器未清除/闭包的错误使用/事件监听没有移除
12. DOM、BOM
- window --- DOM、BOM、JavaScript
- DOM
- 文档对象模型,将页面内表示为可修改的对象 Document Object Model
- BOM
- 浏览器对象模型 Browser Object Model
- 浏览器会对HTML、CSS进行渲染,同时会考虑可以通过JavaScript进行操作。所以浏览器将我们编写的HTML抽象成可以修改的对象。对象可以通过JavaScript进行访问,那么JavaScript可以修改页面。所以将这个抽象的过程称之为BOM
- 浏览器用于处理文档的所有内容的对象
- window/location/history/navigation/screen
13. 异步编程的方法
- CallBack 回调函数 --- func1(func2)
- 优点:简单,容易写
- 缺点:不利于维护和阅读,高度耦合,每个任务只能指定一个回调函数
- 事件监听 --- Func1.on("donw", func2)
- 优点:容易理解,封装done,可以绑定多个事件,去耦合,有利于模块化
- 缺点:整个程序变成事件驱动型,运行流程不清晰
- 发布/订阅
- Promises对象 --- Func1().then(func2)
- Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口
14. 回调函数
- 什么是回调函数
- 回调函数是函数式编程中重要的概念之一,允许我们在需要时将一个函数作为参数传递给另外一个函数,并在特定事件发生时执行
- 回调函数常用于处理异步操作
- 优点
- 提高代码的复用性和灵活性:回调函数可以将一个函数作为参数传递给另一个函数,从而实现模块化编程,提高代码的复用性和灵活性
- 解耦合:回调函数可以将不同模块之间的关系解藕,使得代码更易于维护和扩展
- 可以异步执行:回调函数可以在异步操作完成后被执行,这样避免了阻塞线程,提高应用程序的效率
- 缺点
- 回调函数嵌套过多会导致代码难以维护:如果回调函数嵌套层数过多,代码会变得非常复杂,难以维护
- 回调函数容易造成竞态条件:如果回调函数中有共享资源访问,容易出现竞态条件,导致程序出错
- 代码可读性差:回调函数的使用可能会破坏代码的结构和可读性,尤其是在处理大量数据时
- 回调地狱
- 一个异步请求套着一个异步请求,一个异步请求依赖于另一个的执行结果,使用回调的方式相互嵌套,而这就是回调地狱
- 解决办法
- Promise语法:在then方法中返回一个promise对象
- Generator函数
- ES6异步函数async和await
15. Promise
- 什么是Promise
- Promise是JS中解决异步编程的一种方案。从语法上说Promise是一个构造函数,从功能上说Promise对象来封装一个异步操作并可以获取其结果
- Promise有三种状态,分别是pending、resolved、rejected
- 为什么使用Promise
- 指定回调函数的方式更加灵活,可以在请求发出甚至结束后指定回调函数
- 支持链式调用,可以解决回调地狱问题
- 用法
const promise = new Promise((resolve, reject) => { resolve(value) // 该函数执行时会回调onFulfilled // reject(reason) // 该函数执行时会回调onRejected }) // 监听promise对象的状态 方式一 promise.then(onFulfilled).catch(onRejected) // 监听promise对象的状态 方式二 promise.then(onFulfilled, onRejected)// 手写简化版Promise class hyPromise { constructor(executor) { executor(this.resolve, this.reject) } status = “pending” value = undefined reason = undefined successCallback = undefined failCallback = undefined resolve = ((value) => { if (this.status !== “pending”) return this.status = “fulfilled” this.value = value }) reject = ((reason) => { if (this.status !== “pending”) return this.status = “rejected” this.reason = reason }) then(successCallback, failCallback) { if (this.status === “fulfilled”) { successCallback(this.vallue) } else if (this.status === “rejected”) { failCallback(this.reason) } }
} ```
- 实例方法
- then(onFulfilled, onRejected)
- onFulfilled -> 成功时的回调
- onRejected -> 失败时的回调
- 返回值是一个新的promise对象,所以promise支持链式调用
- catch(onRejected)
- onRejected -> 失败时的回调
- finally(callback)
- callback -> 不管promise最后的状态,在执行完then或者catch指定的回调函数后,都会执行回调
- then(onFulfilled, onRejected)
- 类方法
- all
- 接受一个数组作为参数,数组元素是promise对象,返回一个新的promise对象
- 可以不是数组,但必须是可迭代对象,且返回的每一个成员都是Promise实例
- 只有数组里的所有的promise对象都是fulfilled状态时,返回的promise的状态是fulfilled
- 当数组中的promise对象有一个的rejected状态时,返回的promise的状态是rejected
// 手写Promise.all Promise.hyAll = function(promiseList) { return new Promise((resolve, reject) => { const result = [] const length = promiseList.length let count = 0 promiseList.forEach((promise, index) => { promise.then((data) => { result[index] = data count += 1 if (count = length -1 ) { resolve(result) } },(reason) => { reject(reason) }) }) }) } - any
- 接受一个数组作为参数,数组元素是promise对象,返回一个新的promise对象
- 只要数组实例中有一个变成fulfilled状态,返回的promise对象就会变成fulfilled状态
- 只有等数组中所有的promise实例都变成rejected状态,返回的promise对象才会变成promise状态
- race
- 接受一个数组作为参数,数组元素是promise对象,返回一个新的promise对象
- 只要数组中的实例有一个率先改变,返回的promise对象就跟着改变
- allSettled
- 接受一个数组作为参数,数组元素是promise对象,返回一个新的promise对象
- 只有等数组中所有的promise对象都发生状态改变后,返回的promise对象状态才会跟着改变
- 返回的promise对象,一旦状态发生改变,状态总是fulfilled
- resolve
- 将现有对象转为promise实例
- rejected
- 返回一个新的promise实例,该实例的状态为rejected
- all
16. 生成器
- 什么是迭代器
- 迭代器是帮助我们对某个数据结构进行遍历的对象
- 迭代器也是一个具体的对象,这个对象需要符合迭代器协议
- next方法的要求
- 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象
- done(boolean)
- value
// 封装一个为数组创建迭代器的函数 function createArrayIterator(arr) { let index = 0; return { next: function() { if (index < arr.length) { return { done: false, value: arr[index++] } } else { return { done: true } } } } } - 什么是可迭代对象
- 和迭代器不是一个概念
- 当一个对象符合迭代器协议时,它就是一个可迭代对象
- 这个对象的要求必须实现@@iterator方法,在代码中我们使用Symbol.iterator访问该属性
- 可迭代对象的好处
- 当一个对象变成一个可迭代对象的时候,就可以进行某些迭代操作
- 比如for……of操作时,其实就会调用它的@@iterator方法
- 实现可迭代协议的原生对象
- String、Array、Map、Set、arguments对象、NodeList对象
- 可迭代对象的应用
- JavaScript中语法:for……of、展开语法、yield*、解构赋值
- 创建一个对象时:new Map([Iterable])、new WeakMap([Iterable])、new Set([Iterable])
- 一些方法的调用:Promise.all(Iterable)、Promise.race(Iterable)、Array.from(Iterable)
- 迭代器的中断
- 比如在遍历过程中通过break、return、throw中断了循环操作
- 比如在解构的时候,没有解构所有的值
- 自定义类的迭代实现
class Person { constructor(name, age, height, friends) { this.name = name this.age = age this.height = height this.friends = friends } running() { [Symbol.iterator]() { let index = 0; const iterator = { next: () => { if (index < arr.length) { return { done: false, value: this.friends[index++] } } else { reuturn { done: true } } } } return iterator } } }
- 和迭代器不是一个概念
- 什么是生成器
- 生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等
- function后面会跟上符号:*
- 代码的执行可以被yield控制
- 生成器函数默认在执行时,返回一个生成器对象
- 生成器是事实上是一个种特殊的迭代器
17. async、await和promise
- 相同
- async/await和Promise都是JavaScript中处理异步操作的API
- Promise是一种代表了某个异步操作最终完成(或失败)及其结果值的对象
- async/await是基于Promise的语法糖,它允许我们以更同步的方式编写异步代码
- 不同
- promise可以用.catch和.then的第二个参数来捕获异常,async需要用try catch捕获
- async是promise+generator的语法糖。async是通过Promise和generator打造的,generator相当于next控制执行
- promise可以同时执行多个请求,await不可以
18. 事件循环
- JS的执行机制是由事件循环、执行上下文和异步执行等多方面共同组成的
- JS是一种单线程语言,即在同一时间只能执行一个任务
- JS的执行环境:分为主线程和任务队列两部分。主线程负责执行任务,而任务队列则用于存储等待执行的任务。当主线程完成一个任务后,它会从任务队列中取出下一个任务并执行,这个过程被称为时间循环
- JS的事件循环是指JS引擎不断的从任务队列中取出任务并执行的过程
- JS是单线程的,所以将任务分为同步任务和异步任务,异步任务分为宏任务和微任务
- 先将宏任务添加到队列中,然后将宏任务中的微任务添加到微任务队列中,执行微任务。然后取出宏任务,依次循环
- 宏任务:Ajax、setTimeout、setInterval
- 微任务:promise().then()、nextTick
19. http协议
- 什么是http
- HyperText Transfer Protocol超文本传输协议
- 超文本传输协议是一种用于分布式写作的应用层协议
- 定义了客户端和服务器之间交换报文的格式和方式 默认为80的端口
- 使用tcp作为传输协议 保证了数据的可靠性
- 组成:一个http请求主要包括请求和响应
- 请求
- 请求行
- 请求方法字段
- URL字段
- HTTP协议版本字段
- 请求头
- 键值对组成
- User-Agent:对应展示的浏览器的类型
- Content-type:对应的请求内容和数据类型
- application/x-www-form-urlencoded 数据以&分割的键值对 键值对用=分割
- application/json json类型
- application/xml xml类型
- text/plain 文本类型
- multipart/form-data 表示上传文件
- keep-alive
- 请求体
- get/post所携带的内容
- 请求行
- 响应
- 响应行
- 响应头
- 响应体
- 请求
- 请求方法
- get --- 向服务器获取数据
- post --- 将响应实体交给指定的资源
- head --- 请求一个与get请求响应相同的响应,没有实体
- put 上传文件 用于替换目标资源的所有
- patch 用于对资源的部分修改
- delete 删除指定的资源
- connect 建立一个到目标资源表示的服务器的隧道 通常用于代理服务器
- track 回显服务器收到的请求 主要用于测试和诊断
- 状态响应码
- 200 表示请求被服务器端正常处理
- 201 post请求 创建新的资源
- 301 永久重定向 表示资源被分配了新的URL 并返回该URI
- 4xx 表示客户端发生错误
- 400 请求报文存在语法错误
- 401 未授权的错误 必须携带身份信息
- 403 没有权限访问
- 404 服务器找不到请求资源
- 5xx 服务器错误
- 500 服务器的内部错误,不能执行该请求
- 503 服务器不可用 处于维护或重载状态
20. http和https
- HTTP是超文本传输协议,信息是明文传输;HTTPS则是具有安全性的SSL加密传输协议
- HTTP的链接是很简单的,是无状态的;HTTPS协议是由SSL和HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全
- HTTP和HTTPS使用的完全不同的连接方式,用的端口号也不一样,HTTP是80,HTTPS是443
- HTTP协议需要用到CA申请证书,一般免费证书较少,因此需要一定的费用
21. web缓存
- web缓存分为浏览器缓存和http缓存
- 浏览器缓存:缓存一些必要的数据,比如用户信息、需要携带到后端的参数、列表数据等等
- cookie 4kb
- 存储在客户端,每次请求都作为请求头发送给客户端
- localStorage 5M
- 可以跨页面通讯,不会随着页面的关闭清除,适合长期有效的自动登录
- sessionStorage 5M
- 是会话级别的,不能进行跨页面通讯,页面关闭会被自动清除,适用于短期有效的自动登录(token过期重新请求)
- cookie 4kb
- http缓存
web缓存可以自动保存文档副本的http设备。当web请求抵达缓存时,如果本地有已缓存的副本,就可以从本地存储设备而不是原始服务器中提取这个文档- 强制缓存
- 如果浏览器判断请求的目标资源有效命中强缓存,则可以直接从内存中读取目标资源,无需与服务器做任何通讯
- Expires 已经废弃
- http1.1新增了Cache-control
- Cache-control: max-age=N N就是需要缓存的秒数。从第一次请求资源的时候开始,往后N秒,资源若再次请求,则直接从内存中读取,不与服务器做交互
- 协商缓存
- last-modified 时间戳
- 基于last-modified的协商缓存实现方式是
- 首先需要在服务器读出文件修改时间
- 将读出来的修改时间赋给响应头的last-modified字段
- 最后设置Cacha-control:no-cache
- 当客户端读取到last-modified的时候,会在下次的请求头中携带一个字段: If-Modified-Since,这个请求头中的数据就是服务器第一次修改时候返回的时间
- 之后每次对该资源的请求,都会带上If-Modified-Since字段,服务端拿到这个时间并再次读取该资源的修改,将两者做对比来决定读取缓存还是返回新的资源
- 基于last-modified的协商缓存实现方式是
- ETag 文件指纹
- http1.1新增了ETag,将原来协商缓存的比较时间戳修改成了文件指纹(根据文件内容计算出来的唯一哈希值,文件内容一旦改变则指纹改变)
- 第一次请求某资源的时候,服务端读取文件并计算出文件指纹,将文件指纹放在响应头的etag字段中跟资源一起返回给客户端
- 第二次请求某资源的时候,客户端自动从缓存中读取出上一次服务端返回的ETag也就是文件指纹。并赋给请求头的if-None-Match字段,让上一次的文件指纹跟随请求一起回到服务端
- 服务端拿到请求头中的is-None-Match字段值,并再次读取目标资源并生成文件指纹,两个指纹做对比。如果两个指纹完全吻合,说明文件没有被改变,则返回304状态码和一个新的响应体并return。如果不吻合,说明文件被更改,将新的文件指纹重新存储到响应头的ETag中并返回给客户端
- last-modified 时间戳
- 总结
- 强缓存:首先会通过 Expires/Cache-control 判断请求是否是强缓存,如果是的话,直接从内存读取,不与服务器做交互
- 协商缓存:如果不是强缓存,服务器端需要通过last-modified/ETag判断请求是否被更改,如果没有更改返回304状态码,反之重新设置
- 强制缓存
22. get和post
- URL可见性
- get,url参数是可见的
- post,url参数是不可见的
- 数据传输上
- get通过拼接url传递参数
- post通过body体传输参数
- 缓存性
- get请求是可以缓存的
- post请求是不可以缓存的
- 后退反应
- get请求的页面后退时,不产生影响
- post请求的页面后退时,会重新请求
- 传输数据的大小
- get一般传输数据大小不超过2k~4k
- 安全性
- post比get安全
23. tcp的三次和四次握手
TCP三次握手和四次挥手的本质是TCP通信的连接和断开
- 三次握手
- 客户端向服务端发送一个带有SYN标识符的数据包
- 服务端接收成功后,回传一个带有SYN/ACK标志的数据包传递确认信息,表示我收到了
- 客户端再回传一个带有ACK标志的数据包,表示我知道了,握手结束
- 注:SYN标志标识建立TCP连接,ACK标志标识验证字段
- 四次握手
- 客户端向服务端发送FIN,用来关闭客户端到服务端的数据传送,客户端进入FIN_WATI_1状态
- 服务端收到FIN后,向客户端发送ACK,服务端进入CLOSE_WAIT状态
- 服务端向客户端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入LAST_TCK状态
- 客户端收到FIN后,向服务端发送ACK,服务端进入CLOSED状态,完成四次挥手
24. 跨域问题
- 浏览器的同源策略
- 协议、域名以及端口号相同就为同源,是由Netscape提出的一个著名的安全策略
- 策略主要限制JS的能力:无法读取非同源的 cookie、Storage、indexDB的内容;无法读取非同源的DOM;无法发送非同源的AJAX。更加准确的说应该是发送了请求但被浏览器拦截了,也就是说浏览器会拦截非同源的请求
- 为什么浏览器会有同源策略(为了安全)
- 为了防止恶意网页可以获取其他网站的本地数据。
- 为了防止恶意网站 iframe 其他网站的时候,获取数据。
- 为了防止恶意网站在自已网站有访问其他网站的权利,以免通过cookie免登,拿到数据
- 什么是跨域
- 当浏览器与后端服务器地址不一致,就会存在跨域问题
- 虽然请求会发送出去,但是不让你的JS拿到响应内容,也就形成了不能跨过这个域去访问其他域的功能
- 这里的域指的是协议、端口号和主机都需要一致的url
- 换句话说这两个url必须同源才允许交互
- 解决跨域的方案
- jsonp
- cors跨域资源共享
- 浏览器在发送跨域请求时,会自动在请求头增加Orign字段,如果服务器允许该源访问,就会在响应头增加Access-Control-Allow-Origin
- 中间服务器代理
- vue的反向代理跨域解决
- vue的vue.config.js中的devServer设置proxy代理
25. Object.defineProperty和Proxy
- 实现方式
- Object.defineProperty:它是通过直接在对象上定义或修改属性的方式实现的,属于静态定义
- Proxy:它是通过创建一个代理对象,在这个代理对象上设置拦截器来实现的,属于动态代理
- 支持的操作
- Object.defineProperty:主要用于单个属性的定义和修改,支持的拦截点有限
- Proxy:支持对目标对象的各种操作进行拦截,包括属性的读取、设置、删除、枚举等
- 应用场景
- Object.defineProperty:适用于对已有对象进行属性的定义或修改,适合动态场景
- Proxy:适用于对对象进行动态代理,对目标对象的操作进行灵活控制,适合需要更多自定义行为的场景
- 性能
- Object.defineProperty:在大规模应用时,由于是静态定义,性能相对较好
- Proxy:由于是动态代理,可能存在一些性能开销,但在一些场景下能够提供更灵活的操作