轉載煩請註明原文連接:
https://github.com/Xing-Chuan/blog/blob/master/JavaScript/JavaScript%E4%B9%8BObject%E6%8B%86%E8%A7%A3.md
---javascript
最近把研究 Object 的體會總結了一下, 有 Object 相關聯的屬性、方法和 ES6 後新 Api .java
JavaScript 中有兩種數據類型: 數據屬性和訪問器屬性git
數據屬性有如下幾個描述行爲的屬性:es6
若是想要修改這些系統默認屬性, 能夠經過 ES5 的方法 Object.defineProperty(obj, property, option).github
注意:chrome
訪問器有如下幾個屬性:api
let book = { _year: 2004, edition: 1 }; /* * 第一個參數: 對象 * 第二個參數: 對象的屬性 * 第三個參數: 須要設置的描述屬性 */ Object.defineProperty(book, 'year', { get: function() { return this._year; }, set: function(newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }); book.year = 2009; console.log(book.edition); // 6
注意:數組
若是要一次修改多個參數的描述屬性, 可使用 Object.defineProperties()瀏覽器
let book = {}; Object.defineProperties(book, { _year: { value: 2004 }, edition: { value: 1 } });
構造函數也是普通的函數, 若是沒有經過 new 操做符生成新的實例對象, 那構造函數就是一個普通的函數.
爲了區分構造函數於普通函數, 咱們一般用大駝峯命名法給構造函數命名.
String、Array 等幾乎全部對象都是 Object 的實例, Object 就是一個構造函數.安全
new Array() instanceof Object // true new String() instanceof Object // true new Function() instanceof Object // true
function Info() { this.name = 'XingChuan'; this.age = '10'; } let info1 = new Info(); let info2 = new Info(); console.log(info1.name); // XingChuan console.log(info1.age); // 10 console.log(info2.name); // XingChuan console.log(info2.age); // 10
既然談到構造函數就要談到原型了, 每一個函數都有一個原型(prototype)屬性, 原型存在的意義就是, 原型上的屬性和方法由全部構造函數的實例對象所共享, 若是修改了原型對象上的方法, 那全部實例都會實時更改.
咱們平時使用 Array 和 String 的方法, 都是在其原型對象上, 全部咱們建立的全部數組和字符串均可以享有這些方法.
在 chrome, firefox, safari 中, 有一個瀏覽器私有屬性 __proto__
指向構造函數的 prototype, __proto__
並非官方屬性, 爲了兼容性考慮, 開發中最好不要使用 __proto__
.
下面咱們來具象化一下原型鏈的構成:
構造函數也是普通的函數, 只是咱們拿來生成實例, 因此纔有這個稱謂, 而全部的函數都是 Function 的實例.
function info() { }
// 原型鏈 info.__proto__ => Function.prototype => Object.prototype
全部的函數都是 Function 的實例, 而 Function 是 Object 的實例, 因此有了這條原型鏈.
頗有意思的是, Object、Array、String、Function 都是函數, 因此他們都是 Function 的實例, Function 比較特殊, 它也是自身的實例.
Math 是個例外, 它並非一個函數, 而是一個對象.
console.log(Array instanceof Function) // true console.log(String instanceof Function) // true console.log(Object instanceof Function) // true console.log(Function instanceof Function) // true console.log(Math instanceof Function) // false console.log(Math.__proto__ === Object.prototype); // true
// 原型鏈 Array.__proto__ => Function.prototype => Object.protytype
function Info() { } Info.prototype.name = 'Xingchuan'; Info.prototype.age = 10; Info.prototype.showName = function() { console.log(this.name); }; let info1 = new Info(); info1.showName() // XingChuan let info2 = new Info(); info2.name = 'test'; info2.showName() // test
// 原型鏈 info1.__proto__ => Info.prototype => Object.prototype
實例對象的 __proto__
會指向構造函數的 prototype, 全部的原型對象都是 Object.prototype 的實例, 因此構成了這一條原型鏈.
注意:
console.dir(document.getElementsByTagName('span')[0]); // span元素 => HTMLSpanElement => HTMLElement => Element => Node => EventTarget => Object.prototype console.dir(document.getElementsByTagName('span')[0] instanceof Object); // true
元素的原型鏈很長, 不過能夠看到元素也是 Object 的實例.
prototype
的 constructor 指向 構造函數__proto__
指向構造函數的 prototype
__proto__
指向 Function.prototype
prototype
都是基於 Object.prototype
Object.prototype
constructor
屬性, 指向原型所屬的函數, 用字面量對象改成原型的引用, 會丟失 constructor
這個屬性來張示意圖結尾( 侵刪 ):
在討論 this 指向的問題以前, 先要明確一下, 在嚴格模式下, 未指定環境對象而調用函數,則 this 值不會轉型爲 window.
除非明確把函數添加到某個對象或者調用 apply()或 call(),不然 this 值將是 undefined.
let x = 1; function show() { console.log(this.x); } show(); // 1
普通函數中的 this 指向 window
function Info(){ this.x = 1; } let info1 = new Info(); console.log(info1.x); // 1
構造函數中的 this 指向 new 出來的實例對象, new 這個操做符會改變 this 的指向.
let x = 2; let obj = { x: 1, y: function() { console.log(this.x); } }; obj.y(); // 1 對象方法中的 this 指向調用它的對象.
call、apply、bind(ES5) 的做用就是改變 this 的指向, this 會指向第一個參數.
若是 call、apply、bind(ES5) 調用方法時沒有傳參, 默認 this 指向 window.
bind 只會改變 this 的指向, 並不會執行方法, call 和 apply 則會改變指向時也執行方法.
事件函數中的 this 指向綁定事件的元素.
箭頭函數是 ES6 中新增的方法, 不一樣於其餘狀況中的 this 在調用時才決定指向, 箭頭函數 this 指向定義時的外圍, 在不確認指向的狀況下, 請慎用.
ECMAScript 6 入門對箭頭函數的使用限制作了說明: (1)函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。 (2)不能夠看成構造函數,也就是說,不可使用new命令,不然會拋出一個錯誤。 (3)不可使用arguments對象,該對象在函數體內不存在。若是要用,能夠用Rest參數代替。 (4)不可使用yield命令,所以箭頭函數不能用做Generator函數。
在開發中, 咱們能夠用 typeof 來判斷類型, 但有很大的侷限性.
typeof 1 // "number" typeof '1' // "string" typeof true // "boolean" typeof undefined // "undefined" typeof null // "object" typeof (function(){}) // "function" typeof [] // "object" typeof {} // "object"
typeof 只對一些簡單數據類型有效, 爲了能夠判斷各類內置對象, 咱們須要採起一些 手段
, 使用 Object 原型上的 toString 方法.
Object.prototype.toString.call(1); // "[object Number]" Object.prototype.toString.call('1'); // "[object String]" Object.prototype.toString.call(true); // "[object Boolean]" 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(new Date()); // "[object Date]" Object.prototype.toString.call(Math); // "[object Math]" Object.prototype.toString.call([]); // "[object Array]" Object.prototype.toString.call({}); // "[object Object]"
能夠所有搞定了.
簡潔的寫法能夠減小代碼量也能夠更加優雅, 但代碼是給計算機看的, 同時也是給人看的, 容易發生歧義的地方必定要注意.
若是屬性名與屬性值相同, 能夠忽略不寫.
屬性值是字符串時不可簡寫.
// old let name = 'XingChuan'; let obj = { name: name }; // new let name = 'XingChuan'; let obj = { name }; // error let name = 'XingChuan'; let obj = { name:'name' };
// old let obj = { show: function() { console.log('show'); } }; // new let obj = { show() { console.log('show'); } };
ES5只支持這種字面量定義:
let obj = { name: 'XingChuan', age: 10 };
ES6支持這種寫法:
let obj ={ [name]: 'XingChuan', ['a' + 'ge']: 10 }; // 做爲屬性名的表達式會自動 toString() , 應避免使用對象做爲表達式, 由於 String({}) === '[object Object]'
注意:
Object.is()
基本等同於 ===
, 除卻兩點:
+0 === -0 // true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true
let target = { info: { name: 'name1' } }; let source1 ={ info: { name: 'name2', age: 30 } }; let source2 ={ info: { name: 'name3' } }; Object.assign(target, source1, source2); // { info: {name: 'name3'}} // // Object.assign 是淺拷貝, 若是屬性的值是對象, 就會添加新的引用, 而不是在原有地址上添加屬性. // target會自動轉換爲對象, 因此不能爲 null 或 undefined , 會報錯 // source爲 null 或 undefined 時, 由於沒法轉換爲對象, 會跳過, 但不會報錯 // 若 source 爲字符串, 會以數組的形式複製到 target 中 //
每一個對象屬性都有一個描述對象 Description , 能夠控制是否可被枚舉, 數據屬性的其中之一.
let obj = { name: 'XingChuan' }; Object.getOwnPropertyDescriptor(obj,'name'); // { // configurable: true, // enumerable: true, // 若是可枚舉爲 false , 某些操做會忽略掉這個屬性 // value: "XingChuan", // writable: true // }
ES5 中有 3 個屬性會忽略 enumerable
爲 false
的屬性:
ES6 新增的 Object.assign() 也會忽略描述中不可枚舉的屬性.
數組中的 length 屬性不會被 for...in 獲取就是由於不可枚舉的描述.
Object.getOwnPropertyDescriptor([1,2,3],'length'); // { // configurable: false, // enumerable: false, // value: 3, // writable: true // }
另外, ES6 中也規定了 Class 原型上的方法是不可枚舉的.
for...in 循環遍歷對象自身的和繼承的可枚舉屬性(不含 Symbol 屬性)
Object.keys返回一個數組,包括對象自身的(不含繼承的)全部可枚舉屬性(不含 Symbol 屬性)
Object.getOwnPropertyNames返回一個數組,包含對象自身的全部屬性(不含 Symbol 屬性,可是包括不可枚舉屬性)
Object.getOwnPropertySymbols返回一個數組,包含對象自身的全部 Symbol 屬性
Reflect.ownKeys返回一個數組,包含對象自身的全部屬性,無論屬性名是 Symbol 或字符串,也不論是否可枚舉
以上 5 種方法在遍歷順序上, 遵循如下 3 條規則:
__proto__
, Object.getPrototypeOf(), Object.setPrototypeOf()__proto__
__proto__
指向當前實例的原型對象, 其沒有被 ES6 列爲正式 API, 但由於被瀏覽器廠商普遍使用, 被收入附錄.
某些瀏覽器廠商一樣指向原型對象, 多是另外一種命名方式, 因此爲了兼容性考慮, 最好不要經過它去操做原型.
Object.setPrototypeOf() 是 ES6 設置原型對象的方法
let obj = { x: 10 }; let option = { x: 20, y: 30, z: 40 }; Object.setPrototypeOf(obj, option); obj.x // 10 obj.y // 30 obj.z // 40 // 因原型鏈訪問順序的優先級, obj.x 爲 10 而不是 20, 如 obj 不存在 x 的屬性, obj.x 就會爲 20.
Object.getPrototypeOf(obj) 是 ES6 返回原型對象的方法
Object.keys() 是 ES5 中遍歷屬性的方法, ES6 新增了 Object.values(), Object.entries().
返回對象自身的(不包含繼承的), 可枚舉的鍵值
返回對象自身的(不包含繼承的), 可枚舉的鍵值對數組
ES8 中將數組的拓展運算符引入到了對象中.
let {a, b, ...x} = {a: 1, b:2, c: 3, d: 4}; console.log(x); // {c:3,d:4}
注意:
let x = {name: 'XingChuan', age: 88}; let cloneObj = { ...x };
let x = {name: 'XingChuan', age: 88}; let y = {job: 'developer'}; let cloneObj = { ...x, ...y };
let obj = { ...{x > 1 ? {a: 1} : {} } };
擴展運算符的參數對象之中,若是有取值函數get,這個函數是會執行的.
let runtimeError = { ...a, ...{ get x() { throws new Error('thrown now'); } } };
ES5 中 Object.getOwnPropertyDescriptor(obj, property) 能夠獲取對象屬性的描述對象.
ES8 中 新增了 Object.getOwnPropertyDescriptors(obj) 能夠獲取對象全部屬性的描述對象, 描述對象包括 get 和 set 屬性.
咱們要讀取對象的一個屬性或調用其方法, 爲了避免報錯, 應該先判斷對象是否存在, 而後再讀取其屬性.
若是咱們想讀取 obj.info.xingchuan.name, 安全的寫法應該是下面這樣
let name = obj && obj.info && obj.info.xingchuan && obj.info.xingchuan.name || 'default';
如今提案中引入了 Null 傳導運算符, 簡化了寫法, 能夠寫爲下面這種方式.
let name = obj ?. info ?. xingchuan ?. name || 'default';