前端面试题 – JS 篇
文章目录
-
-
- 前端面试题 – JS 篇
- 1. JS 的三个组成部分
- 2. JS 的内置对象
- 3. 操作数组的方法
- 4. 数据类型检测的方法
- 5. 闭包的定义及特点
- 6. 前端的内存泄露
- 7. 事件委托
- 8. JS 的数据类型及其区别
- 9. 判断数组的方法
- 10. Null 和 Undefined 的区别
- 11. typeof null 返回的结果
- 12. 原型链
- 13. new 操作符具体做了什么
- 14. JS 实现继承的方法
- 15. JS 的设计原理
- 16. this 的指向
- 17. script 标签里的 async 和 defer 区别
- 18. setTimeOut 的最小执行时间
- 19. ES6 的新特性
- 20. call,apply,bind 的区别
- 21. 使用递归时遇到的问题
- 22. 实现深拷贝
- 23. 事件循环
- 24. ajax 的定义及实现原理
- 25. get 和 post 的区别
- 26. Promise 的原理及优缺点
- 27. Promise 和 async await 的区别
- 28. 浏览器的存储方式
- 29. token 的定义及存储位置
- 30. token 的登录流程
- 31. 网站请求到页面渲染的过程
- 32. DOM 树和渲染树的区别
- 33. 精灵图和 base64 的区别
- 34. SVG 图片格式
- 35. 什么是 JWT
- 36. npm 的底层环境
- 37. HTTP 协议的请求头和响应头信息
- 38. 浏览器的缓存策略
- 39. 同源策略
- 40. 防抖和节流
- 41. 什么是 JSON
- 41. 请求数据未返回,怎么解决
- 42. 无感登录
- 43. 大文件上传
- 44. == 和 === 比较符的区别
- 45. JS 中的垃圾回收机制
- 46. addEventListener 默认是捕获还是冒泡
- 47. Class 语法
- 48. Fetch 相较于 XHR 的优点
- 49. 伪数组和数组的区别
- 50. map 和 forEach 的区别
- 51. JS 实现异步的方法
- 52. 实现 localStorage 中定期删除
- 53. Token 能放在 Cookie 中吗
- 54. axios 拦截器
-
1. JS 的三个组成部分
-
ECMAScript:JS的核心内容。基础语法,
var,for,数据类型等- ES5 2009年第5次修订
- ES6 2015年第6次修订,的 JS 新的标准
-
文档对象模型(DOM):DOM 把整个页面规划为元素构成的文档
-
浏览器对象模型(BOM):对浏览器窗口进行访问和操作。
window.(...)
2. JS 的内置对象
MathDateArrayStringObject
3. 操作数组的方法
会改变原数组:push pop sort shift unshift reverse splice
不会改变原数组:
concatjoinmapfilterevery(全部满足条件才返回真)some(其中有满足条件返回真)findIndex(返回满足条件的第一个元素的索引)reduceisArray
4. 数据类型检测的方法
typeof:
- 准确判断基本类型,引用数据类型不管用,对于
function可以正确判断 - 数组 对象
null都会被判断为object - 其输出值的字符串
console.log(typeof { }); // object
console.log(typeof function(){ } ) // function
instanceof:
- 只能判断引用数据类型,不能判断基本数据类型,但
functionFunction Object 都 true - 判断某个对象是否为构造函数的实例
- 注:所有对象的原型链最终都指向
console.log({ } instanceof Object); // true
console.log(2 instanceof Number); // false
constructor
- 几乎可以判断基本数据类型和引用数据类型。
null和undefined不行 - 局限:如果声明一个构造函数并把它的原型指向
Array将出错
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
Object.prototype.toString.call()
- 完美解决方案
var a = Object.prototype.toString;
console.log(a.call(2)); // [object, Number]
console.log(a.call([])); // [object, Array]
同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
这是因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法
5. 闭包的定义及特点
定义:
- 函数嵌套函数,内部函数被外部函数返回并保存下来时,就会产生闭包。
- 闭包就是能够“记住”并访问其定义时作用域内的变量的函数,即使外部函数已经执行完毕
例子:
function cls(a) {
return function closure(){
console.log(a)
}
}
const test = cls('abcd')
test() // 输出,'abcd',闭包记住了变量 a
特点:
- 变量一致保存在内存中,不会被垃圾回收,直到闭包不再被使用
- 重复利用变量,并且这个变量不会污染全局的一种机制
缺点:
- 闭包较多时,会消耗内存,导致页面的性能下降
- 在 IE 浏览器中会内存泄漏
使用场景:防抖,节流,函数嵌套函数避免全局污染
6. 前端的内存泄露
定义:JS 里已经分配地址的对象,由于长时间没有释放或没有办法清除,造成长时间占用内存的现象,会让内存资源浪费,从而导致运行速度慢甚至崩溃
因素:
- 未声明直接赋值的变量
- 未清空的定时器
- 过度的闭包
- 一些引用元素没有被清除
扩展:JS 有自带的垃圾回收机制,长时间未使用的已经分配内存的对象会被自动销毁
7. 事件委托
定义:不给每个子节点单独设置事件监听器,而是设置在其父节点上,然后利用冒泡原理触发父元素的事件,然后处理每个子节点
- 减少绑定事件监听回调函数的数量,从而减少内存的占用,并且能减少对 DOM 元素的交互次数,提升性能
- 可以动态处理子元素
<div id="parent">
<button id="child1">按钮 1</button>
<button id="child2">按钮 2</button>
</div>
<script> // 父元素绑定事件 document.getElementById("parent").addEventListener("click", function(e) { alert("父元素点击事件触发!"); }); // 子元素按钮绑定事件,阻止冒泡,方式一 document.getElementById("child1").addEventListener("click", function(e) { e.stopPropagation(); // 阻止事件冒泡到父元素 alert("按钮 1 被点击!"); }); // 子元素按钮绑定事件,阻止冒泡,方式二 document.getElementById("child2").addEventListener("click", function(e) { alert("按钮 2 被点击!"); }, true); </script>
8. JS 的数据类型及其区别
原始数据类型
NumberStringBooleanUndefined(变量声明未赋值)Null(空值或变量不存在的变量)- ES6 新增:
Symbol(唯一值)BigInt(任意精度的整数)
原始数据类型存储在栈中,是不可变的,大小不固定,频繁被使用的数据。
引用数据类型
ObjectArrayFunctionDateRegExp- ES6 新增:
Set(类似数组,但值不能重复)Map(键值对集合)
引用数据类型存储在堆中,可变的,占据空间大,大小不固定
其他数据类型:NaN(非数字)Infinity(无穷大)
对比区别
基本数据类型保存的是具体的值
let a = 1
let b = a
a = 2
// a = 2; b = 1
- 原始值是不可变的,不能修改,把 a = 2 实际是创建新的内存 2,而原来的 1 的内存不变。
- 变量本身是可变的
引用数据类型保存的是数据的地址
let obj1 = { name:'Alice' }
let obj2 = obj1
obj1.name = 'Bob'
// obj1, obj2 = {name:'Bob'}
obj1和obj2指向同一个对象,由于对象是可变的,所有修改时所有指向该对象的变量都会改变
扩展
在操作系统中,内存被分为栈区和堆区
- 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
- 堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。
9. 判断数组的方法
Array.isArray()
最简洁
Array.isArray([1, 2, 3]); // true
Array.isArray({ }); // false
instanceof
判断对象是否是某个构造函数的实例。对于数组,它检查对象的原型链中是否存在Array.prototype
const arr = [1, 2, 3];
arr instanceof Array; // true
Object.prototype.toString.call()
不会受到Array构造函数的影响。最准确。跨端也能良好运作
Object.prototype.toString.call([1, 2, 3]); // "[object Array]"
Object.prototype.toString.call({ }); // "[object Object]"
10. Null 和 Undefined 的区别
null是空对象,可用于赋值给会返回对象的表变量,做初始化undefined是声明了但未定义,undefined不是保留字==判断两个类型的值返回true,===返回false
11. typeof null 返回的结果
typeof null 的结果是 Object。这是一个 JavaScript 中的历史遗留问题。在最早期的 JavaScript 实现中,null 被设计为一个指向“空对象”的特殊值。为了表示 null,JavaScript 选择了用一个内存地址为 0 的指针,而 typeof 操作符返回的是该值的类型标识符。
12. 原型链
原型:原型是一个普通对象,它可以为构造函数的所有实例共享实例和方法,所有实例引用的原型都是同一个对象
原型链:一个原型链在调用属性和方法的时候,会一次从实例自身、构造函数原型、原型的原型去查找(终点是 Object.prototype)
如何使用:
- 使用
prototype可以把方法挂载到构造函数的原型上 - 使用
__proto__实例属性可以访问到构造函数的原型
作用:共享属性和方法,避免重复的存储,从而减少内存占用
[!WARNING]
在构造函数内写的方法不会在原型上,因此每个实例上的方法都是不一样的,若要使方法在原型上,要使用
prototype来添加,这样每个实例上的方法才是一样的。
13. new 操作符具体做了什么
- 先创建一个空对象
- 把空对象和构造函数通过原型进行链接(新实例共享构造函数原型上的方法)
- 把构造函数的
this绑定到新的空对象上(初始化) - 根据构造函数返回的类型判断。如果是值类型,则返回对象;如果是引用类型,则返回这个引用类型
[!NOTE]
因为在
JS中,构造函数如果显示返回对象则new操作符会返回这个对象,如果是基本类型则忽略返回值返回新创建的实例对象。
模拟实现:
function mynew(Fun, ...args) {
let newObj = { } // 1
newObj.__proto__ = Fun.prototype // 2
const result = Fun.apply(newObj, args) // 3
return result instanceof Object ? result : newObj // 4
}
14. JS 实现继承的方法
- 原型链继承:
- 让一个构造函数 A 的原型是另一个构造函数 B 的实例,那么这个构造函数 A 的实例就具有构造函数 B 实例的属性
- 优点:写法简洁,容易理解
- 缺点:对象实例共享所有继承的方法和属性(一个实例改,其他实例也改),无法向父类构造函数传参
function Parent(gender) {
this.gender = gender
}
Parent.prototype.say = function() {
consolo.log('hello')
}
function Child(gender) { }
Child.prototye = new Parent()
- 借助构造函数继承
- 在子类型的构造函数内部调用父类型的构造函数,使用
apply()或call()将父对象的构造函数绑定到子对象上 - 优点:解决原型链继承不能传参和父类原型共享的问题
- 缺点:a. 继承的方法都在构造函数内部书写,无法实现函数复用。b. 父类型原型上的方法对子类型是不可见的
- 在子类型的构造函数内部调用父类型的构造函数,使用
function Parent(gender) {
this.gender = gender
}
function Child(gender) {
Parent.call(gender)
}
let c1 = new Child('xxx') // c1.gender = 'xxx'
- 组合继承
- a. 使用原型链继承原型上的属性和方法;b. 使用构造函数传递参数
- 优点:解决原型链和构造函数单独继承的缺点
- 缺点:会调用两次父类构造函数
function Parent(gender) {
this.gender = gender
}
Parent.prototype.say = function() {
consolo.log('hello')
}
function Child(gender) {
Parent.call(this, gender)
}
Child.prototye = new Parent()
- ES6 的
class实现继承- 通过
extends关键字实现继承,实质是先创建父类的this,然后用子类的的构造函数修改this - 优点:语法简单,操作方便
- 缺点:某些浏览器不支持
- 通过
class Parent {
constructor(gender) {
this.gender = gender
}
}
class Child extends Parent {
constructor(gender) {
super(gender)
}
// 如果要使用父类的方法,通过 super.xxx 来调用
}
[!NOTE]
因为子类的 this 对象是继承父类的 this 对象然后对其加功,所以必须在子类的构造函数中调用
super方法执行父类构造函数,用来新建父类的 this,然后子类的 this 才能使用
15. JS 的设计原理
- JS 引擎:高级语言 -> 机器码
- 运行上下文:浏览器提供的接口 API,如 DOM,BOM 等
- 调用栈:单线程,因为有很多事件依次触发,多线程会出现很同步问题,主线程就是渲染工作,可能会阻塞
- 事件循环:当监听到调用栈空时从事件队列中取出事件放到调用栈中去执行
- 回调函数
16. this 的指向
- 全局对象中的
this指向window - 全局作用域或普通函数中的
this指向window - 不是箭头函数的情况下,
this指向调用它的对象 - 如果是箭头函数,他的
this在定义时已确定,它没有自己的this,它的this指向外层函数的this,外层函数没有this,则指向window new关键字的this指向将赋值的变量applycallbind可以显示的改变函数的this,前提是非箭头函数- 匿名函数的执行具有全局性,故它的
this指向window
17. script 标签里的 async 和 defer 区别
-
script标签中没有属性时:- 浏览器遇到
script时会立刻加载并执行脚本中的内容,如果加载的脚本过多会阻塞页面的渲染
- 浏览器遇到
-
有
async:- 加载渲染后面的元素会和脚本的加载和执行并行
- 有多个脚本时,不保证脚本的执行顺序
- 脚本执行时元素可能未加载,绑定事件可能不成功
-
有
defer:- 加载渲染后面的元素会和脚本的加载并行,但脚本的执行时机会等所有元素解析完才执行
- 可以保证多个脚本的执行顺序
- 脚本执行时元素一定已经加载完
18. setTimeOut 的最小执行时间
- setTimeOut 最小是 4 ms
- setInterval 最小是 10 ms
- 由 HTML5 规定的
19. ES6 的新特性
- 块级作用域(
let,const)- 作用域仅限于代码块(如,
if语句、for循环等) var声明的变量具有函数作用域,let和const声明的变量具有块级作用域- 不存在变量提升,必须声明后才能使用,故存在暂时性死区(使用在前,声明在后,抛出错误)
- 不能在一个作用域能重复声明(
var可以,后会覆盖前)
- 作用域仅限于代码块(如,
- 箭头函数
- 不能作为构造函数使用,故不可
new - 没有 prototype,也没有
arguments,arguments 指向外部普通函数的 - 不能使用
callapplybind去改变其this,this指向外层第一个函数的this
- 不能作为构造函数使用,故不可
- Promise
- 解决回调地狱,把异步操作队列化,确保异步操作执行完再执行后续代码
- 自身有 all reject resolve race 静态方法
- 原型有 then catch finally 方法来定义后续操作逻辑
- 三种状态:pending(初始),fulfilled(成功),rejected(失败)
asyncawait配合使用,同步代码做异步操作async表明函数内做异步操作,使用了async的函数会返回一个Promiseawait的结果取决于等待的内容 可能是 普通值 / Promise / 普通函数(继续链式调用)- 若
await后的 Promise 是 rejected 则整个async都会中断
- 解构赋值,扩展运算符:从数组或者对象中取值,然后给变量赋值
- 新增
set(不重复)和map(键类型不受限制)数据结构 - 新增定义类的
class语法糖、symbol基本数据类型(唯一值) - 新增函数参数默认值
- 新增模块化:
importexport - 新增
generator,及数组的 API
20. call,apply,bind 的区别
三者的作用都是改变 this 的指向并进行函数调用
call和apply类似,只不过call传参数列表,apply传数组- 由于
call的性能好一些,并且解构赋值就可以替代apply,所以用的多 bind不会立即执行,会返回改变this的函数,这个函数还以传参,故调用时bind()()。bind不可以做构造函数,如果 new 的话,它的 this 就会指向 new 创建的实例
21. 使用递归时遇到的问题
递归指的是一个函数内继续调用函数自身。
递归函数必须写退出条件 return,否则递归函数会一致调用下去,最终导致栈溢出,内存泄漏,页面卡顿。
22. 实现深拷贝
深拷贝是完全拷贝一份新的对象,在堆中新开辟内存空间,拷贝对象被修改后,原对象不受影响,主要针对引用数据类型
实现方式:
- 扩展运算符
- 缺点:只能实现第一层深拷贝,当有多层的时候还是浅拷贝
let obj = { name:'xxx', age:18 }
let obj1 = { ...obj }
JSON.parse(JSON.stringfy())- 缺点:不会拷贝内部函数
let obj = { name:'xxx', age:18, say(){ consolo.log('hello world') }}
let obj1 = { JSON.parse(JSON.stringfy(obj)) }
// obj1:{ name:'xxx', age:18 }
- 递归实现
- 完美解决方案
function DeepCopy(originObj, isDeep) {
let obj = { };
if (originObj instanceof Array) {
obj = [];
}
for (let key in originObj) {
let value = originObj[key];
// 是数组或对象才递归
if (
isDeep &&
typeof value === "object" &&
typeof value !== "null"
) {
obj[key] = DeepCopy(value, isDeep);
} else {
obj[key] = value;
}
}
return obj;
}
23. 事件循环
- JS 是单线程的脚本语言
- 主线程 执行栈 任务队列 宏任务 微任务
- 主线程先执行同步任务,然后执行任务队列的微任务、宏任务,全部执行完后等待主线程调用去执行同步任务,调用完后再去任务队列查看是否有异步任务,这样往复循环的过程就是事件循环
console.log("同步任务 1");
setTimeout(() => {
console.log("宏任务 1");
}, 0);
Promise.resolve().then(() => {
console.log("微任务 1");
});
console.log("同步任务 2");
// 同步任务 1 同步任务 2 微任务 1 宏任务 1
24. ajax 的定义及实现原理
定义:创建交互式网页应用的网页开发技术。再不重新加载整个的页面的前提下,通过 XMR 从服务器交换部分数据,最后通过 JS 操作 DOM 更新页面
实现:
- 创建
XmlHttpRequest对象xmr - 通过这个对象的
open()方法与服务器建立连接 - 构建所需的数据参数,通过
xmr对象的send()方法发送给服务器 - 通过
xmr的onreadystate change事件监听服务器和你的通信状态 - 接受处理服务器返回的数据,更新到 HTML 中
25. get 和 post 的区别
- get 一般是获取数据,post 一般是提交数据
- get 的参数放在 url 上,只能 url 编码,安全性较差;post 的参数放在 body 中,支持很多种编码
- get 请求刷新浏览器或退回没有影响,post 请求退回会重新提交数据
- get 请求会被缓存,post 请求不会缓存
- get 请求会保存在浏览器历史记录中,post 不会保存
26. Promise 的原理及优缺点
原理:
- Promise 对象,封装一个异步操作并且能提供统一接口获取成功或失败的结果
- 构造一个 Promise 实例,传递两个参数
(resolve, reject),都是函数类型 - Promise 的
then方法用于指定状态改变时的确定操作
优点:处理异步操作,当这些异步之间有依赖关系时,可以使用 Promise,链式调用,顺序执行,避免使用回调地狱
缺点:无法取消 Promise,一旦创建就会立即执行,不会中途取消,并且如果不设置回调,Promise 内部抛出错误就无法返回到外面,要用 catch 捕获错误
使用示例:
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("操作成功!");
} else {
reject("操作失败!");
}
});
promise
.then(result => {
console.log(result); // 操作成功时的回调
})
.catch(error => {
console.error(error); // 操作失败时的回调
});
27. Promise 和 async await 的区别
- 都是处理异步操作的方式,
async await是基于 Promise 实现的,都是非阻塞性的 - Promise 是 ES6,
async await的 ES7 的语法 - Promise 返回的对象要用
then catch方法处理,书写是链式的,容易造成代码重叠,而async await是通过try catch进行异常捕获 async await能够让代码看起来像同步一样,只要遇到await就会立刻等待返回结果,然后再执行后面的操作
28. 浏览器的存储方式
- cookies
- H5 标准之前的本地存储方式
- 兼容性好,请求头自带 cookies,存储量小,资源浪费,使用麻烦(封装)
- localStorage
- H5 加入的以键值对存储的标准方式
- 优点:操作方便,永久存储,兼容性较好
- 缺点:保存值的类型被固定,浏览器隐私模式下不能读取,不能被爬虫
- sessionStorage
- 当前页面关闭后就会立刻清理。会话级别的存储方式
- indexedDB
- H5 的标准存储存储方式,以键值对存储,可快速读取,适合 WEB 场景
29. token 的定义及存储位置
定义:验证身份的令牌,用户通过账号密码登陆后,服务器把这些凭证通过加密等一系列操作后,返回的一段字符串
存储位置:
- localStorage
- 后期每次请求接口都需要把他当作一个字段传给服务器
- 容易被 XSS(跨站脚本注入) 攻击,但做好防御,会比存在 cookie 中好,空间容量大
- cookie
- 会自动发送,但不能跨域
- 容易被 CSRF(跨站请求伪造)攻击
30. token 的登录流程
- 客户端账号密码登录
- 服务器收到请求后验证账号密码
- 验证成功服务器会返回资源并签发一个 token 返回给客户端
- 客户端收到这个 token 保存在 cookie 或 localStorage 中
- 客户端每次向服务器发送请求资源时,携带这个 token 作为凭证
- 服务端收到请求先验证 token,通过后才会向服务端返回数据
31. 网站请求到页面渲染的过程
-
URL解析
-
查找缓存
- 强缓存不用再请求
-
DNS 解析
-
建立 TCP 连接
-
发送 HTTP 请求
-
服务器处理请求并返回资源
-
渲染页面
- 浏览器获取 HTML CSS 资源并把 HTML 解析成 DOM 树,把 CSS 解析 成 CSS 规则树
- 再把 DOM 和 CSSOM 合并成渲染树,然后进行计算位置和布局
- 最后把渲染树的每个节点渲染到屏幕上(回流重绘)
-
断开 TCP 连接
32. DOM 树和渲染树的区别
- DOM 树是和 HTML 标签一一对应的,它包含了 head 标签和隐藏元素
- 渲染树不包含 head 标签和隐藏元素(因为不用显示在屏幕上)
- 他们的形成时间不同
33. 精灵图和 base64 的区别
精灵图:把多张小图放在一张大图上,利用定位的方式把小图显示在页面上,当访问页面时可以减少请求图片的数量,以提高加载速度
base64 编码:
- 使用 base64 对图像进行编码,把原本的二进制文件转为64个字符组成的字符串
- base64 会和 html css 一起下载到浏览器,减少请求,减少跨域问题
- 但是若 base64 体积比原图大,则不利于 css 的加载
34. SVG 图片格式
- 基于 XML 语法的图像格式,是通过数学公式对图像的描述,本身是文本文件,故体积小
- SVG 是矢量图,所以不管放大缩小都不会失真(不会影响清晰度),而其他文件是基于像素的
- SVG 可插入页面中,成为 DOM 的一部分,可以使用 JS CSS 对其操作
<svg></svg>
- SVG 可以作为文件引入
<img src=""></img>
- SVG 也可以转为 base64 引入页面
35. 什么是 JWT
JWT (JSON Web Token):是一种标准的、基于 JSON 格式的 Token,它包含了三部分(Header、Payload、Signature),并且通过签名机制确保了数据的完整性和真实性。JWT 是一种特定实现的 Token。
- 前端把账号密码发送给后端后,后端把用户 id 等信息作为 JWT 负载
- 后端把 JWT 负载和头部 Headeer 使用 base64 编码后一起生成签名,返回给前端
- 前端每次请求数据时,把 JWT 放在 HTTP 请求头的 Authorization 字段供给后端进行身份验证
- 后端检查 JWT 签名是否有效,过期,验证通过则使用 JWT 中的用户信息来返回对应的资源
36. npm 的底层环境
npm 全称 “node package manager”。它时 node 的包管理和分发 JS 代码库工具,是分发 node 模块的标准
底层环境组成:Node.js,注册表,文件系统,命令行工具,网站
37. HTTP 协议的请求头和响应头信息
请求头信息
- Accept:所支持的数据类型
- X-Request-With:请求方式
- Host:访问的主机地址
- Referer:访问来源(防跨站请求伪造)
- User-Agent:浏览器类型、版本信息
- Date:访问时间
- Connection:连接方式
- Cookie:用于验证
响应头信息
- Location:告诉浏览器你要去找谁
- Server:服务器的类型
- Content-Type:返回的数据类型
- Refresh:控制浏览器定时刷新
38. 浏览器的缓存策略
强缓存(本地缓存)
- 不发起请求,直接使用内存里的内容
- 比如浏览器直接把 JS CSS img 等放到内存中,下次用户访问时直接从内存中取,不需请求,提高性能
- 如何触发
- HTTP 1.0:时间戳响应标头
- HTTP 1.1:Cache-Control 响应标头
弱缓存(协商缓存)
- 需要向后台发送请求,判断内容是否有变化,没有变化则返回 304,浏览器使用缓存里的内容,否则返回新资源
- 如何触发
- HTTP 1.0:请求头(if-modifie-since 上一次更新时间)、响应头(last-modified),对比
- HTTP 1.1:请求头(if-none-match)、响应头(Etag),都表示内容哈希
39. 同源策略
同源策略指的是 协议+域名+端口号 三者要一致,才能服务器请求资源,否则会产生跨域问题。
特点:
- 同源策略是浏览器的核心,如果没有同源策略将会导致跨站请求伪造攻击。
- 跨域可以发送请求,后端也会返回结果,但是结果被浏览器拦截!
- 允许跨域加载的标签:img link script
解决跨域:JSONP、CORS、websocket、反向代理
但是解决跨域后 CRSF 怎么办?校验 Token!
40. 防抖和节流
防抖:在连续的事件触发过程中,只有在事件停止触发一段时间后,才执行操作
节流:规定一个时间间隔,在这个时间间隔内即使事件多次触发,也只会执行一次操作
41. 什么是 JSON
定义:JSON 是一种纯字符串格式的数据,他本身不提供任何方法,适合在网络中传输
存储方式:JSON 数据存储在 .json 文件中,也可以以字符串形式保存在数据库、cookie中
JSON 提供了 JSON.parse() JSON.stringfy() 等方法供转换:
JSON.parse():JSON 格式转成 JS 对象JSON.stringfy():JS 对象转成 JSON 格式
使用场景:定义接口,序列化,生成 token,配置文件 package.json
41. 请求数据未返回,怎么解决
- 设置一些默认值
- if 条件判断
42. 无感登录
定义:无感登录指的是 token 过期时不需用户重新切换到登录页面登录,而是自动发送请求更新用户本地的 token
实现方案:
-
在响应中拦截,判断 token 是否过期,过期就去调用刷新 token 的接口
- 登录成功后保存 token 和 refresh_token
- 若响应拦截器中的 token 过期,使用 refresh_token 调用刷新 token 的方法
- 替换本地的 token 并把上次错误请求对象中 token 替换掉重新发送请求
- 如果 refresh_token 也过期,清除所有 token 重新登录
-
后端返回过期时间,前端判断 token 的过期时间,去调用刷新 token 的接口
-
设置定时器,定时调用刷新 token 的接口
43. 大文件上传
分片上传:
- 大文件按照一定规则,分成多个相同大小的数据块
- 初始化一个分片上传任何,返回本次上传的唯一标识符
- 按照规则规则把各个数据块上传
- 发送完成后,服务端判断数据上传的完整性来决定是否把数据块合成完整的原始文件
断点续传:
- 服务端返回断点标识符,表示从哪里开始继续上传,而不用从 0 开始
- 或者服务器自己处理
44. == 和 === 比较符的区别
-
==:会先进行类型转换- 一个布尔值,一个其他类型,布尔转数字
- 一个对象,一个原始值,对象转原始值(valueOf,toString 转为数字或字符串)
- 一个字符串,一个数字,字符串转数字,如果不是有效数字就 NaN
-
===- 类型不同 返回 false
- 类型相同 再比较值
45. JS 中的垃圾回收机制
- 创建一个变量时,JS 会为其分配内存
- 当其他变量指向这个对象时,这个对象被引用的次数会增加
- 当没有变量指向这个对象也就是被引用计数为 0 时,内存会被回收
46. addEventListener 默认是捕获还是冒泡
- 默认是冒泡
- addEventListener第三个参数默认为 false 代表执行事件冒泡行为。
- 当为 true 时执行事件捕获行为。
47. Class 语法
- 原型链的语法糖,面向对象的思想
- 方法都放到原型链上,节省内存
- 支持继承,但要用super来执行父类的构造函数
48. Fetch 相较于 XHR 的优点
- 通过 Promise 的机制,实现链式调用,避免回调地狱
- 头部信息,响应信息,请求信息可以设置在不同的对象上,利于处理复杂的场景
- 同源策略下可以设置不携带 cookie,某些场景不需要 cookie 能减少流量
- 支持处理多种数据格式,XHR 只有纯文本或 XML
- fetch 仅支持异步操作,避免阻塞
- 但是请求不能中断
49. 伪数组和数组的区别
- 伪数组和数组同样都能获取长度和用下标索引访问到
- 伪数组不能使用数组的方法
- 伪数组可以用解构赋值、Array.from 的方法转为数组
50. map 和 forEach 的区别
map:- 返回一个新数组,新数组的元素是原数组元素经过回调函数处理后的结果。
- 原数组不会被修改。
- 用于对数组中的元素进行转换
forEach:- 没有返回值(返回
undefined)。 - 通常用于遍历数组并执行某些操作,不会创建新数组。
- 没有返回值(返回
51. JS 实现异步的方法
- Promise.resolve.then()
- 回调函数
- 定时器
- queueMicroTask
52. 实现 localStorage 中定期删除
- 惰性删除
- 存储一个对象,一个是值,一个是存储时间
- 下次登录的时候,获取当前时间和存储时间,如果超过了定期的时间就将其删除
- 定时删除
- 定时遍历缓存中的对象
- 如果key过期了就推入数组
- 每隔1s删除5个,减少cpu占用
53. Token 能放在 Cookie 中吗
最好不要。Token 设置的目的就是为了解决用户登录鉴权的问题,防止 cookie 每次自动发送给服务器带来的 CSRF 攻击,如果放在 Cookie 中,那他也每一次都会发送过去了。
54. axios 拦截器
axios拦截器分为响应和请求拦截器,请求拦截器 在请求发送前进行必要操作处理,例如添加统一cookie、请求体加验证、设置请求头等,相当于是对每个接口里相同操作的一个封装; 响应拦截器 同理,响应拦截器也是如此功能,只是在请求得到响应之后,对响应体的一些处理,通常是数据统一处理等,也常来判断登录失效等


![表情[baoquan]-拾光赋](https://blogs.ink/wp-content/themes/zibll/img/smilies/baoquan.gif)


暂无评论内容