JS 高级(四)ES5保护对象、Object.create()、替换this指向
目录
ES5(ECMAScript 第5个版本)
1. 保护对象
在旧的 js 中,对象自己毫无自保能力,所以 ES5 中提供了一套保护对象自身的机制。ES5 中,对象中每个属性,不再只是一个简单的值,它的底层已经变成了一个缩微的小对象。
三个开关
writable: true | 控制是否可以修改当前属性 |
enumerable: true | 控制是否可以用for in遍历到这个属性(只防for in不防 . ) |
configurable: true | 控制是否可以删除当前属性 控制是否可以修改前两个开关(一旦改为false将不可逆) |
修改开关
只修改一个属性的开关时,格式如下:
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 开关了。
外界使用访问器属性:
取值 | 对象.属性名 | 底层: 自动调用访问器属性的get() |
修改 | 对象.属性名=新值 | 底层: 自动调用访问器属性的set(),将新值传给()中的value形参 |
举例:使用访问器属性保护年龄属性
<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>
保护结构
三个级别 | 用法格式 | 含义 |
防扩展 | Object.preventExtensions(对象) | 禁止给对象添加新属性,但防添加不防删除 |
密封 | Object.seal(对象) | 禁止添加新属性,禁止删除现有属性,可修改属性值 |
冻结 | Object.freeze(对象) | 禁止添加和删除属性,禁止修改一切属性值 |
注意点: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() 用于基于一个现有父对象,创建一个新的子对象继承父对象。如果想创建一个子对象,继承父对象,但是又没有构造函数时,就不能用new,而是用 Object.create()。格式:
var 新子对象=Object.create(父对象, {
//给子对象添加自有属性
//必须用defineProperties函数相同的格式:
属性名:{
value:属性值,
writable:true,
enumerable:true,
configurable:false
},
... : { ... }
})
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 |
往期JavaScript高级博文链接:
相关文章
- js 动态设置 option 的selected 选项
- 原生js实现架子鼓特效
- JS+CSS3 360度全景图插件 - Watch3D.js
- JS框架_(JQuery.js)圆形多选菜单选项
- JS框架_(JQuery.js)网页文字评论弹幕
- JS框架_(coolShow.js)图片旋转动画特效
- JS框架_(Typed.js)彩色霓虹灯发光文字动画
- JS框架_(JQuery.js)带阴影贴纸标签按钮
- JS框架_(Bootstrap.js)实现简单的轮播图
- JS - n次方计算
- baguetteBox.js响应式画廊插件(纯JS)
- JS实现——用3L和5L量出4L的水
- A2D JS框架 - Web API CSRF保护实现
- js的序列化和反序列化
- 原生 js 实现点击按钮复制文本
- [Node.js] Mock an API for Local Development in React with Mirage JS
- [Node.js] Level 6. Socket.io
- js es6 模板字符
- Atitit.js javascript的rpc框架选型
- Atitit.android js 的键盘按键检测Back键Home键和Menu键事件
- 日志库 winston 的学习笔记 - 创建一个使用 winston 的 Node.js 应用
- three.js(JS 三维模型库)介绍和入门
- 编写第一个node.js实例
- leetcode 1351. 统计有序矩阵中的负数 js实现
- babel配置项目目录支持转换es6语法,引入非项目目录js后,引入Js转换无效
- 用GruntJS合并、压缩JS文件
- JS中的call()方法和apply()方法用法总结,以及方法体中this的定义
- 【JS高级】js面向对象三大特性之封装—如何创建对象_05
- 【JS高级】js之正则相关函数以及正则对象_02
- JS赋值运算符详解