ECMAScript 6 (二)

发布时间:2024年01月14日

ECMAScript 6 (二)

面向对象的扩展

不可扩展

一级保护,不可扩展

我们之前在创建的时候,因为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()

之前我们在运算符当中,我们学习过完全等于运算符,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

大整数数据类型BigInt

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()方法可以将其他类型的数据进行bigint类型的转换,但是并不是所有情况下的值都可以被转换

Symbol数据类型

它叫标识类型,页叫做全局唯一标识符

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应用点

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做属性名

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 设置对象的某一个属性

代理Proxy

代理可以理解成是一个代理人,在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]
        }
    }
})
文章来源:https://blog.csdn.net/qq_64558518/article/details/135586474
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。