ES5
1. 保护对象
-
问题:旧的js中的对象毫无自保能力,对象的结构和属性值可以被任意修改
-
解决:ES5中,提供了保护对象属性和结构的新方法
-
保护对象的属性
- 什么是:阻止对象的属性值进行不符合规定的篡改
- 如何做:
-
其实,ES5中已经把每个对象的属性,变成了一个微缩的小对象
-
获得对象中一个属性的描述对象
var 属性的描述对象=Object.getOwnPropertyDescriptor(对象,"属性名")
-
如何修改对象属性的开关
- 强调:禁止使用.直接访问属性的开关,必须用专门的函数
- 如果只修改对象的一个属性的开关:
Object.defineProperty(对象,"属性名",{ 开关名:true或false, ... : .... })
- 问题:
defineProperty
方法一次只能修改对象中一个属性的开关。如果多个属性都需要修改开关,代码就会很繁琐 - 用一个函数批量修改一个对象的多个属性的开关
Object.defineProperties(对象,{ 属性名:{ 开关名:true或false, ... : ... }, 属性名:{ 开关名:true或false, ... : ... },, ... : .... })
- 示例:使用开关保护eric对象的属性
用definePrototy()写
用defineProterties()写<script> "use strict" var eric={ eid:1001,//只读 ename:"埃里克",//不能删除 salary:12000//不能随便用for in遍历 } // 尝试获得eric对象的eid属性的描述对象 var eid_obj=Object.getOwnPropertyDescriptor(eric,"eid"); console.log(eid_obj); // 让eir的eid只读 Object.defineProperty(eric,"eid",{ writable:false, configurable:false//不允许再修改writable }); // 让eir的ename不能删除 Object.defineProperty(eric,"ename",{ configurable:false//不允许删除ename }); // 让eir的salary属性不能被for in 遍历 Object.defineProperty(eric,"salary",{ enumerable:false, configurable:false//不允许再修改enumerable开关 }); // 测试: // 试图打开eid属性的writable开关 // Object.defineProperty(eric,"eid",{ // writable:true, // configurable:true // }) // 报错:提示:不能重定义属性:eid // 试图修改eid属性值 // eric.eid=1002;//报错 // 报错:提示:不能赋值给只读属性eid // 试图删除ename属性 // delete eric.ename;//报错 // 报错:提示:不能删除属性ename // 试图遍历eric所有属性 for(var key in eric){ console.log(`${key}:${eric[key]}`); }//仅遍历到eid和ename属性,遍历不到salary属性 // 试图用.访问salary属性--成功过,可以访问 console.log(`eric的工资是${eric.salary}`);//12000 </script>
<script> "use strict" var eric={ eid:1001,//只读 ename:"埃里克",//不能删除 salary:12000//不能随便用for in遍历 } // 让eir的eid只读 // 让eir的ename不能删除 // 让eir的salary属性不能被for in 遍历 Object.defineProperty(eric,{ eid:{ writable:false, configurable:false//不允许再修改writable }, ename:{ configurable:false//不允许删除ename }, salary:{ enumerable:false, configurable:false//不允许再修改enumerable开关 } }); // 测试: // 试图打开eid属性的writable开关 // Object.defineProperty(eric,"eid",{ // writable:true, // configurable:true // }) // 报错:提示:不能重定义属性:eid // 试图修改eid属性值 // eric.eid=1002;//报错 // 报错:提示:不能赋值给只读属性eid // 试图删除ename属性 // delete eric.ename;//报错 // 报错:提示:不能删除属性ename // 试图遍历eric所有属性 for(var key in eric){ console.log(`${key}:${eric[key]}`); }//仅遍历到eid和ename属性,遍历不到salary属性 // 试图用.访问salary属性--成功过,可以访问 console.log(`eric的工资是${eric.salary}`);//12000 </script>
-
问题:开关的保护功能很弱,不灵活,无法使用自定义的规则保护属性值。
-
解决:给程序的属性请保镖-----访问器属性
-
什么是访问器属性:不实际存储属性值,仅提供对另一个保存数据的属性的保护
-
为什么:属性的开关不够灵活,无法用自定义规则灵活保护属性
-
何时用:只要用自定义规则,灵活保护属性值时,都用访问器属性
-
怎么定义:2步
- 第一步:先将要保护的属性,隐姓埋名,半隐藏
- 第二步:为受保护的属性,请保镖
- 保镖就是访问器属性,但是为对象添加访问器属性,不能直接在对象的{}内添加。只能通过
Object.defineProperty()
或Object.defineProperties()
添加 - 保镖要茂名顶替原属性名:
访问器属性的名称,应该和想要保护的那个属性名一致,才能起到替身的作用 - 保镖一请就是一对儿:
get:function(){return this.受保护的变量}
set:function(value){...}
其中get和set不能改变,必须怎么写
- 保镖就是访问器属性,但是为对象添加访问器属性,不能直接在对象的{}内添加。只能通过
- 怎么用:通过访问器属性来操作受保护的属性
- 用访问器属性保护属性不是为了阻止大家使用,而是为了保障大家在合理的范围内使用属性
- 希望外界通过访问器属性来操作受保护的属性
- 所以,访问器属性的用法和普通的属性完全一样
- 当外界想获得属性值时:
对象.访问器属性
原理:当外界试图获得属性值时,访问器属性会自动调用自己的get函数 - 当外界想修改属性值时:
对象.访问器属性=新值
原理:当外界试图修改属性值时,访问器属性会自动调用自己的set函数,同时将等号右边的新值,自动交给set函数的value形参变量,在保存到受保护的属性中
- 当外界想获得属性值时:
-
示例:使用访问器属性保护对象eric的eage属性
<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开关无法灵活保护属性值,所以保镖也没有writeable开关 } }); // 测试 // 试图读取eric的年龄 console.log(eric.eage); // 试图修改eric的年龄为26 eric.eage=26; console.log(eric.eage); // 试图修改eric的年龄为-2 eric.eage=-2; </script>
输出结果:
-
-
访问器属性中的this
-
-
保护对象的机构:3种级别
- 防扩展:阻止为对象添加新属性
- 旧js中可以随时给对象添加新属性
- 如何禁止为对象添加新属性
Object.preventExtensions(对象)
- 示例:阻止为对象添加新属性
<script> "use strict" var eric={ eid:1001, ename:"埃里克" } // 防止对eric添加新属性 Object.preventExtensions(eric); // 试图为对象新的不同名的eid属性 eric.Eid=1003;//报错 // 报错提示:不能添加属性Eid,(因为)对象时不可扩展的 console.log(eric); </script>
- 密封
- 什么是:既阻止给对象添加新属性,又阻止删除对象的现有属性
- 为什么:因为几乎所有对象中的属性,都应该是禁止删除的,但是每个属性都要写
configurable:false
太忙 - 何时:几乎所有对象都要密封
- 如何做:
Object.seal(对象)
- 原理:
seal()
做了两件事- 自动调用
Object.preventExtensions()
阻止对当前对象的扩展 - 自动为每个属性都添加了
configurable:false
- 自动调用
- 示例:密封一个对象
<script> "use strict" var eric={ eid:1001, ename="埃里克" } // 密封对象 Object.seal(eric); // 试图添加新属性 eric.Eid=1003;//报错 // 试图删除eid属性 delete eric.eid;//报错 </script>
- 大部分一般对象,保护到密封级别(seal)就足够了
- 冻结:
- 什么是:既不能添加删除现有属性,又不能修改属性值
- 何时:如果多个模块共用的对象,就不应该让某一个模块擅自修改对象的属性值,一旦修改,牵一发而动全身
- 如何:
Object.freeze(对象)
- 原理:做了三件事:
- 自动调用
preventExtensions()
阻止添加新属性 - 自动为每个属性添加
configurable:false
- 自动设置每个属性的
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";//报错 // 删除删除obj中现有属性 // delete obj.host;//报错 // 尝试修改obj中host属性值 obj.host="localhost";//报错提示:不能修改只读属性host </script>
- 自动调用
- 防扩展:阻止为对象添加新属性
2. Object.create()
- 什么是:基于一个现有对象,创建新的子对象,来继承这个父对象
- 简单说:没有构造函数,也能创建子对象,继承父对象
- 如何
- 只创建子对象
- 格式:
var 子对象=Object.create(父对象)
- 原理:
- 创建一个新的空对象
- 自动设置新的空对象的
__proto__
继承父对象
- 格式:
- 既创建子对象,又为子对象添加自有属性
- 格式
var 子对象=Object.create(父对象, { //必须采用defineProperties函数同样的格式添加自有属性 自有属性名:{ value:属性值, writable:true, enumerable:true, configurable:false }, 自有属性名:{ value:属性值, writable:true, enumerable:true, configurable:false } })
- 原理:做了3件事
- 创建一个空对象
- 自动设置新的空对象的
__proto__
继承父对象 - 可以为新对象添加自有属性
- 格式
- 只创建子对象
- 示例:基于父对象,创建一个子对象,并未子对象添加自有属性
输出结果:<script> "use strict" var father={ bal:1000000, car:"infiniti" } // 创建一个子对象hmm,继承父对象father var hmm=Object.create(father,{ // 并为子对象hmm,添加两个自有属性 bao:{ value:"LV", writable:true, enumerable:true, configurable:false }, phone:{ value:"华为", writable:true, enumerable:true, configurable:false } }); console.log(hmm); console.log(hmm.bal,hmm.car); console.log(hmm.bao,hmm.phone); </script>
3. call/apply/bind
- 相同点:都能替换函数中不想要的this为想要的对象
- 何时:如歌一个函数中的this不是你想要的,就可以用call/apply/bind将this替换为想要的对象
- 如何:3种情况
- 只在调用函数时,临时替换一次this为指定的对象-----call
- 如何做:
要调用的函数.call(替换this的对象,实参值列表)
- 原理:call做了2件事
- 会调用一次该函数,并将实参值列表传递给形参变量,用于函数内执行
- 会在本次调用时,临时将函数中的this替换为一个指定的对象(call的第一个实参)
- 示例:使用call替换一次函数中的this
<script> // 一个公共计算薪资的函数 function jisuan(base,bonus1,bonus2){ console.log(`${this.ename}的总工资是:${base+bonus1+bonus2}`); } // 创建两个员工对象 var lilei={ename:"Li Lei"}; var hmm={ename:"Han Meimei"}; // lilei想计算自己的薪资 // 错误写法一:此时的this指向window // jisuan(10000,2000,3000); // 错误写法二:报错,因为lilei的原型链上没有jisuan函数 // lilei.jisuan(10000,2000,3000); // 用call写----成功:输出结果:Li Lei的总工资是:15000 jisuan.call(lilei,10000,2000,3000); // call会调用jisuan函数执行 // 并将call从第二个实参值开始的的实参值传递给jisuan()的形参变量 // 同时call会将jisuan()中的this,临时替换为lilei,调用后,恢复原样 </script>
- 如何做:
- 既可替换this,又能打散数组参数-----apply
- 为什么:如果要调用的函数有多个实参值,但是多个实参值却是放在一个数组中给的
- 如何做:
要调用的函数.apply(替换this的对象,数组)
- 原理:
- apply拥有和call相同的功能,都能调用函数,并替换其中的this为指定对象
- 但apply比call多一个功能,apply能先打散数组为多个值,再传参
- 示例:用apply替换this,并打散数组参数
<script> // 一个公共计算薪资的函数 function jisuan(base,bonus1,bonus2){ console.log(`${this.ename}的总工资是:${base+bonus1+bonus2}`); } // 创建两个员工对象 var lilei={ename:"Li Lei"}; var hmm={ename:"Han Meimei"}; // lilei想计算自己的薪资,但是lilei的工资条是一个数组 var arr=[10000,2000,3000]; // 用apply写----输出结果:Li Lei的总工资是:15000 jisuan.apply(lilei,arr); </script>
- 基于原函数创建一个新函数副本,并永久替换this
- 问题:如果需要反复使用替换this后的函数,那么每次
.call(对象名)
就很繁琐 - 如何做:
var 新函数名=原函数.bind(替换this的对象)
- 原理:
.bind()
会创建一个和原函数一模一样的新函数副本,原函数保持不变- 将新函数副本中的this,永久替换为指定的对象
- 结果:将来反复调用新函数,即使不传入替换this的对象,也可以保证this为指定对象
- 如果部分实参值也永久固定不变,也可以用
.bind()
提前绑定到形参变量上
var 新函数名=原函数.bind(替换this的对象,固定不变的一个或多个实参值);
- 强调:已经被
.bind()
永久绑定的实参值,将来调用函数时,无需重复传入。只要从后续未绑定的实参值继续传入即可 - 示例:使用bind永久绑定函数的this
<script> // 一个公共计算薪资的函数 function jisuan(base,bonus1,bonus2){ console.log(`${this.ename}的总工资是:${base+bonus1+bonus2}`); } // 创建两个员工对象 var lilei={ename:"Li Lei"}; var hmm={ename:"Han Meimei"}; // lilei不香总是call(lilei)这么麻烦 // 想自己有一个专属的jisuan()函数 var js_lilei=jisuan.bind(lilei,10000); // 只是赋值jisuan函数的副本,不改变原函数 js_lilei(2000,3000);//输出Li Lei的总工资是:15000 // hmm依然可以使用元函数jisuan(),不受影响 jisuan.call(hmm,3000,4000,5000); //输出:Han Meimei的总工资是:9000 </script>
- 问题:如果需要反复使用替换this后的函数,那么每次
- 只在调用函数时,临时替换一次this为指定的对象-----call
4. 数组新增函数
- 判断:2个
- 判断数组中是否包含符合条件的元素
- 格式:
var bool=arr.some(function(val){ return 判断条件 })
- 原理:
- some中封装了for循环,自动遍历数组中的每个元素
- 每遍历一个元素,就自动调用一次回调函数,在调用回调函数时,自动将当前正在遍历的元素值传递给回调函数的形参val,回调函数内判断当前元素值是否符合要求,并且返回判断结果
- some函数会根据回调函数返回的判断结果,决定有没有必要继续向后执行,如果染回结果为true,循环结束,整体返回true,说明找到符合要求的元素;当前元素的判断结果返回false,则循环继续,直到循环结束,若都为找到符合要求的元素,则整体返回false,说明没有找到符合条件的元素
- 示例:判断数组中是否包含符合条件的元素
<script> var arr1=[1,3,5,3,1]; var arr2=[2,4,6,4,2]; // 判断哪个数组包含偶数 var result1=arr1.some(function(val){ // 返回当前元素是不是偶数的判断结果 return val%2==0; }); var result2=arr2.some(function(val){ return val%2==0; }); console.log(result1,result2);//false true </script>
- 格式:
- 判断数组中是否所有元素都符合条件
- 格式:
var bool=arr.every(function(val){ return 判断条件 })
- 原理:同some只是判断数组内所有元素都满足判断条件返回true,否则返回false
- 示例:判断数组是否全由偶数组成
<script> var arr1=[2,4,5,4,2]; var arr2=[2,4,6,4,2]; // 判断数组是否全是偶数 var result1=arr1.every(function(val){ // 返回当前元素是不是偶数的判断结果 return val%2==0; }); var result2=arr2.every(function(val){ return val%2==0; }); console.log(result1,result2);//false true </script>
- 格式:
- 判断数组中是否包含符合条件的元素
总结:ES5:
- 严格模式: 4个新要求:
- 禁止给未声明的变量赋值
- 静默失败升级为错误
- 匿名函数自调和普通函数调用中的this不再指window,而是undefined
- 禁用了arguments.callee,不推荐使用递归。
- 保护对象:
- 保护对象的属性:
- 使用开关对对象的属性提供简单的保护:
- 每个属性包括三个开关:
writable
: 控制是否可修改属性值enumerable
: 控制是否可被for in遍历到。但是只是半隐藏,只防for in,不防.configurable
: 控制2件事:- 是否可删除该属性
- 是否可修改前两个特性:因为一旦改为false不可逆,所以凡是修改前两个特性时,都要带上
configurable:false
,作为双保险。
- 如果只修改一个属性的开关:
Object.defineProperty(对象, "属性名", { 开关: true 或 false, ... : ... })
- 如果想同时修改一个对象的多个属性的开关:
Object.defineProperties(对象,{ 属性名: { 开关: true 或 false, ... : ... }, 属性名: { ... : ..., } ... : { ... } })
- 每个属性包括三个开关:
- 用访问器属性,保护对象的属性:
- 定义访问器属性: 2步:
- 将被保护的数据属性隐姓埋名,并且半隐藏:
var eric={ _eage:25 } Object.defineProperties(eric,{ _eage:{ enumerable:false, configurable:false },
- 为受保护的数据属性请保镖:
eage:{//保镖应该冒名顶替原数据属性的值 //保镖一请就是一对儿 get:function(){ //专门负责从受保护的数据属性中获取值 return this._eage }, set:function(value){//专门负责将新值经过验证后,再保存回受保护的数据属性中 if(value符合要求){ this._eage=value }else{ throw Error(`错误提示`) } }, enumerable:true, //访问器属性顶替受保护的属性抛头露面 configurable:false, //访问器属性不能被轻易删除 } })
- 将被保护的数据属性隐姓埋名,并且半隐藏:
- 如何使用访问器属性: 访问器属性的用法和普通属性完全一样
- 获取属性值时:
对象.访问器属性
自动调用访问器属性的get - 修改属性值时:
对象.访问器属性=新值
自动调用访问属性的set,同时自动将=右侧的新值传给set的value形参变量。经过验证value后,再赋值。
- 获取属性值时:
- 定义访问器属性: 2步:
- 使用开关对对象的属性提供简单的保护:
- 保护对象的结构: 3个级别:
- 防扩展:
Object.preventExtensions(对象)
只禁止给对象添加新属性 - 密封:
Object.seal(对象)
既禁止给对象添加新属性,又禁止删除现有属性(自动设置configurable:false) - 冻结:
Object.freeze(对象)
既禁止给对象添加新属性
又禁止删除现有属性(自动设置configurable:false)
同时还禁止修改属性值(自动设置writable:false)
- 防扩展:
- 保护对象的属性:
- Object.create()
- 何时: 当没有构造函数时,也想为一个父对象创建子对象
- 如何:
var 子对象=Object.create(父对象,{ 自有属性名:{ value:属性值, writable:true, enumerable:true, configurable:false }, ... : { ... } })
- 做了三件事:
- 创建一个新的空对象
- 让新对象继承父对象
- 为新对象添加自有属性
- 替换this:
- 调用一次函数,同时临时替换一次this:
- 默认call:
要调用的函数.call(替换this的对象, 实参值列表)
- 如果实参值列表是放在一个数组中给的,和要调用的函数发生不一致,应该用apply打散数组,再传参
要调用的函数.apply(替换this的对象, 数组)
- 默认call:
- 反复调用函数,永久替换this,应该用bind:2步
- 先用bind创建函数副本:
var 新函数=原函数.bind(替换this的对象, 固定不变的实参值)
- 再反复调用新函数: 不用再传入替换this的对象和部分已经固定的实参值
新函数(除已经固定的实参值之外其他实参值列表)
- 先用bind创建函数副本:
- 调用一次函数,同时临时替换一次this:
- 数组新增函数:
- 判断:
- 判断数组中是否包含符合条件的元素
var bool=arr.some(function(val){ return val是否符合条件 })
- 判断数组中是否所有元素都符合要求
var bool=arr.every(function(val){ return val是否符合条件 })
- 判断数组中是否包含符合条件的元素
- 判断: