溫故js系列(18)-對象&對象使用

前端學習:教程&模塊化/規範化/工程化/優化&工具/調試&值得關注的博客/Git&面試資源彙總前端

歡迎提issues斧正:對象&對象使用react

Object對象

在 JavaScript 中,對象,是對一些具體事物的一種抽象,全部其餘對象都繼承自這個對象。Object 是一個無序集合,將變量和值集合在一塊兒,能夠存聽任意類型對象(JavaScript中的一切皆對象,這句話應該沒有對錯,不管正反兩個方面,支持者都能說出他們的解釋)。git

對象建立

字面量方式

var obj = {
    key: 'value',
    name : 'xzavier',
    sex : 'male',
};

構造函數方式

var obj = new Object();
obj.key = 'value';
obj.name = 'xzavier';
obj.sex = 'male';

也能夠這樣(不過這樣的話,爲什麼不選擇字面量方式):github

var obj = new Object({
    key: 'value',
    name : 'xzavier',
    sex : 'male',
});

字面量方式和new方式的寫法是等價的,返回的結果是同種類的對象。面試

字面量方式比較好的是你能夠在聲明的時候一次性添多個屬性和值,即鍵/值對;
而構造函數方式,你必須在構造完成以後一個一個地添加屬性。segmentfault

因此,你常常都會被建議使用字面量的方式,咱們也實踐得很是不錯。不少時候,咱們只有在場景須要的狀況下才會使用new 構造函數的方式。好比說拋出錯誤、建立含有變量的正則等:數組

new Error(..)
new RegExp('xzav' + i + 'er');

因此,大多狀況下咱們都使用字面量方式,通常不須要明確地建立對象。JavaScript會在必要的時候自動地將一些基本類型轉換爲對象類型,以便你能使用對象類型中的屬性:瀏覽器

var x = 'xx';
x.length; // 2  擁有String類型對象的屬性
var z = '123.321';
z.toFixed(2); // "123.32" 擁有Number類型對象的方法

內置對象

JavaScript中,有不少內置的Object的子類型,咱們一般稱爲內置對象,它們其實是內建的函數,每個均可以做爲構造函數 new 出一個新構造的相應子類型的實例,固然,也是對象。frontend

String
Number
Boolean
Math
Function
Array
Date
RegExp
Error

如上面所說,建立這些子類型也基本使用字面量的方法。檢測類型的話使用模塊化

Object.prototype.toString.call(obj);

檢測的最全。具體參考文章: 數據類型檢測

Object.prototype對象

在JavaScript中,幾乎全部對象都是Object的實例。而Object有一個屬性prototype,指向原型對象(js裏全部構造函數都有一個prototype屬性,指向原型對象)。咱們在實例化一個對象時,實例會繼承原型對象上的屬性和方法。

能夠控制檯查看 String.prototype
而後再: var str = new String('xzavier');
咱們的str繼承了String.prototype上的屬性和方法,String又繼承了Obeject.prototype上的方法。

不過說是繼承,說是指向引用比較好。由於對象在查找某個屬性的時候,會首先遍歷自身的屬性,若是沒有則會繼續查找[[Prototype]]引用的對象,若是再沒有則繼續查找[[Prototype]].[[Prototype]]引用的對象,依次類推,直到[[Prototype]].….[[Prototype]]undefined

Object.prototype[[Prototype]]就是undefined

圖片描述

在控制檯打印仔細去看:

String.prototype  // ... ...
Obeject.prototype  // ...
Obeject.prototype.prototype // undefined

你還能夠在控制檯輸入:

var str = new String('xzavier');
str

而後一層層的查看__proto__,你本身建立的構造函數同此。其實咱們的實例沒有繼承到方法和屬性,只是添加了個原型屬性,使用的時候往原型鏈上查找,能夠找到父級以及更上一層的原型鏈上的方法,而後使用。

具體參見: 原型鏈

回過頭來,修改Object.prototype就會影響到它的子孫後代。改變Object原型對象會修改到全部經過原型鏈繼承的對象,除非實例的相關屬性和方法沿原型鏈進一步覆蓋。這是一個很是強大的,存在潛在危險的機制,能夠覆蓋或擴展對象的行爲。

Object.prototype.xxx = function(){ console.log('xxx')};

