ECMA-JSONP&Promise
1.JS跨文件访问【需求决定需要跨域】
根据JS的引入顺序,后引入的JS可以访问先引入JS内声明的全局变量或者函数
<!-- html文件 -->
<script src="a.js"></script>
<script src="a.js"></script>
<!-- a.js文件 -->
var num = 10;
function fn(){
console.log(666);
}
<!- b.js文件 -->
console.log(num); // 10
fn(); // 666
2.不受控属性和数据传输
不受控属性【src属性、href属性这2个不受同源策略影响】
根据浏览器的基本安全策略(同源策略)的控制,不同的域之间应该不可相互访问资源和内容,但是在HTML的标签中,有两个元素属性是不受同源策略的控制的,它们是src属性和href属性,这也意味着这两个属性可以加载其它网站的资源
异源数据传输
根据不受控属性的特点,我们可以通过script标签的src属性引入非同源的js文件,并获取到其中的数据,这种数据传输方式,也是jsonp跨域的来源
3.跨域
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的,广义的跨域:
- 资源跳转: A链接、重定向、表单提交
- 资源嵌入: 、
其实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景
CORS(XHR2)
CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。
JSONP
JSONP(JSON with Padding)填充式JSON,是应用JSON的一种新方法,只不过是被包含在函数调用中的JSON。JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数,而数据就是传入回调函数中的JSON数据,JSONP的基本原理是动态创建script标签,通过src属性获取异源js,从而获取到封装好的数据
4.JSONP访问百度接口
JSONP的原理
远程JS文件里面执行函数调用
本地JS文件里面函数声明
API:https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=key
<!-- html -->
<input type="text" id="key" placeholder="请输入关键字">
<ul id="oUl">
<li>htmldda</li>
</ul>
<!-- js -->
var baidu = {
sug: function ({ s }) { // 获取数据 解构s数组
var html = '';
s.forEach(item => { // 遍历s拿到每一项
html += `
<li>
<a href="https://www.baidu.com/s?wd=${item}" target="_blank">${item}</a>
</li>` // 拼接HTML结构
});
oUl.innerHTML = html; // 将HTML插入父元素
}
}
oKey.oninput = function () {
if (!this.value) { // 无输入内容时清空列表,并结束事件函数
oUl.innerHTML = '';
return;
}
var oScript = document.querySelector('#creates'); // 获取标签是否存在
if (oScript) oScript.remove(); // 如果存在先删除,再插入
oScript = document.createElement('script'); // 动态创建script标签
oScript.id = 'creates'; // 给唯一标识,方便第二次查找
oScript.src = `https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=${this.value}`; // 引入接口文件
document.body.appendChild(oScript); // 将标签插入页面
}
5.XSS跨站攻击
XSS的概念
XSS(Cross Site Scripting)攻击全称跨站脚本攻击,为了不与CSS(Cascading Style Sheets)混淆,故将跨站脚本攻击缩写为XSS,XSS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。通俗的来说就是我们的页面在加载并且渲染绘制的过程中,如果加载并执行了意料之外的程序或代码(脚本、样式),就可以认为是受到了XSS攻击。XSS更多是发生在web前端的一种漏洞,所以危害的对象主要还是前端用户
XSS的危害
当我们知道了什么是XSS后,也一定很想知道它到底有什么用,或者有什么危害,如何防御。下面列出关于XSS有关危害:
- 挂马
- 盗取用户cookie
- DOS(拒绝服务)客户端浏览器
- 前端JS挖矿
- 钓鱼攻击,高级的钓鱼技巧
- 删除目标文章、恶意篡改数据、嫁祸
- 劫持用户Web行为,甚至进一步渗透内网
- 爆发Web2.0蠕虫
- 蠕虫式的DDoS攻击
- 蠕虫式挂马攻击、刷广告、刷浏量、破坏网上数据
- 其它安全问题
XSS的分类
XSS有三类:反射型XSS(非持久型)、存储型XSS(持久型)和DOM XSS
反射型XSS
也叫非持久型XSS,交互数据一般不会被存在数据库里面,一次性,所见即所得。一般XSS代码出现在请求URL中,作为参数提交到服务器,服务器解析并响应,响应结果中包含XSS代码,最后浏览器解析并执行
场景:
- 用户A给用户B发送一个恶意构造了Web的URL。
- 用户B点击并查看了这个URL。
- 用户B获取到一个具有漏洞的HTML页面并显示在本地浏览器中。
- 漏洞HTML页面执行恶意JavaScript脚本,将用户B信息盗取发送给用户A,或者篡改用户B看到的数据等
一个简单的例子,我们写这么一个php接口
<?php
echo $_GET['x'];
如果输入x的值未经任何过滤就直接输出,当前端传入x=时,则alert()函数会在浏览器触发
存储型
也叫持久型XSS,主要将XSS代码提交存储在服务器端(数据库,内存,文件系统等),下次请求目标页面时不用再提交XSS代码。当目标用户访问该页面获取数据时,XSS代码会从服务器解析之后加载出来,返回到浏览器做正常的HTML和JS解析执行,XSS攻击就发生了
场景:
- 用户A在网页上创建了某个账户,并且账户信息中包含XSS代码。
- 用户B访问该网站查看XSS代码账户详情页面。
- 服务端返回账户详情页面,和带XSS账户信息。
- 用户B浏览器执行XSS代码,将用户B信息盗取发送给用户A,或者篡改用户B看到的数据等
DOM型
一种特殊的反射型XSS。由客户端的脚本程序可以动态地检查和修改页面内容,而不依赖于服务器端的数据。一般从url中提取数据中含xss,未过滤并在本地执行dom的渲染操作,输入来源多是document.location、document.URL、document.URLUnencoded、document.referrer、window.location等,而触发api多是document.write()、document.writeln()、document.innerHTML、eval()、window.execScript()、window.setInterval()、window.setTimeout()等,DOM XSS和反射型XSS、存储型XSS的差别在于DOM XSS的代码并不需要服务器参与,触发XSS靠的是浏览器端的DOM解析,完全是客户端的事情
场景:
- 用户B访问网站url中带有XSS代码参数
- 浏览器下载该网站JavaScript脚本
- JavaScript脚本有个方法获取URL中XSS代码参数,并且用innerHTML渲染在dom中
- 触发XSS代码,造成XSS攻击,cookie数据失窃
一个简单的例子,比如我们在页面上写如下代码
<script>
eval(location.hash.substr(1));
</script>
当你在链接的末尾加上#alert(1)的时候,页面上就会唤起弹窗,#后的内容是不会发送到服务器端的,仅仅在客户端被接收并执行
如何避免XSS攻击
- innerHTML(原生)、v-html(vue)、dangerouslySetInnerHTML(react)等直接渲染html的函数慎用,确保渲染的数据是由前端写死的、无危害的才可直接使用
- 使用innerHTML(原生)、v-html(vue)、dangerouslySetInnerHTML(react)等直接渲染html的函数渲染请求数据时,需要先把字符串转义:
- 把 > 替换成 >
- 把 < 替换成 <
- 把 & 替换成 &``;
- 把 " 替换成 "``;
- 把 ’ 替换成 '``;
- 字符过滤:
- 过滤掉特殊的HTML标签,例如
关于前端安全的思考
学习规范安全编码
根在人为,如果没有规范的学习过前端知识,特别是安全规范知识,直接跨端做前端开发的时候,很多时候知道一个功能怎么去实现,然后百度找api,找到api发现可以使用即可,通常会发生为了实现功能而忽略安全。所以我们在找到可用api时,还得查历该api是否存在安全隐患,需要如何规避、或者有其他更安全的api
对不可信数据保持敏感
对于任何非前端代码数预置数据,保持敏感对待,是否用于dom操作渲染,如果是则需要做相应的过滤处理,或者换其他安全实现方式
6.回调函数和Array.map函数的封装
回调函数
回调函数就是等会再调函数,和普通的函数不同,回调函数通常用于异步操作,函数的调用不是立即执行而是要等待一段时间,回调函数是将函数交给另一个函数去执行,但是具体的执行时间不确定,但是能确定的是,该函数一定会执行和需要等待一定的时间才会有执行结果
Array.map的封装
js手动封装map方法,完成和数组的map方法相同的功能
function map(arr, cb) {
var list = []; // 定义空数组
for (var i = 0; i < arr.length; i++) {
var res = cb(arr[i], i, arr); // 循环调用回调函数,并将返回值赋给res
list.push(res); // 将res插入空数组中
}
return list; // 返回操作完毕之后的数组
}
7.callback hell(回调地狱)
在我们需要对一个异步操作进行频繁的调用的时候,且要保证一步操作的顺序,可能会出现回调地狱的情况
8.三级接口回调地狱案例
// API1 => 根据学号查询学生班级
// API2 => 根据班级号查询学生学校
// API3 => 根据学校学校名称查询学校地址
function api1(student_id, fn) { // API1: 学生id => 班级信息
ajax({
path: 'http://localhost/api1.php',
params: {
student_id
},
successCB: data => {
fn(data);
}
})
}
function api2(class_num, fn) { // API2: 班级信息 => 学校名称
ajax({
path: 'http://localhost/api2.php',
params: {
class_num
},
successCB: data => {
fn(data);
}
})
}
function api3(school_name, fn) { // API3: 学校名称 => 学校地址
ajax({
path: 'http://localhost/api3.php',
params: {
school_name
},
successCB: data => {
fn(data);
}
})
}
api1('010101', data => {
api2(data.class_num, data => {
api3(data.school_name, data => {
console.log(data.school_address);
})
})
});
9.Promise
简介
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理
特点
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。
10.Promise的使用
const requestData = function (url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.onload = function () { // onreadystatechange只要返回的状态码只要变化时就回调一次函数,而onload只有状态码为4时才能回调一次函数
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject();
}
}
xhr.onerror = function () {
reject();
}
xhr.send();
})
}
requestData(url).then(data => {}).catch(e => {});
promise实例的then方法默认返回一个成功状态的promise对象
11.Promise改写三级接口调用,链式调用
// API1: http://localhost/api1.php
// API2: http://localhost/api2.php
// API3: http://localhost/api3.php
function api1(student_id) { // 调用api1通过学生student_id获取学生班级信息
return new Promise((resolve) => {
ajax({
path: 'http://localhost/api1.php',
params: {
student_id
},
successCB: data => {
resolve(data);
}
})
});
}
function api2(class_num) { // 调用api2通过学生class_num获取学校名称
return new Promise((resolve, reject) => {
ajax({
path: 'http://localhost/api2.php',
params: {
class_num
},
successCB: data => {
resolve(data);
}
})
});
}
function api3(school_name) { // 调用api3通过学生school_name获取学校地址
return new Promise((resolve) => {
ajax({
path: 'http://localhost/api3.php',
params: {
school_name
},
successCB: data => {
resolve(data);
}
})
});
}
api1('010101')
.then(({ data: { class_num } }) => {
return api2(class_num);
})
.then(({ data: { school_name } }) => {
return api3(school_name);
})
.then(({ data: { school_address } }) => {
console.log(school_address)
})
successCB: data => {
resolve(data);
}
})
});
}
api1('010101')
.then(({ data: { class_num } }) => {
return api2(class_num);
})
.then(({ data: { school_name } }) => {
return api3(school_name);
})
.then(({ data: { school_address } }) => {
console.log(school_address)
})