Просмотр исходного кода

optimizations for moment. dont subclass anymore

Adam Shaw 11 лет назад
Родитель
Сommit
0faea05526
3 измененных файлов с 152 добавлено и 130 удалено
  1. 6 6
      src/date-formatting.js
  2. 146 113
      src/moment-ext.js
  3. 0 11
      src/util.js

+ 6 - 6
src/date-formatting.js

@@ -4,8 +4,8 @@
 
 
 // call this if you want Moment's original format method to be used
-function momentFormat(mom, formatStr) {
-	return moment.fn.format.call(mom, formatStr);
+function oldMomentFormat(mom, formatStr) {
+	return oldMomentProto.format.call(mom, formatStr); // oldMomentProto defined in moment-ext.js
 }
 
 
@@ -31,10 +31,10 @@ function formatDateWithChunks(date, chunks) {
 // addition formatting tokens we want recognized
 var tokenOverrides = {
 	t: function(date) { // "a" or "p"
-		return momentFormat(date, 'a').charAt(0);
+		return oldMomentFormat(date, 'a').charAt(0);
 	},
 	T: function(date) { // "A" or "P"
-		return momentFormat(date, 'A').charAt(0);
+		return oldMomentFormat(date, 'A').charAt(0);
 	}
 };
 
@@ -50,7 +50,7 @@ function formatDateWithChunk(date, chunk) {
 		if (tokenOverrides[token]) {
 			return tokenOverrides[token](date); // use our custom token
 		}
-		return momentFormat(date, token);
+		return oldMomentFormat(date, token);
 	}
 	else if (chunk.maybe) { // a grouping of other chunks that must be non-zero
 		maybeStr = formatDateWithChunks(date, chunk.maybe);
@@ -178,7 +178,7 @@ function formatSimilarChunk(date1, date2, chunk) {
 		unit = similarUnitMap[token.charAt(0)];
 		// are the dates the same for this unit of measurement?
 		if (unit && date1.isSame(date2, unit)) {
-			return momentFormat(date1, token); // would be the same if we used `date2`
+			return oldMomentFormat(date1, token); // would be the same if we used `date2`
 			// BTW, don't support custom tokens
 		}
 	}

+ 146 - 113
src/moment-ext.js

@@ -2,13 +2,18 @@
 var ambigDateOfMonthRegex = /^\s*\d{4}-\d\d$/;
 var ambigTimeOrZoneRegex =
 	/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/;
+var newMomentProto = moment.fn; // where we will attach our new methods
+var oldMomentProto = $.extend({}, newMomentProto); // copy of original moment methods
+var allowValueOptimization;
+var setUTCValues; // function defined below
+var setLocalValues; // function defined below
 
 
 // Creating
 // -------------------------------------------------------------------------------------------------
 
 // Creates a new moment, similar to the vanilla moment(...) constructor, but with
-// extra features (ambiguous time, enhanced formatting). When gived an existing moment,
+// extra features (ambiguous time, enhanced formatting). When given an existing moment,
 // it will function as a clone (and retain the zone of the moment). Anything else will
 // result in a moment in the local zone.
 fc.moment = function() {
@@ -19,7 +24,8 @@ fc.moment = function() {
 fc.moment.utc = function() {
 	var mom = makeMoment(arguments, true);
 
-	// Force it into UTC because makeMoment doesn't guarantee it.
+	// Force it into UTC because makeMoment doesn't guarantee it
+	// (if given a pre-existing moment for example)
 	if (mom.hasTime()) { // don't give ambiguously-timed moments a UTC zone
 		mom.utc();
 	}
@@ -33,8 +39,8 @@ fc.moment.parseZone = function() {
 	return makeMoment(arguments, true, true);
 };
 
-// Builds an FCMoment from args. When given an existing moment, it clones. When given a native
-// Date, or called with no arguments (the current time), the resulting moment will be local.
+// Builds an enhanced moment from args. When given an existing moment, it clones. When given a
+// native Date, or called with no arguments (the current time), the resulting moment will be local.
 // Anything else needs to be "parsed" (a string or an array), and will be affected by:
 //    parseAsUTC - if there is no zone information, should we parse the input in UTC?
 //    parseZone - if there is zone information, should we force the zone of the moment?
@@ -44,21 +50,14 @@ function makeMoment(args, parseAsUTC, parseZone) {
 	var isAmbigTime;
 	var isAmbigZone;
 	var ambigMatch;
-	var output; // an object with fields for the new FCMoment object
+	var mom;
 
 	if (moment.isMoment(input)) {
-		output = moment.apply(null, args); // clone it
-
-		// the ambig properties have not been preserved in the clone, so reassign them
-		if (input._ambigTime) {
-			output._ambigTime = true;
-		}
-		if (input._ambigZone) {
-			output._ambigZone = true;
-		}
+		mom = moment.apply(null, args); // clone it
+		transferAmbigs(input, mom); // the ambig flags weren't transfered with the clone
 	}
 	else if (isNativeDate(input) || input === undefined) {
-		output = moment.apply(null, args); // will be local
+		mom = moment.apply(null, args); // will be local
 	}
 	else { // "parsing" is required
 		isAmbigTime = false;
@@ -84,44 +83,44 @@ function makeMoment(args, parseAsUTC, parseZone) {
 		// otherwise, probably a string with a format
 
 		if (parseAsUTC) {
-			output = moment.utc.apply(moment, args);
+			mom = moment.utc.apply(moment, args);
 		}
 		else {
-			output = moment.apply(null, args);
+			mom = moment.apply(null, args);
 		}
 
 		if (isAmbigTime) {
-			output._ambigTime = true;
-			output._ambigZone = true; // ambiguous time always means ambiguous zone
+			mom._ambigTime = true;
+			mom._ambigZone = true; // ambiguous time always means ambiguous zone
 		}
 		else if (parseZone) { // let's record the inputted zone somehow
 			if (isAmbigZone) {
-				output._ambigZone = true;
+				mom._ambigZone = true;
 			}
 			else if (isSingleString) {
-				output.zone(input); // if not a valid zone, will assign UTC
+				mom.zone(input); // if not a valid zone, will assign UTC
 			}
 		}
 	}
 
-	return new FCMoment(output);
-}
+	mom._fullCalendar = true; // flag for extended functionality
 
-// Our subclass of Moment.
-// Accepts an object with the internal Moment properties that should be copied over to
-// `this` object (most likely another Moment object). The values in this data must not
-// be referenced by anything else (two moments sharing a Date object for example).
-function FCMoment(internalData) {
-	extend(this, internalData);
+	return mom;
 }
 
-// Chain the prototype to Moment's
-FCMoment.prototype = createObject(moment.fn);
 
-// We need this because Moment's implementation won't create an FCMoment,
-// nor will it copy over the ambig flags.
-FCMoment.prototype.clone = function() {
-	return makeMoment([ this ]);
+// A clone method that works with the flags related to our enhanced functionality.
+// In the future, use moment.momentProperties
+newMomentProto.clone = function() {
+	var mom = oldMomentProto.clone.apply(this, arguments);
+
+	// these flags weren't transfered with the clone
+	transferAmbigs(this, mom);
+	if (this._fullCalendar) {
+		mom._fullCalendar = true;
+	}
+
+	return mom;
 };
 
 
@@ -135,7 +134,7 @@ FCMoment.prototype.clone = function() {
 // SETTER
 // You can supply a Duration, a Moment, or a Duration-like argument.
 // When setting the time, and the moment has an ambiguous time, it then becomes unambiguous.
-FCMoment.prototype.time = function(time) {
+newMomentProto.time = function(time) {
 	if (time == null) { // getter
 		return moment.duration({
 			hours: this.hours(),
@@ -146,7 +145,7 @@ FCMoment.prototype.time = function(time) {
 	}
 	else { // setter
 
-		delete this._ambigTime; // mark that the moment now has a time
+		this._ambigTime = false; // mark that the moment now has a time
 
 		if (!moment.isDuration(time) && !moment.isMoment(time)) {
 			time = moment.duration(time);
@@ -171,22 +170,14 @@ FCMoment.prototype.time = function(time) {
 // Converts the moment to UTC, stripping out its time-of-day and timezone offset,
 // but preserving its YMD. A moment with a stripped time will display no time
 // nor timezone offset when .format() is called.
-FCMoment.prototype.stripTime = function() {
+newMomentProto.stripTime = function() {
 	var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
 
-	// set the internal UTC flag
-	moment.fn.utc.call(this); // call the original method, because we don't want to affect _ambigZone
+	this.utc(); // set the internal UTC flag (will clear the ambig flags)
+	setUTCValues(this, a.slice(0, 3)); // set the year/month/date. time will be zero
 
-	this.year(a[0]) // TODO: find a way to do this in one shot
-		.month(a[1])
-		.date(a[2])
-		.hours(0)
-		.minutes(0)
-		.seconds(0)
-		.milliseconds(0);
-
-	// Mark the time as ambiguous. This needs to happen after the .utc() call, which calls .zone(), which
-	// clears all ambig flags. Same concept with the .year/month/date calls in the case of moment-timezone.
+	// Mark the time as ambiguous. This needs to happen after the .utc() call, which calls .zone(),
+	// which clears all ambig flags. Same with setUTCValues with moment-timezone.
 	this._ambigTime = true;
 	this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset
 
@@ -194,7 +185,7 @@ FCMoment.prototype.stripTime = function() {
 };
 
 // Returns if the moment has a non-ambiguous time (boolean)
-FCMoment.prototype.hasTime = function() {
+newMomentProto.hasTime = function() {
 	return !this._ambigTime;
 };
 
@@ -205,111 +196,84 @@ FCMoment.prototype.hasTime = function() {
 // Converts the moment to UTC, stripping out its timezone offset, but preserving its
 // YMD and time-of-day. A moment with a stripped timezone offset will display no
 // timezone offset when .format() is called.
-FCMoment.prototype.stripZone = function() {
+newMomentProto.stripZone = function() {
 	var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
 	var wasAmbigTime = this._ambigTime;
 
-	moment.fn.utc.call(this); // set the internal UTC flag
-
-	this.year(a[0]) // TODO: find a way to do this in one shot
-		.month(a[1])
-		.date(a[2])
-		.hours(a[3])
-		.minutes(a[4])
-		.seconds(a[5])
-		.milliseconds(a[6]);
+	this.utc(); // set the internal UTC flag (will clear the ambig flags)
+	setUTCValues(this, a); // will set the year/month/date/hours/minutes/seconds/ms
 
 	if (wasAmbigTime) {
 		// the above call to .utc()/.zone() unfortunately clears the ambig flags, so reassign
 		this._ambigTime = true;
 	}
 
-	// Mark the zone as ambiguous. This needs to happen after the .utc() call, which calls .zone(), which
-	// clears all ambig flags. Same concept with the .year/month/date calls in the case of moment-timezone.
+	// Mark the zone as ambiguous. This needs to happen after the .utc() call, which calls .zone(),
+	// which clears all ambig flags. Same with setUTCValues with moment-timezone.
 	this._ambigZone = true;
 
 	return this; // for chaining
 };
 
 // Returns of the moment has a non-ambiguous timezone offset (boolean)
-FCMoment.prototype.hasZone = function() {
+newMomentProto.hasZone = function() {
 	return !this._ambigZone;
 };
 
-// this method implicitly marks a zone
-FCMoment.prototype.zone = function(tzo) {
+// this method implicitly marks a zone (will get called upon .utc() and .local())
+newMomentProto.zone = function(tzo) {
 
-	if (tzo != null) {
-		// FYI, the delete statements need to be before the .zone() call or else chaos ensues
-		// for reasons I don't understand. 
-		delete this._ambigTime;
-		delete this._ambigZone;
+	if (tzo != null) { // setter
+		// these assignments needs to happen before the original zone method is called.
+		// I forget why, something to do with a browser crash.
+		this._ambigTime = false;
+		this._ambigZone = false;
 	}
 
-	return moment.fn.zone.apply(this, arguments);
+	return oldMomentProto.zone.apply(this, arguments);
 };
 
 // this method implicitly marks a zone
-FCMoment.prototype.local = function() {
-	var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
+newMomentProto.local = function() {
+	var a = this.toArray(); // year,month,date,hours,minutes,seconds,ms as an array
 	var wasAmbigZone = this._ambigZone;
 
-	// will happen anyway via .local()/.zone(), but don't want to rely on internal implementation
-	delete this._ambigTime;
-	delete this._ambigZone;
-
-	moment.fn.local.apply(this, arguments);
+	oldMomentProto.local.apply(this, arguments); // will clear ambig flags
 
 	if (wasAmbigZone) {
 		// If the moment was ambiguously zoned, the date fields were stored as UTC.
 		// We want to preserve these, but in local time.
-		this.year(a[0]) // TODO: find a way to do this in one shot
-			.month(a[1])
-			.date(a[2])
-			.hours(a[3])
-			.minutes(a[4])
-			.seconds(a[5])
-			.milliseconds(a[6]);
+		setLocalValues(this, a);
 	}
 
 	return this; // for chaining
 };
 
-// this method implicitly marks a zone
-FCMoment.prototype.utc = function() {
-
-	// will happen anyway via .local()/.zone(), but don't want to rely on internal implementation
-	delete this._ambigTime;
-	delete this._ambigZone;
-
-	return moment.fn.utc.apply(this, arguments);
-};
-
 
 // Formatting
 // -------------------------------------------------------------------------------------------------
 
-FCMoment.prototype.format = function() {
-	if (arguments[0]) {
+newMomentProto.format = function() {
+	if (this._fullCalendar && arguments[0]) { // an enhanced moment? and a format string provided?
 		return formatDate(this, arguments[0]); // our extended formatting
 	}
 	if (this._ambigTime) {
-		return momentFormat(this, 'YYYY-MM-DD');
+		return oldMomentFormat(this, 'YYYY-MM-DD');
 	}
 	if (this._ambigZone) {
-		return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
+		return oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
 	}
-	return momentFormat(this); // default moment original formatting
+	return oldMomentProto.format.apply(this, arguments);
 };
 
-FCMoment.prototype.toISOString = function() {
+newMomentProto.toISOString = function() {
 	if (this._ambigTime) {
-		return momentFormat(this, 'YYYY-MM-DD');
+		return oldMomentFormat(this, 'YYYY-MM-DD');
 	}
 	if (this._ambigZone) {
-		return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
+		return oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
 	}
-	return moment.fn.toISOString.apply(this, arguments);
+	return oldMomentProto.toISOString.apply(this, arguments);
 };
 
 
@@ -317,23 +281,29 @@ FCMoment.prototype.toISOString = function() {
 // -------------------------------------------------------------------------------------------------
 
 // Is the moment within the specified range? `end` is exclusive.
-FCMoment.prototype.isWithin = function(start, end) {
+// FYI, this method is not a standard Moment method, so always do our enhanced logic.
+newMomentProto.isWithin = function(start, end) {
 	var a = commonlyAmbiguate([ this, start, end ]);
 	return a[0] >= a[1] && a[0] < a[2];
 };
 
 // When isSame is called with units, timezone ambiguity is normalized before the comparison happens.
-// If no units are specified, the two moments must be identically the same, with matching ambig flags.
-FCMoment.prototype.isSame = function(input, units) {
+// If no units specified, the two moments must be identically the same, with matching ambig flags.
+newMomentProto.isSame = function(input, units) {
 	var a;
 
+	// only do custom logic if this is an enhanced moment
+	if (!this._fullCalendar) {
+		return oldMomentProto.isSame.apply(this, arguments);
+	}
+
 	if (units) {
 		a = commonlyAmbiguate([ this, input ], true); // normalize timezones but don't erase times
-		return moment.fn.isSame.call(a[0], a[1], units);
+		return oldMomentProto.isSame.call(a[0], a[1], units);
 	}
 	else {
 		input = fc.moment.parseZone(input); // normalize input
-		return moment.fn.isSame.call(this, input) &&
+		return oldMomentProto.isSame.call(this, input) &&
 			Boolean(this._ambigTime) === Boolean(input._ambigTime) &&
 			Boolean(this._ambigZone) === Boolean(input._ambigZone);
 	}
@@ -344,9 +314,16 @@ $.each([
 	'isBefore',
 	'isAfter'
 ], function(i, methodName) {
-	FCMoment.prototype[methodName] = function(input, units) {
-		var a = commonlyAmbiguate([ this, input ]);
-		return moment.fn[methodName].call(a[0], a[1], units);
+	newMomentProto[methodName] = function(input, units) {
+		var a;
+
+		// only do custom logic if this is an enhanced moment
+		if (!this._fullCalendar) {
+			return oldMomentProto[methodName].apply(this, arguments);
+		}
+
+		a = commonlyAmbiguate([ this, input ]);
+		return oldMomentProto[methodName].call(a[0], a[1], units);
 	};
 });
 
@@ -380,3 +357,59 @@ function commonlyAmbiguate(inputs, preserveTime) {
 
 	return outputs;
 }
+
+// Transfers all the flags related to ambiguous time/zone from the `src` moment to the `dest` moment
+function transferAmbigs(src, dest) {
+	if (src._ambigTime) {
+		dest._ambigTime = true;
+	}
+	else if (dest._ambigTime) {
+		dest._ambigTime = false;
+	}
+
+	if (src._ambigZone) {
+		dest._ambigZone = true;
+	}
+	else if (dest._ambigZone) {
+		dest._ambigZone = false;
+	}
+}
+
+
+// Sets the year/month/date/etc values of the moment from the given array
+function setMomentValues(mom, a) {
+	mom.year(a[0] || 0)
+		.month(a[1] || 0)
+		.date(a[2] || 0)
+		.hours(a[3] || 0)
+		.minutes(a[4] || 0)
+		.seconds(a[5] || 0)
+		.milliseconds(a[6] || 0);
+}
+
+// Can we set the moment's internal date directly?
+allowValueOptimization = '_d' in moment() && 'updateOffset' in moment;
+
+// Utility function. Accepts a moment and an array of the UTC year/month/date/etc values to set.
+// Assumes the given moment is already in UTC mode.
+setUTCValues = allowValueOptimization ? function(mom, a) {
+	// simlate what moment's accessors do
+	mom._d.setTime(Date.UTC.apply(Date, a));
+	moment.updateOffset(mom, false); // keepTime=false
+} : setMomentValues;
+
+// Utility function. Accepts a moment and an array of the local year/month/date/etc values to set.
+// Assumes the given moment is already in local mode.
+setLocalValues = allowValueOptimization ? function(mom, a) {
+	// simlate what moment's accessors do
+	mom._d.setTime(+new Date( // FYI, there is now way to apply an array of args to a constructor
+		a[0] || 0,
+		a[1] || 0,
+		a[2] || 0,
+		a[3] || 0,
+		a[4] || 0,
+		a[5] || 0,
+		a[6] || 0
+	));
+	moment.updateOffset(mom, false); // keepTime=false
+} : setMomentValues;

+ 0 - 11
src/util.js

@@ -281,17 +281,6 @@ function createObject(proto) {
 }
 
 
-// Copies specifically-owned (non-protoype) properties of `b` onto `a`.
-// FYI, $.extend would copy *all* properties of `b` onto `a`.
-function extend(a, b) {
-	for (var i in b) {
-		if (b.hasOwnProperty(i)) {
-			a[i] = b[i];
-		}
-	}
-}
-
-
 function applyAll(functions, thisObj, args) {
 	if ($.isFunction(functions)) {
 		functions = [ functions ];