Cesium原理篇:Property


之前主要是Entity的一個大概流程,本文主要介紹Cesium的屬性,比如defineProperties,Property(ConstantProperty,CallbackProperty,ConstantPositionProperty)以及createPropertyDescriptor的相關內容,研究一下Cesium對Object的屬性設計和使用方式。

       我們以Entity為例,看看它是如何封裝自己的屬性:

function Entity(options) { var id = options.id; if (!defined(id)) { id = createGuid(); } this._id = id; this._name = options.name; this._description = undefined; this._position = undefined; this._rectangle = undefined; } // Key 1:defineProperties defineProperties(Entity.prototype, { id : { get : function() { return this._id; } }, // Key 2:createRawPropertyDescriptor name : createRawPropertyDescriptor('name'), // Key 3:createPropertyDescriptor description : createPropertyDescriptor('description'), // Key 4:createPositionPropertyDescriptor position : createPositionPropertyDescriptor('position'), // Key 5:createPropertyTypeDescriptor rectangle : createPropertyTypeDescriptor('rectangle', RectangleGraphics) });

       如上,我截取了五個屬性:id,name,description,position,rectangle。每個屬性的創建方式可以說大同小異。下面看看用戶是如何使用Entity的這些屬性:

var obj = new Cesium.Entity(); var id = obj.id; obj.id = 123; // 不起作用 obj.name = "Hello World"
obj.description = "empty entity";

       如上說明亮點,Cesium中的成員變量和屬性方法之間就是一個下划線_,比如成員變量為_id,對應的屬性方法為id,這是Cesium中的一個規范。另外這些屬性也是提供了set&get方法來進行賦值和取值,所以可以直接通過=運算符。下面我們由易到難,分別介紹。

defineProperties

       Cesium.defineProperties本質上就是Object.defineProperties。我們不妨看一下它的定義:

Object.defineProperties(obj, props)

       結合之前Entity.id代碼,我們大概可以掌握通過Object.defineProperties封裝多個屬性的方式。通過下面這個范例,我們來強化一下理解:

