ES6學習筆記---對象的擴展


1、屬性、方法可以簡寫
比如屬性簡寫:

var foo = 'bar'; var baz = {foo}; // 等同於 var baz = {foo: foo}; function f(x, y) { return {x, y}; } // 等同於 function f(x, y) { return {x: x, y: y}; }

比如方法簡寫:

var o = { method() { return "Hello!"; } }; // 等同於 var o = { method: function() { return "Hello!"; } };

這種簡寫可以給我們帶來哪些便捷呢?

Example 1:函數的返回值。
在ES5中我們會這樣寫:

function a(){ var data = {}; data.x =1; data.y = 10; data.z = 100; return data; } a(); //{x:1,y:10,z:100}

在ES6中我們會這樣寫:

function a() { var x = 1,y = 10,z = 100; return {x, y, z}; } a(); //{x:1,y:10,z:100}

Example 2:模塊輸出變量

var ms = {}; function getItem (key) { return key in ms ? ms[key] : null; } function setItem (key, value) { ms[key] = value; } function clear () { ms = {}; } module.exports = { getItem, setItem, clear };

自己對這個有點不明白為什么模塊輸出變量非常合適使用這種簡潔方法,歡迎了解的朋友賜教,3Q。


2、屬性名表達式
ES6允許用表達式作為屬性名,表達式要放在[ ]方括號內使用,而ES5只支持直接用標識符作為屬性名,舉個例子:

//標識符 var o = { obj : 1, obj2: 'test' } //表達式 var obj = 'how are you'; var o = { [obj]: 1, ['hello world']: 'test' }; o[obj]; // 1 o['hello world']; //test

還可以用表達式定義方法名:

var a = 'ello'; var obj = { ['h'+ a]() { return 'hi Jack'; } }; obj.hello() // hi Jack

注:屬性名表達式與簡潔表示法,不能同時使用

// 錯誤寫法 var foo = 'bar'; var bar = 'abc'; var baz = { [foo] }; // 正確寫法 var foo = 'bar'; var baz = { [foo]: 'abc'};

3、方法的name屬性
函數的name屬性,返回函數名。對象方法也是函數,因此也有name屬性。

var person = { sayName: function() { console.log(this.name); }, get firstName() { return "Nicholas" } } var doSomething = function() { return; }; (new Function()).name // "anonymous" 我的執行結果:" doSomething.bind().name // "bound doSomething" 我的執行結果:'bound' person.sayName.name // "sayName" 我的執行結果:"" 雖然sayName有name屬性,但是輸出結果卻是空 person.firstName.name // "get firstName" 我的執行結果:undefined 控制台顯示firstName沒有name屬性

其中我對這的bind方法有些疑惑,我們知道bind大都是jquery綁定事件用的,但是這里的用法一定不是這樣的,所以我就查了一下,果然不同:

如果你希望將一個對象的函數賦值給另外一個變量后,這個函數的執行上下文仍然為這個對象,那么就需要用到bind方法。(與call和apply作用相似)。

如果對象的方法是一個Symbol值,那么name屬性返回的是這個Symbol值的描述。

const key1 = Symbol('description'); const key2 = Symbol(); var obj = { [key1]() {}, [key2]() {}, }; obj[key1].name // "[description]" 我的執行結果:"" obj[key2].name // ""

這兩處的運行結果與阮大神的測試結果有些不同,可能是我直接用chrome控制台測試的緣故,待我再多用幾種方法執行代碼看看結果的。


4、Object.is()
Object.is用來比較兩個值是否嚴格相等。它與嚴格比較運算符(===)的行為基本一致。
不過它的不同之處只有兩個:一是+0不等於-0,二是NaN等於自身。

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

ES5可以通過下面的代碼,部署Object.is

