windowAPI
主要是记录 windowAPI 的使用
🐕defer / async
defer 和 async 都是异步加载
- defer 具有推迟的意思,就是说当 Dom 树加载完毕之后执行,可以获取真实的 dom
- async 具有异步的意思,是自己异步加载,加载完毕之后就立即执行,会阻塞 dom 的渲染
<script async src="js/vendor/jquery.js"></script>
<script async src="js/script2.js"></script>
<script async src="js/script3.js"></script>
async
TIP
三者的调用顺序是不确定的。
jquery.js 可能在 script2 和 script3 之前或之后调用,如果这样,后两个脚本中依赖 jquery 的函数将产生错误,因为脚本运行时 jquery 尚未加载
defer
TIP
添加 defer 属性的脚本将按照在页面中出现的顺序加载, 因此第二个示例可确保 jquery.js 必定加载于 script2.js 和 script3.js 之前, 同时 script2.js 必定加载于 script3.js 之前。
脚本调用策略小结:
- 如果脚本无需等待页面解析,且无依赖独立运行,那么应使用 async。
- 如果脚本需要等待页面解析,且依赖于其它脚本,调用这些脚本时应使用 defer,将关联的脚本按所需顺序置于 HTML 中。
preload 提前加载并不执行
TIP
提供一种声明式的命令,让浏览器提前加载资源(加载后并不执行),在需要执行的时候再执行
- 将加载和执行分离开,不阻塞渲染和 document 的 onload 事件
- 提前加载指定资源,不再出现依赖的 font 字体隔了一段时间才刷出
使用 HTTP 响应头的 Link 字段创建
某些场景下知道其必定会加载的,则可以设置 preload 进行预加载,如:
<link rel="preload" as="font" href="https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff">
<link rel="preload" as="script" href="https://a.xxx.com/xxx/PcCommon.js">
<link rel="preload" as="script" href="https://a.xxx.com/xxx/TabsPc.js">
如何区分 preload 和 prefetch
⭐一个是预执行,另一个是预请求
- preload 是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源;不管资源是否被使用
- prefetch 是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源(有空闲时加载)
避免错用 preload 加载跨域资源
在我们的场景中,已知 antd.css 会依赖 font 文件,所以我们可以对这个字体文件进行 preload:
<link rel="preload" as="font" href="https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff">
然而我发现这个文件加载了两次:
原因是对跨域的文件进行 preload 的时候,我们必须加上 crossorigin 属性:
<link rel="preload" as="font" crossorigin href="https://at.alicdn.com/t/font_zck90zmlh7hf47vi.woff">
Dns-prefetch(dns预获取)
是尝试在请求资源之前解析域名。 仅对跨域域上的 DNS 查找有效
Promise
all
是一个空数组
<script setup>
import { ref } from "vue";
let all = ref([]);
Promise.all([]).then(res => {
all.value = res;
});
</script>
Promise.all([])的结果是:[]
race
一个 pending 状态的 promise
<script setup>
import { ref } from "vue";
let race = ref("");
Promise.race([]).then(res => {
race.value = res;
});
</script>
Promise.race([])的结果是:即:一个 `pending` 状态
await
可以模拟,只需要返回一个 then
函数,相当于一个回调函数
function a() {
return {
then(onFull) {
return onFull(10);
},
};
}
function b() {
return a();
}
b().then(
res => {
console.log(res); // 10
}
);
🔗any
返回第一个 成功
的 promise
,如果 失败
不管
与 race
不同的是,race
是返回第一个完成的 promise
,不管他是不是 成功
或者是 失败
const pErr = new Promise((resolve, reject) => {
reject("总是失败");
});
const pSlow = new Promise((resolve, reject) => {
setTimeout(resolve, 500, "最终完成");
});
const pFast = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "很快完成");
});
Promise.any([pErr, pSlow, pFast]).then((value) => {
console.log(value);
// pFast 第一个兑现
});
🔗URLSearchParams
返回一个iterator
,可以有 values
,keys
,entries
传入参数
- 传入对象
var params4 = new URLSearchParams({ foo: 1, bar: 2 });
console.log(params4.toString()); // foo=1&bar=2
- 传入
entries
var params3 = new URLSearchParams([
["foo", 1],
["bar", 2],
]);
console.log(params3.toString()); // // foo=1&bar=2
- 传入字符串 有没有问号都无所谓,因为它会自动去除
var searchParams = new URLSearchParams("?key1=value1&key2=value2");
// var searchParams = new URLSearchParams("key1=value1&key2=value2");
for (var pair of searchParams.entries()) {
// key1,value1
// key2,value2
console.log(pair[0] + ", " + pair[1]);
}
var url = new URL("https://example.com?foo=1&bar=2");
// url.search = foo=1&bar=2
var params = new URLSearchParams(url.search);
console.log(params.toString());
方法
append
let url = new URL("https://example.com?foo=1&bar=2");
let params = new URLSearchParams(url.search);
//添加第二个 foo 搜索参数。
params.append("foo", 4);
//查询字符串变成:'foo=1&bar=2&foo=4'
get
let params = new URLSearchParams("name=Jonathan&age=18");
let name = params.get("name"); // is the string "Jonathan"
let age = parseInt(params.get("age"), 10); // is the number 18
let address = params.get("address"); // null
has
let url = new URL("https://example.com?foo=1&bar=2");
let params = new URLSearchParams(url.search);
params.has("bar") === true; //true
keys
// 建立一个测试用 URLSearchParams 对象
var searchParams = new URLSearchParams("key1=value1&key2=value2");
// 输出键
for (var key of searchParams.keys()) {
// key1,key2
console.log(key);
}
values
var searchParams = new URLSearchParams("key1=value1&key2=value2");
// 输出值
for (var value of searchParams.values()) {
// value1,value2
console.log(value);
}
和 URL 的关系
const urlObj = new URL(location.href);
urlObj.searchParams.get("cccc")
urlObj.searchParams instanceof URLSearchParams // true
const urlObj = new URLSearchParams(location.search);
urlObj.get("cccc")
Blob
Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作
File 接口基于 Blob,继承了 blob 的功能并将其扩展以支持用户系统上的文件
const obj = { hello: "world" };
const blob = new Blob([JSON.stringify(obj)], {
type: "application/json",
});
从 blob 中提取数据
const text = await new Response(blob).text();
或者
const text = await blob.text();
或者
const reader = new FileReader();
reader.addEventListener("loadend", () => {
// reader.result 包含被转化为类型化数组的 blob 中的内容
});
reader.readAsArrayBuffer(blob);
🔗URL.createObjectURL
objectURL = URL.createObjectURL(object);
INFO
object 用于创建 URL 的 File 对象、Blob 对象或者 MediaSource 对象。 返回值 一个 DOMString 包含了一个对象 URL,该 URL 可用于指定源 object 的内容。
WARNING
内存管理 在每次调用 createObjectURL() 方法时,都会创建一个新的 URL 对象,即使你已经用相同的对象作为参数创建过。当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放。
🐩Blob URL不是持久化的URL,不能被保存或分享。它仅在创建它的文档中有效,并且对其他文档不可见。
var img = document.createElement("img");
// createObjectURL会生成一个 localhost:8080/xxxxx
img.src = window.URL.createObjectURL(files[i]);
img.height = 60;
img.onload = function () {
window.URL.revokeObjectURL(this.src);
};
revokeObjectURL()
销毁之前使用URL.createObjectURL()方法创建的 URL 实例。
🔗FileReader
INFO
FileReader 对象异步访问使用 File 或 Blob 对象指定要读取的文件或数据。
其中 File 对象可以是来自用户在一个<input>元素上选择文件后返回的 FileList 对象,也可以来自拖放操作生成的 DataTransfer 对象
FileReader.readAsDataURL
开始读取指定的 Blob 中的内容。一旦完成,result 属性中将包含一个 data: URL 格式的 Base64 字符串以表示所读取文件的内容。
function previewFile() {
var preview = document.querySelector("img");
var file = document.querySelector("input[type=file]").files[0];
var reader = new FileReader();
reader.addEventListener(
"load",
function () {
preview.src = reader.result;
},
false,
);
if (file) {
reader.readAsDataURL(file);
}
}
FileReader.readAsText
开始读取指定的 Blob 中的内容。一旦完成,result 属性中将包含一个字符串以表示所读取的文件内容。会触发 loadend
方法
🔗structuredClone
// Create an object with a value and a circular reference to itself.
const original = { name: "MDN" };
original.itself = original;
// Clone it
const clone = structuredClone(original);
console.assert(clone !== original); // the objects are not the same (not same identity)
console.assert(clone.name === "MDN"); // they do have the same values
console.assert(clone.itself === clone); // and the circular reference is preserved
JSON
JSON 的类型
- string
- number
- object
- array
- null
- boolean
只有这6种类型
JSON.stringify
// Expected output: "{"x":5,"y":6}"
console.log(JSON.stringify({ x: 5, y: 6 }));
// Expected output: "[3,"false",false]"
console.log(
JSON.stringify([new Number(3), new String("false"), new Boolean(false)])
);
// Expected output: "{"x":[10,null,null,null]}"
console.log(
JSON.stringify({ x: [10, undefined, function () {}, Symbol("")] })
);
// Expected output: ""2006-01-02T15:04:05.000Z""
console.log(JSON.stringify(new Date(2006, 0, 2, 15, 4, 5)));
var obj = {
foo: "foo",
toJSON: function () {
return "bar";
},
};
JSON.stringify(obj); // '"bar"'
JSON.stringify({ x: obj }); // '{"x":"bar"}'
// '{}'
JSON.stringify({ [Symbol("foo")]: "foo" });
描述
JSON.stringify()
将值转换为相应的 JSON 格式:
- 转换值如果有 toJSON() 方法,使用其返回值
- 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值
- 函数、undefined 和 Symbol 单独转化返回
undefined
,如果出现在 数组 中,转化为 null - 循环引用会报错
- Date 类型调用
toString
,转化为字符串 - NaN、infinite 和 null 转化 为 null
replacer
如果 replacer 是一个数组,数组的值代表将被序列化成 JSON 字符串的属性名。
JSON.stringify(foo, ['week', 'month']);
// '{"week":45,"month":7}', 只保留“week”和“month”属性值。
replacer 一个函数
function replacer(key, value) {
if (typeof value === "string") {
return undefined;
}
return value;
}
var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7};
// {"week":45,"month":7}
var jsonString = JSON.stringify(foo, replacer);
Json.parse
JSON.parse("123"); // 可以
JSON.parse("abcd132"); // 失败,没有引号
JSON.parse('"abcd132"');
// 可以,因为 abcd132 必须限定为一个字符串
var str = '{"name":"小明","age":18}';
// 不能是
var str = "{'name':'小明', 'age':18}";
// 不能用逗号结尾
JSON.parse("[1, 2, 3, 4, ]");
JSON.parse('{"foo" : 1, }');
Error
RangeError
创建一个 error 实例,表示错误的原因:数值变量或参数超出其有效范围
ReferenceError
无效引用 ReferenceError(引用错误) 对象代表当一个不存在的变量被引用时发生的错误。
(function (arg) {})(abcde); // abcde 并不存在,使用了一个不存在的变量
SyntaxError
当 Javascript 语言解析代码时,Javascript 引擎发现了 不符合语法规范的 tokens 或 token 顺序时抛出
[1,2,3].forEach(item=>console.log(item);)
// VM714:1 Uncaught SyntaxError: missing ) after argument list
TypeError
变量或参数不属于有效类型
当传入函数的操作数或参数的类型并非操作符或函数所预期的类型时,将抛出一个 TypeError 类型错误
// 1. 不是一个有效函数
var abcdf = 123;
abcdf();
// 2. 不属于有效类型
a.push(231);
var a = [];
捕获错误
捕获Promise错误 - unhandledrejection
window.addEventListener("unhandledrejection", event => {
// 要阻止默认事件,否则还是有错误
event.preventDefault();
console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
})
function a() {
new Promise((resolve, reject) => {
reject(12);
});
}
a();
事件
dragover/drop
TIP
必须要阻止 元素的 dragover 默认事件,默认事件是打开文件
<div
style="width:100px;height:100px;background-color: yellow;"
@drop="drop"
@dragover.prevent
draggable="true">
</div>
<script lang="ts" setup>
const drop = (e) => {
console.log(e, "drop")
e.target.appendChild(dragged);
}
</script>
事件捕获
默认是 false
,冒泡事件
INFO
事件执行先捕获,然后找到对应的元素,然后冒泡到父级元素
捕获就像捕鱼一样,从上往下执行
第三个参数传递 true 切换为捕获模式,可以在捕获时触发事件
TIP
在 掘金/知乎 这种外链,可以使用这种
// 可以有效阻止某些用户没有权限
// 如果想阻拦全局a标签的跳转,使用e.preventDefault
window.addEventListener(
"click",
function (e) {
console.log("window", e.target.nodeName);
e.stopPropagation();
e.preventDefault();
},
true
);
document.getElementById("people").addEventListener("click", () => {
console.log("people");
});
阻止事件执行 signal
let abortController = new AbortController()
inner.addEventListener("click",function(){
console.log("inner")
},{
signal:abortController.signal
})
setTimeout(()=>{
abortController.abort()
},2000)
mouseenter & mouseover
🔗requestAnimationFrame
浏览器在重绘之前调用指定的回调函数更新动画。
TIP
若你想在浏览器重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用 requestAnimationFrame()。
requestAnimationFrame() 是一次性的。
var start = null;
var element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';
function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
element.style.left = Math.min(progress / 10, 200) + 'px';
if (progress < 2000) {
// 不断的自我调用,因为是一次性的
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
下次重绘之前继续更新下一帧动画
requestAnimationFrame 嵌套
使用两个嵌套的 requestAnimationFrame
是为了确保回调函数 cb 在下一帧渲染之前执行。这样做的目的是为了优化性能和动画的流畅度。
如果只使用一个 requestAnimationFrame
,那么回调函数 cb 将会在下一帧渲染时执行。但是,由于 JavaScript 是单线程的,如果在同一帧中执行的任务过多,可能会导致页面卡顿或动画不流畅。
通过嵌套两个 requestAnimationFrame
,可以将回调函数的执行时间分散到两个连续的帧中。第一个 requestAnimationFrame
会在当前帧结束时调用,而第二个 requestAnimationFrame
会在下一帧开始时调用。这样可以确保回调函数 cb 在下一帧渲染之前执行,从而提高性能和动画的流畅度。
因此,使用两个嵌套的 requestAnimationFrame
是为了确保回调函数在下一帧渲染之前执行,以优化性能和动画的流畅度。
function nextTickFrame(fn: FrameRequestCallback) {
requestAnimationFrame(() => {
requestAnimationFrame(fn)
})
}
🔗encodeURIComponent/🔗encodeURI
TIP
与 encodeURI() 相比,此函数会编码更多的字符,包括 URI 语法的一部分。
encodeURIComponent 转义除了如下所示外的所有字符:
不转义的字符: A-Z a-z 0-9 - _ . ! ~ * ' ( )
var set1 = ";,/?:@&=+$"; // 保留字符
var set2 = "-_.!~*'()"; // 不转义字符
var set3 = "#"; // 数字标志
var set4 = "ABC abc 123"; // 字母数字字符和空格
console.log(encodeURI(set1)); // ;,/?:@&=+$
console.log(encodeURI(set2)); // -_.!~*'()
console.log(encodeURI(set3)); // #
console.log(encodeURI(set4)); // ABC%20abc%20123 (空格被编码为 %20)
console.log(encodeURIComponent(set1)); // %3B%2C%2F%3F%3A%40%26%3D%2B%24
console.log(encodeURIComponent(set2)); // -_.!~*'()
console.log(encodeURIComponent(set3)); // %23
console.log(encodeURIComponent(set4)); // ABC%20abc%20123 (空格被编码为 %20)
🔗URL
let s = new URL(
"http://zs:123456@localhost:8080/directorPerformance/todo?id=1#name=zs#age=5"
);
searchParams 只读对象
URLSearchParams对象,可用于访问search
中找到的各个查询参数。
// https://some.site/?id=123
const parsedUrl = new URL(window.location.href);
console.log(parsedUrl.searchParams.get("id")); // "123"
url可写
function constructURL(param) {
const { category, limit, userId } = param;
const baseURL = "https://fakestoreapi.com/products";
const url = new URL(baseURL);
const params = new URLSearchParams();
// 添加参数,更改 url
if (category) url.pathname += `/category/${category}`;
if (limit) params.append('limit', Number(limit).toString());
if (userId) params.append('userId', Number(userId).toString());
// 可读可写
url.search = params.toString();
return url.toString();
}
let r = constructURL({
category: 'mugs',
limit: 10,
userId: 1
})
// https://fakestoreapi.com/products/category/mugs?limit=10&userId=1
console.log(r);
IntersectionObserver
var observer = new IntersectionObserver(callback[, options])
因为可以监听多个元素,所以 callback 回调函数的第一个参数是数组,数组中的每个元素都是一个 IntersectionObserverEntry
对象。
options:
root 监听元素的祖先元素的element 对象,其边界盒作为视口
rootMargin 距离边界盒的一组偏移量,类型为字符串,与margin相同,默认值是
0px 0px 0px 0px
threshold 一个包含阈值的列表,按升序排列,列表中的每个阈值都是监听对象的交叉区域与边界区域的比率。当监听对象的任何阈值被越过时,都会生成一个通知(Notification)。如果构造器未传入值,则默认值为 0。
// Register IntersectionObserver
const io = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// Add 'active' class if observation target is inside viewport
if (entry.intersectionRatio > 0) {
entry.target.classList.add("active");
}
// Remove 'active' class otherwise
else {
entry.target.classList.remove("active");
}
});
});
// Declares what to observe, and observes its properties.
const boxElList = document.querySelectorAll(".box");
boxElList.forEach((el) => {
io.observe(el);
});
🐹== 比较规则
- 两端类型相同,比较值
- 只要存在
NaN
,返回false
undefined
和null
只有与自身比较,或者互相比较,返回true
- ⭐两端都是原始类型,转化为数字js
var a = "a" console.log(a == 1) // false //a 转化为数字是 NaN, NaN 比较任何值都是false
- ⭐一端是原始类型,一端是对象类型,把对象转换成原始类型后进行第1步
对象转原始类型
- 先使用
[Symbol.toPrimitive]
方法,判断是否可以获取到原始值 - 调用
valueOf
方法,是否可以获取原始值 - 调用
toString
方法,是否可以获取原始值
- 先使用
[Symbol.toPrimitive]
接受 3 个参数 ,分别是 default
,string
和 number
,有一些明确是用 string
或者是 number
string
- window.alert(obj)
- 模板字符串
${obj}
- test[obj] = 123
number
- 一元(+obj)
- - ,*,/, > < 关系运算符
- Math.pow / String.prototype.slice 内部方法
default
- 二元加(obj + obj2)
- == !=
如果有 [Symbol.toPrimitive]
优先使用
let obj = {
value:10,
[Symbol.toPrimitive](hint){
console.log(hint)
return 2
},
toString(){
return this.value + 10;
},
valueOf(){
return this.value + 20;
}
}
let x0 = obj == 1;
x0 // hint default
let x = obj + 1;
x // hint default
let x2 = obj + ''
x2 // hint default
let x3 = +obj
x3 // hint number
如果没有
let obj = {
value:10,
toString(){
return this.value + 10;
},
valueOf(){
return this.value + 20;
}
}
// true
let x0 = obj == 30;
// 31
let x = obj + 1;
// "30"
let x2 = obj + ''
// 30
let x3 = +obj
//'20'
let x4 = `${obj}`
除非特别是 string,否则使用 valueOf
+ 相加规则
[1,2] + {n:1}
[1,2].valueOf = [1,2] // 非原始值
[1,2].toString = "1,2" // 原始值
({n:1}).valueOf() = {n:1} //非原始值
({n:1}).toString() = "[object Object]" // 原始值
"1,2[object Object]"
循环
for 循环
使用 label
语法,可以退出外层 for
循环
outer:for(let i =0;i<10;i++){
inner:for(let j = 0;j< 10;j++){
if(j == 5){
break outer;
}
console.log(j) // [1,2,3,4,5]
}
}
Reflect
⭐调用对象的基本方法什么是基本方法
let o = {}
o.a = 1
此时会触发会触发外层方法,外层方法触发 对象深处的 [[set]]
方法
使用 Object.keys
这种暴露出来的方法的时候,外层方法会做出判断,会把 enumerable
或者 symbol
属性进行拦截
let obj = { a: 1, b: 2, c: 3 };
Object.defineProperty(obj, 'd', {
value: 4,
enumerable: false
})
console.log("🚀", Object.keys(obj)); // [a,b,c]
console.log("🚀 ", Reflect.ownKeys(obj));// [a,b,c,d]
但是使用 Reflect
直接操作的是 基本方法
,不会遭到拦截
再举一个例子
let obj = {
a: 1,
b: 2,
get c() {
return this.a + this.b;
}
};
console.log("🚀", obj.c); // 3
由于外层方法把 this
已经确定,所以 obj.c
为 3
但是使用 Reflect
,可以改变 this
指向
let r = Reflect.get(obj, "c", { a: 2, b: 5 })
console.log(r) // 7
在 proxy 中的使用
使用 target[key]
读取的 this 是原始对象 obj
,而不是代理对象,不会触发 proxy
的 get 方法
let obj = {
a: 1,
b: 2,
get c() {
console.log(this) // { obj }
return this.a + this.b;
}
};
let p = new Proxy(obj, {
get(target, key) {
// 使用这种方式读取的 obj.c 中的 this是原始对象
return target[key]
}
})
使用 reflect
读取,第三个参数 可以更改 this 指向
let obj = {
a: 1, b: 2, get c() {
console.log(this)
return this.a + this.b;
}
};
let p = new Proxy(obj, {
get(target, key) {
console.log(key) //c,a,b
return Reflect.get(target, key, p);
}
})
p.c
但是直接传入proxy 返回值 p
不够灵活,使用 receiver
get(target, key,receiver) {
console.log(key) //c,a,b
return Reflect.get(target, key, receiver);
}
⭐for循环执行顺序
for(let i = 0; i < 100; i++){}
编译器会这么执行
- 创建变量 i,并赋值为 0
- 只要 i 小于 100 就执行
- 每次遍历结束后 +1
所以遍历完毕, i = 100;
🔗falsy
假值(falsy,有时写为 falsey)是在 Boolean
上下文中认定为 false
的值。
JavaScript 在需要用到布尔类型值的上下文中使用强制类型转换将值转换为布尔值,例如条件语句和循环语句。
- 0
- false
- undefined
- null
- NaN
- ''
if (undefined) {
// 执行不到这里
}
if (0) {
// 执行不到这里
}
if (NaN) {
// 执行不到这里
}
if ("") {
// 执行不到这里
}
// && 只有当 第一个参数是 true 时才会走到下一个变量处
console.log(false && "dog"); // false
console.log(0 && "dog"); // 0
if else
else if 包含了前面 if 为 非 的条件
其实是两个条件
let a = 1;
let b = 2;
if(a == 1){
// 不可以进入
}else if(b == 2){
console.log(1)
}
let a = 1;
let b = 2;
if(a == 2){
// 可以进入,因为 a != 2
}else if(b == 2){
console.log(1)
}