var str = new String('xzavier');
str.xxx();  // xxx

因此:

Don’t modify objects you don’t own

不過,不少時候咱們本身寫代碼,用原型屬性擴展方法是很是實用的。

// 數組去重
Array.prototype.unique = function(){
    return [...new Set(this)];
}
[1,2,3,'4',3,4,3,1,'34',2].unique(); //[1, 2, 3, "4", 4, "34"]

對象的遍歷

數組是對象,字符串是對象,均可以遍歷。遍歷詳解參考: 流程控制

這兒就說說對象{}類型的遍歷:

for...in 語句能夠用來枚舉對象的屬性,爲遍歷對象屬性設計的。

var xzavier = { 
    'name' : 'xzavier', 
    'age' : 23,
    'job' : 'Jser',
    'width' : 100,
    'height' : 100,
    'border' : 10
};
for (var i in xzavier) { 
    console.log(i);
}
//name age job width height border
for (var i in xzavier) { 
    console.log(xzavier[i]);
}
//xzavier 23 Jser 100 100 10

在設計對象的時候,鍵值最好是以字符串,而非數字字符串,如'0', '123',這樣會致使對象重排。
好比:

var obj = {
    '0': 1,
    'abc': 2,
    'def': 3,
    '2': 4,
    'ghi': 5
};

打印obj:

Object {0: 1, 2: 4, abc: 2, def: 3, ghi: 5, __proto__: Object }

用for in 語句輸出:

for (var i in obj) {
  console.log(oo[i]);
}  
// 1 4 2 3 5 

for (var i in obj) {
  console.log(i);
}
// 0 2 abc def ghi

雖然咱們平時使用不會受到什麼影響,也不會這麼設計。可是你在不在意,它始終在這裏。

react的for循環key值就建議不要以index爲值,也有這個緣由,具體很少述。

對象的判斷

咱們常常會遇到判斷對象類型,這兒就不說typeof了,詳情請參考:數據類型判斷

這兒簡寫一下這個不會出錯的方法:

Object.prototype.toString.call('xz'); //"[object String]"
Object.prototype.toString.call(123);  //"[object Number]"
Object.prototype.toString.call(true); //"[object Boolean]"
Object.prototype.toString.call([1,2]); //"[object Array]"
Object.prototype.toString.call({name:'xz'}); //"[object Object]"
Object.prototype.toString.call(function(){}); //"[object Function]"
Object.prototype.toString.call(null); //"[object Null]"
Object.prototype.toString.call(undefined); //"[object Undefined]"
Object.prototype.toString.call(); //"[object Undefined]"
Object.prototype.toString.call(new Date()); //"[object Date]"
Object.prototype.toString.call(/xz/);  //"[object RegExp]"
Object.prototype.toString.call(Symbol()); //"[object Symbol]"

var obj = {name:"Xzavier", age:23};
var a = [1,2,3];

function isType(obj) {
    return Object.prototype.toString.call(obj).slice(8, -1);
}
isType(obj);  // "Object" 
isType(a)  // "Array"

可是,不少時候咱們在處理數據的時候,須要判斷一個對象是否爲{}:

var isEmptyObject = function(obj) {
    for (var name in obj) {
        return false;
    }
    return true;
};
isEmptyObject({});  // true
isEmptyObject({name: 'xzavier'}); //false

對象屬性值訪問

閱讀了溫故js系列(1)- 數據類型存儲,咱們知道,引用數據類型值指保存在堆內存中的對象。也就是,變量中保存的實際上的只是一個指針,這個指針指向內存中的另外一個位置,該位置保存着對象,訪問方式是按引用訪問。

var obj = {
    key: 'value',
    name : 'xzavier',
    sex : 'male',
    'x-v': 'xz',
    'xx!': 'xxxx',
};

在訪問一個對象的一個屬性時,使用.["..."]操做符。obj.keyobj['key'] 都訪問obj中相同的位置,返回值都是'xzavier',因此這兩種方式都在代碼中常用到。

而這兩種訪問方式的區別是,.操做符後面須要跟一個標識符(Identifier)兼容的屬性名,而["..."]語法基本能夠接收任何兼容UTF-8/unicode的字符串做爲屬性名。

