Merge pull request #11188 from michalzielanski/late-obj-init

Late object initialization
This commit is contained in:
Andreas Hocevar
2020-06-24 10:21:43 +02:00
committed by GitHub
9 changed files with 143 additions and 93 deletions
+3 -1
View File
@@ -128,7 +128,9 @@ class Feature extends BaseObject {
* @api * @api
*/ */
clone() { clone() {
const clone = new Feature(this.getProperties()); const clone = new Feature(
this.hasProperties() ? this.getProperties() : null
);
clone.setGeometryName(this.getGeometryName()); clone.setGeometryName(this.getGeometryName());
const geometry = this.getGeometry(); const geometry = this.getGeometry();
if (geometry) { if (geometry) {
+21 -10
View File
@@ -4,7 +4,7 @@
import Event from './events/Event.js'; import Event from './events/Event.js';
import ObjectEventType from './ObjectEventType.js'; import ObjectEventType from './ObjectEventType.js';
import Observable from './Observable.js'; import Observable from './Observable.js';
import {assign} from './obj.js'; import {assign, isEmpty} from './obj.js';
import {getUid} from './util.js'; import {getUid} from './util.js';
/** /**
@@ -95,9 +95,9 @@ class BaseObject extends Observable {
/** /**
* @private * @private
* @type {!Object<string, *>} * @type {Object<string, *>}
*/ */
this.values_ = {}; this.values_ = null;
if (opt_values !== undefined) { if (opt_values !== undefined) {
this.setProperties(opt_values); this.setProperties(opt_values);
@@ -112,7 +112,7 @@ class BaseObject extends Observable {
*/ */
get(key) { get(key) {
let value; let value;
if (this.values_.hasOwnProperty(key)) { if (this.values_ && this.values_.hasOwnProperty(key)) {
value = this.values_[key]; value = this.values_[key];
} }
return value; return value;
@@ -124,7 +124,7 @@ class BaseObject extends Observable {
* @api * @api
*/ */
getKeys() { getKeys() {
return Object.keys(this.values_); return (this.values_ && Object.keys(this.values_)) || [];
} }
/** /**
@@ -133,7 +133,14 @@ class BaseObject extends Observable {
* @api * @api
*/ */
getProperties() { getProperties() {
return assign({}, this.values_); return (this.values_ && assign({}, this.values_)) || {};
}
/**
* @return {boolean} The object has properties.
*/
hasProperties() {
return !!this.values_;
} }
/** /**
@@ -156,11 +163,12 @@ class BaseObject extends Observable {
* @api * @api
*/ */
set(key, value, opt_silent) { set(key, value, opt_silent) {
const values = this.values_ || (this.values_ = {});
if (opt_silent) { if (opt_silent) {
this.values_[key] = value; values[key] = value;
} else { } else {
const oldValue = this.values_[key]; const oldValue = values[key];
this.values_[key] = value; values[key] = value;
if (oldValue !== value) { if (oldValue !== value) {
this.notify(key, oldValue); this.notify(key, oldValue);
} }
@@ -187,9 +195,12 @@ class BaseObject extends Observable {
* @api * @api
*/ */
unset(key, opt_silent) { unset(key, opt_silent) {
if (key in this.values_) { if (this.values_ && key in this.values_) {
const oldValue = this.values_[key]; const oldValue = this.values_[key];
delete this.values_[key]; delete this.values_[key];
if (isEmpty(this.values_)) {
this.values_ = null;
}
if (!opt_silent) { if (!opt_silent) {
this.notify(key, oldValue); this.notify(key, oldValue);
} }
+32 -29
View File
@@ -40,21 +40,21 @@ class Target extends Disposable {
/** /**
* @private * @private
* @type {!Object<string, number>} * @type {Object<string, number>}
*/ */
this.pendingRemovals_ = {}; this.pendingRemovals_ = null;
/** /**
* @private * @private
* @type {!Object<string, number>} * @type {Object<string, number>}
*/ */
this.dispatching_ = {}; this.dispatching_ = null;
/** /**
* @private * @private
* @type {!Object<string, Array<import("../events.js").Listener>>} * @type {Object<string, Array<import("../events.js").Listener>>}
*/ */
this.listeners_ = {}; this.listeners_ = null;
} }
/** /**
@@ -65,13 +65,10 @@ class Target extends Disposable {
if (!type || !listener) { if (!type || !listener) {
return; return;
} }
let listeners = this.listeners_[type]; const listeners = this.listeners_ || (this.listeners_ = {});
if (!listeners) { const listenersForType = listeners[type] || (listeners[type] = []);
listeners = []; if (listenersForType.indexOf(listener) === -1) {
this.listeners_[type] = listeners; listenersForType.push(listener);
}
if (listeners.indexOf(listener) === -1) {
listeners.push(listener);
} }
} }
@@ -92,14 +89,17 @@ class Target extends Disposable {
if (!evt.target) { if (!evt.target) {
evt.target = this.eventTarget_ || this; evt.target = this.eventTarget_ || this;
} }
const listeners = this.listeners_[type]; const listeners = this.listeners_ && this.listeners_[type];
let propagate; let propagate;
if (listeners) { if (listeners) {
if (!(type in this.dispatching_)) { const dispatching = this.dispatching_ || (this.dispatching_ = {});
this.dispatching_[type] = 0; const pendingRemovals =
this.pendingRemovals_[type] = 0; this.pendingRemovals_ || (this.pendingRemovals_ = {});
if (!(type in dispatching)) {
dispatching[type] = 0;
pendingRemovals[type] = 0;
} }
++this.dispatching_[type]; ++dispatching[type];
for (let i = 0, ii = listeners.length; i < ii; ++i) { for (let i = 0, ii = listeners.length; i < ii; ++i) {
if ('handleEvent' in listeners[i]) { if ('handleEvent' in listeners[i]) {
propagate = /** @type {import("../events.js").ListenerObject} */ (listeners[ propagate = /** @type {import("../events.js").ListenerObject} */ (listeners[
@@ -115,14 +115,14 @@ class Target extends Disposable {
break; break;
} }
} }
--this.dispatching_[type]; --dispatching[type];
if (this.dispatching_[type] === 0) { if (dispatching[type] === 0) {
let pendingRemovals = this.pendingRemovals_[type]; let pr = pendingRemovals[type];
delete this.pendingRemovals_[type]; delete pendingRemovals[type];
while (pendingRemovals--) { while (pr--) {
this.removeEventListener(type, VOID); this.removeEventListener(type, VOID);
} }
delete this.dispatching_[type]; delete dispatching[type];
} }
return propagate; return propagate;
} }
@@ -132,7 +132,7 @@ class Target extends Disposable {
* Clean up. * Clean up.
*/ */
disposeInternal() { disposeInternal() {
clear(this.listeners_); this.listeners_ && clear(this.listeners_);
} }
/** /**
@@ -140,10 +140,10 @@ class Target extends Disposable {
* order that they will be called in. * order that they will be called in.
* *
* @param {string} type Type. * @param {string} type Type.
* @return {Array<import("../events.js").Listener>} Listeners. * @return {Array<import("../events.js").Listener>|undefined} Listeners.
*/ */
getListeners(type) { getListeners(type) {
return this.listeners_[type]; return (this.listeners_ && this.listeners_[type]) || undefined;
} }
/** /**
@@ -152,6 +152,9 @@ class Target extends Disposable {
* @return {boolean} Has listeners. * @return {boolean} Has listeners.
*/ */
hasListener(opt_type) { hasListener(opt_type) {
if (!this.listeners_) {
return false;
}
return opt_type return opt_type
? opt_type in this.listeners_ ? opt_type in this.listeners_
: Object.keys(this.listeners_).length > 0; : Object.keys(this.listeners_).length > 0;
@@ -162,11 +165,11 @@ class Target extends Disposable {
* @param {import("../events.js").Listener} listener Listener. * @param {import("../events.js").Listener} listener Listener.
*/ */
removeEventListener(type, listener) { removeEventListener(type, listener) {
const listeners = this.listeners_[type]; const listeners = this.listeners_ && this.listeners_[type];
if (listeners) { if (listeners) {
const index = listeners.indexOf(listener); const index = listeners.indexOf(listener);
if (index !== -1) { if (index !== -1) {
if (type in this.pendingRemovals_) { if (this.pendingRemovals_ && type in this.pendingRemovals_) {
// make listener a no-op, and remove later in #dispatchEvent() // make listener a no-op, and remove later in #dispatchEvent()
listeners[index] = VOID; listeners[index] = VOID;
++this.pendingRemovals_[type]; ++this.pendingRemovals_[type];
+6 -2
View File
@@ -199,6 +199,11 @@ class EsriJSON extends JSONFeature {
writeFeatureObject(feature, opt_options) { writeFeatureObject(feature, opt_options) {
opt_options = this.adaptOptions(opt_options); opt_options = this.adaptOptions(opt_options);
const object = {}; const object = {};
if (!feature.hasProperties()) {
object['attributes'] = {};
return object;
}
const properties = feature.getProperties();
const geometry = feature.getGeometry(); const geometry = feature.getGeometry();
if (geometry) { if (geometry) {
object['geometry'] = writeGeometry(geometry, opt_options); object['geometry'] = writeGeometry(geometry, opt_options);
@@ -214,9 +219,8 @@ class EsriJSON extends JSONFeature {
), ),
}); });
} }
delete properties[feature.getGeometryName()];
} }
const properties = feature.getProperties();
delete properties[feature.getGeometryName()];
if (!isEmpty(properties)) { if (!isEmpty(properties)) {
object['attributes'] = properties; object['attributes'] = properties;
} else { } else {
+24 -21
View File
@@ -212,29 +212,32 @@ class GML2 extends GMLBase {
context.serializers = {}; context.serializers = {};
context.serializers[featureNS] = {}; context.serializers[featureNS] = {};
} }
const properties = feature.getProperties();
const keys = []; const keys = [];
const values = []; const values = [];
for (const key in properties) { if (feature.hasProperties()) {
const value = properties[key]; const properties = feature.getProperties();
if (value !== null) { for (const key in properties) {
keys.push(key); const value = properties[key];
values.push(value); if (value !== null) {
if ( keys.push(key);
key == geometryName || values.push(value);
typeof (/** @type {?} */ (value).getSimplifiedGeometry) === 'function' if (
) { key == geometryName ||
if (!(key in context.serializers[featureNS])) { typeof (/** @type {?} */ (value).getSimplifiedGeometry) ===
context.serializers[featureNS][key] = makeChildAppender( 'function'
this.writeGeometryElement, ) {
this if (!(key in context.serializers[featureNS])) {
); context.serializers[featureNS][key] = makeChildAppender(
} this.writeGeometryElement,
} else { this
if (!(key in context.serializers[featureNS])) { );
context.serializers[featureNS][key] = makeChildAppender( }
writeStringTextNode } else {
); if (!(key in context.serializers[featureNS])) {
context.serializers[featureNS][key] = makeChildAppender(
writeStringTextNode
);
}
} }
} }
} }
+24 -21
View File
@@ -843,29 +843,32 @@ class GML3 extends GMLBase {
context.serializers = {}; context.serializers = {};
context.serializers[featureNS] = {}; context.serializers[featureNS] = {};
} }
const properties = feature.getProperties();
const keys = []; const keys = [];
const values = []; const values = [];
for (const key in properties) { if (feature.hasProperties()) {
const value = properties[key]; const properties = feature.getProperties();
if (value !== null) { for (const key in properties) {
keys.push(key); const value = properties[key];
values.push(value); if (value !== null) {
if ( keys.push(key);
key == geometryName || values.push(value);
typeof (/** @type {?} */ (value).getSimplifiedGeometry) === 'function' if (
) { key == geometryName ||
if (!(key in context.serializers[featureNS])) { typeof (/** @type {?} */ (value).getSimplifiedGeometry) ===
context.serializers[featureNS][key] = makeChildAppender( 'function'
this.writeGeometryElement, ) {
this if (!(key in context.serializers[featureNS])) {
); context.serializers[featureNS][key] = makeChildAppender(
} this.writeGeometryElement,
} else { this
if (!(key in context.serializers[featureNS])) { );
context.serializers[featureNS][key] = makeChildAppender( }
writeStringTextNode } else {
); if (!(key in context.serializers[featureNS])) {
context.serializers[featureNS][key] = makeChildAppender(
writeStringTextNode
);
}
} }
} }
} }
+10 -4
View File
@@ -207,16 +207,22 @@ class GeoJSON extends JSONFeature {
object.id = id; object.id = id;
} }
const geometry = feature.getGeometry(); if (!feature.hasProperties()) {
if (geometry) { return object;
object.geometry = writeGeometry(geometry, opt_options);
} }
const properties = feature.getProperties(); const properties = feature.getProperties();
delete properties[feature.getGeometryName()]; const geometry = feature.getGeometry();
if (geometry) {
object.geometry = writeGeometry(geometry, opt_options);
delete properties[feature.getGeometryName()];
}
if (!isEmpty(properties)) { if (!isEmpty(properties)) {
object.properties = properties; object.properties = properties;
} }
return object; return object;
} }
+7 -5
View File
@@ -24,9 +24,6 @@ describe('ol.events.EventTarget', function () {
expect(eventTarget).to.be.a(EventTarget); expect(eventTarget).to.be.a(EventTarget);
expect(eventTarget).to.be.a(Disposable); expect(eventTarget).to.be.a(Disposable);
}); });
it('creates an empty listeners_ object', function () {
expect(Object.keys(eventTarget.listeners_)).to.have.length(0);
});
it('accepts a default target', function (done) { it('accepts a default target', function (done) {
const defaultTarget = {}; const defaultTarget = {};
const target = new EventTarget(defaultTarget); const target = new EventTarget(defaultTarget);
@@ -36,16 +33,21 @@ describe('ol.events.EventTarget', function () {
}); });
target.dispatchEvent('my-event'); target.dispatchEvent('my-event');
}); });
it('does not initialize objects in advance', function () {
expect(eventTarget.pendingRemovals_).to.be(null);
expect(eventTarget.dispatching_).to.be(null);
expect(eventTarget.listeners_).to.be(null);
});
}); });
describe('#hasListener', function () { describe('#hasListener', function () {
it('reports any listeners when called without argument', function () { it('reports any listeners when called without argument', function () {
expect(eventTarget.hasListener()).to.be(false); expect(eventTarget.hasListener()).to.be(false);
eventTarget.listeners_['foo'] = [function () {}]; eventTarget.addEventListener('foo', function () {});
expect(eventTarget.hasListener()).to.be(true); expect(eventTarget.hasListener()).to.be(true);
}); });
it('reports listeners for the type passed as argument', function () { it('reports listeners for the type passed as argument', function () {
eventTarget.listeners_['foo'] = [function () {}]; eventTarget.addEventListener('foo', function () {});
expect(eventTarget.hasListener('foo')).to.be(true); expect(eventTarget.hasListener('foo')).to.be(true);
expect(eventTarget.hasListener('bar')).to.be(false); expect(eventTarget.hasListener('bar')).to.be(false);
}); });
+16
View File
@@ -101,6 +101,22 @@ describe('ol.Object', function () {
}); });
}); });
describe('hasProperties', function () {
it('has no properties after creation', function () {
expect(o.hasProperties()).to.eql(false);
});
it('has properties after set', function () {
o.set('foo', 1);
expect(o.hasProperties()).to.eql(true);
});
it('has no properties after unset all', function () {
o.unset('foo');
expect(o.hasProperties()).to.eql(false);
});
});
describe('notify', function () { describe('notify', function () {
let listener1, listener2; let listener1, listener2;