JavaScript 基础知识
一、变量类型和计算
1. 值类型和引用类型
- 值类型:值是基础类型,包括
number string boolean Symbol
。 - 引用类型:值是对象,包括
null
(特殊的应用类型)。
值类型和引用类型的区别:
- 值类型存储:变量(key)和值(value)都存储在栈中
- 值类型存储:变量(key)和值(value)存储在栈中,此时的值(value)存储的是对象的在堆中的内存地址,对象存储在堆中。
null
指向的是一个空的地址
2. 类型判断
(1)typeof 运算符
- 识别所有值类型
- 识别函数
- 判断是否是引用类型
object
(不可再细分)
// 判断所有值类型
let a; // typeof a === 'undefined'
const str = "abc"; // typeof str === 'string'
const n = 100; // typeof n === 'number'
const b = true; // typeof b === 'boolean'
const s = Symbol("s"); // typeof s === 'symbol'
// 判断引用类型
const a = {}; // typeof a === 'object'
const b = null; // typeof b === 'object'
// 判断方法
function a() {} // typeof a === 'function'
2
3
4
5
6
7
8
9
10
11
12
13
(2)深拷贝
- 注意判断是值类型和引用类型
- 注意判断是数组还是对象
- 递归
function deepClone(param = {}) {
if (param === null || typeof !== 'object') {
return param;
}
let result;
if (param instanceof Array) {
result = []
} else {
result = {}
}
for (let key in param) {
// 保证 key 不是原型上的属性
if (param.hasOwnProperty(key)) {
// 递归
result[key] = deepClone(param[key])
}
}
return result
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
3. 变量计算问题
+
运算符
(1)const a = 100 + 1; // 101
const b = 100 + "1"; // "1001"
const c = 100 + true; // 101
const d = 100 + false; // 100
const e = true + "1"; // "true1"
2
3
4
5
注意:+
会进行类型转换
==
运算符
(2)100 == "100"; // true
0 == ""; // true
0 == false; // true
false == ""; // true
null == undefined; // true
2
3
4
5
注意:如何使用===
,以上结果相反,==
会进行类型转换。因此,建议除了== null
之外,其他一律用===
const obj = { x: 100 };
if (obj.a == null) {
}
// 相当于:
if (obj.a == null || obj.a === undefined) {
}
2
3
4
5
6
7
(2)if 语句和逻辑运算符
使用 if 进行逻辑判断时,会对变量进行类型转换
以下概念非官方概念
truly
变量:!!a === true
的变量falsely
变量:!!a === false
的变量
当为truly
变量时,if
条件为真,当falsely
变量时,条件为假
以下是falsely
变量,除此之外都是truly
变量
!!0 === false;
!!NaN === false;
!!"" === false;
!!null === false;
!!undefined === false;
!!false === false;
2
3
4
5
6
二、原型和原型链
1. class 和继承
2. 原型
- 每一个
class
都有显示原型prototype
- 每个实例都有隐式原型
__proto__
- 实例的
__proto__
指向对应class
的prototype
3. 原型链
4. 类型判断 instanceof
instanceof
运算符返回一个布尔值,表示对象是否为某个构造函数的实例。 instanceof
的原理是检查右边构造函数的 prototype
属性,是否在左边对象的原型链上。
因此,下面两种写法是等价的。
v instanceof Vehicle;
// 等同于
Vehicle.prototype.isPrototypeOf(v);
2
3
注意,instanceof 运算符只能用于对象,不适用原始类型的值。
原型对象的 constructor 属性
prototype
对象有一个constructor
属性,默认指向prototype
对象所在的构造函数。- 由于
constructor
属性定义在prototype
对象上面,意味着可以被所有实例对象继承。
Student.prototype.constructor === Student; // true
student.__proto__.constructor === Student; // true
2
三、作用域和闭包
1. 作用域
作用域: 当前的执行上下文,变量(变量名,函数名)或表达式生效的范围 变量对象: 每一个执行环境都有一个变量对象,包括变量和函数。函数的执行环境的变量对象还包括arguments
对象和形参。 作用域链: 每一个执行环境都有一个作用域链,本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。作用域链的最前端,始终是当前正在执行的代码所在环境的变量对象。 作用域的查找机制: 只能是向上找,不能向下找
2. 闭包
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(即函数内部引用了外部变量),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
自由变量查找规则:从函数定义的地方开始,向上级作用域查找,而不是在执行的地方
四、异步基础
1. 单线程和异步
- JS 是单线程语言,只能同时做一件事儿
- 浏览器和 nodejs 已支持 JS 启动进程,如 Web Worker
- JS 和 DOM 渲染共用同一个线程,因为 JS 可修改 DOM 结构
- 遇到等待(网络请求,定时任务)不能卡住
- 需要异步
- 回调 callback 函数形式
2. 异步和同步
- 基于 JS 是单线程语言
- 异步不会阻塞代码执行
- 同步会阻塞代码执行
3. 异步的应用场景
- 处理耗时任务,如网络请求
- 定时任务
4. Promise
解决
callback hell(回调地狱)
问题而产生,嵌套调用 -> 流式调用
五、异步进阶
1. event loop
(1)背景
- JS 是单线程运行的
- 异步要基于回调来实现
- event loop 就是异步回调的实现原理
(2)event loop 过程
- 同步代码,一行一行放在
Call Stack
中执行 - 遇到异步,会先“记录”下来,等待异步回调触发(定时、网络请求等结束)
- 时机到了,就移动到
Callback Quene
中 - 如果
Call Stack
为空(即同步代码执行完毕),Event Loop
开始工作 - 轮询查找
Callback Quene
,如有任务则移动到Call Stack
中执行 Event Loop
继续轮询查找
2. promise 进阶
- 三种状态
- 状态的表现和变化
then
和catch
对状态的影响
(1)三种状态
- pending 等待(正在处理)
- resolved 解决(成功)
- rejected 拒绝(失败)
状态的变化和特点:
- pending -> resolved 或 pending -> rejected
- resolved 和 rejected 互斥,只有一种结果
- 变化不可逆
(2)状态的表现和变化
pending -> resolved
:触发then
的回调pending -> rejected
:触发catch
的回调then
函数正常返回状态为resolved
的promise
,里面有报错则返回状态为rejected
的promise
catch
函数正常返回状态为resolved
的promise
,里面有报错则返回状态为rejected
的promise
代码演示:
// Promise.resolve() 返回一个状态为 resolved 的 Promise
Promise.resolve()
.then(() => {
console.log(1);
throw new Error("err"); // 抛出异常,then 返回状态为 rejected 的 Promise
})
.catch(() => {
console.log(2); // 未报错,catch 返回状态为 resolved 的 Promise
})
.then(() => {
console.log(3); // 未报错,then 返回状态为 resolved 的 Promise
});
// 结果:
// 1
// 2
// 3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
3. async/await
(1)背景
- 异步回调
callback hell
Promise then catch
链式调用,但也是基于回调函数async/await
是同步语法,彻底消灭回调函数
(2)使用
(3)async/await 和 Promise 的关系
- 执行
async
函数,返回的是Promise
对象 await
相当于Promise
的then
try...catch
可捕获异常,代替了Promise
的catch
代码演示:
async function fn() {
return 200; // 最后会转换成 return Promise.resolve(200)
// return Promise.resolve(200),和上面的结果一样
}
fn(); // 返回 Promise.resolve(200)
!(async function() {
// 以下三种结果都是一样
const d1 = await fn(); // 200,fn()返回的是 Promise.resolve(200)
const d2 = await 200; // 200,最终会转换成 await Promise.resolve(200)
const d3 = await Promise.resolve(200); // 200
const p1 = Promise.reject('err1')
try {
const res1 = await p1;
}catch(e => {
console.log(e) // err1
})
const p2 = Promise.reject('err2'); // rejected 状态,会报错
const res2 = await p2; // 若以 await 相当于 then 来看,then不会执行,所以这一步没有执行
console.log('res2', res2); // 不会打印
})()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
(4)异步的本质
- async/await 是解决异步回调的解决方案,是一种语法糖
- async/await 并不能改变 JS 单线程的本质,还是得有基于 event loop 事件循环的异步回调
代码演示:
async function async1() {
console.log("async1 start"); // 2
await async2();
console.log("async1 end"); // 5
// await 的后面,都可以看错是 callback 里的内容,即异步,所有同步代码执行完成后才会执行异步
}
async function async2() {
console.log("async2"); // 3
}
console.log("script start"); // 1
async1();
console.log("script end"); // 4
2
3
4
5
6
7
8
9
10
11
12
13
14
(5)for...of 的使用
同步循环,
for...in,for,forEach
,一次性遍历所有元素 异步循环,for...of
,会等待异步执行完毕后才继续下一轮循环
4. 微任务/宏任务
(1)什么是宏任务,什么是微任务
- 宏任务:
setTimeout,setInterval,Ajax,DOM事件
- 微任务:
Promise,async/await
- 微任务先于宏任务执行
(2)event loop 和 DOM 渲染
- 每次
Call Stack
空闲时(即每次轮询结束),即同步任务执行完毕 - 尝试渲染
DOM
,若此时DOM
结构有变化则渲染DOM
,若否则触发Event Loop
(3)微任务和宏任务的区别
- 宏任务:DOM 渲染后触发,如 setTimeout
- 微任务:DOM 渲染钱触发,如 Promise
六、Web API
1. DOM
DOM(Document Object Model 文档对象模型)
(1)DOM 的本质
- xml
- html
- 树(DOM 树)
(2)DOM 节点操作
1. 节点获取
getElementById
getElementsByTagName
getElementsByClassName
querySelectorAll
2. attribute
标签上的属性,如
<p item-data="dddd" style="font-size: 12px;">
中的item-data
和style
可以通过以下 API 进行读写:
- getAttribute
- setAttribute
p.setAttribute("item-data", "aaaa"); // <p item-data="aaaa"></p>
p.getAttribute("item-data"); // aaaa
2
3. property
HTMLElment
对象的property
属性,即标签上的attribute
,可以通过 JS 读写
<p class="a"></p>;
p.className; // a
p.style.width = "12px"; // <p class="a" style="width:12px;"></p>
2
3
4
4. property 和 attribute 的区别
property
是DOM
中的属性,是JavaScript
里的对象;attribute
是HTML
标签上的特性,它的值只能够是字符串;attributes
是属于property
的一个子集- 两者的修改都可能引起
DOM
重新渲染(attribute
的修改一定会引起DOM
重新渲染)
(3)DOM 结构操作
1. 新增节点
createElement
,创建新节点appendChild
,将新节点插入到某个容器,如果是现有节点则移动
2. 获取子元素列表,获取父元素
childNodes
属性
<div id="parent">
<p>a</p>
<p>b</p>
<p>c</p>
</div>;
const div = document.getElementById("parent");
// 1. 因为 childNodes 是一个类数组,需要用 slice 变成真正的数组
// 2. childNodes 中包含一些 text,需要把真正的 p 标签过滤出来,nodeType为1就是真正的标签
const pList = Array.prototype.slice
.call(div.childNodes)
.filter((child) => child.nodeType === 1);
2
3
4
5
6
7
8
9
10
11
12
13
3. 删除元素
removeChild
div.removeChild(childNode);
(4)DOM 性能
- DOM 操作非常“昂贵”,避免频繁的 DOM 操作
- 对 DOM 查询做缓存
- 将频繁操作改为一次性操作
// 将频繁操作改为一次性操作
const listNode = document.getElmentById("list");
const frag = document.createDocumentFragment();
for (let x = 0; x < 10; x++) {
const li = document.createElement("li");
li.innerHTML = "List Item " + x;
frag.appendChild(li);
}
listNode.appendChild(frag);
2
3
4
5
6
7
8
9
10
11
12
13
2. BOM
BOM-Browser Object Model
- navigator
- screen
- location
- history
(1)navigator
获取 userAgent 信息:
const ua = navigator.userAgent;
const isChrome = ua.indexOf("Chrome");
2
(2)screen
获取屏幕大小
screen.width
screen.height
(3)location
获取地址信息:
location.href
:整个网址信息,包括路径参数,hash 等location.protocol
:协议,如http:
,https:
location.hostname
:主机名,如何www.baidu.com
location.pathname
:路径,如/index.html/user
location.search
:查询参数,?
后面的key value
location.hash
:hash
参数,#
后面的参数
(4)history
路由信息:
history.back()
history.forward()
3. 事件
- 概述
- 事件冒泡
- 事件代理
(1)概述
DOM
的事件操作(监听和触发),都定义在 EventTarget
接口。所有节点对象都部署了这个接口 EventTarget
实例的三个方法:
addEventListener
removeEventListener
dispatchEvent
addEventListener
target.addEventListener(type, listener[, useCapture]);
该方法接受三个参数。
type
:事件名称,大小写敏感。listener
:监听函数。事件发生时,会调用该监听函数。useCapture
:布尔值或对象,为布尔值时表示监听函数是否在捕获阶段(capture
),默认为false
(监听函数只在冒泡阶段被触发)。为对象时,有以下属性:capture
:布尔值,表示该事件是否在捕获阶段触发监听函数。once
:布尔值,表示监听函数是否只触发一次,然后就自动移除。passive
:布尔值,表示监听函数不会调用事件的preventDefault
方法。如果监听函数调用了,浏览器将忽略这个要求,并在监控台输出一行警告。
removeEventListener
target.addEventListener(type, listener[, useCapture]);
注意:
- 必须和使用
addEventListener
同一个节点 listener
不能是匿名函数useCapture
必须和addEventListener
时一样
dispatchEvent
target.dispatchEvent(event);
该方法返回一个布尔值,只要有一个监听函数调用了 Event.preventDefault()
,则返回值为 false
,否则为 true
。
(2)事件传播
一个事件发生后,会在子元素和父元素之间传播(propagation
)事件的传播有三个阶段:
- 第一阶段:从
window
对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase
)。 - 第二阶段:在目标节点上触发,称为“目标阶段”(
target phase
)。 - 第三阶段:从目标节点传导回
window
对象(从底层传回上层),称为“冒泡阶段”(bubbling phase
)。
(3)事件代理
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(
delegation
)。
- 代码简洁
- 减少浏览器内存占用
- 但不要滥用,如适用于 list item 的 click 事件监听
// 通用的事件绑定函数
function bindEvent(elem, type, selector, callback) {
if (callback == null) {
callback = selector;
selector = null;
}
elem.addEventListener(type, (e) => {
let target = e.target;
if (selector) {
// 需要代理
if (target.matches(selector)) {
callback.call(target, e);
}
} else {
// 不需要代理
callback.call(target, e);
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
4. Ajax
XMLHttpRequest
- 状态码
- 跨域:同源策略,跨域解决方案
(1)使用 XMLHttpRequest 手写简易的 post 和 get 方法
// GET 请求
const xhr = new XMLHttpRequest();
// 第三个参数 false 表示异步
xhr.open("GET", "/data/test.json", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText);
}
}
};
// get 请求不传参数时,填null
xhr.send(null);
// POST 请求
const xhr = new XMLHttpRequest();
// 第三个参数 false 表示异步
xhr.open("POST", "/login", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText);
}
}
};
const postData = {
userName: "张三",
password: "123456",
};
// 需要转成字符串
xhr.send(JSON.stringify(postData));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
(2)状态码
xhr.readyState
状态码:
- 0 - (未初始化)还没有调用
send
方法 - 1 - (载入)已经调用
send
方法,正在发送请求 - 2 - (载入完成)
send
方法执行完成,已经接收到全部响应内容 - 3 - (交互)正在解析响应内容
- 4 - (完成)响应内容解析完成,可以在客户端调用
xhr.status
状态码:(即http
状态码)
- 2xx - 表示成功处理请求,如 200
- 3xx - 需要重定向,浏览器直接自动跳转,如 301(永久重定向)、302(临时重定向)、304(资源未改变,可以使用浏览器缓存)
- 4xx - 客户端请求错误,如 404(资源未找到)、403(无权限)
- 5xx - 服务器端错误
(3)跨域
1. 同源策略
- 出于安全性的考虑,
ajax
请求时,浏览器要求当前网页和server
必须同源 - 同源:协议、域名和端口,三者必须一致
- 跨域的例子,如 前端:
http://a.com:8080/
;后端:https://b.com/api/xxx
加载图片、css、js 可无视同源策略:
<img src="跨域的图片地址"
/>,可用于统计打点,可使用第三方统计服务<link href="跨域的 css 地址"
/><script href="跨域的 js 地址"
/>,可实现JSONP
2. 跨域
- 所有的跨域,都必须经过
server
端允许和配合 - 未经
server
端允许就实现跨域,说明浏览器有漏洞,危险信号
3. 跨域的方法
JSONP
CORS
(1)JSONP 跨域
关键点:
<script>
可绕过跨域限制- 服务器可以任意动态拼接数据返回
<script>
window.callback = function (data) {
// 跨域信息
console.log(data); // "{ x: 100, y: 200 }"
};
</script>
<!-- 通过 script 标签进行跨域请求,返回 callback({ x: 100, y: 200 }) -->
<script src="https://www.abc.com/test.js"></script>
<script>
window.func = function (data) {
// 跨域信息
console.log(data); // "{ x: 100, y: 200 }"
};
</script>
<!-- 通过 script 标签进行跨域请求,自定义 callback 名称,通过服务器动态拼接,返回 func({ x: 100, y: 200 }) -->
<script src="https://www.abc.com/test.js?callback=func"></script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(1)CORS 跨域
在服务端中设置:
// 第二个参数填写允许跨域的域名称,不建议直接写 "*"
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8011");
response.setHeader("Access-Control-Allow-Headers", "X-Request-With");
response.setHeader(
"Access-Control-Allow-Methods",
"PUT,POST,GET,DELETE,OPTIONS"
);
// 接收跨域的cookie
response.setHeader("Access-Control-Allow-Credentials", "true");
2
3
4
5
6
7
8
9
10
4. ajax 常用插件
- jQuery.ajax()
- fetch
- axios
5. 存储
(1)cookie
- cookie 存储于客户端本地
- 携带于
http
请求头中,本身为浏览器和server
通讯而设计 - 被“借用”来进行本地存储
- 可用
document.cookie = '...'
来修改
缺点:
- 存储大小有限制,最大
4KB
http
请求时需要发送到服务端,增加请求数据量- 只能用
document.cookie = '...'
修改,太过简陋
(2)localStorage 和 sessionStorage
- HTML5 专门为前端存储而设计,最大可存储
5M
- API 简单易用
setItem getItem
- 不会随着
http
请求被发送出去 - 以字符串形式存储
区别:
localStorage
数据会永久存储,除非代码或手动删除sessionStorage
数据只会存在于当前会话,浏览器关闭则清空- 一般用
localStorage
会更多一些
六、HTTP 协议
1. http 状态码
(1)状态码分类
1xx
:服务器收到请求2xx
:请求成功,如 2003xx
:重定向,如 3024xx
:客户端错误,如 404(not found)5xx
:服务端错误,如 500
(2)常见状态码
200
:请求成功301
:永久重定向(配合location
,header
的location
返回重定向的地址,浏览器自动跳转,如原始资源永久性移除,迁移到另一个地方。或域名更改等)302
:临时重定向(配合location
,header
的location
返回重定向的地址,浏览器自动跳转,如原始资源临时性迁移,随时会改变注意,可能造成网址 URL 劫持)304
:资源未被修改,可以使用浏览器缓存404
:资源未找到403
:禁止访问,没有权限500
:服务器错误504
:网关超时
(3)关于协议和规范
状态码是 http 协议的规范,不能违反规范,如 IE 浏览器
2. http methods
(1)传统的 methods
get
:获取服务器的数据post
:向服务器提交数据
(2)现在的 methods
get
:获取数据post
:新建数据patch/put
:更新数据delete
:删除数据
3. Restful API
- 一种新的 API 设计方法(早已推广使用)
- 传统 API 设计:把每个 url 当做一个功能
- Restful API 设计:把每个 url 当做一个唯一的资源
如何设计资源
- 不使用 url 参数
- 用 method 表示操作类型
4. http headers
(1)常见的 Request Headers
Accept
:浏览器可接收的数据格式Accept-Encoding
:浏览器可接收的压缩算法,如 gzipAccept-Languange
:浏览器可接收的语言,如 zh-CNConnection:keep-alive
:一次 TCP 连接重复使用cookie
Host
:请求的服务器User-Agent
:(简称 UA)浏览器信息Content-Type
:发送数据的格式,如 application/json
(2)常见的 Response Headers
Content-Type
:发送数据的格式,如 application/jsonContent-length
:返回数据的大小,多少字节Content-Encoding
:返回数据的压缩算法,如 gzipSet-Cookie
:设置 cookiecache-control
:缓存控制
(3)缓存相关的 headers
5. http 缓存
(1)关于缓存
- 缓存:浏览器首次加载资源时,对一些资源保存起来,下次访问时尽量避免网络请求,优先使用缓存
- 缓存目的:提升加载速度,性能优化
- 可以被缓存资源:静态资源(js css img)
(2)http 缓存-强制缓存
由服务端设置响应头开启缓存:如 Cache-Control : max-age=3423422
Cache-Control
的值:
max-age
:设置缓存有效时间,在时效内时不向服务器请求资源,直接从浏览器缓存读取数据。不在时效内时,重新请求服务器资源,再缓存,如此反复。no-cache
:不用缓存,直接请求服务器资源,不管服务器如何处理no-store
:不用缓存,也不让服务端做缓存private
:只允许客户端(浏览器等最终的客户端),不允许中间路由和代理做缓存public
:允许中间的路由、代理做缓存
关于 Expires
- 同在 Response Headers 中
- 同为控制缓存过期
- 已被 Cache-Control : max-age 代替
(3)http-协商缓存
1. 协商缓存策略
- 服务端缓存策略(由服务端判断是否使用缓存内容)
- 服务端判断客户端资源,是否和服务端资源一样
- 一致则返回 304,否则返回 200 和最新的资源
2. 资源标识
- 在 Response Headers 中,有两种
Last-Modified
资源的最后修改时间Etag
资源的唯一标识(一个字符串,类似人类的指纹)
3. Last-Modified 和 Etag
- 两者可以共存,会优先使用 Etag
- Last-Modified 只能精确到秒级
- 如果资源重复生成,而内容不变,则 Etag 更精确
七、开发环境
八、运行环境
1. 网页的加载过程
2. 前端性能优化
3. 手写防抖函数 debounce
(1)场景
- 监听一个输入框,文字变化后会触发 change 事件
- 直接用 keyup 事件,则会频繁触发 change 事件
- 防抖:用户输入结束或暂停时,才会触发 change 事件
(2)代码实现
function debounce(fn, delay = 500) {
let timer = null;
return function () {
// 清除计时器,重新计时
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this.arguments);
timer = null;
}, delay);
};
}
2
3
4
5
6
7
8
9
10
11
12
13
4. 手写节流函数 throttle
(1)场景
- 拖拽一个元素时,要随时拿到该元素被拖拽的位置
- 直接用 drag 事件,则会频繁触发,很容易造成卡顿
- 节流:无论拖拽速度多快,都会以稳定的时间间隔触发一次,如 100ms
(2)代码实现
function throttle(fn, delay = 100) {
let timer = null;
return function () {
// 如果 timer 存在,则忽略不执行,直接返回
if (timer) {
return;
}
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, delay);
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
5. 安全
(1)XSS 跨站请求攻击
1. 场景
- 一个博客网站,发表一篇博客,其中嵌入
<script>
脚本 - 脚本内容:获取 cookie,发送到我的服务器(服务器配合跨域)
- 发布这篇博客,有人查看它,我轻松查看访问者的 cookie
2. XSS 预防
- 替换特殊字符,如
<
变为<
,>
变为>
<script>
变成<script>
,直接显示,而不会作为脚本执行- 前端要替换,后端也要替换,都做总不会出错
(2)XSRF 攻击
1. 场景
- 你正在购物,看中了某个商品,商品 id 是 100
- 付费接口是
xx.com/pay?id=100
,但没有任何验证 - 我是攻击者,我看中了一个商品,id 是 200
- 我向你发送一封电子邮件,邮件标题很吸引人
- 但邮件正文隐藏
<img src="xxx.com/pay?id=200"/>
- 你一查看邮件,就帮我购买了 id 是 200 的商品
2. XSS 预防
- 使用
post
接口 - 增加验证,例如密码,短信验证码,指纹等