一、 原型和原型链
- 所有的引用类型(数组、函数、对象)可以自由扩展属性(除null以外)。
- 引用类型有一个_ _ proto_ _属性(也叫隐式原型,它是一个普通的对象)。
- 函数有一个prototype属性(这也叫显式原型,它也是一个普通的对象)。
- 引用类型的_ _ proto_ _指向它构造函数的prototype。
- 当试图得到一个对象的属性时,如果这个对象本身不存在这个属性,那么就会去它的_ _ proto_ _属性(也就是它的构造函数的’prototype’属性)中去寻找。
function Person() {
}
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
-
原型链:利用原型让一个引用类型继承另一个引用类型的属性和方法。然后层层递进,就构成了实例与原型的链条。
new操作符具体干了什么呢?
1、创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型。
2、属性和方法被加入到 this 引用的对象中。
3、新创建的对象由 this 所引用,并且最后隐式的返回 this 。
闭包
闭包是能够读取其他函数内部变量的函数
1.优点:
- 变量长期驻扎在内存中;
- 避免全局变量的污染;
- 私有成员的存在 ;
2. 特性 - 函数套函数;
- 内部函数可以直接使用外部函数的局部变量或参数;
- 变量或参数不会被垃圾回收机制回收 GC;
3.缺点
常驻内存 会增大内存的使用量 使用不当会造成内存泄露,详解: -
内存泄漏
:每个浏览器会有自己的一套回收机制,当分配出去的内存不使用的时候便会回收;内存泄露的根本原因就是你的代码中分配了一些‘顽固的’内存,浏览器无法进行回收,如果这些’顽固的’内存还在一直不停地分配就会导致后面所用内存不足,造成泄露。 - 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
// setTimeout保存循环中的值
for(var i=0; i< 10; i++) {
setTimeout((function(i) {
console.log(i);
})(i));
}
i = null;
堆和栈
堆和栈在不同的场景下,有不同的含义:
(1)程序内存布局场景下,堆与栈表示两种内存管理方式;
(2)数据结构场景下,堆与栈表示两种常用的数据结构。
- 这里说的是内存中的堆和栈
变量都存放在内存中
内存给变量开辟了两块区域,分别为栈区域和堆区域
栈的特点,开口向上,速度快,容量小
堆的特点,速度稍慢,容量比较大
-
栈(stack)
栈区存放基本类型和引用类型的指针
-
堆(heap)
堆区存放引用类型,比如:Array,Object对象
补充:
基本类型: undefined,boolean,number,string,null.
引用类型:object、Array、RegExp、Date、Function、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)。
数据查询速度比较,stack远远大于heap。
在实际开发过程中,偶尔遇到栈溢出的情况,stack overflow错误,因为stack创建时候,大小是确定的,超过额度大小就会发生栈溢出【当js出现死循环或者错误的递归时候】。heap大小是不确定的,需要可以一直累加。
js是单线程的,那么怎么利用多核的CPU呢?H5的Web Worker标准,允许js脚本创建多个线程,但是子线程受主线程的控制,且不能操作DOM。
stack是线程独占的,heap是线程共有的。
- 基本包装类型
String Number Boolean这三个基本类型有其对应的包装对象,包装对象有其对应的属性和方法,调用方法的过程是在后台发生的
var str = 'hello'; //string 基本类型
var s2 = str.charAt(0); //在执行到这一句的时候 后台会自动完成以下动作 :
(
var _str = new String('hello'); // 1 找到对应的包装对象类型,然后通过包装对象创建出一个和基本类型值相同的对象
var s2 = _str.chaAt(0); // 2 然后这个对象就可以调用包装对象下的方法,并且返回结给s2.
_str = null; // 3 之后这个临时创建的对象就被销毁了, str =null;
)
alert(s2);//h
alert(str);//hello
注意这一瞬间,我们并没有改变原字符串str的值,只是新建了一个_str对象得出结果,赋值给s2
引用类型创建的对象在执行期间一直存在,包装类型创建的对象只存在了一瞬间
- 浅拷贝和深拷贝
- 浅拷贝,拷贝了对象的引用,原对象变化时,拷贝对象变化
- 深拷贝,拷贝了对象的值,原对象变化时,拷贝对象不变
深拷贝的方法 -
obj2 = Object.assign({}, obj1)
创建了一个空对象,把obj1属性复制过去,obj1改变不会影响obj2 -
obj2 = JSON.parse(JSON.stringify(obj1))
JSON.stringify把对象转成字符串,JSON.parser把字符串转成新对象 - 递归拷贝
浏览器渲染原理
1.浏览器渲染过程
- HTML文档解析成DOM树 。
- 处理CSS标记,构成层叠样式表模型CSSOM(CSS Object Model)
- 将DOM和CSSOM合并为渲染树(rendering tree)。
在这一过程中,不是简单的将两者合并就行了。渲染树只会包括需要显示的节点和这些节点的样式信息,如果某个节点是 display: none 的,那么就不会在渲染树中显示。 -
当浏览器生成渲染树之后,就会根据渲染树来进行布局。这一个阶段浏览器要做的事情就是要弄清楚每个节点在页面中的确切位置和大小。通常这一个行为也叫做自动重排。
布局流程的输出是一个盒模型,它会精确的捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都将转换成屏幕上的绝对像素。
2.渲染阻塞
构建DOM树和CSSOM树时,遇到JS,整个解析进程必须等待JS的执行完成才能够继续,这就是所谓的JS阻塞页面。
每次去执行JavaScript脚本都会严重地阻塞DOM树的构建,如果JavaScript脚本还操作了CSSOM,而正好这个CSSOM还没有下载和构建,浏览器甚至会延迟脚本执行和构建DOM,直至完成其CSSOM的下载和构建。所以,script标签的位置很重要。
由于CSSOM负责存储渲染信息,浏览器就必须保证在合成渲染树之前,CSSOM和DOM的解析完全结束,浏览器才会进入下一步的渲染,这就是CSS阻塞渲染。
CSS阻塞渲染意味着,在CSSOM完备前,页面将一直处理白屏状态,这就是为什么样式放在head中,仅仅是为了更快的解析CSS,保证更快的首次渲染。
需要注意的是,即便你没有给页面任何的样式声明,CSSOM依然会生成,默认生成的CSSOM自带浏览器默认样式。
3. 回流(重排)和重绘(reflow和repaint)
HTML默认是流式布局的,但CSS和JS会打破这种布局,改变DOM的外观样式以及大小和位置。因此我们就需要知道两个概念:replaint和reflow。
3.1 reflow(回流/重排)
当浏览器发现布局发生了变化,这个时候就需要倒回去重新渲染,大家称这个回退的过程叫reflow
。
reflow
会从html这个root frame开始递归往下,依次计算所有的结点几何尺寸和位置,以确认是渲染树的哪一部分发生变化还是整个渲染树。reflow几乎是无法避免的,因为只要用户进行交互操作,就势必会发生页面的一部分的重新渲染,且通常我们也无法预估浏览器到底会reflow哪一部分的代码,因为他们会相互影响。
3.2 repaint(重绘)
repaint
则是当我们改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸和位置没有发生改变。
注意,display:none会触发reflow,visibility: hidden只会触发repaint
visibiliy是隐藏元素,但元素仍然占据着布局空间,它会被渲染成一个空框。所以visibility:hidden只会触发repaint,因为没有发生位置变化。
另外,修改了元素的样式,浏览器并不会立刻reflow或repaint一次,而是会把这样的操作积攒一批,然后做一次reflow,这又叫异步reflow或增量异步reflow。
但是在有些情况下,比如resize窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行reflow。
3.3 引起reflow
现代浏览器会对回流做优化,它会等到足够数量的变化发生,再做一次批处理回流。
- 页面第一次渲染(初始化)
- DOM树变化(如:增删节点)
- Render树变化(如:padding改变)
- 浏览器窗口resize
- 获取元素的某些属性
浏览器为了获得正确的值也会提前触发回流,这样就使得浏览器的优化失效了,这些属性包括offsetLeft、offsetTop、offsetWidth、offsetHeight、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、调用了getComputedStyle()。
3.4 引起repaint
reflow回流必定引起repaint重绘,重绘可以单独触发。
背景色、颜色、字体改变(注意:字体大小发生变化时,会触发回流)
3.5 减少reflow、repaint触发次数
- 用transform做形变和位移可以减少reflow
- 避免逐个修改节点样式,尽量一次性修改
- 使用DocumentFragment将需要多次修改的DOM元素缓存,最后一次性append到真实DOM中渲染
- 可以将需要多次修改的DOM元素设置display:none,操作完再显示。(因为隐藏元素不在render树内,因此修改隐藏元素不会触发回流重绘)
- 避免多次读取某些属性
- 通过绝对位移将复杂的节点元素脱离文档流,形成新的Render Layer,降低回流成本
4. 几条关于优化渲染效率的建议
- 合法地去书写HTML和CSS ,且不要忘了文档编码类型。
- 样式文件应当在head标签中,而脚本文件在body结束前,这样可以防止阻塞的方式。
- 简化并优化CSS选择器,尽量将嵌套层减少到最小。
- DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。
- 如果某个样式是通过重排得到的,那么最好缓存结果。避免下一次用到的时候,浏览器又要重排
- 不要一条条地改变样式,而要通过改变class,或者csstext属性,一次性地改变样式。
- 尽量用transform来做形变和位移
- 尽量使用离线DOM,而不是真实的网面DOM,来改变元素样式。比如,操作Document Fragment对象,完成后再把这个对象加入DOM。再比如,使用cloneNode()方法,在克隆的节点上进行操作,然后再用克隆的节点替换原始节点。
- 先将元素设为display: none(需要1次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次重排和重绘)。这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染。
- position属性为absolute或fixed的元素,重排的开销会比较小,因为不用考虑它对其他元素的影响
- 只在必要的时候,才将元素的display属性为可见,因为不可见的元素不影响重排和重绘。另外,visibility : hidden的元素只对重绘有影响,不影响重排。
- 使用window.requestAnimationFrame()、window.requestIdleCallback()这两个方法调节重新渲染。
http状态码
分类 | 分类描述 |
---|---|
1** | 信息,服务器收到请求,需要请求者继续执行操作 |
2** | 成功,操作被成功接收并处理 |
3** | 重定向,需要进一步的操作以完成请求 |
4** | 客户端错误,请求包含语法错误或无法完成请求 |
5** | 服务器错误,服务器在处理请求的过程中发生了错误 |
状态码 | 状态码英文名称 | 中文描述 |
---|---|---|
200 | OK | 请求成功。 |
301 | Moved Permanently | 永久移动。资源(网页等)被永久转移到其它UR |
302 | Found | 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI |
303 | See Other | 查看其它地址。与301类似。使用GET和POST请求查看 |
404 | Not Found | 请求的资源(网页等)不存在 |
500 | Internal Server Error | 服务器内部错误,无法完成请求 |
使用301跳转的场景
1)域名到期不想续费(或者发现了更适合网站的域名),想换个域名。
2)在搜索引擎的搜索结果中出现了不带www的域名,而带www的域名却没有收录,这个时候可以用301重定向来告诉搜索引擎我们目标的域名是哪一个。
3)空间服务器不稳定,换空间的时候。
ajax
Ajax(Asynchronous JavaScript And XML),异步 JavaScript 和 XML,用于异步请求数据,在不刷新网页的情况下更新页面数据,提升用户体验
3.优缺点
优点:
- 不刷新页面的情况下更新数据
- 使用异步的方式与服务器通信,不打断用户的操作
- 可将一些后端的工作移到前端,减少服务器与带宽的负担
- Ajax使得界面与应用分离,也就是数据与呈现分离
缺点 - AJAX技术给用户带来很好的用户体验但是会暴露比以前更多的数据和服务器逻辑
- 对搜索引擎支持较弱
4.实现
核心XMLHttpRequest (简称XHR),可以不刷新界面获取更新数据,老版本的IE里使用ActiveXObject
readyState
存有XMLHttpRequest
的状态。从 0 到 4 发生变化。
0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪
status
200: "OK"
404: 未找到页面
当 readyState 等于 4 且状态为 200 时,表示响应已就绪:
ajax
Let Ajax change this text
5.axios
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中
- 从浏览器中创建
XMLHttpRequests
- 从 node.js 创建
http
请求 - 支持
Promise
API - 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御
XSRF
get参数:{params:{}}, post参数:{}
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
浏览器数据存储
存储的方式有:
cookie,localstorage,sessionstorage
cookie的弊端:
1.每次请求都会携带cookie里面的信息,增加流量的消耗
2.明文存储不安全
3.用户登录之后,关闭页面,重新打开之后为什么还能获取之前的用户信息?
用户登录成功之后后台会随机生成一个用户登录信息(sessionId),并将登录信息存放在cookie中,下次访问页面时,服务端会先去cookie中获取sessionId,并判断其真实有效性(可以用来处理单点登录)
跨域
1.跨域出现的原因:浏览器的同源策略(同源策略是浏览器的安全策略)
- 同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。
- 是用于隔离潜在恶意文件的重要安全机制。
- 同源(协议、域名、端口号相同)
当前页面url | 被请求页面url | 是否跨域 | 原因 |
---|---|---|---|
http://www.test.com/ | http://www.test.com/index.html | 否 | 同源(协议、域名、端口号相同) |
http://www.test.com/ | https://www.test.com/index.html | 跨域 | 协议不同(http/https) |
http://www.test.com/ | http://www.baidu.com/ | 跨域 | 主域名不同(test/baidu) |
http://www.test.com/ | http://blog.test.com/ | 跨域 | 子域名不同(www/blog) |
http://www.test.com:8080/ | http://www.test.com:7001/ | 跨域 | 端口号不同(8080/7001) |
2.非同源限制
- 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
- 无法接触非同源网页的 DOM
- 无法向非同源地址发送 AJAX 请求
3.跨域解决方法
(1) 设置document.domain解决无法读取非同源网页的 Cookie问题
因为浏览器是通过document.domain属性来检查两个页面是否同源,因此只要通过设置相同的document.domain,两个页面就可以共享Cookie(此方案仅限主域相同,子域不同的跨域应用场景。)
// 两个页面都设置
document.domain = 'test.com';
(2) 跨文档通信 API:window.postMessage()
调用postMessage方法实现父窗口http://test1.com向子窗口http://test2.com发消息(子窗口同样可以通过该方法发送消息给父窗口)
它可用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的iframe消息传递
- 上面三个场景的跨域数据传递
// 父窗口打开一个子窗口
var openWindow = window.open('http://test2.com', 'title');
// 父窗口向子窗口发消息(第一个参数代表发送的内容,第二个参数代表接收消息窗口的url)
openWindow.postMessage('Nice to meet you!', 'http://test2.com');
调用message事件,监听对方发送的消息
// 监听 message 消息
window.addEventListener('message', function (e) {
console.log(e.source); // e.source 发送消息的窗口
console.log(e.origin); // e.origin 消息发向的网址
console.log(e.data); // e.data 发送的消息
},false);
(3) JSONP
JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,兼容性好(兼容低版本IE),缺点是只支持get请求,不支持post请求。
核心思想:网页通过添加一个
// 向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
// 处理服务器返回回调函数的数据
- jQuery ajax
$.ajax({
url: 'http://www.test.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "handleCallback", // 自定义回调函数名
data: {}
});
- Vue.js
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'handleCallback'
}).then((res) => {
console.log(res);
})
(4) 配置webpack代理解决跨域
proxyTable: {
'/api': {
target: 'http://beta-pvg.goms.com.cn',
changeOrigin: true, //是否跨域
pathRewrite: {
'^/api': ''
}
}
},
4.CORS
CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。
(1) 普通跨域请求:只需服务器端设置Access-Control-Allow-Origin
(2) 带cookie跨域请求:前后端都需要进行设置
【前端设置】根据xhr.withCredentials字段判断是否带有cookie
web安全问题
1.SQL注入(SQL Injection)
定义
由于程序中对用户输入检查不严格,用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据
,这就是所谓的SQL Injection,即SQL注入。
原因分析
其本质是对于输入检查不充分,导致SQL语句将用户提交的非法数据当作语句的一部分来执行。
风险
SQL盲注:如果系统屏蔽了详细的错误信息,那么对攻击者而言就是盲注入,可能会查看、修改或删除数据库条目和表
使用SQL注入的认证旁路:可能会绕开 Web 应用程序的认证机制
例子
预防措施
- 不要使用动态SQL
避免将用户提供的输入直接放入SQL语句中;最好使用准备好的语句和参数化查询,这样更安全。 - 不要将敏感数据保留在纯文本中
加密存储在数据库中的私有/机密数据;这样可以提供了另一级保护,以防攻击者成功地排出敏感数据。 - 限制数据库权限和特权
将数据库用户的功能设置为最低要求;这将限制攻击者在设法获取访问权限时可以执行的操作。 - 避免直接向用户显示数据库错误
攻击者可以使用这些错误消息来获取有关数据库的信息。 - 对访问数据库的Web应用程序使用Web应用程序防火墙(WAF)
这为面向Web的应用程序提供了保护,它可以帮助识别SQL注入尝试;根据设置,它还可以帮助防止SQL注入尝试到达应用程序(以及数据库)。 - 定期测试与数据库交互的Web应用程序
这样做可以帮助捕获可能允许SQL注入的新错误或回归。 - 将数据库更新为最新的可用修补程序
这可以防止攻击者利用旧版本中存在的已知弱点/错误。
2.跨站脚本攻击(XSS)
定义
XSS攻击是Web攻击中最常见的攻击方法之一,它是通过对网页注入可执行代码且成功地被浏览器 执行,达到攻击的目的
形成了一次有效XSS攻击,一旦攻击成功,它可以获取用户的联系人列表,然后向联系人发送虚假诈骗信息,可以删除用户的日志等等,有时候还和其他攻击方式同时实 施比如SQL注入攻击服务器和数据库、Click劫持、相对链接劫持等实施钓鱼,它带来的危害是巨 大的,是web安全的头号大敌。
分类
XSS反射型攻击,恶意代码并没有保存在目标网站,通过引诱用户点击一个链接到目标网站的恶意链接来实施攻击的。
XSS存储型攻击,恶意代码被保存到目标网站的服务器中,这种攻击具有较强的稳定性和持久性,比较常见场景是在博客,论坛等社交网站上,但OA系统,和CRM系统上也能看到它身影,比如:某CRM系统的客户投诉功能上存在XSS存储型漏洞,黑客提交了恶意攻击代码,当系统管理员查看投诉信息时恶意代码执行,窃取了客户的资料,然而管理员毫不知情,这就是典型的XSS存储型攻击。
例子:
对用户提交的所有内容进行过滤,对url中的参数进行过滤,过滤掉会导致脚本执行的相关内容;
对动态输出到页面的内容进行html编码,使脚本无法在浏览器中执行。
对输入的内容进行过滤,可以分为黑名单过滤和白名单过滤。黑名单过滤虽然可以拦截大部分的XSS攻击,但是还是存在被绕过的风险。白名单过滤虽然可以基本杜绝XSS攻击,但是真实环境中一般是不能进行如此严格的白名单过滤的。
对输出进行html编码,就是通过函数,将用户的输入的数据进行html编码,使其不能作为脚本运行。
如下,是使用php中的htmlspecialchars函数对用户输入的name参数进行html编码,将其转换为html实体
#使用htmlspecialchars函数对用户输入的name参数进行html编码,将其转换为html实体
$name = htmlspecialchars( $_GET[ 'name' ] );
如下,图一是没有进行html编码的,图2是进行了html编码的。经过html编码后script标签被当成了html实体。
我们还可以服务端设置会话Cookie的HTTP Only属性,这样,客户端的JS脚本就不能获取Cookie信息了
3.跨站请求伪造(CSRF/XSRF )
在浏览器的同源策略的约束下,对于跨域资源交互进行处理时,【通常允许跨域资源嵌入(Cross-origin embedding)】
下面是常见跨域资源嵌入示例:
- 标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。
- 嵌入图片。支持的图片格式包括PNG,JPEG,GIF,BMP,SVG,...
3.1 CSRF 攻击原理
- 用户浏览位于目标服务器 A 的网站。并通过登录验证。
- 获取到 cookie_session_id,保存到浏览器 cookie 中
- 在未登出服务器 A ,并在 session_id 失效前用户浏览位于 hacked server B 上的网站。
- server B 网站中的嵌入资源起了作用,迫使用户访问目标服务器 A
- 由于用户未登出服务器 A 并且 sessionId 未失效,请求通过验证,非法请求被执行
3.2 如何防御 CSRF
- referer 验证解决方案。
最简单的方法依赖于浏览器引用页头部。大多数浏览器会告诉 Web 服务器,哪个页面发送了请求。如:
POST /bank/transfer.aspx HTTP/1.1
Referer: http://evilsite.com/myevilblog
User-Agent: Mozilla/4....
Host: www.altoromutual.com
Content-Length: 42
Cookie: SessionId=x3q2v0qpjc0n1c55mf35fxid;
不少站点通过 referer 验证来防止盗链本站图片资源。
不过由于 http 头在某些版本的浏览器上存在可被篡改的可能性,所以这个解决方案并不完善
- Token 解决方案
令牌解决方案向表单添加一个参数,让表单在用户注销时或一个超时期限结束后过期
或者将服务端动态生成的 Token 加入到 自定义 http 请求头参数中
POST /bank/transfer.aspx HTTP/1.1
Referer: https://www.altoromutual.com/bank
xsrftoken: JKBS38633jjhg0987PPll
User-Agent: Mozilla/4....
Host: www.altoromutual.com
Content-Length: 42
Cookie: SessionId=x3q2v0qpjc0n1c55mf35fxid;
creditAccount=1001160141&transferAmount=10
token 解决方案的问题在于前后端代码的巨大变更。并且每一步都动态生成 token 并且对 token 进行验证的话,也会造成额外的资源开销。可以尝试在关键性操作的地方再加上 token 验证逻辑。但是,token 验证所带来的前后端代码的变动所带来的消耗,则需要慎重考虑
- userId 解决方案
相对于 token 这样的需要前后端逻辑作出改动,以及造成额外资源开销的方式以外还有一种小巧的防范措施。就是用户每次请求关键性数据时,后端接口在设计时都需要用户提交相关的 userId。userId 可以存储到浏览器的 localStorage 中,这样便能进一步提高接口安全性,防止跨站请求伪造。