ES5(ECMAScript 第5个版本)
1. 保护对象
在旧的 js 中,对象自己毫无自保能力,所以 ES5 中提供了一套保护对象自身的机制。ES5 中,对象中每个属性,不再只是一个简单的值,它的底层已经变成了一个缩微的小对象。
三个开关
修改开关
只修改一个属性的开关时,格式如下:
Object.defineProperty(对象名, "属性名", { 开关:true或false, ... : ... })
需要注意,只要修改 writable 和 enumerable 两个开关时,都要同时修改 configurable:false,这样可以阻止别人的程序重新打开之前关闭的开关。
以上方法每次只能修改一个属性的开关,如果对象中有很多属性都需要保护,则代码会很繁琐,所以有一种新的格式,专门用于修改多个属性的开关:
Object.defineProperties(对象名,{ 属性名1:{ 开关名: true或false, ... : ... }, 属性名2:{ 开关名: true或false, ... : ... }, })
举例:使用开关,保护单个对象属性
<script> "use strict"; var eric = { eid: 1001, ename: "埃里克", salary: 12000 } //员工编号只读 Object.defineProperty(eric, "eid", { writable: false, //且不想让别人重新打开writable开关 configurable: false //不可逆! }); //禁止删除ename属性 Object.defineProperty(eric, "ename", { configurable: false }) //禁止随意遍历薪资属性 Object.defineProperty(eric, "salary", { enumerable: false, configurable: false //不可逆! }) </script>
举例:使用开关,保护多个对象属性
<script> "use strict"; var eric = { eid: 1001, ename: "埃里克", salary: 12000 } //要求:员工编号只读,禁止删除ename属性,禁止随意遍历薪资属性 Object.defineProperties(eric, { eid: { writable: false, configurable: false }, ename: { configurable: false }, salary: { enumerable: false, configurable: false } }) </script>
访问器属性
如果我们想要使用灵活的自定义规则来保护属性值时,以上三个开关是不适用的。所以,访问器属性应运而生。它自己不保存属性值,只提供对另一个数据属性的保护,相当于“保镖”。
定义访问器属性步骤:
(1)定义小黑屋(半隐藏的数据属性),转移原对象中原属性的值;
Object.defineProperty(原对象,"小黑屋",{ value:原对象.要保护的属性, writable:true, enumerable:false, //小黑屋不能轻易被人发现 configurable:false //小黑屋不可删除 })
(2)定义访问器属性替身,和两个“保镖”;
Object.defineProperty(原对象,"要保护的原属性名",{ get:function(){ //this->访问器属性eage所在的当前对象->eric return this.小黑屋; }, set:function(value){ if(判断条件){ this.小黑屋=value; }else{ throw Error("自定义错误提示"); } }, //访问器属性自己不存值,只提供保护,所以没有value属性 //因为替身必须替真实属性抛头露面,必须可以被for in发现 enumerable:true; (必须设置true) //因为替身不能随意删除,所以 configurable:false })
注意正是因为 writable 不好用,我们才被迫用访问器属性代替 writable,所以用了 get/set 之后,就不再用 writable 开关了。
外界使用访问器属性:
形参
举例:使用访问器属性保护年龄属性
<script> var eric = { ename: "埃里克", eage: 25 } // 要求:年龄可以修改,但必须介于18~65之间 // 1.定义小黑屋属性,转移原对象中原属性的值 Object.defineProperty(eric, "xiaoheiwu", { value: eric.eage, writable: true, enumerable: false, //不可轻易被发现 configurable: false //不可随意删除 }) // 2.定义访问器属性替身 Object.defineProperty(eric, "eage", { get: function () { console.log(`eage调用了get(),返回${this.xiaoheiwu}到外部`); return this.xiaoheiwu; }, set: function (value) { if (value >= 18 && value <= 65) { // 此处this指代当前访问器所在的对象 this.xiaoheiwu = value; } else { throw Error("年龄必须介于18~65之间"); } }, enumerable: true, configurable: false }) console.log(eric); // 外界 // 获得eric的eage值 console.log(eric.eage);//25 // 修改eric的eage值 eric.eage = 60; console.log(eric.eage);//60 eric.eage = -1;//Uncaught Error: 年龄必须介于18~65之间 </script>
保护结构
注意点:seal() 会自动遍历对象中每个属性,自动调用 preventExtensions,且自动设置每个属性的 configurable:false,所有属性禁止删除,所以如果用了 seal(),就不用再写 preventExtensions() 和 configurable:false。
freeze() 会自动遍历对象中每个属性,自动设置每个属性的 configurable:false,所有属性禁止删除。而且会自动遍历对象中每个属性,自动设置每个属性的 writable:false,所有属性只读。
举例:分别使用三个级别保护对象结构;
<script> var eric = { eid: 1, ename: "埃里克" } // 规定eid只读,ename禁止删除 Object.defineProperties(eric, { eid: { writable: false } // ename: { // configurable: false // } }) // 禁止给eric添加新属性 // 1.防扩展 // Object.preventExtensions(eric); // 2.密封(常用) Object.seal(eric); // 3.冻结(不常用) // Object.freeze(eric); // 试图添加新属性 eric.salary = 20000; console.log(eric.salary); //该属性为undefined,无法添加 // 试图删除属性eid delete eric.eid; console.log(eric); //eid属性依然在,无法删除 // 试图修改eid属性的值 eric.ename = "李湘"; console.log(eric); //用冻结时打印该值仍为埃里克,无法修改 </script>
2. Object.create()
Object.create() 主要作用为创建新对象、设置新对象继承父对象、强行为新对象添加自有属性。
举例:使用 Object.create() 创建子对象继承父对象;
<script> // 父对象 var father = { money: 1000000000000, car: "玛莎拉蒂" } // 子对象 var hmm = Object.create(father, { // 创建自身属性必须用defineProperties函数相同的格式 phone: { value: "iphone 24", writable: true, enumerable: false, configurable: false }, bar: { value: "LV", writable: true, enumerable: false, configurable: false } }) console.log(hmm); console.log(hmm.money, hmm.car) //此时子对象既有父对象的属性,也有自身的属性(继承) </script>
3. 替换this
如果系统自动指定的 this 对象不是我们想要的,我们就可主动更换 this 指向的对象。
(1)调用函数时,临时替换一次函数中的 this 为指定对象,使用关键词 call。
格式:要调用的函数.call(替换this的对象, 实参值1, 实参值2,...)
举例:使用call,临时替换计算器函数中的 this 为指定员工对象;
<script> // 公用计算器函数 function jisuan(base, bonus1, bonus2) { console.log(`${this.sname}的总工资为:${base+bonus1+bonus2}`); } var lilei = { sname: "李雷" } var hmm = { sname: "韩梅梅" } // 错误写法: // jisuan(12000, 1000, 1000); // lilei.jisuan(12000, 1000, 1000); // 正确写法: // 要调用的函数.call(替换this的对象, 实参值1, 实参值2,...) jisuan.call(lilei, 13000, 2000, 520); jisuan.call(hmm, 1000, 3000, 5000) </script>
(2)如果多个实参值放在一个数组中,则既需要替换 this,又要拆散数组再传参使用。使用关键字 apply。
格式:要调用的函数 .apply(替换this的对象, 包含实参值的数组)
举例:使用 apply 替换 this 同时,拆散数组再传参;
<script> // 公用计算器函数 function jisuan(base, bonus1, bonus2) { console.log(`${this.sname}的总工资为:${base+bonus1+bonus2}`); } var lilei = { sname: "李雷" } var hmm = { sname: "韩梅梅" } var arr = [12000, 1000, 3000]; var arr2 = [13000, 2000, -5000]; // 要调用的函数.apply(替换this的对象, 包含实参值的数组) jisuan.apply(lilei, arr); jisuan.apply(hmm, arr2) </script>
(3)创建函数副本,并永久绑定 this,创建一个和原函数一模一样的新函数,永久替换新函数中的 this 为指定对象。使用关键字 bind。
注意,因为 bind() 已经提前将指定对象替换了新函数中的 this,所以后续每次调用时,不需要再替换 this,除此之外 bind() 不但可以提前永久绑定 this,而且还能永久替换部分形参变量为固定的实参值。
格式:
var 新函数=原函数.bind(替换this的对象)
var 新函数=原函数.bind(替换this的对象, 不变的实参值)
举例:创建lilei专属的jisuan2(),并永久绑定this和底薪;
<script> // 公用计算器函数 function jisuan(base, bonus1, bonus2) { console.log(`${this.sname}的总工资为:${base+bonus1+bonus2}`); } var lilei = { sname: "李雷" } var hmm = { sname: "韩梅梅" } // var 新函数=原函数.bind(替换this的对象, 不变的实参值) var jisuan2 = jisuan.bind(lilei, 12000); jisuan2(2000, 3000); //17000 jisuan2(2000, -3000); //11000 // 被bind()永久绑定的this,即使用call也无法再替换为其它对象 jisuan2.call(hmm, 1000, 2000); //仍显示lilei </script>
替换this知识小结:
a. 只在一次调用函数时,临时替换一次this,使用call
b. 既要替换一次this,又要拆散数组再传参,使用apply
c. 创建新函数副本,并永久绑定this,使用bind