Object.defineProperty(Object, 'is', { value: function(x, y) { if (x === y) { // 針對+0 不等於 -0的情況 return x !== 0 || 1 / x === 1 / y; } // 針對NaN的情況 return x !== x && y !== y; }, configurable: true, enumerable: false, writable: true });

5、Object.assign()
Object.assign方法用來將源對象(source)的所有可枚舉屬性,復制到目標對象(target)。它至少需要兩個對象作為參數,第一個參數是目標對象,后面的參數都是源對象。只要有一個參數不是對象,就會拋出TypeError錯誤。

var target = { a: 1 }; var source1 = { b: 2 }; var source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}

注:如果目標對象與源對象有同名屬性,或多個源對象有同名屬性,則后面的屬性會覆蓋前面的屬性。

var target = { a: 1, b: 1 }; var source1 = { b: 2, c: 2 }; var source2 = { c: 3 }; Object.assign(target, source1, source2); target // {a:1, b:2, c:3}

Object.assign只拷貝自身屬性,不可枚舉的屬性(enumerablefalse)和繼承的屬性不會被拷貝。

Object.assign({b: 'c'}, Object.defineProperty({}, 'invisible', { enumerable: false, value: 'hello' }) ) // { b: 'c' } Object.assign({b: 'c'}, Object.defineProperty({}, 'invisible', { enumerable: true, value: 'hello' }) ) // {b: "c", invisible: "hello"}

屬性名為Symbol值的屬性,也會被Object.assign拷貝。

Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' }) // { a: 'b', Symbol(c): 'd' }

對於嵌套的對象,Object.assign的處理方法是替換,而不是添加。

var target = { a: { b: 'c', d: 'e' } } var source = { a: { b: 'hello' } } Object.assign(target, source) // { a: { b: 'hello' } }

上面代碼中,target對象的a屬性被source對象的a屬性整個替換掉了,而不會得到{ a: { b: 'hello', d: 'e' } }的結果。這通常不是開發者想要的,需要特別小心。有一些函數庫提供Object.assign的定制版本(比如Lodash_.defaultsDeep方法),可以解決深拷貝的問題。

看到這產生了疑問:Lodash是什么?_.defaultsDeep又是怎么解決的呢?
於是我搜了一些資料:

  • lodash 其實是一個 JavaScript 實用工具庫,提供一致性,模塊化,性能和配件等功能。類似underscore,是它的下一代。

  • _.defaultsDeep(object, [sources])
    目標Object中設定的值是缺省值,不能被sources的相同property覆蓋,但是用遞歸的方式分配默認值

_.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } }); // { 'user': { 'name': 'barney', 'age': 36 } }

注意,Object.assign可以用來處理數組,但是會把數組視為對象。

Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]

其中,4覆蓋1,5覆蓋2,因為它們在數組的同一位置,所以就對應位置覆蓋了。

Object.assign還有很多用處,下面就看一下吧:

  • 為對象添加屬性

class Point { constructor(x, y) { Object.assign(this, {x, y}); } }

這樣就給Point類的對象實例添加了x、y屬性。

  • 為對象添加方法

Object.assign(SomeClass.prototype, { someMethod(arg1, arg2) { ··· }, anotherMethod() { ··· } }); // 等同於下面的寫法 SomeClass.prototype.someMethod = function (arg1, arg2) { ··· }; SomeClass.prototype.anotherMethod = function () { ··· };

上面代碼使用了對象屬性的簡潔表示法,直接將兩個函數放在大括號中,再使用assign方法添加到SomeClass.prototype之中。

  • 克隆對象

function clone(origin) { return Object.assign({}, origin); }

上面代碼將原始對象拷貝到一個空對象,就得到了原始對象的克隆。

不過,采用這種方法克隆,只能克隆原始對象自身的值,不能克隆它繼承的值。如果想要保持繼承鏈,可以采用下面的代碼。

function clone(origin) { let originProto = Object.getPrototypeOf(origin); return Object.assign(Object.create(originProto), origin); }