function obj(){ this._id = undefined; } var o1 = new obj(); Object.defineProperties(obj.prototype, { // or o1 "property1": { //value: "Hello", //writable:true set: function(value){ this._id = value; }, get: function(){ return this._id; } }, "property2": { value: "Hello", writable: false, configurable:false  } // etc. etc. }); o1.property1 = 100; o1._id = 150; var nValue = o1.property1; o1.property2 = 200;// writable屬性為false,賦值無效

       如上的代碼中,我們創建了obj這個function,然后對obj.prototype這個對象創建了兩個屬性property1&property2,兩個屬性創建的方式略有不同,我是想通過這種不同來強調需要注意的地方,大家在編碼的時候還是要遵守一種風格,保持一致性。首先,props參數,value,set&get,writable,confugurable以及enumerable。value和set&get都是用來賦值的,兩種方式是有沖突的,二選一,兩中方式略有不同,個人理解如果采用set&et,property1就是_id的引用。其次,writable,confugurable以及enumerable三個屬性的默認值為false,具體的作用可以參考API,當然通過字面意思也不難理解。最后,defineProddrties的第一個參數為obj,所以可以使用原型或new一個obj出來,Cesium使用的是前者,用不用原型對應的原型鏈還是有一些差別的。下面是對比圖,前者是obj+set&get的方式,set和get也是obj實例的,后者是prototype+set&get,property1為引用,而set&get是prototype的實例。想一下,假如是prototype+value呢?這個其實是prototype的內容,我們就不過於跑題了。

compare

       這樣,通過defineProperties的屬性封裝,我們保證了每一個Entity中,其原型中,對每一個屬性都對應一個set和get方法的能力(需要與否則看實際情況),同時屬性值對應function中的實例(而不是原型中提供該屬性)。

Property

       在進入createProperty這類方法前,我們先了解一下CesiumProperty,然后在一步步的展開。首先,個人認為這些Property在本質上和defineProperties並無本質的區別,defineProperties直接封裝一些基本類型,如果是Object,這種accessor是引用的形式。因此,通過Property的封裝,將引用或復制的權利交給Object的設計者,同時提供一些特殊功能,滿足特殊的需求,比如SampledProperty提供插值功能,MaterialProperty則專門針對材質。這里我們主要通過兩個有代表性的,Entity中涉及到的Property,了解一下Cesium中Property的實現。

  • ConstantProperty
  • CallbackProperty

ConstantProperty

       如下是ConstantProperty的一個精簡的代碼片段,結合個人的注釋,應該能對其有一個大概的認識:

function ConstantProperty(value) { // 屬性值 this._value = undefined; // 是否提供克隆方法 this._hasClone = false; // 是否提供判斷相等的方法 this._hasEquals = false; // 提供屬性值變化的事件 this._definitionChanged = new Event(); this.setValue(value); } defineProperties(ConstantProperty.prototype, { // 屬性是否為常量,只讀  isConstant : { value : true  }, definitionChanged : { get : function() { return this._definitionChanged; } } }); // 獲取屬性值的方法,如果提供clone方法,如果沒有直接返回_value ConstantProperty.prototype.getValue = function(time, result) { return this._hasClone ? this._value.clone(result) : this._value; }; // 賦值方法,判斷是否提供了_hasClone&_hasEquals方法 // 屬性值變化會產生event事件 ConstantProperty.prototype.setValue = function(value){} // 判斷是否相等的方法 // 如果_hasEquals為true,則會調用該對象的equal方法來判斷 ConstantProperty.prototype.equals = function(other) {}

      可見,相比基本類型,ConstantProperty是一個高級版的屬性,首先在構造函數的參數中,value可以是一個基本類型,如此,該ConstantProperty會蛻化成defineProperties下直接封裝的屬性,當然,getValue,setValue以及equals方法本質上也是基本類型之間的賦值和對比,唯一不同的是提供了屬性值變化的事件。如果value是一個obj,則用戶可以提供clone以及equals方法,滿足自己的特殊需要,當然你也可以不提供,那該obj就是默認的賦值和對比操作符。

CallbackProperty

       下面是該屬性的一個精簡版,主要列出和ConstantProperity的不同處,方便對比。

function CallbackProperty(callback, isConstant) { this._callback = undefined; this._isConstant = undefined; this._definitionChanged = new Event(); this.setCallback(callback, isConstant); } defineProperties(CallbackProperty.prototype, { isConstant : { get : function() { return this._isConstant; } } }); CallbackProperty.prototype.getValue = function(time, result) { return this._callback(time, result); };

       首先,ConstantProperity就是傳進來一個value,返回該value,就是一個直來直去的思路。但有些時候事情並不是一成不變,比如在不同的狀態下,希望返回不同的屬性值。這種情況就需要提供回調方法,用戶自己提供一個callback函數,CallbackProperty則通過setValue來綁定該回調方法,通過getValue來調用該方法,至於返回的屬性值是什么,用戶通過該callback方法自己負責。這就是CallbackProperty的應用場景。

Property

       當然,除了ConstantProperity和CallbackProperty之外,Cesium還提供了CompositeProperty、SampledProperty、TimeIntervalCollectionProperty、MaterialProperty、PositionProperty、ReferenceProperty等。各個屬性內部實現不一,但都會提供setValue&getValue等方法。當然,為了統一標准,Cesium提供了Property類,提供了一套接口來供用戶調用:

Property.equals = function(left, right) Property.arrayEquals = function(left, right) Property.isConstant = function(property) Property.getValueOrUndefined = function(property, time, result) { return defined(property) ? property.getValue(time, result) : undefined; };

       這樣,用戶可以不必過於糾結具體的property,通過Property提供的方法獲取結果,比如上述代碼中給出的Property.getValueOrUndefined,內部調用getValue獲取屬性值。

createPropertyDescriptor

       當然,上述的這些Property還是處於Object類型,和obj._id還是同一個級別,和還需要經過封裝,提供property1這樣的形式,使其符合defineProperties規范,這樣才能提供accessor的能力。這就是createProperty函數的作用:

function createProperty(name, privateName, subscriptionName, configurable, createPropertyCallback) { var obj = { configurable : configurable, get : function() { return this[privateName]; }, set : function(value) { var oldValue = this[privateName]; value = createPropertyCallback(value); if (oldValue !== value) { this[privateName] = value; this._definitionChanged.raiseEvent(this, name, value, oldValue); } if (defined(value) && defined(value.definitionChanged)) { this[subscriptionName] = value.definitionChanged.addEventListener(function() { this._definitionChanged.raiseEvent(this, name, value, value); }, this); } } }; return obj; }

       如上就是createProperty方法的一個精簡版,粗看一下,創建了一個obj並返回,而且該對象有三個成員變量:configurable,set&get(function),和之前介紹的defineProperties.props參數如出一轍,現在可以肯定這個方法的作用就是把各種Property加工成符合defineProperties規范的屬性。當然這樣的判斷並不嚴謹,感覺的成分更大一些,我們結合Entity.description屬性的創建過程具體了解一下。

function Entity(options) { this._description = undefined; } defineProperties(Entity.prototype, { id : { get : function() { return this._id; } }, description : createPropertyDescriptor('description') });

       如上是Entity.description屬性,同時我還保留了id屬性,用來參考。如果把createPropertyDescriptor函數替換為createProperty返回的obj,大概如下:

function Entity(options) { this._description = undefined; } defineProperties(Entity.prototype, { id : { get : function() { return this._id; } }, description : { configurable : configurable, get : function() { // ……  }, set : function(value) { // ……  } }; });

       這樣就確定一定以及肯定了這個結論:createProperty就是返回一個符合defineProperties規范的屬性。刨根問底的話,還是有兩處疑問,首先,Entity.description調用的createPropertyDescriptor,又是如何傳遞到createProperty方法。其次,我們只是大概了了解了createProperty,內部具體做了什么還沒有關注。逐個擊破。

createPropertyDescriptor('description'); function createPropertyDescriptor(name, configurable, createPropertyCallback) { return createProperty(name, '_' + name.toString(), '_' + name.toString() + 'Subscription', defaultValue(configurable, false), defaultValue(createPropertyCallback, createConstantProperty)); } function createConstantProperty(value) { return new ConstantProperty(value); }

        可見,description是該屬性的accessor,對應Entity內部的屬性變量是'_' + name.toString(),也就是_description,這個是Cesium默認的屬性變量和屬性accessor接口之間的規范。configurable默認為false,createPropertyCallback如果為null,則采用ConstantProperty類型,對應的是createConstantProperty回調函數,暴露了我C++程序員的身份。這樣,createPropertyDescriptor就很自然的過渡到createProperty方法。接着,再看看createProperty內部的實現:

function Entity(options) { this._description = undefined; } defineProperties(Entity.prototype, { id : { get : function() { return this._id; } }, description : { configurable : false, get : function() { return this[_description]; }, set : function(value) { var oldValue = this[_description]; var value = new ConstantProperty(value); if (oldValue !== value) { this[_description] = value; this._definitionChanged.raiseEvent(this, "description', value, oldValue); } } }; });

       此時privatename就是_description,相比id屬性,description屬性本質上沒有差別,只是多了一些繁枝縟節,多了一些異常判斷,多了一些Event事件響應,當然還有對value的封裝,比如description時,將value封裝成了ConstantProperty類型,可以算的上是一個加強版的屬性accessor方式。

       有了這樣一套機制,不用寫過多的代碼,通過createPropertyDescriptor就可以很好的實現屬性accessor的封裝,代碼間接,同時考慮了很多異常,並會有事件來滿足用戶的各種情況,提高穩定性。同時createPropertyCallback參數也提供了更多的選擇,來滿足我們的各類需求,或者沒有需求,比如在Entity中,對屬性accessor用如下三種形式:

  • createRawPropertyDescriptor
  • createPropertyTypeDescriptor
  • createPositionPropertyDescriptor

       有了上面的理解,我們大概做一下說明,應該不難理解他們的差別和不同的作用,我們在自己的使用場景中可以因地制宜。

createRawPropertyDescriptor&createPositionPropertyDescriptor

function createRawProperty(value) { return value; } function createRawPropertyDescriptor(name, configurable) { return createPropertyDescriptor(name, configurable, createRawProperty); }
function createConstantPositionProperty(value) { return new ConstantPositionProperty(value); } function createPositionPropertyDescriptor(name) { return createPropertyDescriptor(name, undefined, createConstantPositionProperty); }

       這個沒啥可說的吧,RawProperty比ConstantProperty還簡單,人如其名,不加任何粉飾。同理createPositionPropertyDescriptor和createPropertyDescriptor也差不多,只是Property不是默認的ConstantProperty,而是ConstantPositionProperty。

createPropertyTypeDescriptors

rectangle : createPropertyTypeDescriptor('rectangle', RectangleGraphics) function createPropertyTypeDescriptor(name, Type) { return createPropertyDescriptor(name, undefined, function(value) { if (value instanceof Type) { return value; } return new Type(value); }); }

       這個稍有不同,通過閉包的方式,這時對應的createPropertyCallback是用戶自定義的,他甚至不是一個Property而是一個function類。我們可以這樣理解,createPropertyDescriptor只是負責封裝實現屬性accessor,createPropertyCallback原意是讓用戶創建各類Property對象,但在Entity里面擴展了一下,並沒用嚴格規定必須是Property類,而是可以用其他的function類,讓createPropertyDescriptor的使用場景更廣泛了。下面這種寫法是否看起來就親切很多,只是因為這類Graphics太多,所以Cesium用模版的思想抽象為Type參數,簡化編碼提高穩定,這也是值得我們學習的一點,Cesium在代碼質量上的標准還是很嚴格的。

function createRectangleGraphics(value) { return new RectangleGraphics(value); } function createPropertyTypeDescriptor(name) { return createPropertyDescriptor(name, undefined,createRectangleGraphics); }

CallbackProperty

       剛才我們已經比較全面的介紹了Property相關的各類情況,盡管我們提到了CallbackProperty,但並沒有涉及如何使用,有點遺忘的再回去看看CallbackProperty的實現,重點是getValue方法,我們下面通過一個例子,看看如何通過CallbackProperty實現自定義效果,范例鏈接

function createDescriptionCallback() { var description = "Hello,World, "; return function(time, result) { return description + time.toString(); }; } entity.description = new Cesium.CallbackProperty(createDescriptionCallback(), true); Property.getValueOrUndefined(entity.description,time);

       當然,得益於之前Entity.description通過createPropertyDescriptor提供了accessor接口,這里,等號運算符會調用set方法,只是將oldValue(ConstantProperty)更改為value(CallbackProperty)。此時,內部通過Property.getValueOrUndefined->CallbackProperty.getValue->this._callback,實現回調函數的調用。

總結

       如上是Cesium在Property模塊的一些實現原理和使用方式,我們簡單總結一下知識點如下,不知道大家有什么收獲,是否能夠從Cesium優雅的設計和封裝中學到什么奇技淫巧。

  • Property
  • ConstantProperty
  • CallbackProperty
  • defineProperties
  • createPropertyDescriptor
  • createRawPropertyDescriptor
  • createPositionPropertyDescriptor
  • createPropertyTypeDescriptor


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM