摘要:上一篇你不知道的筆記寫在前面這是年第一篇博客,回顧去年年初列的學習清單,發現僅有部分完成了。當然,這并不影響年是向上的一年在新的城市穩定連續堅持健身三個月早睡早起游戲時間大大縮減,學會生活。
上一篇:《你不知道的javascript》筆記_this
寫在前面這是2019年第一篇博客,回顧去年年初列的學習清單,發現僅有部分完成了。當然,這并不影響2018年是向上的一年:在新的城市穩定、連續堅持健身三個月、早睡早起、游戲時間大大縮減,學會生活。根據上一年目標完成情況,新一年的計劃將采取【敏捷迭代】的方式:制定大方向,可臨時更新小目標的策略。哈哈,話不多說..
在學習《javascript高級程序設計》這本書時,關于對象和原型,之前寫過下面幾篇文章,可作參考:
《javascript高級程序設計》筆記:對象數據屬性和訪問器屬性
《javascript高級程序設計》筆記:創建對象
《javascript高級程序設計》筆記:原型圖解
《javascript高級程序設計》筆記:繼承
一、對象基本知識 1.1 數據屬性與訪問屬性《javascript高級程序設計》筆記:對象數據屬性和訪問器屬性,這篇文章對數據屬性和訪問器屬性有基本的介紹了,下面會做出一點補充說明:
(1)默認值
通過非Object.defineProperty的方式聲明的屬性默認值
var obj = { a: 1 }; Object.getOwnPropertyDescriptor(obj, "a"); /*{ value: 1, writable: true, enumerable: true, configurable: true }*/
通過Object.defineProperty的方式聲明的屬性默認值
var obj = {}; Object.defineProperty(obj, "a", {}); Object.getOwnpropertyDescriptor(obj, "a"); /*{ value: undefined, writable: false, enumerable: false, configurable: false }*/
(2)屬性configurable
configurable屬性設為false為不可逆過程
configurable:false其他屬性無法修改同時會禁止刪除該屬性
特例:configurable: false時,writable的狀態可由true改為false,但不能由false變為true
var obj = {}; Object.defineProperty(obj, "a", { configurable: false, writable: true, value: 1 })// {a: 1} Object.defineProperty(obj, "a", { configurable: false, writable: false, value: 2 })// {a: 2} Object.defineProperty(obj, "a", { configurable: false, writable: false, value: 3 })// Uncaught TypeError: Cannot redefine property: a
(3)區分數據屬性和訪問屬性
當我們同時定義這兩個屬性時,會提示錯誤:
var obj = { _a: 1 }; Object.defineProperty(obj, "a", { configurable: true, enumerable: true, writable: true, value: 1, get() { return this._a; }, set(newVal) { this._a = newVal * 10; } }); Object.getOwnPropertyDescriptor(obj, "a"); // Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute
因此:這兩個屬性不能同時存在,根據他們特有的屬性反過來判斷究竟是哪種屬性即可
(4)屬性enumerable: false的對象如何遍歷
可通過Object.getOwnPropertyNames獲取key,再遍歷
var obj = {}; Object.defineProperty(obj, "a", {value: 10}); // 一般的遍歷無法獲取 for(let k in obj){ console.log(k) }// undefined Object.getOwnPropertyNames(obj); // ["a"]1.2 不變性
(1)對象常量
創建方式:數據屬性:wraitable:false,configurable:false
特點:不可修改/重定義/刪除
var obj = {}; Object.defineProperty(obj, "MY_CONSTANT", { value: 10, writable: false, configurable: false, }) obj.MY_CONSTANT = 11; console.log(obj.MY_CONSTANT); // 非嚴格模式下為10,嚴格模式報錯
同樣的,如果這個對象常量是對象
var obj = {}; Object.defineProperty(obj, "MY_CONSTANT_OBJ", { value: {a: 1}, writable: false, configurable: false, }) obj.MY_CONSTANT_OBJ.b = 2; console.log(obj.MY_CONSTANT_OBJ); // {a: 1, b: 2}
因此:和const定義的普通常量類似,只要對象的地址不變便不會報錯
(2)禁止擴展
創建方式:Object.preventExtensions()
特點:不可添加新屬性(可刪除可修改可重定義)
判斷:Object.isExtensible()不可擴展返回false
var obj = {a: 1, b: 2}; Object.preventExtensions(obj); obj.c = 3; // {a: 1, b: 2} 不可添加 obj.a = 2; // {a: 2, b: 2} 可修改 delete obj.a; // {b: 2} 可刪除 Object.defineProperty(obj, "b", { configurable: false, value: 3 }); delete obj.b; // {b: 3} 可重定義 Object.isExtensible(obj); // false
(3)密封
創建方式:Object.seal()
特點:不可添加/修改/刪除
判斷:Object.isSealed()密封時返回true
密封是在禁止擴展的基礎上禁止刪除和重定義。相當于把現有屬性標記為configurable:false
var obj = {a: 1, b: 2}; Object.seal(obj); obj.c = 3; // {a: 1, b: 2} 不可添加 delete obj.a; // {a: 1, b: 2} 不可刪除 // configurable:false 不可重定義 obj.a = 2; // {a: 2, b: 2} 可修改 Object.isSealed(obj); // true
(4)凍結
創建方式:Object.freeze()
特點:最高級別的不可變
判斷:Object.isFrozen()
凍結是在密封的基礎上把現有屬性標記為writable:false,所有屬性相當于常量屬性
1.3 存在性in操作符:檢查屬性是否在對象及其原型鏈中
hasOwnProperty:僅檢查屬性是否在該對象上
因此,可結合二者來判斷屬性是否僅存在與原型鏈上
function hasPrototypeProperty(obj, property) { return (property in obj) && !(obj.hasOwnProperty(property)); }
另外,可獲取的某個對象所有key(未遍歷至原型鏈),再判斷指定屬性是否在對象內;
遍歷方法:
Object.keys(); Object.getOwnPropertyNames(); // 可獲取不可枚舉的屬性1.4 深淺拷貝
理解深淺拷貝需要對內存有一定理解,如果對基礎類型和復雜類型(值類型和引用類型)在內存中的區別仍沒有清楚的認識,可參考《javascript高級程序設計》筆記:值類型與引用類型這篇文章,否則忽略即可
值類型數據是直接存在于棧內存中,復制操作是直接開辟內存并存值;
引用類型數據棧內存中存儲的只是堆內存中真實數據的一個引用,復制操作僅復制引用,并未真實復制堆中的數據;
根據上面的認識,我們思考一下:什么是深拷貝,什么是淺拷貝?
深淺拷貝僅僅是針對引用類型而言,深拷貝是按照目標對象的結構復制一份完全相同的對象,新的對象與原對象各個嵌套層級上內存完全獨立,修改新對象不會更改原對象;引用類型的拷貝中除深拷貝外的均為淺拷貝(不完全深拷貝)
本文僅介紹我在項目中經常使用的深淺拷貝幾種方式,不對底層實現探究:
擴展運算符【淺】
var obj = {a: 1, b: 2}; var arr = [1,2,3,4]; // 對象 var copyObj = {...obj}; copyObj === obj; // false // 數組 var copyArr = [...arr]; copyArr === arr; // false
Object.assign()【淺】
// 對象 var copyObj = Object.assign({}, obj); copyObj === obj; // false // 數組 var copyArr = Object.assign([], arr); copyArr === arr; // false
數組也是對象,因此Object.assign也可使用,只是一般不這么用且有更簡單的方式
數組拷貝實現【淺】
var copyArr1 = arr.slice(); var copyArr2 = arr.concat();
lodash中的clone/cloneDeep【淺/深】
工具庫lodash中提供了深淺拷貝的方法,簡單易用且能夠按需引入
// 全部引入 import _ from "lodash"; // _.clone() _.cloneDeep() // 按需引入 import clone from "lodash/clone"; import cloneDeep from "lodash/cloneDeep";
JSON方式【深】
這個是平時項目中最常用的深拷貝方式,局限性就是,無法拷貝方法
JSON.parse(JSON.stringify(obj));
其實,深拷貝就是通過遞歸逐級淺拷貝實現的,因為對于復雜類型的元素均為值類型的淺拷貝便是深拷貝。例:[1,2,3].slice()便是深拷貝
如需更深入,可參考:JavaScript 淺拷貝與深拷貝
二、原型&繼承類/繼承描述了一種代碼的組織結構形式——一種在軟件中對真實世界中問題領域的建模方法
下面會對這種建模慢慢闡述
2.1 原型非常慶幸,之前寫過《javascript高級程序設計》筆記:原型圖解的文章得到了許多朋友的認可。關于原型的一些知識這里面也說的七七八八了,讀完《你不知道的javascript》后再做些許補充
下面搬出經典的鐵三角鎮樓,在原型圖解中已做說明,在此不嘮述
(1)原型有什么用
為什么要抽離出原型的概念?在js中原型就相當于類;類有什么作用呢?
書中有一個恰當的比喻:類相當于建造房子時的藍圖,實例相當于我們需要真實建造的房子
這個藍圖(類)抽離出了房子的諸多特性(屬性),如寬/高/占地/窗戶數量/材料等等。我們建造房子(創建實例)時只需要按照這些搭建即可,而不是從零開始
回到編程中:有了類的概念,針對一些具有公共的屬性和方法對象,我們可以將其抽離出來,以便下次使用,簡化我們構建的過程
這個是js中數組的【類】,實例以后就能夠直接使用,而無需將公用的方法定義在實例上
(2)屬性屏蔽規則
屬性查找規則:沿著原型鏈查找,到最近的對象截止,若一直到Object.prototype也無法找到,則返回undefined;
反言之,屬性的屏蔽規則亦是如此?原型鏈上,同名屬性靠前會屏蔽掉后面的同名屬性?答案遠沒有這么簡單,分為三種情況考慮:(以myObject.foo = "bar";為例)
1. 如果在原型鏈上層存在名為foo的普通數據訪問屬性并且沒有被標記為只讀writable:true,那么直接在myObject中添加一個名為foo的新屬性
function Fn() {} Fn.prototype.foo = "this is proto property"; var myObject = new Fn(); myObject.foo = "this is my own property"; myObject.foo; // "this is my own property";
2. 如果原型鏈上層存在foo,但是被標記為只讀writable:false,那么無法修改已有屬性或者在myObject中創建屏蔽屬性
function Fn() {} Object.defineProperty(Fn.prototype, "foo", { value: "this is proto property", writable: false, }); var myObject = new Fn(); myObject.foo = "this is my own property"; myObject.foo; // "this is proto property";
3. 如果在原型鏈上層存在foo并且他是一個setter,那么一定會調用這個setter
function Fn() {} Object.defineProperty(Fn.prototype, "foo", { set(newValue) { this._foo = "haha! this is setter prototype"; }, get() { return this._foo; } }); var myObject = new Fn(); myObject.foo = "this is my own property"; myObject.foo; // "haha! this is setter prototype";
(3)一個面試題
var anotherObject = { a: 2 }; var myObject = Object.create(anotherObject); // node1 myObject.a++; anotherObject.a; // ? myObject.a; // ? // node2
上面問號處輸出值為多少?
分析:
node1和node2處分別執行下面的代碼并輸出
// node1 anotherObject.hasOwnProperty("a"); // true myObject.hasOwnProperty("a"); // false // node2 anotherObject.hasOwnProperty("a"); // true myObject.hasOwnProperty("a"); // true
看到了什么,執行完myObject.a++;后實例對象創建了一個自己的屬性;為什么?自增操作相當于myObject.a = myObject.a + 1首先查找到屬性,后在實例對象上創建一個新的同名屬性,屏蔽原型上的屬性;
答案:2 3
《javascript高級程序設計》筆記:繼承這篇文章分析了各種繼承的情況,一步一步演化至更精致的繼承情況
(1)繼承有什么用
需求:為我的汽車建一個對象
以前:{id: XX, 牌照: XX, 品牌: XX, 油耗: XX, 載人: XX, 顏色: XX .. }
建模的思想來搭建類和繼承體系:
抽離交通工具的類 Vehicle = {油耗: XX, 載人: XX, 顏色: XX, drive()}
抽離汽車的類 Car = {繼承Vehicle, 品牌: XX }
實例化我的汽車 {繼承Car, id: XX, 牌照: XX}
抽離類并繼承,加上實例對象特有的屬性便能夠具體某一對象,達到高效利用的目的
(2)新的繼承
下面的方式便是在“組合繼承”的進一步“精致”,當然還有class語法糖(后續計劃..)
function Foo(name) { this.name = name; } Foo.prototype.sayName = function() { console.log(this.name); } var foo = new Foo("xiaoming"); console.log(foo) function Bar(name, id) { Foo.call(this, name) this.id = id; } // Object.create()方式 Bar.prototype = Object.create(Foo.prototype); // Object.setPrototypeOf()方式 // Object.setPrototypeOf( Bar.prototype, Foo.prototype ); Bar.prototype.sayId = function() { console.log(this.id); } var bar = new Bar("xiaofeng", 1234) console.log(bar);
兩者對比而言,顯然Object.setPrototypeOf()方式更加完備一些(無需再次聲明constructor)
2.3 原型本質【行為委托】對象之間并非從屬關系,而是委托關系,js中委托關系正是通過Object.create()完成Object.create()方法創建一個新對象,使用現有的對象來提供新創建的對象的__proto__
在上面的繼承中,我們已經看到了Object.create()的身影;下面來對比類和委托思維模型
// 類/繼承模型 function Foo(who) { this.me = who; } Foo.prototype.identify = function() { return "I am " + this.me; }; function Bar(who) { Foo.call( this, who ); } Bar.prototype = Object.create( Foo.prototype ); Bar.prototype.speak = function() { alert( "Hello, " + this.identify() + "." ); }; var b1 = new Bar( "b1" ); var b2 = new Bar( "b2" ); b1.speak(); b2.speak();
// 委托模型 Foo = { init(who) { this.me = who; }, identify() { return "I am " + this.me; } }; Bar = Object.create( Foo ); Bar.speak = function() { alert( "Hello, " + this.identify() + "." ); }; var b1 = Object.create( Bar ); b1.init( "b1" ); var b2 = Object.create( Bar ); b2.init( "b2" ); b1.speak(); b2.speak();
類風格的代碼強調的是實體與實體之間的關系,委托風格的代碼強調的是對象之間的關聯關系;
如何選用?像上面章節舉的例子:交通工具-->汽車-->具體某個汽車的關系,選用類;沒有太大關聯的對象可直接用委托實現
上一篇:《你不知道的javascript》筆記_this
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100761.html
下一篇:《你不知道的javascript》筆記_對象&原型 寫在前面 上一篇博客我們知道詞法作用域是由變量書寫的位置決定的,那this又是在哪里確定的呢?如何能夠精準的判斷this的指向?這篇博客會逐條闡述 書中有這樣幾句話: this是在運行時進行綁定的,并不是在編寫時綁定,它的上下文取決于函數調用時的各種條件this的綁定和函數聲明的位置沒有任何關系,只取決于函數的調用方式當一個函數被調用時...
摘要:基礎原型原型鏈構造函數默認有這一行張三李四構造函數擴展其實是的語法糖其實是的語法糖其實是使用判斷一個函數是否是一個變量的構造函數原型規則和示例所有的引用類型數組對象函數,都具有對象屬性即可自有擴展的屬性,除外所有的引用類型數組對象函數, JavaScript基礎 —— 原型&&原型鏈 構造函數 function Foo(name, age) { this.name = na...
摘要:然而事實上并不是。函數本身也是一個對象,但是給這個對象添加屬性并不能影響。一圖勝千言作者給出的解決方案,沒有麻煩的,沒有虛偽的,沒有混淆視線的,原型鏈連接不再赤裸裸。所以是這樣的一個函數以為構造函數,為原型。 注意:本文章是個人《You Don’t Know JS》的讀書筆記。在看backbone源碼的時候看到這么一小段,看上去很小,其實忽略了也沒有太大理解的問題。但是不知道為什么,我...
摘要:初識在中有兩種特別的基本數據類型初學者對其也很模糊或者直接認為它倆相等。作為函數參數,表示該函數的參數不是對象對象原型鏈的終點。對象屬性沒有賦值,該屬性為當函數沒有返回值時,默認返回第一次分享文章,如有錯誤請斧正 1.初識 null & undefined 在javascript 中有兩種特別的基本數據類型 null undefined 初學者 對其也很模糊或者直接認為它倆相等。 確實...
摘要:如果存在于原型鏈上層,賦值語句的行為就會有些不同。中包含的屬性會屏蔽原型鏈上層的所有屬性,因為總是會選擇原型鏈中最底層的屬性。如果不直接存在于中而是存在于原型鏈上層時會出現的三種情況。類構造函數原型函數,兩個函數通過屬性和屬性相關聯。 1 [[Prototype]] 對于默認的 [[Get]] 操作來說,如果無法在對象本身找到需要的屬性,就會繼續訪問對象的 [[Prototype]] ...
閱讀 4723·2021-11-15 11:39
閱讀 2691·2021-11-11 16:55
閱讀 2200·2021-10-25 09:44
閱讀 3504·2021-09-22 16:02
閱讀 2433·2019-08-30 15:55
閱讀 3122·2019-08-30 13:46
閱讀 2656·2019-08-30 13:15
閱讀 1944·2019-08-30 11:12