在JS里子類利用Object.getPrototypeOf去調用父類方法,用來獲取對象的原型。用它可以模仿Java的super。

  • 將多個對象合並成一個對象

    • 多個對象合並到某個對象

      const merge =(target, ...sources) => Object.assign(target, ...sources);
    • 多個對象合並到一個新對象

      const merge = (...sources) => Object.assign({}, ...sources);
  • 為屬性指定默認值

const DEFAULTS = { logLevel: 0, outputFormat: 'html' }; function processContent(options) { let options = Object.assign({}, DEFAULTS, options); }

上面代碼中,DEFAULTS對象是默認值,options對象是用戶提供的參數。Object.assign方法將DEFAULTSoptions合並成一個新對象,如果兩者有同名屬性,則option的屬性值會覆蓋DEFAULTS的屬性值。

注: 由於存在深拷貝的問題,DEFAULTS對象和options對象的所有屬性的值,都只能是簡單類型,而不能指向另一個對象。否則,將導致DEFAULTS對象的該屬性不起作用。


6、屬性的可枚舉性
對象的每個屬性都有一個描述對象(Descriptor),用來控制該屬性的行為。Object.getOwnPropertyDescriptor方法可以獲取該屬性的描述對象。

var obj = { foo: 123 }; Object.getOwnPropertyDescriptor(obj, 'foo') // {value: 123, writable: true, enumerable: true, configurable: true} var o = Object.defineProperty({}, 'display', { enumerable: false, value: 'block' }) Object.getOwnPropertyDescriptor(o,'display') // {value: "block", writable: false, enumerable: false, configurable: false}

ES5有三個操作,如果enumerablefalse則對其不起作用:
- for...in 循環:只遍歷對象自身的和繼承的可枚舉的屬性。

  • Object.keys():返回對象自身的所有可枚舉的屬性的鍵名。
    - JSON.stringify():只串行化對象自身的可枚舉的屬性。

ES6新增了兩個操作,會忽略enumerable為false的屬性。

  • Object.assign():只拷貝對象自身的可枚舉的屬性。

  • Reflect.enumerate():返回所有for...in循環會遍歷的屬性。

這五個操作之中,只有for...inReflect.enumerate()會返回繼承的屬性。

實際上,引入enumerable的最初目的,就是讓某些屬性可以規避掉for...in操作。比如,對象原型的toString方法,以及數組的length屬性,就通過這種手段,不會被for...in遍歷到。

Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable // false Object.getOwnPropertyDescriptor([], 'length').enumerable // false

另外,ES6規定,所有Class的原型的方法都是不可枚舉的。

Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable // false

總的來說,操作中引入繼承的屬性會讓問題復雜化,大多數時候,我們只關心對象自身的屬性。所以,盡量不要用for...in循環,而用Object.keys()代替。


7、__proto__屬性Object.setPrototypeOf(),Object.getPrototypeOf()
(1)__proto__屬性(注:建議不要使用)
__proto__屬性(前后各兩個下划線),用來讀取或設置當前對象的prototype對象。

// es5的寫法 var obj = Object.create(someOtherObj); obj.method = function() { ... } // es6的寫法 var obj = { method: function() { ... } } obj.__proto__ = someOtherObj;

該屬性沒有寫入ES6的正文,而是寫入了附錄,原因是__proto__前后的雙引號,說明它本質上是一個內部屬性,而不是一個正式的對外的API,只是由於瀏覽器廣泛支持,才被加入了ES6。標准明確規定,只有瀏覽器必須部署這個屬性,其他運行環境不一定需要部署,而且新的代碼最好認為這個屬性是不存在的。因此,無論從語義的角度,還是從兼容性的角度,都不要使用這個屬性,而是使用下面的Object.setPrototypeOf()(寫操作)、Object.getPrototypeOf()(讀操作)、Object.create()(生成操作)代替。

在實現上,__proto__調用的是Object.prototype.__proto__,具體實現如下。