obj.x-v  // 報錯
obj.xx!  // 報錯
obj['x-v']  // "xz"
obj['xx!']  // "xxxx"

由於x-zxx!不是一個合法的標識符屬性名。

["..."] 還能夠動態組建屬性名,這在合適的場景下很是好用。

var obj = {
    number1: 'xx',
    number2: 'yy',
    number3: 'zz'
}
var number = 2;
var select_name = obj['number' + number];  // 'yy'

屬性描述符

var xz = {name: 'xzavier'}
Object.getOwnPropertyDescriptor( xz, "name" );

正如函數名,獲取屬性描述符,打印以下:

//  configurable: true,  是否可配置
//  writable: true,   是否可寫
//  value: 'xzavier',
//  enumerable: true   是否可枚舉
//  __proto__: Object

咱們不多關注到這些,如有須要,咱們關注的莫過於configurable,writable,enumerable等。
能夠經過Object的defineProperty方法修改值的屬性,通常咱們使用defineProperty是給對象添加屬性,在這個使用的基礎上,能夠對這個值的屬性進行配置。

Object.defineProperty( obj, "name", {
    value: 'xzavier-1',
    configurable: true,  //是否可配置
    writable: false,   //是否可寫
    enumerable: true   //是否可枚舉
});

obj.name // xzavier-1
obj.name = 'xzavier-2' // 不會報錯,但在"use strict"模式下,會報錯TypeError
obj.name // xzavier-1  值沒有被修改

這是writable,接下來講說enumerable,是否可枚舉:

enumerable表述屬性是否能在特定的對象屬性枚舉操做中出現,好比在for..in,for...of中遍歷。若是enumerable被設置爲false,那麼這個屬性將不會出如今枚舉中,不過它依然能夠被屬性訪問方式訪問。

var obj = {
    number1: 'xx',
    number2: 'yy',
    number3: 'zz'
}
Object.defineProperty( obj, "name0", {
    value: 'xzavier-1',
    configurable: true,  //是否可配置
    writable: true,   //是否可寫
    enumerable: false   //是否可枚舉
});
for(var i in obj) {
    console.log(i);  // number1 number2 number3  不會出現number0
}
for(var i of keys(obj)) {
    console.log(i);  // number1 number2 number3  不會出現number0
}

再把enumerable設置爲true就能夠繼續在枚舉中遍歷到了。

Object.defineProperty( obj, "name0", {
    enumerable: true   //是否可枚舉
});
for(var i in obj) {
    console.log(i);  // number1 number2 number3 number0
}
for(var i in keys(obj)) {
    console.log(i);  // number1 number2 number3 number0
}

最後說下configurable ,表示屬性是否能夠進行以上操做,便是否可配置。它是一個單向操做,不可逆。

Object.defineProperty( obj, "name4", {
    value: 'xzavier-4',
    configurable: false,  //是否可配置
    writable: false,   //是否可寫
    enumerable: true   //是否可枚舉
});

屬性的configurable一旦被設爲false,將不可逆轉,再用defineProperty設置屬性將會報錯。

Object.defineProperty( obj, "name4", {
    value: 'xzavier-4',
    configurable: true,  //是否可配置
    writable: true,   //是否可寫
    enumerable: true   //是否可枚舉
});
// TypeError

Object方法

Object.assign()

Object.assign(target, ...sources)方法用於從一個或多個源對象的全部可枚舉屬性的值複製到目標對象上,有相同屬性的時候,目標對象上的屬性會被覆蓋,最終返回目標對象。
接收參數:target目標對象...sources一到多個源對象 不傳則直接返回目標對象

咱們能夠:

1.複製對象

var obj = { a: 1 };
var obj_1 = Object.assign({}, obj);  // { a: 1 }

2.合併對象

var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };

var obj = Object.assign({}, o1, o2, o3);  // { a: 1, b: 2, c: 3 }

Object.create()

Object.create(prototype, descriptors) 方法建立一個具備指定原型且可選擇性地包含指定屬性的對象。接收參數:prototype,必需,要用做原型的對象,能夠爲 null。descriptors,可選,包含一個或多個屬性描述符的 JavaScript 對象。數據屬性描述符包含value,以及 writable,enumerable,configurable,即上面所講。

