好長時間沒有寫博客了,昨天花了些時間又整理了下以前發佈過的《Ember.js之computed Property》文章,並建立了一個測試代碼庫,花了些時間,但願能使用測試代碼的方式,來演示如何使用Ember.js同時能避免升級Ember版本後,一些功能上的變化所帶來的隱含Bug。html
若是你們對Ember.js有興趣想一塊兒研究的話,歡迎你們一塊兒維護測試代碼庫 :)前端
本文主要是針對Ember.Array中的[]和@each,數組方法進行詳細分析。git
JS前端框架之Ember.js系列github
Ember中兩者都可用於計算屬性的綁定,使用十分方便,能夠像以下方式定義依賴關係:api
1 totalArea: Ember.computed('sizeArray.[]', function () { 2 return this.get('sizeArray').reduce(function (prev, cur) { 3 return prev + Ember.get(cur, 'height') * Ember.get(cur, 'width'); 4 }, 0); 5 }),
或者:數組
1 totalArea: Ember.computed(‘sizeArray.@each', function () { 2 return this.get('sizeArray').reduce(function (prev, cur) { 3 return prev + Ember.get(cur, 'height') * Ember.get(cur, 'width'); 4 }, 0); 5 }),
這樣就定義了一個依賴於數組sizeArray的計算屬性,創建一個綁定關係,只要sizeArray發生變化,totalArea就會被從新計算,使用十分簡單,只要在function前面羅列出依賴的屬性便可。雖然使用簡單,可是在使用上仍是有些細節的,下一節將用測試代碼來說解使用上的細節。前端框架
注:Ember計算屬性有多種寫法,這裏給出了Ember推薦寫法,更多具體細節請參考文章《Ember.js之computed Property-1. What is CP》app
(Ember v1.13.7) 框架
當CP屬性依賴於.property('columns.@each.isLoaded')時:源碼分析
- columns裏面任何一個元素的isLoaded屬性發生變化。
- columns增長元素或者刪除子元素。
- columns數組自己被從新賦值。
- 不能依賴@each.owner.@each.name 。
1 test('computed property depend on @each.height', function (assert) { 2 var Size = Ember.Object.extend({height: 0, width: 0}); 3 var rectangle = Ember.Object.extend({ 4 totalArea: Ember.computed('sizeArray.@each.height', function () { 5 return this.get('sizeArray').reduce(function (prev, cur) { 6 return prev + Ember.get(cur, 'height') * Ember.get(cur, 'width'); 7 }, 0); 8 }), 9 sizeArray: [ 10 Size.create({height: 10, width: 10}), 11 Size.create({height: 10, width: 10}) 12 ] 13 }).create(); 14 var sizeArray = rectangle.get('sizeArray'); 15 16 assert.equal(rectangle.get('totalArea'), 200, "the total area should be 200"); 17 18 sizeArray.pushObject(Size.create({height: 10, width: 10})); 19 assert.equal(rectangle.get('totalArea'), 300, "the total area should be 300 after added"); 20 21 sizeArray.removeAt(0); 22 assert.equal(rectangle.get('totalArea'), 200, "the total area should be 200 after removed"); 23 24 sizeArray[0].set('height', 20); 25 assert.equal(rectangle.get('totalArea'), 300, "the total area should be 300 after 'height' changed"); 26 27 sizeArray[0].set('width', 20); 28 assert.equal(rectangle.get('totalArea'), 300, "the total area should not be changed after 'width' changed"); 29 30 sizeArray.clear(); 31 assert.equal(rectangle.get('totalArea'), 0, "the total area should be 0 after reset rectangle.sizeArray"); 32 });
2. Array.@each特性
當CP屬性依賴於.property('columns.@each')時:
- columns增長或刪除元素。
- columns自身被替換或從新賦值。
1 test('computed property depend on @each', function (assert) { 2 var Size = Ember.Object.extend({height: 0, width: 0}); 3 var rectangle = Ember.Object.extend({ 4 totalArea: Ember.computed('sizeArray.@each', function () { 5 return this.get('sizeArray').reduce(function (prev, cur) { 6 return prev + Ember.get(cur, 'height') * Ember.get(cur, 'width'); 7 }, 0); 8 }), 9 sizeArray: [ 10 Size.create({height: 10, width: 10}), 11 Size.create({height: 10, width: 10}) 12 ] 13 }).create(); 14 var sizeArray = rectangle.get('sizeArray'); 15 16 assert.equal(rectangle.get('totalArea'), 200, "the total area should be 200"); 17 18 sizeArray.pushObject(Size.create({height: 10, width: 10})); 19 assert.equal(rectangle.get('totalArea'), 300, "the total area should be 300 after added"); 20 21 sizeArray.removeAt(0); 22 assert.equal(rectangle.get('totalArea'), 200, "the total area should be 200 after removed"); 23 24 sizeArray[0].set('height', 20); 25 assert.equal(rectangle.get('totalArea'), 200, "the total area should not be changed"); 26 27 sizeArray.clear(); 28 assert.equal(rectangle.get('totalArea'), 0, "the total area should be 0 after reset rectangle.sizeArray"); 29 });
3. Array.[]特性
當CP屬性依賴於.property('columns.[]')時:
- 與綁定.property(‘columns.@each') 行爲相同。
1 test('computed property depend on []', function (assert) { 2 var Size = Ember.Object.extend({height: 0, width: 0}); 3 var rectangle = Ember.Object.extend({ 4 totalArea: Ember.computed('sizeArray.[]', function () { 5 return this.get('sizeArray').reduce(function (prev, cur) { 6 return prev + Ember.get(cur, 'height') * Ember.get(cur, 'width'); 7 }, 0); 8 }), 9 sizeArray: [ 10 Size.create({height: 10, width: 10}), 11 Size.create({height: 10, width: 10}) 12 ] 13 }).create(); 14 var sizeArray = rectangle.get('sizeArray'); 15 16 assert.equal(rectangle.get('totalArea'), 200, "the total area should be 200"); 17 18 sizeArray.pushObject(Size.create({height: 10, width: 10})); 19 assert.equal(rectangle.get('totalArea'), 300, "the total area should be 300 after added"); 20 21 sizeArray.removeAt(0); 22 assert.equal(rectangle.get('totalArea'), 200, "the total area should be 200 after removed"); 23 24 sizeArray[0].set('height', 20); 25 assert.equal(rectangle.get('totalArea'), 200, "the total area should not be changed"); 26 27 sizeArray.clear(); 28 assert.equal(rectangle.get('totalArea'), 0, "the total area should be 0 after reset rectangle.sizeArray"); 29 });
(Ember v2.0.0)
2015-08-17 除建議用[]代替@each,暫未發現其餘變化。
[參考代碼](https://github.com/emberjs/ember.js/blob/v2.0.0/packages/ember-metal/lib/computed.js#L224)
注: 更多關於計算屬性問題,請參考文章《Ember.js之computed Property-3.CP重要原則》
Array.@each返回的是一個特殊類型,這個類型便於實現綁定。
1 '@each': computed(function() { 2 if (!this.__each) { 3 // ES6TODO: GRRRRR 4 var EachProxy = requireModule('ember-runtime/system/each_proxy')['EachProxy']; 5 6 this.__each = new EachProxy(this); 7 } 8 9 return this.__each; 10 })
[參考這裏](https://github.com/emberjs/ember.js/blob/stable-1-13/packages/ember-runtime/lib/mixins/array.js#L516)
Array.[]繼承自Ember.Enumerable.[]而且返回this。
1 '[]': computed({ 2 get(key) { 3 return this; 4 }, 5 set(key, value) { 6 this.replace(0, get(this, 'length'), value); 7 return this; 8 } 9 }),
[參考這裏](https://github.com/emberjs/ember.js/blob/stable-1-13/packages/ember-runtime/lib/mixins/array.js#L164)
兩者之間惟一的區別是Array.[]返回的是一對象自身(普通對象組成的數組), 而Array.@each返回的是EachProxy對象, 針對普通對象組成的數組而言,僅僅能檢測到數組長度的變化和對象自己的變化,對數組內容發生變化則不得而知,而EachProxy對每個元素增長observerListener監聽器,當數組內容發生變化時,通知數組發生變動,實現了數組元素這一級別的監聽。
1 var EachProxy = EmberObject.extend({ 2 3 init(content) { 4 this._super(...arguments); 5 this._content = content; 6 content.addArrayObserver(this); 7 8 // in case someone is already observing some keys make sure they are 9 // added 10 forEach(watchedEvents(this), function(eventName) { 11 this.didAddListener(eventName); 12 }, this); 13 }, 14 15 ...
[Ember-@each](http://emberjs.com/api/classes/Ember.Array.html#property__each)
[Emer-EachProxy](https://github.com/emberjs/ember.js/blob/stable-1-13/packages/ember-runtime/lib/system/each_proxy.js)
[Ember-study](https://github.com/Cuiyansong/ember-table-learnning)