Object.defineProperty(Object.prototype, '__proto__', { get() { let _thisObj = Object(this); return Object.getPrototypeOf(_thisObj); }, set(proto) { if (this === undefined || this === null) { throw new TypeError(); } if (!isObject(this)) { return undefined; } if (!isObject(proto)) { return undefined; } let status = Reflect.setPrototypeOf(this, proto); if (! status) { throw new TypeError(); } }, }); function isObject(value) { return Object(value) === value; }

如果一個對象本身部署了__proto__屬性,則該屬性的值就是對象的原型。

Object.getPrototypeOf({ __proto__: null }) // null

(2)Object.setPrototypeOf()
Object.setPrototypeOf方法的作用與__proto__相同,用來設置一個對象的prototype對象。它是ES6正式推薦的設置原型對象的方法。

// 格式 Object.setPrototypeOf(object, prototype) // 用法 var o = Object.setPrototypeOf({}, null);

這個方法等同於下面的函數:

function (obj, proto) { obj.__proto__ = proto; return obj; }

例子如下:

var proto = {}; var obj = {x:1,y:2} Object.setPrototypeOf(obj,proto); proto.y = 3; proto.z = 5; proto.p = 9; obj // {x:1,y:2,__proto__:{p:9,y:3,z:5}} obj.y // 2 obj.z // 5 obj.p // 9

(3)Object.getPrototypeOf()
該方法與setPrototypeOf方法配套使用,用於讀取一個對象的prototype對象。
例子如下:

function Dog(){} var dog = new Dog(); Object.getPrototypeOf(dog) === Dog.prototype; // true Object.setPrototypeOf(dog,Object.prototype); Object.getPrototypeOf(dog) === Dog.prototype; // false

 

顯然Object.prototype與Dog.prototype是不同的,所以為false。


8、Object.observe(),Object.unobserve() 這兩個函數是ES7的一部分,不屬於ES6。

Object.observe方法用來監聽對象(以及數組)的變化。一旦監聽對象發生變化,就會觸發回調函數。

Object.observe方法接受兩個參數,第一個參數是監聽的對象,第二個函數是一個回調函數。

var user = {}; Object.observe(user, function(changes){ changes.forEach(function(change) { user.fullName = user.firstName+" "+user.lastName; }); }); user.firstName = 'Michael'; user.lastName = 'Jackson'; user.fullName // 'Michael Jackson' 我的執行結果:undefined

疑惑點:大神的結果是怎么得到的呢?而我的卻是undefined。

上面代碼中,Object.observer方法監聽user對象。一旦該對象發生變化,就自動生成fullName屬性。

利用這個方法可以做很多事情,比如自動更新DOM

var div = $("#foo"); Object.observe(user, function(changes){ changes.forEach(function(change) { var fullName = user.firstName+" "+user.lastName; div.text(fullName); }); });

上面代碼中,只要user對象發生變化,就會自動更新DOM。如果配合jQuerychange方法,就可以實現數據對象與DOM對象的雙向自動綁定。

回調函數的changes參數是一個數組,代表對象發生的變化。下面是一個更完整的例子。

var o = {}; function observer(changes){ changes.forEach(function(change) { console.log('發生變動的屬性:' + change.name); console.log('變動前的值:' + change.oldValue); console.log('變動后的值:' + change.object[change.name]); console.log('變動類型:' + change.type); }); } Object.observe(o, observer);

參照上面代碼,Object.observe方法指定的回調函數,接受一個數組(changes)作為參數。該數組的成員與對象的變化一一對應,也就是說,對象發生多少個變化,該數組就有多少個成員。每個成員是一個對象(change),它的name屬性表示發生變化源對象的屬性名,oldValue屬性表示發生變化前的值,object屬性指向變動后的源對象,type屬性表示變化的種類。基本上,change對象是下面的樣子。

