为什么要保护对象
在旧的js中的对象毫无自保能力,对象的结构和属性值可以被任意修改。
比如下面的例子
var eric = {
eid: 1,
ename: "xiaoming",
money: "10",
car: "自行车",
ID: "112345677896541236", //隐私信息不能被随意遍历出来
};
console.log(eric);
delete eric.car; //属性car被删除了
eric.money = "10000000"; //属性money的值被修改了
for (const key in object) {
console.log(object[key]); //结果所有属性值全部暴露出去
}
输出结果
这样子对象就显得十分的脆弱,定义的对象有点任人宰割的情况,那就需要把对象给保 护起来。其实ES5中,已经提供了保护对象属性和结构的新方法,接下来让我们一起学习一下是如何保护对象。
什么是保护对象
保护对象是指阻止对对象的属性值进行不符合规定的篡改,那ES5是如何保护的呢?
其实,ES5中已经把每个对象的属性,变成了一个缩微的小对象,
如下图:
是不是像上图画的那样呢?我们来验证一下,我们通过下面代码获得自己的属性的描述信息
var 属性的描述对象=Object.getOwnPropertyDescriptor(对象, "属性名");
<script>
"use strict"; //记得要开启严格模式
var eric = {
eid: 1,
ename: "xiaoming",
money: "10",
car: "自行车",
ID: "112345677896541236", //隐私信息不能被随意遍历出来
};
var obj = Object.getOwnPropertyDescriptor(eric, "eid");
//var 属性的描述对象=Object.getOwnPropertyDescriptor(对象, "属性名");
console.log(obj);
</script>
打印出来结果
可以看出对象的属性中真的还有一个缩微的小对象,小对象还有4个属性分别为value(值)、writable(可写)、enumerable(可枚举)、configurable(可配置)。介绍一下writable、enumerable、configurable属性
- writable:可写,默认值为true,在默认情况下可以修改属性值。如果writable设为false,则该属性值将无法修改,变成只读模式;
- enumerable:可枚举,默认值为true,在默认情况下可以被for in 遍历,如果enumerable设为false,则该属性值将无法被for in遍历,但可以被console.log()打印出来,属于半隐藏;
- configurable:可配置,默认值为true,在默认情况下该属性可以被删除掉的,如果configurable设为false,那么该属性不可删除,且writable,enumerable,configurable将无法再次修改;
如何保护对象
writable、enumerable、configurable就像是一个个开关一样,如果想对象中的一个属性的值不想被修改,将writable设为false就可以了,其余也是一样的道理。那应该怎么设置呢?我们可以这样设置
Object.defineProperty(对象, "属性名", {
writable: true或false,
... : ...
... : ...
})
还是来看跟上面一样的例子
<script>
"use strict"; //记得要开启严格模式
var eric = {
eid: 1, //只读模式
ename: "xiaoming", //不被遍历
money: "10", //不能被删除
car: "自行车", //不能被删除
ID: "112345677896541236",
};
//保护对象,使用Object.defineProperty(对象, "属性名", {}) 这种方法 每次只能保护一个属性
Object.defineProperty(eric, "eid", {
writable: false, //让eric的eid属性的值不能随便被修改
configurable: false, //不允许再修改writable
});
Object.defineProperty(eric, "ename", {
enumerable: false, //让eric的ename属性不能随便被for in遍历
configurable: false, //不允许再修改enumerable
});
Object.defineProperty(eric, "money", {
enumerable: false, //让eric的ename属性不能随便被for in遍历
configurable: false, //不允许再修改enumerable
});
Object.defineProperty(eric, "car", {
configurable: false, //不允许删除car属性
});
//修改eid的值 结果失败
eric.eid = 3;
console.log(eric.eid);
// 报错 Cannot assign to read only property 'eid' of object 不能给只读属性赋值
//用for in 遍历出ename
for (var key in eric) {
console.log(`${key}:${eric[key]}`);
//只遍历出了eid car ID
}
//删除属性 money
delete eric.money; //删除失败
console.log(eric.money);
//报错 Cannot delete property 'money' of 不能删除属性money
</script>
上面代码运行后会有两处报错,第一个是“不能给只读属性赋值”,第二个是“不能删除属性money”,所以就成功的将对象保护起来不被随意的篡改。
但是,使用上面的代码保护有很明显的缺点,就是每个属性都要设置一次这样代码看起来就很臃肿,也不便于管理,还有没有其他简单点的办法呢?ES5中还有下面的方法可以保护对象
Object.defineProperties(对象, {
属性名:{
writable: true或false,
... : ...
},
属性名:{
... : ...
},
... : { ... }
})
还是上面的例子
<script>
"use strict"; //记得要开启严格模式
var eric = {
eid: 1, //只读模式
ename: "xiaoming", //不被遍历
money: "10", //不能被删除
car: "自行车", //不能被删除
ID: "112345677896541236",
};
//保护对象,使用Object.defineProperties(对象, {})
Object.defineProperties(eric, {
eid: {
writable: false, //让eric的eid属性的值不能随便被修改
configurable: false, //不允许再修改writable
},
ename: {
enumerable: false, //让eric的ename属性不能随便被for in遍历
configurable: false, //不允许再修改enumerable
},
money: {
enumerable: false, //让eric的ename属性不能随便被for in遍历
configurable: false, //不允许再修改enumerable
},
car: {
configurable: false, //不允许删除car属性
},
});
//修改eid的值 结果失败
eric.eid = 3;
console.log(eric.eid);
// 报错 Cannot assign to read only property 'eid' of object 不能给只读属性赋值
//用for in 遍历出ename
for (var key in eric) {
console.log(`${key}:${eric[key]}`);
//只遍历出了eid car ID
}
//删除属性 money
delete eric.money; //删除失败
console.log(eric.money);
//报错 Cannot delete property 'money' of 不能删除属性money
</script>
运行的结果还是跟上面的一样的!也可以实现保护对象的功能,但是,上面的保护功能很弱,不灵活,无法使用自定义的规则保护属性值。如何解决这个问题呢?
给程序的属性请保镖——访问器
- 什么是访问器属性: 不实际存储属性值,仅提供对另一个保存数据的属性的保护。
- 为什么使用访问器属性: 上面的保护不够灵活,无法用自定义规则灵活保护属性值
- 什么时候用到访问器属性:只要用自定义规则,灵活保护属性值时,都用访问器属性。
这个访问器就好比给属性请一个保镖,做什么事情都让保镖先试试水,安全了才执行。看看下面的需求
需求:定义一个对象,对象有个eage属性,要求年龄可以修改,但年龄必须在18-45之间
用访问器该怎么做呢?
现在对象中定义一个_eage属性,利用Object.defineProperties里的enumerable属性设置为false将_age隐姓埋名 — 不想让外人随意使用。再从Object.defineProperties里面“请保镖”eage,用eage来代替_eage属性,在eage里面写 get:function(){ reutrn this.受保护的变量} 和 set:function(value){ … } 其中 get 和 set 不能改变,必须这么写。
来看以下代码
<script>
//要求年龄可修改,但是年龄必须介于18~65之间
var eric={
eid:1001,
ename:"埃里克",
_eage:25 //隐姓埋名 —— 不想让外人随意使用
}
Object.defineProperties(eric,{
_eage:{ //半隐藏
enumerable:false,
configurable:false
},
//请保镖:
eage:{//冒名顶替
//保镖一请就是一对儿
//专门负责从受保护的属性中,获取属性值
get:function(){
console.log("自动调用eage的get()")
//返回受保护的属性_eage的值
return this._eage;
},
//专门负责将要修改的新值,结果验证后,才保存到受保护的属性中
set:function(value){
console.log(`自动调用eage的set(${value})`)
if(value>=18&&value<=65){
this._eage=value;
}else{
throw Error(`年龄必须介于18~65之间!`)
}
},
enumerable:true, //让eage替_eage抛头露面
configurable:false //不能轻易删除保镖
//因为保镖不实际保存属性值,所以没有value属性
//因为writable开关无法灵活保护属性值,所以保镖也没有writable开关
}
})
//观察现在eric的对象结构是怎么样的
console.log(eric)
</script>
输出的结果
如果外界想要修_eage属性值时,都会先去修改eage这个保镖,如果修改的值符合set规定的规则后,才保存到受保护的属性中,就像下图所示
在上面的代码的下边加上以下代码
//外界
//试图读取eric的年龄时
console.log(eric.eage)
//试图修改eric的年龄为26
eric.eage=26;
console.log(eric.eage)
//试图修改eric的年龄为-2
eric.eage=-2;
输出结果:
这样子就做出这个需求了,那么还有一个问题,上面代码中的set:function({})中的this指向的是谁呢?通过上面打印出了eric其实答案就已经出来了。这里的this是指eric对象。
保护对象的结构
有三种级别
- 防扩展: 阻止为对象添加新属性
在旧js中: 可以随时给对象添加新属性
如何禁止为对象添加新属性:使用Object.preventExtensions(对象)
示例: 阻止为对象添加新属性:
<script>
"use strict";
var eric={
eid:1001,
ename:"埃里克"
}
//希望eid只读
Object.defineProperty(eric,"eid",{
writable:false,
configurable:false
})
//防止对eric添加新属性:
Object.preventExtensions(eric);
//试图为对象添加新的不同名的eid属性,另起炉灶
eric.Eid=1003; //报错:
//Cannot add property Eid, object is not extensible
// 不能 添加 属性 Eid(因为)对象是 不 扩展 可以
// 不可扩展
console.log(eric);
</script>
- 密封:
什么是密封:既 阻止给对象添加新属性,又阻止删除对象的现有属性
为什么要用密封:因为几乎对象中的所有属性,都应该是禁止删除的,但是每个属性都要写configurable:false,太麻烦了!所以几乎定义对象都应该使用密封
如何使用密封:Object.seal(对象)
原理: seal()做了两件事:
1). 自动调用Object.preventExtensions()阻止对当前对象的扩展
2). 自动为每个属性都添加configurable:false,从此我们不需要再手动为每个属性添加configurable:false
示例: 密封一个对象
<script>
"use strict";
var eric={
eid:1001,
ename:"埃里克"
}
//希望eid只读,且不能删除
//希望ename也不能删除
Object.defineProperties(eric,{
eid:{
writable:false,
//configurable:false
},
})
//密封对象:
Object.seal(eric)
//试图添加新属性:
//eric.Eid=1003;//报错:
//Cannot add property Eid, object is not extensible
//试图删除eid属性
//delete eric.eid; //报错: Cannot delete property 'eid'
//试图删除ename属性
//delete eric.ename; //报错: Cannot delete property 'ename'
console.log(eric);
</script>
- 冻结:
什么是冻结: 既不能添加删除现有属性,又不能修改属性值,是一种十分严格的保护模式。
什么时候用: 如果多个模块共用的对象,就不应该让某一个模块擅自修改对象的属性值,一旦修改,牵一发而动全身。
怎么用:Object.freeze(对象)
原理: 做了三件事:
1). 也自动调用preventExtensions()阻止添加新属性
2). 也自动为每个属性添加configurable:false
3). 自动设置每个属性的writable:false
示例: 冻结对象
<script>
"use strict";
var obj={
host:"192.168.0.100",
port:3306,
db:"xz"
}
//希望obj对象中所有属性,禁止修改,禁止删除
//且禁止给obj添加新属性
Object.freeze(obj);
//尝试给obj添加新属性:
//obj._host="127.0.0.1"; //报错: Cannot add property _hosts
//尝试删除obj中现有属性
//delete obj.host; //报错: Cannot delete property 'host'
//尝试修改obj中的host属性值:
//obj.host="localhost"; //报错: Cannot assign to read only property 'host'
</script>
东哥笔记