一级保护,不可扩展
我们之前在创建的时候,因为JS是一个弱语言,所以对象在创建好之后,任然可以二次添加删除修改属性
同时在ES6里面,虽然推出了const,但是const锁栈不锁堆,所以对于对象的保护任然欠缺,这个时候ES6推出了新的技术用于保护对象
const obj = {
userName:"张三"
}
obj.age = 20;
obj.userName = "李四"
如果我们希望一个对象构建好之后,不再去添加新的属性和方法,我们就需要把这个对象设置成一个一级保护对象:不可扩展对象
const obj = {
userName:"张三"
}
Object.preventExtensions(obj);
obj.age = 20 //这个时候age是无法添加上去的
二级保护:不可扩展,不可删除
const obj = {
userName:"张三",
sex:"男"
}
Object.seal(obj);
obj.age = 20; //无法新增
delete obj.sex; //false
三级保护:不可扩展,可能删除、不能修改,把堆锁死
let obj = {
userName:"张三",
sex:"男"
}
let obj1 = {
aa:1
}
Object.freeze(obj);
obj.age = 20;
delete obj.sex;
obj.userName = "李四"
obj = obj1
obj.aa = 2;
说明:
冻结可以锁堆,但是不能锁栈,所以如果想堆栈全锁,可以使用const声明
之前我们在运算符当中,我们学习过完全等于运算符,Object.is()则比完全等于运算符还要严格,它主要是用于对象的判断,它判断的是内存
let obj1 = {
userName:"张三"
}
let obj2 = obj1; //浅拷贝,栈里面的地址是相同的
let obj3 = {
userName:"张三"
}
console.log(Object.is(obj1,obj2)); //true
console.log(Object.is(obj1,obj3)); //false
求一个数的多少次方可以使用指数运算,运算符使用 **
let num = 3 ** 5
JS中所有的数值都是number类型,而number类型是不分浮点和整数,但是范围有限,所以JS在进行数值很大的运算的时候会容易溢出
let num1 = 2 ** 513
let num2 = 2 ** 513 + 1
console.log(num1 == num2); //true
结果比较是个true,这是不对的,因为这个数值已经超过JS的最大处理范围,它计算不过来
所以ES6为了解决这个问题,推出了BigInt
let num3 = 2n;
let num4 = 0.5n; //报错
现在再去运算上面的例子
let num1 = 2n ** 513n
let num2 = 2n ** 513n + 1n
console.log(num1 == num2); //false
注意:
bigInt不能和Number混合运算
使用BigInt()方法可以将其他类型的数据进行bigint类型的转换,但是并不是所有情况下的值都可以被转换
它叫标识类型,页叫做全局唯一标识符
let s1 = Symbol();
let s2 = Symbol();
console.log(s1 == s2);
Symbol是通过调用Symbol方法得到的,每次结果都不一样,它是全局唯一的
let s1 = Symbol.for("张三");
let s2 = Symbol.for("张三");
console.log(s1 == s2); //true
这里有个情况注意下
let s3 = Symbol("张三");
let s4 = Symbol("张三");
console.log(s3 == s4); //false
分析:
1、for方法我们可以理解成从张三的身上拿去一个标识符,那么就意味张三身上应该事前有标识符在的,而且是唯一的,所以不管是s1还是s2,从张三身上拿到的都是一样的标识符
2、s3和s4就不太一样,它们没有使用for,所以我们认为这个过程是先获取了一个标识符然后再贴给张三,所以s3和s4不一样
Symbol是具备唯一性,不会重复,所以当某些东西不能重复的时候,我们会使用它,再对象里面,属性名是不能重复的,如果重复了,则后面的属性会覆盖掉前面的属性值
obj = {
userName:"张三",
age:18,
userName:"李四"
}
现在李四会把张三覆盖掉,但是我们想两个属性值都留下来,怎么办?
obj = {
[Symbol("userName")]:"张三",
age:18,
[Symbol("userName")]:"李四"
}
上面的代码,对象内有两个相同的属性名,为了把相同的属性名对应的属性值都保留下来,我们可以使用Symbol,但是实际开发中,我们不会像上面 这样写
实际上我们再有多个对象的时候,我们可能会合并对象,那么这个时候某些属性不需要被合并掉,怎么办?
let obj1 = {
[Symbol("userName")]:"张三",
age:18
}
let obj2 = {
[Symbol("userName")]:"李四",
sex:"女"
}
let obj3 = {
...obj1,
...obj2
}
Symbol最大的应用点就是做属性名,之前我们学习过遍历对象
1、for…in
2、Object.keys()
3、Object.getOwnPropertyNames()
这几种遍历对象的时候,如果属性名是Symbol 是得不到
let obj1 = {
[Symbol("userName")]:"张三",
age:18
}
let obj2 = {
[Symbol("userName")]:"李四",
sex:"女"
}
let obj3 = {
...obj1,
...obj2
}
var propertyArr = Object.getOwnPropertyNames(obj3); //遍历不到symbol属性
如果想要获取Symbol属性名,需要一个单独的方法
var propertyArr = Object.getOwnPropertySymbols(obj3);
最后注意一点,JSON在序列化对象的时候,不会操作Symbol
总结:
1、Symbol是默认不重复的,它是全局唯一的
2、Symbol是基础数据类型,它必须通过Symbol()返回值获取,每次获取的都不一样
3、Symbol.for()可以得到相同的标识名
4、Symbol(“描述信息”) 在创建标识的时候添加描述信息
5、在Symbol中,有一些特殊值,目前用的最多的就是 [Symbol.iterator] 迭代器
它是ES6中新增的一个函数类型,旨在解决迭代的问题
function* abc(){
}
在普通函数里面,我们可以通过return返回一个值,并且只能返回一次,因为函数一旦碰到return就直接结束了
试想一下,有没有一种函数可以多次返回,这个时候迭代器就来帮你解决问题
要实现迭代器就必须通过生成器函数的执行来获得
function* abc(){
//生成器函数内部是可以返回多个值
//要返回多个值使用yield
yield "a";
yield "b";
yield "c";
return "张三"
}
var x = abc() //生成器函数执行以后,得到迭代器
分析:
在上面的代码中,我们的abc生成器函数使用yield返回了a,b,c 三个值,最后又返回了 张三,整个生成器函数向外传递了4个值
但是,这4个值并不是一次性传递出去的,而是一个一个返回的
function* abc(){
//生成器函数内部是可以返回多个值
//要返回多个值使用yield
yield "a";
yield "b";
yield "c";
return "张三"
}
var x = abc() //生成器函数执行以后,得到迭代器
let a = x.next();
let b = x.next();
let c = x.next();
let d = x.next();
分析:
每一次next方法的执行就会返回一个yield的结果,直到最后一个return结束,返回的结果是一个对象,对象中value属性表示返回的数据,done表示是否迭代完毕
一个生成器函数的执行可以得到一个迭代器,这个迭代器可以得到一个next方法,这个时候注意,这个next是可以接收参数的,将接收的参数传回给生成器函数的内部
function* abc(str){
console.log(str);
let a1 = yield "a";
console.log(a1);
let b1 = yield "b";
console.log(b1);
let c1 = yield "c";
console.log(c1);
return "张三"
}
var x = abc("张三") //生成器函数执行以后,得到迭代器
let a = x.next();
let b = x.next("李四");
let c = x.next("王五");
let d = x.next("赵六");
注意:
这里我们看到结果,张三是没有打印出来的,如果生成器函数要接收初次next的参数,应该直接给到str中接收
function* xyz(){
yield "a";
yield "b";
yield* def(); //yield "张三"; yield "李四"
yield "c";
yield "d"
}
function* def{
yield "张三";
yield "李四"
}
最终打印结果应该是 a b 张三 李四 c d
迭代就是把一群东西(数据集合)一个一个拿出来的过程,并且只能顺着拿,一旦迭代结束不能重新开始,也不能break
迭代器就是一个个返回的过程,可以多次返回,迭代器只能通过生成器函数得到
接口可以理解成是一种规范,只要实现了这个接口,就一定实现了这个规范,实现了这个规范就可以具备这个能力
当我们生成迭代器的时候生成一个迭代器轴,这个轴内部有一个默认状态,这个状态决定了done这个属性的值是true还是false
1、suspended 暂停状态
2、closed 关闭状态,当前迭代结束进行关闭状态
迭代器本身是可以遍历,在上面的代码中我们可以看到,我们需要使用next方法一个一个去拿,这样很麻烦
只要实现了迭代器接口的就可以使用for…of ,而迭代器本身就是一个规范,所以肯定是可以使用for…of
function* xyz(){
yield "a";
yield "b";
yield* def(); //yield "张三"; yield "李四"
yield "c";
yield "d"
}
function* def(){
yield "张三";
yield "李四"
}
let z = xyz();
for(let item of z){
console.log(item);
}
如果可以使用for…of 就一定可以使用展开运算符
console.log(...z);
注意:
迭代器一旦结束不能重新开始,所以上面的for…of与展开运算符不能同时使用
主动实现迭代器接口,只需要在对象内部添加一个Symbol(Symbol.iterator)
let obj1 = {
0:"张三",
1:"李四",
2:"王五",
3:"赵六",
length:4,
*[Symbol.iterator](){
let index = 0;
while(index < this.length){
yield this[index];
index++
}
}
}
//以上是我们自定义的类数组
for(let item of obj1){
console.log(item);
}
我们可以把反射看做成镜子里面的object对象,这个镜子比较厉害,可以把看的到和看不到的全照出来
var obj1 = {
sex:"男",
[Symbol("userName")]:"张三"
}
Object.defineProperty(obj1,"age",{
value:18,
enumerable:false
})
var arr1 = Object.keys(obj1);
var arr2 = Object.getOwnPropertyNames(obj1);
var arr3 = Object.getOwnPropertySymbols(obj1);
var arr4 = Reflect.ownKeys(obj1);
1、apply相当于之前方法.apply一样(存疑???????????)
2、consturct 相当于new调用一个构造函数
class Student{
constructor(userName){
this.userName = userName
}
}
let s1 = Reflect.construct(Student,["李四"]);
let s2 = new Student("张三")
3、defineProperty 在一个对象上面添加一个新属性,相当于Object.defineProperty
4、deleteProperty 删除对象的一个属性,相当于delete
5、get获取对象某一个属性值
6、getOwnPropertyDescriptor 获取当前对象属性的描述信息,相当于Object.getOwnPropertyDescriptor
7、getPrototypeof 获取当前对象的原型
8、has 判断某一个对象内是否有某个属性,相当于Object.hasOwnProperty()
9、isExtensible 判断一个对象是否为一级保护对象
10、ownKeys 获取对象的所有属性名
11、set 设置对象的某一个属性
代理可以理解成是一个代理人,在JS里面代理实现的其实就是一个全局拦截
代理里面两个核心
1、代理对象proxy
2、目标对象target
var obj = {
userName:"张三",
work:"厨子",
money:10000,
address:"上海"
}
let obj_proxy = new Proxy(obj,{
})
控制台打印两个对象会发现代理比目标多了两个东西
handler 具体的代理了目标对象的哪些操作
Target 代理的目标对象
obj_proxy.money = 200
我们执行了代理对象money属性的赋值操作,对应的目标对象money属性也一同变化
全局拦截
var obj = {
userName:"张三",
work:"厨子",
money:10000,
address:"上海"
}
let obj_proxy = new Proxy(obj,{
//取值操作
get(target,p){
if(p == "money"){
return "不关你的事"
}else{
target[p]
}
},
//赋值操作
set(target,p,v){
if(p == "money"){
console.log("给我涨工资么?")
}else{
target[p] = v;
}
}
})
代码分析:
上面的get是取值操作,所有的取值操作都会触发get方法,而set方法则是代理所有的赋值操作,所以所有的赋值操作都会触发set方法
在上面的代码中我们其实还有问题,我们的obj_proxy作为代理对象是要去保护目标对象的,但是结果却没有
我们依然可以通过目标对象直接去调用,怎么样让代理对象真正的保护好目标对象?
let p1 = new Proxy((()=>{
return {
userName:"张三",
password:"123",
sex:"男",
age:20,
height:170
}
})(),{
get(target,p){
if(p == "userName" || p == "sex"){
return target[p]
}else{
return undefined
}
},
set(target,p,v){
if(p == "userName" || p == "sex"){
target[p] = v;
}
}
})
代码分析:
上面的代码里面,我们的目标对象并没有暴露在外面,我们目标对象是直接通过立即执行函数返回的一个对象,这样外部就访问不到这个对象,只能访问到p1代理对象
let student = new Proxy((() => {
return {
userName:"张三",
_sex:"男",
age:20,
hobby:"sleep",
_IDCard:"1234567",
_tel:"1311111111",
address:"北京"
}
})(),{
get(target,p){
return target[p]
},
set(target,p,v){
if(p.startsWith("_")){
return
}else{
target[p] = v;
}
},
//代理对象的删除操作
deleteProperty(target,p){
if(p.startsWith("_")){
return
}else{
return delete target[p]
}
}
})