var change = { object: {...}, type: 'update', name: 'p2', oldValue: 'Property 2' }

Object.observe方法目前共支持監聽六種變化。

  • add:添加屬性

  • update:屬性值的變化

  • delete:刪除屬性

  • setPrototype:設置原型

  • reconfigure:屬性的attributes對象發生變化

  • preventExtensions:對象被禁止擴展(當一個對象變得不可擴展時,也就不必再監聽了)

Object.observe方法還可以接受第三個參數,用來指定監聽的事件種類。
舉個例子,當發生delete事件時,才會調用回調函數。

Object.observe(o, observer, ['delete']);

Object.unobserve方法用來取消監聽。

Object.unobserve(o, observer);

9、對象的擴展運算符
目前,ES7有一個提案,將rest參數/擴展運算符(...)引入對象。Babel轉碼器已經支持這項功能。
(1)Rest參數
Rest參數用於從一個對象取值,相當於將所有可遍歷的、但尚未被讀取的屬性,分配到指定的對象上面。所有的鍵和它們的值,都會拷貝到新對象上面。

let {a,b,c} = {a:1,b:2,d:3,e:4} a //1 b //2 c //undefined let {a,b, ...c} = {a:1,b:2,c:3,d:4} a //1 b //2 c //{c:3,d:4} let obj = { a: { b: 9 } }; let { ...x } = obj; obj.a.b = 6; console.log(x.a.b); 

從上面的代碼可以看出Rest參數的拷貝是淺拷貝,即如果一個鍵的值是復合類型的值(對象,數組,函數)、那么Rest參數拷貝的是這個值的引用,而不是這個值的副本。

而且Rest參數不會拷貝繼承自原型對象的屬性。

let s1 = {a:1}; let s2 = {b:2}; Object.setPrototypeOf(s2,s1); let s3 = { ...s2}; s3 // {b:2} s2.a // 1

上面代碼中,對象o3是o2的復制,但是只復制了o2自身的屬性,沒有復制它的原型對象o1的屬性。

(2)擴展運算符
擴展運算符用於取出參數對象的所有可遍歷屬性,拷貝到當前對象之中。

let z = { a: 3, b: 4 }; let n = { ...z }; n // { a: 3, b: 4 } Object.assign({},z); // { a: 3, b: 4 }

從上面的例子可以看出此處Object.assign與擴展運算符( ...)的用法一樣,即:{ ...a} 等同於 Object.assign({}, a)

擴展運算符還可以合並兩個對象。

let a = { x:1 }; let b = { y:2 }; let ab = { ...a, ...b} ab //{ x:1,y:2 }

擴展運算符還可以自定義屬性,會在新對象之中,覆蓋掉原有參數。

let a = { x:4, z:8 }; let p = { ...a, x:1, y:2 }; p // { x: 1, z: 8, y: 2 } //等同於 let c = Object.assign({}, a, { x: 1, y: 2 }); c // { x: 1, z: 8, y: 2 }

如果把自定義屬性放在擴展運算符前面,就變成了設置新對象的默認屬性值。

let a = { x:4, z:8 }; let p = { x: 1, y: 2, ...a }; p // { x: 4, y: 2, z: 8 } //等同於 let q = Object.assign({ x:1, y:2 }, a); q // { x: 4, y: 2, z: 8 }

需要注意:擴展運算符的參數對象中,如果有取值函數get,這個函數是會執行的

// 並不會拋出錯誤,因為x屬性只是被定義,但沒執行 let aWithXGetter = { ...a, get x() { throws new Error('not thrown yet'); } }; // 會拋出錯誤,因為x屬性被執行了 let runtimeError = { ...a, ...{ get x() { throws new Error('thrown now'); } } };

如果擴展運算符的參數是null或undefined,這個兩個值會被忽略,不會報錯。

let emptyObject = { ...null, ...undefined }; emptyObject // 報錯,輸出{}

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com