跨域与JSONP
# 跨域与JSONP
# 1. 同源策略
同源策略(Same Origin Policy, SOP)是浏览器的一项重要安全机制,用于保护用户数据的安全性和隐私。它限制了不同源之间的交互,以防止潜在的恶意操作。下面详细介绍同源策略的概念、定义和限制。
# 1.1 什么是同源
同源指的是两个页面具有相同的协议(协议如 http
、https
)、域名(域名如 example.com
)和端口(如 :80
、:443
)。如果三个部分都相同,则这两个页面被认为是同源的。
示例:
https://example.com:443/page1
和https://example.com:443/page2
是同源。http://example.com:80/page1
和https://example.com:80/page2
是不同源(不同的协议)。
代码示例:
// 比较两个 URL 是否同源的函数
function isSameOrigin(url1, url2) {
var a = document.createElement('a');
a.href = url1;
var origin1 = a.protocol + '//' + a.host;
a.href = url2;
var origin2 = a.protocol + '//' + a.host;
return origin1 === origin2;
}
console.log(isSameOrigin('https://example.com:443/page1', 'https://example.com:443/page2')); // true
console.log(isSameOrigin('http://example.com:80/page1', 'https://example.com:443/page2')); // false
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1.2 什么是同源策略
同源策略是浏览器的一种安全机制,用于限制不同源之间的交互。这种策略的目的是为了防止潜在的恶意操作,例如跨站脚本攻击(XSS)和跨站请求伪造(CSRF)。同源策略的主要限制包括:
无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB:
- JavaScript 代码不能读取或修改不同源的 Cookie 或本地存储数据。
- 例如,
example.com
的网页不能访问otherdomain.com
的 Cookie 或 LocalStorage 数据。
// 尝试读取非同源网站的 Cookie(将会失败) console.log(document.cookie); // 只会显示当前同源网站的 Cookie
1
2无法接触非同源网页的 DOM:
- JavaScript 代码不能直接访问和操作不同源网页的 DOM 元素。
- 例如,
example.com
的脚本不能直接修改otherdomain.com
页面上的内容。
// 尝试访问不同源的 DOM(将会抛出安全错误) // document.getElementById('someElement'); // 这将失败,如果跨域
1
2无法向非同源地址发送 Ajax 请求:
- 浏览器会阻止通过 JavaScript 发起的跨域 Ajax 请求,除非服务器支持跨域资源共享(CORS)。
- 例如,
example.com
的 JavaScript 不能向api.otherdomain.com
发送请求,除非该 API 允许跨域请求。
// 尝试发送跨域请求(将会被浏览器阻止) fetch('https://api.otherdomain.com/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error)); // 可能会失败,取决于 CORS 设置
1
2
3
4
5
总结
同源策略是现代浏览器为了保护用户数据和隐私所实现的一项重要安全措施。它确保了网站之间的数据隔离,并防止了许多类型的网络攻击。在开发过程中,需要注意如何在遵守同源策略的同时,实现合法的跨域交互,例如通过配置 CORS 或使用 JSONP 进行跨域请求。
# 2. 跨域
跨域是指在浏览器的同源策略下,不同源之间进行资源交互的情况。由于浏览器的同源策略限制了不同源之间的直接交互,因此在进行跨域操作时,需要采取特定的技术手段来解决这些限制。
# 2.1 什么是跨域
跨域指的是两个 URL 的协议、域名或端口不一致的情况。换句话说,当你尝试访问一个与当前页面不同源的 URL 时,就是跨域操作。同源则是指两个 URL 的协议、域名和端口都相同,这样可以被认为是同源的。
跨域的根本原因是浏览器的同源策略(Same Origin Policy),它不允许不同源之间进行直接的资源交互。具体而言,当一个网页尝试从不同源(协议、域名或端口)加载或发送数据时,浏览器会阻止这些操作,以防止潜在的安全问题。
示例:
同源 URL:
// 同源的 URL 示例(相同协议、域名和端口) const url1 = 'https://example.com:443/page'; const url2 = 'https://example.com:443/anotherpage';
1
2
3跨域 URL:
// 不同源的 URL 示例(不同协议、域名或端口) const url1 = 'https://example.com:443/page'; // 同源 const url2 = 'http://example.com:80/anotherpage'; // 跨域(协议不同) const url3 = 'https://otherdomain.com:443/page'; // 跨域(域名不同)
1
2
3
4
# 2.2 浏览器对跨域请求的拦截
浏览器的同源策略会拦截跨域请求,主要表现在以下几个方面:
XMLHttpRequest 和 Fetch API:
- 当使用
XMLHttpRequest
或Fetch
API 发起跨域请求时,浏览器会阻止这些请求,除非服务器明确允许这些跨域请求。这是因为跨域请求可能会暴露敏感数据或遭受恶意攻击。
// 尝试发送跨域请求的代码(会被浏览器拦截,除非服务器允许) fetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error)); // 跨域请求会失败,取决于 CORS 设置
1
2
3
4
5- 当使用
Cookies 和 LocalStorage:
- JavaScript 代码不能访问不同源的 Cookies 或 LocalStorage 数据。浏览器会阻止访问非同源的数据,以保护用户的隐私和安全。
// 尝试访问不同源的 Cookies(会失败) console.log(document.cookie); // 只能访问当前同源网站的 Cookies
1
2DOM 操作:
- JavaScript 代码无法访问和修改不同源的 DOM 元素。例如,尝试通过 JavaScript 访问嵌入的 iframe 内容时,如果 iframe 和父页面不在同一个源下,浏览器将阻止这种操作。
// 尝试访问不同源的 iframe 内容(会被浏览器阻止) const iframe = document.getElementById('myIframe'); const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; console.log(iframeDoc.body.innerHTML); // 跨域访问会失败
1
2
3
4
# 2.3 跨域的解决方案
为了处理跨域问题,开发者可以使用以下几种常见的技术和策略:
CORS(跨域资源共享):
- 服务器通过设置适当的 HTTP 头部(如
Access-Control-Allow-Origin
)来允许跨域请求。这是一种标准的解决方案,用于允许特定源或所有源访问资源。
// 服务器设置 CORS 头部的示例(Node.js 示例) const express = require('express'); const app = express(); app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); // 允许所有源 res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); next(); }); app.get('/data', (req, res) => { res.json({ message: 'This is a CORS-enabled response.' }); }); app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15- 服务器通过设置适当的 HTTP 头部(如
JSONP(JSON with Padding):
- JSONP 是一种通过
<script>
标签请求数据的跨域技术,只适用于 GET 请求。服务器返回的数据被包装在一个函数调用中,客户端通过这个函数接收数据。
// JSONP 请求的示例 function handleResponse(data) { console.log(data); } // 动态创建 JSONP 请求 const script = document.createElement('script'); script.src = 'https://api.example.com/data?callback=handleResponse'; document.head.appendChild(script);
1
2
3
4
5
6
7
8
9- JSONP 是一种通过
代理服务器:
- 使用代理服务器将跨域请求转发到目标服务器,从而绕过浏览器的同源策略限制。这种方法通常在开发环境中使用,或者通过在服务器端配置代理来解决跨域问题。
// 使用 Node.js 的 http-proxy-middleware 配置代理(在开发服务器中) const { createProxyMiddleware } = require('http-proxy-middleware'); app.use('/api', createProxyMiddleware({ target: 'https://api.example.com', changeOrigin: true, }));
1
2
3
4
5
6
7
# 3. JSONP
JSONP(JSON with Padding)是一种解决跨域问题的技术,它通过利用 <script>
标签的特性来绕过浏览器的同源策略限制。以下是对 JSONP 的详细介绍及其实现过程。
# 3.1 什么是 JSONP
JSONP 是一种允许跨域请求数据的技术。它通过动态生成 <script>
标签来实现跨域数据访问,因为 <script>
标签不受同源策略限制。
# 3.2 JSONP 的实现原理
由于浏览器的同源策略限制,Ajax 请求无法访问非同源的资源。但 <script>
标签不受同源策略限制,它可以加载任何来源的 JavaScript 文件。因此,JSONP 的实现原理如下:
- 动态创建一个
<script>
标签。 - 设置
<script>
标签的src
属性为跨域的接口地址,并在 URL 中附加一个回调函数名作为参数。 - 当请求的数据返回时,服务器会将数据包裹在回调函数中,浏览器加载这个
<script>
文件并执行回调函数。 - 回调函数执行后,客户端就可以处理返回的数据。
# 3.3 JSONP 的缺点
JSONP 的缺点包括:
- JSONP 仅支持 GET 请求,不支持 POST 请求。
- JSONP 不是一种标准的 XMLHttpRequest 请求方式,它不使用 XMLHttpRequest 对象,因此不具有一些标准请求的功能,比如设置请求头部等。
- JSONP 存在安全风险,因为服务器可以执行任意的 JavaScript 代码,这可能会导致 XSS(跨站脚本)攻击。
- JSONP 请求的返回结果无法直接控制或处理错误。
# 3.4 jQuery 中的 JSONP
jQuery 提供了对 JSONP 请求的支持。通过 jQuery 的 $.ajax()
方法,我们可以很方便地发起 JSONP 请求。
基本的 JSONP 请求示例:
$.ajax({
url: 'http://ajax.frontend.itheima.net:3006/api/jsonp?name=zs&age=20', // 请求的 URL 地址
dataType: 'jsonp', // 指定请求的数据类型为 JSONP
success: function(res) {
console.log(res); // 处理返回的数据
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('JSONP 请求失败:', textStatus, errorThrown);
}
});
2
3
4
5
6
7
8
9
10
在发起 JSONP 请求时,jQuery 会自动生成一个回调函数名,并在 URL 中附加 callback
参数,其名称类似 jQuery123456789
。
自定义参数及回调函数名称:
$.ajax({
url: 'http://ajax.frontend.itheima.net:3006/api/jsonp?name=zs&age=20',
dataType: 'jsonp',
jsonp: 'callback', // 发送到服务器的回调函数参数名称,默认是 callback
jsonpCallback: 'abc', // 自定义的回调函数名称,默认是 jQueryxxx 格式
success: function(res) {
console.log(res); // 处理返回的数据
},
error: function(jqXHR, textStatus, errorThrown) {
console.error('JSONP 请求失败:', textStatus, errorThrown);
}
});
2
3
4
5
6
7
8
9
10
11
12
# 3.5 jQuery 中 JSONP 的实现过程
在 jQuery 中,JSONP 的实现过程是通过动态创建和移除 <script>
标签来发起请求的。具体步骤如下:
- 动态创建一个
<script>
标签,并设置其src
属性为 JSONP 请求的 URL。 - 将
<script>
标签添加到<head>
部分或<body>
中,这样浏览器就会发送请求并加载 JavaScript 文件。 - 当服务器返回的数据时,数据会被包裹在指定的回调函数中。
- jQuery 会将这个回调函数添加到全局作用域中,并执行它。
- 请求完成后,jQuery 会自动从 DOM 中移除创建的
<script>
标签,以保持页面的整洁。
示例代码:
function jsonpCallback(data) {
console.log('JSONP 回调函数接收到的数据:', data);
}
// 动态创建 <script> 标签发起 JSONP 请求
var script = document.createElement('script');
script.src = 'http://ajax.frontend.itheima.net:3006/api/jsonp?name=zs&age=20&callback=jsonpCallback';
document.head.appendChild(script);
// 清理工作(可选)
script.onload = function() {
document.head.removeChild(script); // 请求完成后移除 <script> 标签
};
2
3
4
5
6
7
8
9
10
11
12
13
通过以上方法,你可以利用 JSONP 实现跨域数据访问,但需注意其安全风险,并考虑是否使用更现代的解决方案,如 CORS。
# 4. 防抖和节流
防抖(debounce)和节流(throttle)是两种常用的性能优化技术,用于处理频繁触发的事件。它们的主要目的是减少事件处理函数的执行频率,以提高性能和用户体验。以下是对这两种技术的详细介绍。
# 4.1 什么是节流
节流(throttle)是一种控制事件处理函数执行频率的技术。它的核心思想是:在规定的时间间隔内,只允许事件处理函数执行一次。即使事件被频繁触发,也会按照设定的时间间隔来限制事件处理函数的执行频率。
节流的实现原理:
- 使用一个定时器来控制函数的执行。
- 在每次触发事件时,检查定时器是否已经存在。
- 如果定时器不存在,则执行事件处理函数,并设置定时器。
- 定时器到期后,清除定时器,并允许下一次事件处理函数执行。
# 4.2 节流的应用场景
节流技术通常用于以下场景:
- 滚动事件处理: 在处理滚动条滚动事件时,可以使用节流来减少计算频率,提高性能。例如,懒加载图片或实现无限滚动时,可以通过节流来降低滚动事件处理的频率。
- 鼠标连续触发事件: 当用户快速移动鼠标时,可以使用节流来减少事件处理频率,从而避免频繁更新 DOM 元素,优化性能。
# 4.3 节流阀的概念
节流阀(throttle)可以理解为一个控制事件执行的开关。具体概念如下:
- 节流阀为空: 表示可以执行下次操作。
- 节流阀不为空: 表示不能执行下次操作。
- 操作执行完后,重置节流阀为空。 这样可以允许下一次操作的执行。
- 每次执行操作前,必须检查节流阀是否为空。
# 4.4 使用节流优化鼠标跟随效果
以下是使用节流技术优化鼠标跟随效果的示例代码:
$(function() {
var angel = $('#angel'); // 选择要跟随鼠标的元素
var timer = null; // 1. 定义一个定时器作为节流阀
$(document).on('mousemove', function(e) {
if (timer) {
return; // 3. 判断节流阀是否为空,如果不为空,则说明距离上次执行间隔不足
}
// 设置节流阀,16毫秒后执行事件处理函数
timer = setTimeout(function() {
$(angel).css('left', e.pageX + 'px').css('top', e.pageY + 'px'); // 更新元素的位置
timer = null; // 2. 清空节流阀,准备下一次事件触发
}, 16); // 每16毫秒执行一次
});
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
代码解释:
var angel = $('#angel');
:选择要跟随鼠标的元素。var timer = null;
:定义一个定时器,用作节流阀。$(document).on('mousemove', function(e) {...});
:为文档添加mousemove
事件监听器。if (timer) { return; }
:检查节流阀是否为空,如果不为空,直接返回,避免重复执行。timer = setTimeout(function() {...}, 16);
:设置一个定时器,在 16 毫秒后执行事件处理函数。16 毫秒是约 60 帧每秒的时间间隔。$(angel).css('left', e.pageX + 'px').css('top', e.pageY + 'px');
:更新元素的位置以跟随鼠标。
# 4.5 总结防抖和节流的区别
防抖(Debounce):
- 目的: 如果事件被频繁触发,防抖技术能确保只有最后一次事件处理函数会被执行。前面触发的事件都会被忽略。
- 应用场景: 搜索框输入提示、窗口大小调整、表单验证等。
- 实现方式: 设置一个定时器,在每次触发事件时清除之前的定时器,并设置新的定时器。只有在设定的延迟时间内没有新事件触发时,才执行处理函数。
节流(Throttle):
- 目的: 如果事件被频繁触发,节流技术能减少事件处理函数的执行频率,即每隔一定时间只执行一次处理函数。
- 应用场景: 滚动条事件、窗口大小调整、按钮点击等。
- 实现方式: 使用定时器控制事件处理函数的执行,确保在设定的时间间隔内只执行一次。
通过合理使用防抖和节流技术,可以有效优化性能,减少不必要的计算和 DOM 操作,提高用户体验。