JSONP 核心原理:script
标签不受同源策略约束,所以可以用来进行跨域请求,优点是兼容性好,但是只能用于 GET 请求;
实现:
const jsonp = (url, params, callbackName) => {
const generateUrl = () => {
let dataSrc = "";
for(let key in params) {
if(params.hasOwnProperty(key)) {
dataSrc += `${
key}=${
params[key]}&`
}
}
dataSrc += `callback=${
callbackName}`;
return `${
url}?${
dataSrc}`;
}
return new Promise((resolve, reject) => {
const scriptEle = document.createElement('script');
scriptEle.src = generateUrl();
document.body.appendChild(scriptEle);
window[callbackName] = data => {
resolve(data);
document.removeChild(scriptEle);
}
});
}
在说Unicode
之前需要先了解一下ASCII
码:ASCII 码(American Standard Code for Information Interchange
)称为美国标准信息交换码。
ASCII
码可以表示的编码有限,要想表示其他语言的编码,还是要使用Unicode
来表示,可以说Unicode
是ASCII
的超集。
Unicode
全称 Unicode Translation Format
,又叫做统一码、万国码、单一码。Unicode
是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
Unicode
的实现方式(也就是编码方式)有很多种,常见的是UTF-8、UTF-16、UTF-32和USC-2。
UTF-8
是使用最广泛的Unicode
编码方式,它是一种可变长的编码方式,可以是1—4个字节不等,它可以完全兼容ASCII
码的128个字符。
注意: UTF-8
是一种编码方式,Unicode
是一个字符集合。
UTF-8
的编码规则:
Unicode
编码,因此对于英文字母,它的Unicode
编码和ACSII
编码一样。Unicode
码 。来看一下具体的Unicode
编号范围与对应的UTF-8
二进制格式 :
编码范围(编号对应的十进制数) | 二进制格式 |
---|---|
0x00—0x7F (0-127) | 0xxxxxxx |
0x80—0x7FF (128-2047) | 110xxxxx 10xxxxxx |
0x800—0xFFFF (2048-65535) | 1110xxxx 10xxxxxx 10xxxxxx |
0x10000—0x10FFFF (65536以上) | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
那该如何通过具体的Unicode
编码,进行具体的UTF-8
编码呢?步骤如下:
Unicode
编码的所在的编号范围,进而找到与之对应的二进制格式Unicode
编码转换为二进制数(去掉最高位的0)X
中,如果有X
未填,就设为0来看一个实际的例子:
“马” 字的Unicode
编码是:0x9A6C
,整数编号是39532
(1)首选确定了该字符在第三个范围内,它的格式是 1110xxxx 10xxxxxx 10xxxxxx
(2)39532对应的二进制数为1001 1010 0110 1100
(3)将二进制数填入X中,结果是:11101001 10101001 10101100
1. 平面的概念
在了解UTF-16
之前,先看一下平面的概念: Unicode
编码中有很多很多的字符,它并不是一次性定义的,而是分区进行定义的,每个区存放65536(216)个字符,这称为一个平面,目前总共有17 个平面。
最前面的一个平面称为基本平面,它的码点从0 — 216-1,写成16进制就是U+0000 — U+FFFF
,那剩下的16个平面就是辅助平面,码点范围是 U+10000—U+10FFFF
。
2. UTF-16 概念:
UTF-16
也是Unicode
编码集的一种编码形式,把Unicode
字符集的抽象码位映射为16位长的整数(即码元)的序列,用于数据存储或传递。Unicode
字符的码位需要1个或者2个16位长的码元来表示,因此UTF-16
也是用变长字节表示的。
3. UTF-16 编码规则:
U+0000—U+FFFF
的字符(常用字符集),直接用两个字节表示。U+10000—U+10FFFF
之间的字符,需要用四个字节表示。4. 编码识别
那么问题来了,当遇到两个字节时,怎么知道是把它当做一个字符还是和后面的两个字节一起当做一个字符呢?
UTF-16
编码肯定也考虑到了这个问题,在基本平面内,从 U+D800 — U+DFFF
是一个空段,也就是说这个区间的码点不对应任何的字符,因此这些空段就可以用来映射辅助平面的字符。
辅助平面共有 220 个字符位,因此表示这些字符至少需要 20 个二进制位。UTF-16
将这 20 个二进制位分成两半,前 10 位映射在 U+D800 — U+DBFF
,称为高位(H),后 10 位映射在 U+DC00 — U+DFFF
,称为低位(L)。这就相当于,将一个辅助平面的字符拆成了两个基本平面的字符来表示。
因此,当遇到两个字节时,发现它的码点在 U+D800 —U+DBFF
之间,就可以知道,它后面的两个字节的码点应该在 U+DC00 — U+DFFF
之间,这四个字节必须放在一起进行解读。
5. 举例说明
以 “” 字为例,它的 Unicode
码点为 0x21800
,该码点超出了基本平面的范围,因此需要用四个字节来表示,步骤如下:
0x21800 - 0x10000
0001000110 0000000000
U+D800
对应的二进制数为 1101100000000000
, 将0001000110
填充在它的后10 个二进制位,得到 1101100001000110
,转成 16 进制数为 0xD846
。同理,低位为 0xDC00
,所以这个字的UTF-16
编码为 0xD846 0xDC00
UTF-32
就是字符所对应编号的整数二进制形式,每个字符占四个字节,这个是直接进行转换的。该编码方式占用的储存空间较多,所以使用较少。
比如“马” 字的Unicode编号是:U+9A6C
,整数编号是39532
,直接转化为二进制:1001 1010 0110 1100
,这就是它的UTF-32编码。
Unicode、UTF-8、UTF-16、UTF-32有什么区别?
Unicode
是编码字符集(字符集),而UTF-8
、UTF-16
、UTF-32
是字符集编码(编码规则);UTF-16
使用变长码元序列的编码方式,相较于定长码元序列的UTF-32
算法更复杂,甚至比同样是变长码元序列的UTF-8
也更为复杂,因为其引入了独特的代理对这样的代理机制;UTF-8
需要判断每个字节中的开头标志信息,所以如果某个字节在传送过程中出错了,就会导致后面的字节也会解析出错;而UTF-16
不会判断开头标志,即使错也只会错一个字符,所以容错能力教强;UTF-8
就比UTF-16
节省了很多空间;而如果字符内容全部是中文这样类似的字符或者混合字符中中文占绝大多数,那么UTF-16
就占优势了,可以节省很多空间;如果没有defer或async属性,浏览器会立即加载并执行相应的脚本。它不会等待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。
defer 和 async属性都是去异步加载外部的JS脚本文件,它们都不会阻塞页面的解析,其区别如下:
题目描述:手写 Promise 以及 Promise.all Promise.race 的实现
实现代码如下:
class Mypromise {
constructor(fn) {
// 表示状态
this.state = "pending";
// 表示then注册的成功函数
this.successFun = [];
// 表示then注册的失败函数
this.failFun = [];
let resolve = (val) => {
// 保持状态改变不可变(resolve和reject只准触发一种)
if (this.state !== "pending") return;
// 成功触发时机 改变状态 同时执行在then注册的回调事件
this.state = "success";
// 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里为模拟异步
setTimeout(() => {
// 执行当前事件里面所有的注册函数
this.successFun.forEach((item) => item.call(this, val));
});
};
let reject = (err) => {
if (this.state !== "pending") return;
// 失败触发时机 改变状态 同时执行在then注册的回调事件
this.state = "fail";
// 为了保证then事件先注册(主要是考虑在promise里面写同步代码) promise规范 这里模拟异步
setTimeout(() => {
this.failFun.forEach((item) => item.call(this, err));
});
};
// 调用函数
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
// 实例方法 then
then(resolveCallback, rejectCallback) {
// 判断回调是否是函数
resolveCallback =
typeof resolveCallback !== "function" ? (v) => v : resolveCallback;
rejectCallback =
typeof rejectCallback !== "function"
? (err) => {
throw err;
}
: rejectCallback;
// 为了保持链式调用 继续返回promise
return new Mypromise((resolve, reject) => {
// 将回调注册到successFun事件集合里面去
this.successFun.push((val) => {
try {
// 执行回调函数
let x = resolveCallback(val);
//(最难的一点)
// 如果回调函数结果是普通值 那么就resolve出去给下一个then链式调用 如果是一个promise对象(代表又是一个异步) 那么调用x的then方法 将resolve和reject传进去 等到x内部的异步 执行完毕的时候(状态完成)就会自动执行传入的resolve 这样就控制了链式调用的顺序
x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);
} catch (error) {
reject(error);
}
});
this.failFun.push((val) => {
try {
// 执行回调函数
let x = rejectCallback(val);
x instanceof Mypromise ? x