咱們能夠:

1.建立一個新對象:

var obj = {
    name: 'xzavier'
}

var o = Object.create(obj);
o.name; // 'xzavier'

// 可是 name屬性並不是o的自定義屬性
o.hasOwnProperty('name'); //false  你在瀏覽器操做以後展開也能夠清晰的看到

2.建立一個空對象(沒有原型的對象)
Object.create(null)建立一個擁有空[[Prototype]]連接的對象,即這個對象沒有原形鏈。

var obj = Object.create(null);
在控制檯會看到返回 object[ No Properties ]

那這樣的東西建立來又什麼用呢,它能夠做爲一種數據存儲方式,不用擔憂它會被原型之類的污染,不用擔憂會有原型鏈查找。它就是一個不會存在一個你意想不到的屬性的存儲結構。因此,能夠放心使用它。

3.繼承

function Parent() {}
    
Parent.prototype.say = function() {
    console.info("Hello World");
};

function Childs() {
  Parent.call(this);
}

Childs.prototype = Object.create(Parent.prototype);

var child = new Childs();

child instanceof Childs; //true.
child instanceof Parent; //true.

child.say();  // Hello World

Object.is()

ES6以前,比較兩個值是否相等,使用相等運算符(==)和嚴格相等運算符(===)。具體參見:代碼中的哪些判斷

Object.is()的出現主要是讓如下狀況出現:

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

Object.is()使用的是嚴格相等運算符(===)作判斷,對+0,-0,NaN作了特殊處理

Object()

Object() // {}
Object({})  // {}
Object(undefined) // {}
Object(null) // {}  
Object(1) // 同 new Number(1)
Object(NaN) // 同 new Number(NaN)
Object('xzavier') // 同 new String('xzavier')
Object(false) // 同 new Boolean(false) 
Object([1,2,3]) // [1,2,3]
Object({name: 'xzavier'}) // {name: "xzavier"}
Object(function x(){}) // function x(){}

Object()創造一個「真」對象,返回的都是一個truthy值,是一個對象,因此在if()判斷中都是一個真值。

Object.prototype上的方法

Object.prototype上的方法每每都會被繼承到你實例化的或者字面量形式聲明的數據類型中。咱們能夠直接在實例上使用:

[1,2,3].toString();  //"1,2,3"
({a: 1}).valueOf();  // {a: 1}
......

對象的其餘使用

關於對象的使用,上面所羅列的都是咱們常常遇到的。咱們也常用對象的特性,作一些事情。

數組去重:

Array.prototype.unique = function() {
      var arr = [];
      var hash = {};
      for (var i = 0; i < this.length; i++) {
        var item = this[i];
        var key = typeof(item) + item
        if (hash[key] !== 1) {
              arr.push(item);
              hash[key] = 1;
        }
      } 
      return arr;
}
[1,2,3,'4',3,4,3,1,'34',2].unique(); //[1, 2, 3, "4", 4, "34"]

hash去重的核心是構建了一個 hash 對象來替代 indexOf。判斷hash的key是否已經存在來去重。

最後說一下,文章裏面提到的[[Prototype]],__proto__,prototype

你打印來看,咱們只會看到__proto__,因此起做用的是__proto____proto__是對象的內置屬性,是每一個對象都有的屬性,可是這個屬性使用不標準,因此不建議直接使用。可是,咱們的原型鏈就是基於 __proto__的。經過構造函數獲得的實例的 __proto__ 屬性,指向其對應的原型對象 String.prototype,這正如文中咱們打印 var str = new String('xzavier') 中看到的同樣。

[[Prototype]]是一個隱藏屬性,指向的是這個對象的原型。幾乎每一個對象有一個[[prototype]]屬性。

prototype是每一個函數對象都具備的屬性,指向原型對象,若是原型對象被添加屬性和方法,那麼由應的構造函數建立的實例會繼承prototype上的屬性和方法,這也是咱們在代碼中常常遇到的。構造函數產生實例時,實例經過其對應原型對象的 constructor 訪問對應的構造函數對象。因此,咱們繼承出來的實例每每沒有constructor,只是經過原型鏈查找,會讓咱們產生錯覺,可參見本系列原型鏈文章。

相關文章
相關標籤/搜索