一、迭代器
在javaScript
中,迭代器是一个具体的对象
,这个对象需要符合迭代器协议:
- 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准;
- 在javascript中这个标准就是一个
特定的next方法
;
next
方法有如下的要求:
- 有一个参数或者无参数的
函数
- 返回一个对象,这个对象中包含
done
、value
键,比如{done:false,value:“123”} - 如果迭代器可以产生下一个值,则
done
为false,value
可以为任何值想要返回的值 - 如果迭代完毕,则
done
为true,value
为undefined
let iterator = {
next: function () {
return {
value: 1, //可以返回任何值
done: false
}
}
}
tips:迭代器中断
当使用key...of
之类迭代时,如果中途break
中断迭代,会触发迭代器的return
函数
let iterator = {
next: function (val) {
if (index === 3) {
return {
value: undefined,
done: true,
};
}
return {
value: index++,
done: false,
};
},
return: function () {
console.log("迭代器中断了!!!");
return {
done: false,
};
},
};
obj[Symbol.iterator] = function () {
return iterator;
};
for (let i of obj) {
console.log(i);
break; //会触发迭代器的return函数
}
二、可迭代对象
- 该对象必须实现一个特定的函数
[Symbol.iterator]
- 这个特定的函数需要返回一个迭代器(这个迭代器用于迭代当前对象)
let obj = {
a: 1,
b: 2,
c: 3,
};
let index = 0;
//实现迭代器
let iterator = {
next: function (val) {
if (index === 3) {
return {
value: undefined,
done: true,
};
}
return {
value: index++,
done: false,
};
},
};
//实现特定函数
obj[Symbol.iterator] = function () {
return iterator;
};
for (let i of obj) {
console.log(i);
}
内置可迭代对象
String
、Array
、TypedArray
、Map
和 Set
都是内置可迭代对象
三、生成器
- 生成器是ES6中新增的一种
函数控制、使用
的方案,它可以让我们更加灵活的控制函数什么时候继续执行,什么时候暂停执行
等 - 使用
function*
来定义生成器函数
,注意是函数,并不是生成器
,箭头函数前面加*
会语法错误 - 调用生成器函数会返回一个生成器,一种特殊的迭代器
- 调用生成器的
next
函数会执行生成器函数
,直到遇到yield
关键字 - 调用
next
函数时,可以传参,例如next(123)
,注意传给第一个 next() 的值会被忽略
function* a(val) {
console.log(1);
console.log(2);
yield console.log(7); //这个7也会执行
console.log(3);
console.log(4);
// return //遇到return会提前结束,done的值会改为true,不再执行后面的代码
let c = yield 5; //要返回的值,c会接受第三次next传进来的值
console.log(c); //打印 第三次调用next,也就是说第二个yield会接收第三次next传参
console.log(5);
console.log(6);
}
let b = a("第一个next传进去的值会被忽略,所以一般在这里进行传参");
console.log(b.next());
console.log(b.next());
console.log(b.next("第三次调用next"));
可控制的提前结束:不调用next
,调用return
可提前结束
function* a() {
console.log(1);
console.log(2);
yield 7;
console.log(3);
console.log(4);
yield 8;
console.log(5);
console.log(6);
}
let b = a();
console.log(b.next()); //函数内部执行打印了 1 2
console.log(b.return()); //并没有打印3 4 ,直接结束了,返回了{done:true,value:undefined}
console.log(b.next());
console.log(b.next());
例子
使用for...of
遍历对象的value
let obj = {
a: 1,
b: 2,
c: 3,
[Symbol.iterator]: function* () {
let arr = Object.keys(this);
for (let i = 0; i < arr.length; i++) {
yield this[arr[i]];
}
}
}
for (let val of obj) {
console.log(val); // 1 2 3
}
四、Map
- Map的键可以是对象或者原始类型
- Map 中的一个键只能出现一次;它在 Map 的集合中是独一无二的
- Map值的顺序是根据插入顺序来的,不像
Object
值的顺序是符合一定规则,和插入顺序无关 - Map是可迭代的,每次迭代后会返回一个形式为
[key, value]
的数组 - Map的键的比较基于
零值相等算法
,这就意味着NaN===NaN
,剩下所有其他的值是根据===
运算符的结果判断是否相等
五、WeakMap
WeakMap
是一种键值对的集合,其中的键必须是对象
或Symbol
,并且不会创建对它的键的强引用,也就是说如果当前作为键的对象
如果没有其他的地方能访问到,那将会被垃圾回收,对应的值
如果没有其他地方能访问到,那么也会被作为垃圾回收的候选对象
- WeakMap是不可迭代的
六、Set
set
中的元素只会出现一次,即集合中的元素是唯一的。- NaN被认为相等,其他的值则基于
===
运算符的语义进行相等比较
1.集合运算
let set1 = new Set([1,2,3,4,5,6,7,8,9,10]);
let set2 = new Set([7,8,9,10,11,12,13,14]);
let set3 = new Set([15,16,17,18]);
let set4 = new Set([1,2,3]);
console.log(set1.difference(set2)) //[1,2,3,4,5,6] //set1与set2的差集
console.log(set1.intersection(set2)) //[7,8,9,10] //set1与set2的交集
console.log(set1.symmetricDifference(set2)) //[1,2,3,4,5,6,11,12,13,14]
console.log(set1.union(set2)) //[1,2,3,4,5,6,7,8,9,10,11,12,13,14]
console.log(set1.isDisjointFrom(set2)) // false
console.log(set1.isDisjointFrom(set3)) // true 判断两个集合是否有相同的值,没有就是true
console.log(set4.isSubsetOf(set1)) // true set4是否属于set1
console.log(set4.isSupersetOf(set1)) // false set4是否不属于set1
具体看MDN关于集合运算的说明
2.类集合对象
类集合对象是提供以下内容的对象:
- 一个
size
属性,包含一个数字 - 一个
has()
方法,接受一个元素并返回一个布尔值。 - 一个
keys()
方法,返回一个集合中的元素的迭代器
七、weakSet
- 值只能是
对象
或者Symbol
WeakSet
中对象的引用为弱引用。如果没有其他的对WeakSet
中对象的引用存在,那么这些对象会被垃圾回收。
八、对象的内部方法
对象的内部方法是指对象中无法再进行分割的最最基础的方法,是不对用户进行暴露的,也就是说用户是无法直接使用的,只能间接的通过一些方法进行操作,比如Object.keys()
就是间接调用[[GetOwnProperty]]
,但是Object.keys()
在调用获取到值后,会做更多的操作,比如过滤掉不可枚举的值。在es6
之前,是没有办法直接使用的,在es6
之后提供了一个Reflect
内置对象来进行操作这些内部方法
1. [[GetPrototypeOf]]
- 返回对象的原型(即
__proto__
) - 对应操作
Object.getPrototypeOf(obj); //这个方法并没有做其他的操作
Reflect.getPrototypeOf(obj);
2. [[SetPrototypeOf]]
- 设置对象的原型
- 对应操作
Object.setPrototypeOf(obj, proto);
Reflect.setPrototypeOf(obj, proto);
3. [[IsExtensible]]
- 判断对象是否可扩展(能否添加新属性)
- 对应操作
Object.isExtensible(obj);
Reflect.isExtensible(obj);
4. [[PreventExtensions]]
- 阻止对象扩展(不允许添加新属性)
- 对应操作
Object.preventExtensions(obj);
Reflect.preventExtensions(obj);
5. [[GetOwnProperty]]
- 返回对象自身属性的描述符(value、writable 等)
- 对应操作
Object.getOwnPropertyDescriptor(obj, prop);
Reflect.getOwnPropertyDescriptor(obj, prop);
6. [[DefineOwnProperty]]
- 定义或修改对象的自身属性
- 对应操作
Object.defineProperty(obj, prop, descriptor);
Reflect.defineProperty(obj, prop, descriptor);
这也是为什么vue2的响应式有很多的缺陷的原因,因为Object.defineProperty
只是调用了对象其中一个内部方法,并不能拦截到[[delete]]
之类的其他内部方法
7. [[HasProperty]]
- 检查对象是否包含某个属性(包括原型链上的属性)
- 对应操作
prop in obj;
Reflect.has(obj, prop);
8. [[Get]]
- 获取对象属性的值
- 对应操作
obj.prop;
Reflect.get(obj, prop);
9. [[Set]]
- 设置对象属性的值
- 对应操作
obj.prop = value;
Reflect.set(obj, prop, value);
10. [[Delete]]
- 删除对象的属性
- 对应操作
delete obj.prop;
Reflect.deleteProperty(obj, prop);
11. [[OwnPropertyKeys]]
- 返回对象自身所有属性键(包括 Symbol 和不可枚举属性)。
- 对应操作
Object.keys(obj); //严格版,不能返回不可枚举属性
Reflect.ownKeys(obj);
12. [[Call]] 函数专有
- 执行普通函数调用(非 new 调用)
- 对应操作
fun(); //直接调用
const proxy = new Proxy(func, {
apply(target, thisArg, args) { //监听函数的调用
console.log("Function called");
return Reflect.apply(target, thisArg, args);
}
});
13. [[Construct]] 函数专有
- 通过 new 运算符创建实例(构造函数调用)
- 对应操作
new Func(); // 构造函数调用
const proxy = new Proxy(Func, {
construct(target, args, newTarget) { //拦截构造函数调用
console.log("Instance created");
return Reflect.construct(target, args, newTarget);
}
});
九、作用域
作用域是当前的执行上下文,在其中的值和表达式“可见”(可被访问)
。如果一个变量或表达式不在当前的作用域中,那么它是不可用的。作用域也可以堆叠成层次结构,子作用域可以访问父作用域,反过来则不行。
JavaScript 的作用域分以下三种:
- 全局作用域:脚本模式运行所有代码的默认作用域
- 模块作用域:模块模式中运行代码的作用域,也就是
<script type=module></script>
里面的代码 - 函数作用域:由函数创建的作用域
- 块级作用域:用一对
花括号(一个代码块)
创建出来的作用域,let
或const
声明的变量也属于这个作用域
十、原型和原型链
javaScript 中所有的对象
都有一个内置属性
,称为它的prototype(原型)
。可分为隐式原型和显式原型,函数
是一种可调用的特殊对象
,它天生自带一个显式原型,通过prototype
来访问,普通对象是通过new
一个函数
生成的,它天生自带一个隐式原型__proto__
来访问的,原型本身也是一个对象,故原型对象也会有它自己的原型,逐渐构成了原型链。原型链终止于拥有 null 作为其原型的对象上
let obj = {}; //语法糖,底层是new Object({})
console.log(obj.prototype); //undefined
console.log(obj.__proto__) // 一大堆属性
console.log(obj.__proto__ === Object.prototype) //true
通过上面的例子可以看出来,obj是由Object函数new
出来的,所以它只有隐式原型__proto__
,并且__proto__
指向Object函数的显式原型prototype
,这样就形成了一个三角关系,通过不停的找三角关系就能画出整个原型链,这里有两个特殊点,就是Object.prototype的原型是null
,以及Function
函数对象的__proto__
和prototype
是同一个
十一、this指向
以下研究的只是函数中的this指向,且不包括箭头函数
,箭头函数是另一种情况
最外层打印this,在node环境指向一个{}
,在浏览器环境中,如果是非严格模式是window
,严格模式是undefined
,
调用方式 | 示例 | 函数中的this指向 |
---|---|---|
通过new调用 | new method() | 新对象 |
直接调用 | method() | 非严格模式是全局对象,严格模式是undefined |
通过对象调用 | obj.method() | 前面的对象 |
call、apply、bind | method.call(ctx) | 第一个参数 |
在非严格模式下,一个特殊的过程称为this
替换确保this
的值总是一个对象。这意味着:
- 如果一个函数被调用时
this
被设置为undefined
或null
,this
会被替换为全局对象
。 - 如果函数被调用时 this 被设置为一个
原始值
,this 会被替换为原始值的包装对象
。
在箭头函数
中,this 保留了闭合词法上下文
的 this 值。换句话说,当对箭头函数求值时,语言不会创建一个新的 this 绑定。
箭头函数
在其周围的作用域上创建一个 this 值的闭包,这意味着箭头函数的行为就像它们是“自动绑定”的——无论如何调用,this 都绑定到函数创建时的值。在其他函数内部创建的箭头函数也是如此:它们的this
值保持为闭合词法上下文的this
。就算用call
、apply
、bind
也不能改变其this的指向
let obj = {
a: 1,
b: () => {
console.log(this); //依旧打印全局对象
},
};
obj.b.call({c:1});
总结:普通函数内部的this
指向是在运行时才能确定,箭头函数内部的this
是在创建时就确定了,不会再进行更改
function abc(){
console.log(this) //打印全局对象
}
abc.apply(undefined);
十二、globalThis
一个适用于任何运行环境的获取全局变量
方法,在以前,从不同的 JavaScript 环境中获取全局对象需要不同的语句。在 Web 中,可以通过window
、self
或者 frames
取到全局对象,但是在 Web Workers 中,只有self
可以。在 Node.js 中,它们都无法获取,必须使用global
。
十三、Class
类声明的类体在严格模式
下执行。class
声明与let
非常相似:
- class 声明的作用域既可以是块级作用域,也可以是函数作用域
- class 声明只能在其声明位置之后才能访问(暂时性死区)。因此 class 声明通常被认为是不可变量提升的(与函数声明不同)。
- class 声明在脚本顶层声明时不会在
globalThis
上创建属性(与函数声明不同) - 在同一作用域内,class 声明不能被任何其他声明
重复声明
Class只是一种语法糖,底层还是会转换成普通的构造函数
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
say() {
console.log(`${this.name}的年龄是${this.age}`);
}
static sayHello() {
console.log("hello");
}
}
转成普通函数
function Person2(name, age) {
"use strict"; //Class内部是严格模式
if (!new.target) {
//Class是不能直接调用的,new.target指向当前的Class或者函数,普通函数直接调用返回undefined
throw new Error(
"Class constructor Person2 cannot be invoked without new"
);
}
this.name = name;
this.age = age;
}
// 普通方法是放在函数的原型上,但是Class里面的普通方法是不可枚举的
Object.defineProperty(Person2.prototype, "say", {
value: function () {
if (new.target) {
throw new Error("内部函数不能被new调用");
}
console.log(`${this.name}的年龄是${this.age}`);
},
enumerable: false, //不可枚举
});
// 静态方法是放在函数本身上的
Object.defineProperty(Person2, "sayHello", {
value: function () {
if (new.target) {
throw new Error("is not a constructor");
}
console.log("hello");
},
});
十四、同源策略
同源策略是一个重要的安全策略,它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互。当协议
、主机
、端口
都相同的时候,称为同源
,反之为异源
以下是可能嵌入跨源的资源的一些示例:
- 使用
<script src="…"></script>
标签嵌入的 JavaScript 脚本。语法错误信息只能被同源脚本中捕捉到。 - 使用
<link rel="stylesheet" href="…">
标签嵌入的 CSS。由于 CSS 的松散的语法规则,CSS 的跨源需要一个设置正确的Content-Type
标头。如果样式表是跨源的,且 MIME 类型不正确,资源不以有效的 CSS 结构开始,浏览器会阻止它的加载。 - 通过
<img>
展示的图片。 - 通过
<video>
和<audio>
播放的多媒体资源。 - 通过
<object>
和<embed>
嵌入的插件。 - 通过
@font-face
引入的字体。一些浏览器允许跨源字体(cross-origin fonts),另一些需要同源字体 - 通过
<iframe>
载入的任何资源。站点可以使用X-Frame-Options
标头来阻止这种形式的跨源交互。
通过标签引入异源的资源也会触发跨域,只是浏览器对这些资源的跨域限制很轻微,只有对ajax
请求的限制很严格
十五、asnyc、defer、preload、prefetch
asnyc
、defer
是用于script
标签都不阻塞html
解析,区别在于,asnyc
是异步
的意思,当js文件异步加载完毕会立即执行,也会阻塞html
解析,而defer
是推迟
的意思,就算js文件加载完毕,也会等待html解析完毕之后再执行,也就是推迟执行。注意这两种方式只适用于外部脚本,也就是单独抽离成一个js文件,没有写在html文件内部的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script defer> //defer不生效,一样导致页面卡五秒
const test = () => {
let date = new Date().getTime();
while (new Date().getTime() - date < 5000) {}
};
test();
</script>
<div>我加载好了</div>
</body>
</html>
preload
、prefetch
是用于link
标签,加载外部资源的,只加载不执行
,preload
的优先级高于prefecth
,preload
是立即加载资源,prefecth
是浏览器在空闲的时候去加载外部资源
十六、Proxy
Proxy
对象用于创建一个对象的代理,从而实现基本操作
的拦截和自定义
(如属性查找、赋值、枚举、函数调用等)。结合Reflect
能实现完美的属性劫持
const p = new Proxy(target, handler)
- target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
- handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理对象的行为。
创建可撤销的Proxy
对象
Proxy.revocable(target, handler);
拦截操作:
- handler.getPrototypeOf()
读取代理对象的原型时,该方法就会被调用
let obj = {
a:1,
b:2
}
let proxy = new Proxy(obj,{
// 拦截获取原型
getPrototypeOf(target){
return {}
}
})
console.log(Object.getPrototypeOf(proxy))
console.log(proxy.__proto__)
</script>
- handler.setPrototypeOf()
设置代理对象的原型时,该方法就会被调用
let obj = {
a: 1,
b: 2,
};
let proxy = new Proxy(obj, {
// 拦截设置原型
setPrototypeOf(target, prototype) {
console.log("正在设置原型");
Reflect.setPrototypeOf(target, prototype);
return true;
},
});
Object.setPrototypeOf(proxy, { a: 1, 1: 2 });
console.log(Object.getPrototypeOf(proxy));
- handler.isExtensible()
方法用于拦截对对象的Object.isExtensible()
。
let obj = {
a: 1,
b: 2,
};
let proxy = new Proxy(obj, {
isExtensible(target) {
console.log("正在判断是否可扩展");
return Reflect.isExtensible(target);
},
});
console.log(Object.isExtensible(proxy),"是否可扩展");
- handler.preventExtensions()
拦截Object.preventExtensions(),返回一个布尔值,判断一个对象是否不可扩展 - handler.getOwnPropertyDescriptor()
拦截Object.getOwnPropertyDescriptor()
,获取对象上某个属性的描述符 - handler.defineProperty()
拦截Object.defineProperty()
- handler.has()
in
操作符的捕捉器。 - handler.get()
属性读取操作的捕捉器。 - handler.set()
属性设置操作的捕捉器。 - handler.deleteProperty()
delete
操作符的捕捉器。 - handler.ownKeys()
Object.getOwnPropertyNames
方法和Object.getOwnPropertySymbols
方法的捕捉器。 - handler.apply()
函数调用操作的捕捉器 - handler.construct()
new
操作符的捕捉器。
十七、Reflect
flect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler 的方法相同,这里就不一一列举了。Reflect 不是一个函数对象,因此它是不可构造的
十八、Object
- 获取 BigInt 和 Symbol 的封装对象
当用new
调用BigInt()
和Symbol()
构造函数时会抛出一个错误,以阻止创建封装对象而不是基本类型值的常见错误。为这些类型创建封装对象的唯一方法是使用它们调用 Object():
const numberObj = new Number(1);
console.log(typeof numberObj); // "object"
const bigintObj = Object(1n);
console.log(typeof bigintObj); // "object"
const symbolObj = Object(Symbol("foo"));
console.log(typeof symbolObj); // "object"
1.静态方法
- Object.assign()
Object.assign() 静态方法将一个或者多个源对象中所有可枚举
的自有属性
复制到目标对象,并返回修改后的目标对象。该方法只有第一层
才是深拷贝
let obj = new Object({a:1,b:2,c:Symbol(1),d:{e:10}});
let obj1 = Object.assign({},obj);
obj1.a = 10;
obj1.d.e = 20;
console.log(obj1); //{a: 10, b: 2, c: Symbol(1), d: {e:20}}
console.log(obj);//{a: 1, b: 2, c: Symbol(1), d: {e:20}}
- Object.create()
Object.create() 静态方法以一个现有对象作为原型,创建一个新对象{}
。
Object.create(proto)
Object.create(proto, propertiesObject)
let obj = new Object({a:1,b:2,c:Symbol(1),d:{e:10}});
let obj3 = Object.create(obj, {
a: {
value: 10,
writable: true,
enumerable: true,
configurable: true,
},
b: {
value: 20,
writable: true,
enumerable: true,
configurable: true,
},
});
console.log(obj3); //{a:10,b:20}
console.log(Reflect.getPrototypeOf(obj3) === obj); // true
- Object.defineProperty()
Object.defineProperty() 静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。
let obj3 = Object.create({});
Object.defineProperty(obj3,'a',{
value:10,
writable:true,
enumerable:true,
configurable:true
});
console.log(obj3.a); //10
- Object.defineProperties()
Object.defineProperties() 静态方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。和上面的方法区别是,可以一次性创建多个属性
const object1 = {};
Object.defineProperties(object1, {
property1: {
value: 42,
writable: true,
},
property2: {},
});
- Object.entries()
Object.entries() 静态方法返回一个数组,包含给定对象自有
的可枚举字符串键
属性的键值对。
let obj4 = {a:1,b:2,c:{d:3},[Symbol('e')]:1}
let arr = Object.entries(obj4);
console.log(arr); //[['a',1],['b',2],['c',{d:3}']],没有Symbol属性
- Object.freeze()
Object.freeze()
静态方法可以使一个对象被冻结。冻结对象可以防止扩展,并使现有的属性不可写入和不可配置。被冻结的对象不能再被更改:不能添加新的属性,不能移除现有的属性,不能更改它们的可枚举性、可配置性、可写性或值,对象的原型也不能被重新指定。freeze()
返回与传入的对象相同的对象。这种冻结只是一种浅层冻结
冻结一个对象是 JavaScript 提供的最高完整性级别保护措施。
const obj = {
prop() {},
foo: "bar",
};
// 冻结前:可以添加新属性,也可以更改或删除现有属性
obj.foo = "baz";
obj.lumpy = "woof";
delete obj.prop;
// 冻结。
const o = Object.freeze(obj);
// 返回值和我们传入的对象相同。
o === obj; // true
// 对象已冻结。
Object.isFrozen(obj); // === true
// 现在任何更改都会失败。
obj.foo = "quux"; // 静默但什么都没做
// 静默且没有添加属性
obj.quaxxor = "the friendly duck";
// 严格模式下,这样的尝试会抛出 TypeError
function fail() {
"use strict";
obj.foo = "sparky"; // 抛出 TypeError
delete obj.foo; // 抛出 TypeError
delete obj.quaxxor; // 返回 true,因为属性‘quaxxor’从未被添加过。
obj.sparky = "arf"; // 抛出 TypeError
}
fail();
// 尝试通过 Object.defineProperty 更改;
// 下面的两个语句都会抛出 TypeError。
Object.defineProperty(obj, "ohai", { value: 17 });
Object.defineProperty(obj, "foo", { value: "eit" });
// 同样无法更改原型
// 下面的两个语句都会抛出 TypeError。
Object.setPrototypeOf(obj, { x: 20 });
obj.__proto__ = { x: 20 };
- Object.fromEntries()
Object.fromEntries() 静态方法将键值对列表转换为一个对象。
const entries = new Map([
["foo", "bar"],
["baz", 42],
]);
const obj = Object.fromEntries(entries); //entries必须是可迭代对象
console.log(obj);
// Expected output: Object { foo: "bar", baz: 42 }
- Object.getOwnPropertyDescriptor()
获取对象上面某个键的属性描述符
const object1 = {
property1: 42,
};
const descriptor1 = Object.getOwnPropertyDescriptor(object1, "property1");
console.log(descriptor1.configurable);
// Expected output: true
console.log(descriptor1.value);
// Expected output: 42
- Object.getOwnPropertyDescriptors()
获取对象上面所有键的属性描述符
,包括Symbol
- Object.getOwnPropertyNames()
Object.getOwnPropertyNames() 静态方法返回一个数组,其包含给定对象中所有自有属性(包括不可枚举属性,但不包括使用 symbol 值作为名称的属性)。 - Object.getOwnPropertySymbols()
Object.getOwnPropertySymbols() 静态方法返回一个包含给定对象所有自有 Symbol 属性的数组。 - Object.getPrototypeOf()
Object.getPrototypeOf() 静态方法返回指定对象的原型(即内部 [[Prototype]] 属性的值。 - Object.groupBy()
Object.groupBy() 静态方法根据提供的回调函数返回的字符串值对给定可迭代对象中的元素进行分组。返回的对象具有每个组的单独属性,其中包含组中的元素的数组。
这个静态方法实用性很强,就是兼容性太差了
const inventory = [
{ name: "asparagus", type: "vegetables", quantity: 9 },
{ name: "bananas", type: "fruit", quantity: 5 },
{ name: "goat", type: "meat", quantity: 23 }
];
const restock = { restock: true };
const sufficient = { restock: false };
const result = Object.groupBy(inventory, ({ quantity }) =>
quantity < 6 ? "restock" : "sufficient",
);
console.log(result.restock); // [{ name: "bananas", type: "fruit", quantity: 5 }]
console.log(result.sufficient);
//[{ name: "asparagus", type: "vegetables", quantity: 9 },{ name: "goat", type: "meat", quantity: 23 }]
- Object.hasOwn()
如果指定的对象自身
有指定的属性,(包括不可枚举和Sybmol属性)
,则静态方法 Object.hasOwn() 返回 true。如果属性是继承的或者不存在,该方法返回 false。 - Object.is()
Object.is() 静态方法确定两个值是否为相同值,它和===
的区别在于它们处理带符号的 0
和NaN
值的时候,===
会将-0
和+0
视为相等,NaN
和NaN
视为不相等,Object.is
则反之 - Object.seal()
密封一个对象会阻止其扩展并且使得现有属性不可配置。密封对象有一组固定的属性:不能添加新属性
、不能删除现有属性
或更改其可枚举性
和可配置性
、不能重新分配其原型
。只要现有属性的值是可写的,它们仍然可以更改。seal() 返回传入的同一对象。与Object.freeze()
的区别在于,Object.seal()
可以重新赋值
const obj = {
prop() {},
foo: "bar",
};
// 可以添加新属性,可以更改或删除现有属性。
obj.foo = "baz";
obj.lumpy = "woof";
delete obj.prop;
const o = Object.seal(obj);
o === obj; // true
Object.isSealed(obj); // true
// 更改密封对象的属性值仍然有效。
obj.foo = "quux";
// 但不能将数据属性转换成访问者属性,反之亦然。
Object.defineProperty(obj, "foo", {
get() {
return "g";
},
}); // 抛出 TypeError
// 现在,除了属性值之外的任何更改都将失败。
obj.quaxxor = "the friendly duck";
// 静默不添加属性
delete obj.foo;
// 静默不添删除属性
// ...且严格模式下,这种尝试将会抛出 TypeError。
function fail() {
"use strict";
delete obj.foo; // 抛出一个 TypeError
obj.sparky = "arf"; // 抛出一个 TypeError
}
fail();
// 尝试通过 Object.defineProperty 添加属性也会抛出错误。
Object.defineProperty(obj, "ohai", {
value: 17,
}); // 抛出 TypeError
Object.defineProperty(obj, "foo", {
value: "eit",
}); // 更改现有属性值
- Object.preventExtensions()
Object.preventExtensions() 静态方法可以防止新属性被添加到对象中(即防止该对象被扩展)。它还可以防止对象的原型被重新指定
let obj = {a:1,b:2};
let obj1 = Object.preventExtensions(obj);
obj1.c = 3;
delete obj1.a;
console.log(obj1); //{b: 2}
- Object.keys()
Object.keys() 静态方法返回一个由给定对象自身
的可枚举
的字符串键
属性名组成的数组。
Object.keys() 返回一个数组,其元素是字符串,对应于直接在对象上找到的可枚举的字符串键属性名。这与使用for...in
循环迭代相同,只是 for…in 循环还会枚举原型链
中的属性。Object.keys() 返回的数组顺序和与 for…in 循环提供的顺序相同。 - Object.values()
Object.values() 静态方法返回一个给定对象的自有可枚举字符串
键属性值组成的数组。 - Object.isExtensible()
Object.isExtensible() 静态方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。 - Object.isFrozen()
Object.isFrozen() 静态方法判断一个对象是否被冻结。 - Object.isSealed()
Object.isSealed() 静态方法判断一个对象是否被密封。 - Object.setPrototypeOf()
Object.setPrototypeOf() 静态方法可以将一个指定对象的原型(即内部的 [[Prototype]] 属性)设置为另一个对象或者 null。
2.原型上的方法
- Object.prototype.valueOf()
valueOf可以将调用方原样输出,且类型不变(包装对象要转换成原始值)
十九、 强缓存和协商缓存
首先要明确的一点时,无论强缓存还是协商缓存都是服务端
做的事,前端是影响不了的
-
强缓存
响应头
中有Expires
字段,后面带具体过期日期,目前用的较少了
现在的强缓存是通过响应体
里面的Cache-Control
字段,并且值为max-age=xxx
,这个xxx
代表的距离这次请求,在xxx
秒之类不需要发请求了,直接取浏览器缓存,max-age
优先级高于Expires
-
协商缓存
当Cache-Control
字段的值等于-no-cache
时表明不使用本地缓存,需要使用协商缓存
-
Last-Modify/If-Modify-Since:浏览器第一次请求一个资源的时候,会在响应头里面加上
Last-Modify
,表示该资源文件的最后修改时间,当第二次请求该资源的时候,请求头
会包含If-Modify-Since
并且值等于Last-Modify
,服务端通过这个值来进行比对,来判断资源是否有变动,如果没有变动,则返回304
,使用浏览器缓存,这种协商缓存有一定的缺陷,如果在一秒钟之内进行了修改,Last-Modify的值并不会体现出来,导致更新不及时 -
Etag/If-None-Match:web服务器响应请求时,会告诉浏览器当前资源的在服务器的唯一标识。当再次请求该资源的时候,如果发现有Etag声明,就会在请求头里面加上
If-None-Match
,值就是Etag,通过和比对来判断资源是否修改过
协商缓存会向服务器发送请求,强缓存不会发送请求
Cache-Control
还有其他值 -
public:可以被所有的用户缓存,包括终端用户和CDN等代理服务器
-
private: 只能被终端用户的浏览器缓存,不允许CDN等缓存
二十、请求报文
请求格式由三部分组成
- 请求行
格式为:<方法> <请求目标> <HTTP版本>
POST /api/user/login HTTP/1.1
请求方法 请求路径 请求协议
- 请求头
键值对形式,提供请求的附加信息,每行一个头部字段。
Host: example.com
Content-Type: application/json
Content-Length: 28
- 空行
请求头和请求体之间必须有一个空行(\r\n)
,用于分隔。
- 请求体
传递给服务器的一些信息
{"username": "admin", "password": "123"}
下面是Content-Type
为form-data
格式的请求报文
POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 348
----WebKitFormBoundary7MA4YWxkTrZu0gW
//上面的分隔符
Content-Disposition: form-data; name="username"
//表单字段名
admin
//表单字段值,使用\r\n换行
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
//字段名,如果是文件,filename是文件名
Content-Type: image/jpeg
//文件类型
<这里是文件photo.jpg的二进制数据(实际内容会替换为字节流)>
----WebKitFormBoundary7MA4YWxkTrZu0gW--
//分隔符后面加--,表示结束了
二十一、Web Worker
二十二、WebAssembly
WebAssembly 是一种新的编码方式,可以在现代的 Web 浏览器
中运行,不像以前只能运行javascript
,它可以将诸如C++
、C
、Rust
、AssemblyScript(类似于TS)
等源语言提供一个有效的编译目标,并且提高性能
具体API查看MDN
这里使用的是assemblyScript来编写的,首先需要安装它
npm install --save-dev assemblyscript
在package.json中配置脚本命令
"scripts": {
"assembly": "asc --target abc"
},
在根路径创建asconfig.json
文件,assemblyscript
会来读这个配置文件
{
"entries": [
"./assembly.ts" //入口文件路径
],
"options": {
"buildings":"esm" //编译成esm模块
},
"targets": {
"abc": { //abc对应脚本命令里面的target
"optimize": true,
"outFile": "dist/myModule.release.wasm", //产物路径
"textFile": "dist/myModule.release.wat", //可阅读wat的路径
"sourceMap": true //sourceMap不多说
}
}
}
assembly.ts文件
// 导出一个加法函数
export function add(a: i32, b: i32): i32 {
let val = logNumber(a + b);
return a + b + val;
}
// 导出一个内存操作示例:计算数组的和
export function sumArray(arr: Int32Array): i32 {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
declare function logNumber(num: i32): i32; //这个是外部导入给wasm文件使用的,用于js和wasm通信的
执行npm run assembly
后就会生成wasm文件了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
async function init() {
let obj = {
// 这个env对象是必要的,不然会报错
// TypeError: WebAssembly.instantiate(): Import #1 "env": module is not an object or function
env: {
abort: () => {
console.error("WASM abort called!");
},
},
// 因为给wasm文件传递了一个函数,所以这里必须要加上,不然也要报错
assembly:{
logNumber: (num) => num * 2,
}
};
// const module = await WebAssembly.compileStreaming(
// fetch("./dist/myModule.release.wasm"),
// obj
// );
// const instance = await WebAssembly.instantiate(module, obj);
//上面的可以简化成下面的代码
const {instance} = await WebAssembly.instantiateStreaming(
fetch("./dist/myModule.release.wasm"),
obj
);
console.log(instance.exports.add(3, 5));
}
init();
</script>
</body>
</html>
在vite
中使用wasm
文件更加简单
预编译的 .wasm 文件可以通过?init
来导入。 默认导出一个初始化函数,返回值为所导出WebAssembly.Instance
实例对象的 Promise:
import init from './example.wasm?init'
init().then((instance) => {
instance.exports.test()
})
init
函数还可以将传递给WebAssembly.instantiate
的导入对象作为其第二个参数:
init({
imports: {
someFunc: () => {
/* ... */
},
},
}).then(() => {
/* ... */
})
其他内容查看vite官网