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是否符合條件 })
- 判斷數組中是否包含符合條件的元素
- 判斷: