上一篇:《你不知道的javascript》筆記_thisjavascript
這是2019年第一篇博客,回顧去年年初列的學習清單,發現僅有部分完成了。固然,這並不影響2018年是向上的一年:在新的城市穩定、連續堅持健身三個月、早睡早起、遊戲時間大大縮減,學會生活。根據上一年目標完成狀況,新一年的計劃將採起【敏捷迭代】的方式:制定大方向,可臨時更新小目標的策略。哈哈,話很少說..java
在學習《javascript高級程序設計》這本書時,關於對象和原型,以前寫過下面幾篇文章,可做參考:node
《javascript高級程序設計》筆記:對象數據屬性和訪問器屬性,這篇文章對數據屬性和訪問器屬性有基本的介紹了,下面會作出一點補充說明:面試
(1)默認值編程
經過非Object.defineProperty
的方式聲明的屬性默認值segmentfault
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
ide
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)對象常量
建立方式:數據屬性: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
,全部屬性至關於常量屬性
in
操做符:檢查屬性是否在對象及其原型鏈中hasOwnProperty
:僅檢查屬性是否在該對象上
所以,可結合兩者來判斷屬性是否僅存在與原型鏈上
function hasPrototypeProperty(obj, property) { return (property in obj) && !(obj.hasOwnProperty(property)); }
另外,可獲取的某個對象全部key
(未遍歷至原型鏈),再判斷指定屬性是否在對象內;
遍歷方法:
Object.keys(); Object.getOwnPropertyNames(); // 可獲取不可枚舉的屬性
理解深淺拷貝須要對內存有必定理解,若是對基礎類型和複雜類型(值類型和引用類型)在內存中的區別仍沒有清楚的認識,可參考《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 淺拷貝與深拷貝
類/繼承描述了一種代碼的組織結構形式——一種在軟件中對真實世界中問題領域的建模方法
下面會對這種建模慢慢闡述
很是慶幸,以前寫過《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
)
對象之間並不是從屬關係,而是委託關係,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();
類風格的代碼強調的是實體與實體之間的關係,委託風格的代碼強調的是對象之間的關聯關係;
如何選用?像上面章節舉的例子:交通工具-->汽車-->具體某個汽車
的關係,選用類;沒有太大關聯的對象可直接用委託實現