《你不知道的javascript》筆記_對象&原型

上一篇:《你不知道的javascript》筆記_thisjavascript

寫在前面

這是2019年第一篇博客,回顧去年年初列的學習清單,發現僅有部分完成了。固然,這並不影響2018年是向上的一年:在新的城市穩定、連續堅持健身三個月、早睡早起、遊戲時間大大縮減,學會生活。根據上一年目標完成狀況,新一年的計劃將採起【敏捷迭代】的方式:制定大方向,可臨時更新小目標的策略。哈哈,話很少說..java

在學習《javascript高級程序設計》這本書時,關於對象和原型,以前寫過下面幾篇文章,可做參考:node

  1. 《javascript高級程序設計》筆記:對象數據屬性和訪問器屬性
  2. 《javascript高級程序設計》筆記:建立對象
  3. 《javascript高級程序設計》筆記:原型圖解
  4. 《javascript高級程序設計》筆記:繼承

1、對象基本知識

1.1 數據屬性與訪問屬性

《javascript高級程序設計》筆記:對象數據屬性和訪問器屬性,這篇文章對數據屬性和訪問器屬性有基本的介紹了,下面會作出一點補充說明:面試

(1)默認值編程

  1. 經過Object.defineProperty的方式聲明的屬性默認值segmentfault

    var obj = { a: 1 };
    Object.getOwnPropertyDescriptor(obj, 'a');
    /*{
        value: 1, 
        writable: true,
        enumerable: true, 
        configurable: true
    }*/
  2. 經過Object.defineProperty的方式聲明的屬性默認值數組

    var obj = {};
    Object.defineProperty(obj, 'a', {});
    Object.getOwnpropertyDescriptor(obj, 'a');
    /*{
        value: undefined,
        writable: false,
        enumerable: false,
        configurable: false
    }*/

(2)屬性configurableide

  1. configurable屬性設爲false爲不可逆過程
  2. configurable:false其餘屬性沒法修改同時會禁止刪除該屬性
  3. 特例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、原型&繼承

類/繼承描述了一種代碼的組織結構形式——一種在軟件中對真實世界中問題領域的建模方法

下面會對這種建模慢慢闡述

2.1 原型

很是慶幸,以前寫過《javascript高級程序設計》筆記:原型圖解的文章獲得了許多朋友的承認。關於原型的一些知識這裏面也說的七七八八了,讀完《你不知道的javascript》後再作些許補充

下面搬出經典的鐵三角鎮樓,在原型圖解中已作說明,在此不嘮述

clipboard.png

(1)原型有什麼用

爲何要抽離出原型的概念?在js中原型就至關於類;類有什麼做用呢?

書中有一個恰當的比喻:類至關於建造房子時的藍圖,實例至關於咱們須要真實建造的房子

這個藍圖(類)抽離出了房子的諸多特性(屬性),如寬/高/佔地/窗戶數量/材料等等。咱們建造房子(建立實例)時只須要按照這些搭建便可,而不是從零開始

回到編程中:有了類的概念,針對一些具備公共的屬性和方法對象,咱們能夠將其抽離出來,以便下次使用,簡化咱們構建的過程

clipboard.png

這個是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

上面問號處輸出值爲多少?

分析:
node1node2處分別執行下面的代碼並輸出

// 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

2.2 繼承

《javascript高級程序設計》筆記:繼承這篇文章分析了各類繼承的狀況,一步一步演化至更精緻的繼承狀況

(1)繼承有什麼用

需求:爲個人汽車建一個對象
之前:{id: XX, 牌照: XX, 品牌: XX, 油耗: XX, 載人: XX, 顏色: XX .. }
建模的思想來搭建類和繼承體系:

  1. 抽離交通工具的類 Vehicle = {油耗: XX, 載人: XX, 顏色: XX, drive()}
  2. 抽離汽車的類 Car = {繼承Vehicle, 品牌: XX }
  3. 實例化個人汽車 {繼承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);

clipboard.png

二者對比而言,顯然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();

clipboard.png

// 委託模型
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();

clipboard.png

類風格的代碼強調的是實體與實體之間的關係,委託風格的代碼強調的是對象之間的關聯關係;

如何選用?像上面章節舉的例子:交通工具-->汽車-->具體某個汽車的關係,選用類;沒有太大關聯的對象可直接用委託實現

上一篇:《你不知道的javascript》筆記_this

相關文章
相關標籤/搜索