performance tuning

This commit is contained in:
Leo Horie 2014-11-05 00:25:52 -05:00
parent c7069b9ae4
commit a607060267

View file

@ -1,10 +1,6 @@
Mithril = m = new function app(window, undefined) { Mithril = m = new function app(window, undefined) {
var sObj = "[object Object]", sArr = "[object Array]", sStr = "[object String]" var sObj = "[object Object]", sArr = "[object Array]", sStr = "[object String]", sFn = "function"
function type(obj) {return {}.toString.call(obj)} var type = {}.toString
function isObj(obj) {return obj != null && type(obj) == sObj}
function isArr(obj) {return type(obj) == sArr}
function isFn(obj) {return typeof obj == "function"}
function isStr(obj){ return type(obj) == sStr}
var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/
var voidElements = /AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR/ var voidElements = /AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR/
@ -23,7 +19,7 @@ Mithril = m = new function app(window, undefined) {
*/ */
function m() { function m() {
var args = [].slice.call(arguments) var args = [].slice.call(arguments)
var hasAttrs = isObj(args[1]) && !("tag" in args[1]) && !("subtree" in args[1]) var hasAttrs = args[1] != null && type.call(args[1]) == sObj && !("tag" in args[1]) && !("subtree" in args[1])
var attrs = hasAttrs ? args[1] : {} var attrs = hasAttrs ? args[1] : {}
var classAttrName = "class" in attrs ? "class" : "className" var classAttrName = "class" in attrs ? "class" : "className"
var cell = {tag: "div", attrs: {}} var cell = {tag: "div", attrs: {}}
@ -41,7 +37,7 @@ Mithril = m = new function app(window, undefined) {
var children = hasAttrs ? args[2] : args[1] var children = hasAttrs ? args[2] : args[1]
if (isArr(children)) { if (type.call(children) == sArr) {
cell.children = children cell.children = children
} }
else { else {
@ -82,7 +78,7 @@ Mithril = m = new function app(window, undefined) {
//- it simplifies diffing code //- it simplifies diffing code
if (data == null) data = "" if (data == null) data = ""
if (data.subtree === "retain") return cached if (data.subtree === "retain") return cached
var cachedType = type(cached), dataType = type(data) var cachedType = type.call(cached), dataType = type.call(data)
if (cached == null || cachedType != dataType) { if (cached == null || cachedType != dataType) {
if (cached != null) { if (cached != null) {
if (parentCache && parentCache.nodes) { if (parentCache && parentCache.nodes) {
@ -176,7 +172,7 @@ Mithril = m = new function app(window, undefined) {
//the second clause (after the pipe) matches text nodes //the second clause (after the pipe) matches text nodes
subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || []).length subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || []).length
} }
else subArrayCount += isArr(item) ? item.length : 1 else subArrayCount += type.call(item) == sArr ? item.length : 1
cached[cacheCount++] = item cached[cacheCount++] = item
} }
if (!intact) { if (!intact) {
@ -202,12 +198,14 @@ Mithril = m = new function app(window, undefined) {
else if (data != null && dataType == sObj) { else if (data != null && dataType == sObj) {
if (!data.attrs) data.attrs = {} if (!data.attrs) data.attrs = {}
if (!cached.attrs) cached.attrs = {} if (!cached.attrs) cached.attrs = {}
var dataAttrKeys = Object.keys(data.attrs)
//if an element is different enough from the one in cache, recreate it //if an element is different enough from the one in cache, recreate it
if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) { if (data.tag != cached.tag || dataAttrKeys.join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) {
clear(cached.nodes) if (cached.nodes.length) clear(cached.nodes)
if (cached.configContext && isFn(cached.configContext.onunload)) cached.configContext.onunload() if (cached.configContext && typeof cached.configContext.onunload == sFn) cached.configContext.onunload()
} }
if (!isStr(data.tag)) return if (type.call(data.tag) != sStr) return
var node, isNew = cached.nodes.length === 0 var node, isNew = cached.nodes.length === 0
if (data.attrs.xmlns) namespace = data.attrs.xmlns if (data.attrs.xmlns) namespace = data.attrs.xmlns
@ -218,23 +216,26 @@ Mithril = m = new function app(window, undefined) {
cached = { cached = {
tag: data.tag, tag: data.tag,
//set attributes first, then create children //set attributes first, then create children
attrs: setAttributes(node, data.tag, data.attrs, {}, namespace), attrs: dataAttrKeys.length ? setAttributes(node, data.tag, data.attrs, {}, namespace) : {},
children: build(node, data.tag, undefined, undefined, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace, configs), children: data.children != null && data.children.length > 0 ?
build(node, data.tag, undefined, undefined, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace, configs) :
data.children,
nodes: [node] nodes: [node]
} }
if (cached.children && !cached.children.nodes) cached.children.nodes = []
//edge case: setting value on <select> doesn't work before children exist, so set it again after children have been created //edge case: setting value on <select> doesn't work before children exist, so set it again after children have been created
if (data.tag == "select" && data.attrs.value) setAttributes(node, data.tag, {value: data.attrs.value}, {}, namespace) if (data.tag == "select" && data.attrs.value) setAttributes(node, data.tag, {value: data.attrs.value}, {}, namespace)
parentElement.insertBefore(node, parentElement.childNodes[index] || null) parentElement.insertBefore(node, parentElement.childNodes[index] || null)
} }
else { else {
node = cached.nodes[0] node = cached.nodes[0]
setAttributes(node, data.tag, data.attrs, cached.attrs, namespace) if (dataAttrKeys.length) setAttributes(node, data.tag, data.attrs, cached.attrs, namespace)
cached.children = build(node, data.tag, undefined, undefined, data.children, cached.children, false, 0, data.attrs.contenteditable ? node : editable, namespace, configs) cached.children = build(node, data.tag, undefined, undefined, data.children, cached.children, false, 0, data.attrs.contenteditable ? node : editable, namespace, configs)
cached.nodes.intact = true cached.nodes.intact = true
if (shouldReattach === true && node != null) parentElement.insertBefore(node, parentElement.childNodes[index] || null) if (shouldReattach === true && node != null) parentElement.insertBefore(node, parentElement.childNodes[index] || null)
} }
//schedule configs to be called. They are called after `build` finishes running //schedule configs to be called. They are called after `build` finishes running
if (isFn(data.attrs["config"])) { if (typeof data.attrs["config"] == sFn) {
var context = cached.configContext = cached.configContext || {} var context = cached.configContext = cached.configContext || {}
// bind // bind
@ -246,7 +247,7 @@ Mithril = m = new function app(window, undefined) {
configs.push(callback(data, [node, !isNew, context, cached])) configs.push(callback(data, [node, !isNew, context, cached]))
} }
} }
else if (!isFn(dataType)) { else if (typeof dataType != sFn) {
//handle text nodes //handle text nodes
var nodes var nodes
if (cached.nodes.length === 0) { if (cached.nodes.length === 0) {
@ -301,11 +302,11 @@ Mithril = m = new function app(window, undefined) {
//we don't ignore `key` because it must be unique and having it on the DOM helps debugging //we don't ignore `key` because it must be unique and having it on the DOM helps debugging
if (attrName === "config") continue if (attrName === "config") continue
//hook event handlers to the auto-redrawing system //hook event handlers to the auto-redrawing system
else if (isFn(dataAttr) && attrName.indexOf("on") == 0) { else if (typeof dataAttr == sFn && attrName.indexOf("on") == 0) {
node[attrName] = autoredraw(dataAttr, node) node[attrName] = autoredraw(dataAttr, node)
} }
//handle `style: {...}` //handle `style: {...}`
else if (attrName === "style" && isObj(dataAttr)) { else if (attrName === "style" && dataAttr != null && type.call(dataAttr) == sObj) {
for (var rule in dataAttr) { for (var rule in dataAttr) {
if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule] if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule]
} }
@ -346,9 +347,9 @@ Mithril = m = new function app(window, undefined) {
if (nodes.length != 0) nodes.length = 0 if (nodes.length != 0) nodes.length = 0
} }
function unload(cached) { function unload(cached) {
if (cached.configContext && isFn(cached.configContext.onunload)) cached.configContext.onunload() if (cached.configContext && typeof cached.configContext.onunload == sFn) cached.configContext.onunload()
if (cached.children) { if (cached.children) {
if (isArr(cached.children)) { if (type.call(cached.children) == sArr) {
for (var i = 0; i < cached.children.length; i++) unload(cached.children[i]) for (var i = 0; i < cached.children.length; i++) unload(cached.children[i])
} }
else if (cached.children.tag) unload(cached.children) else if (cached.children.tag) unload(cached.children)
@ -375,13 +376,19 @@ Mithril = m = new function app(window, undefined) {
return nodes return nodes
} }
function flatten(data) { function flatten(data) {
var flattened = [] var index = 0
for (var i = 0; i < data.length; i++) { loop: while (true) {
for (var i = index; i < data.length; i++) {
var item = data[i] var item = data[i]
if (isArr(item)) flattened.push.apply(flattened, flatten(item)) if (type.call(data[i]) == sArr) {
else flattened.push(item) index = i
data = data.concat.apply([], data)
continue loop
} }
return flattened }
break
}
return data
} }
function autoredraw(callback, object) { function autoredraw(callback, object) {
return function(e) { return function(e) {
@ -449,7 +456,7 @@ Mithril = m = new function app(window, undefined) {
m.prop = function (store) { m.prop = function (store) {
//note: using non-strict equality check here because we're checking if store is null OR undefined //note: using non-strict equality check here because we're checking if store is null OR undefined
if ((isObj(store) || isFn(store)) && isFn(store.then)) { if (((store != null && type.call(store) == sObj) || typeof store == sFn) && typeof store.then == sFn) {
return propify(store) return propify(store)
} }
@ -462,7 +469,7 @@ Mithril = m = new function app(window, undefined) {
var index = roots.indexOf(root) var index = roots.indexOf(root)
if (index < 0) index = roots.length if (index < 0) index = roots.length
var isPrevented = false var isPrevented = false
if (controllers[index] && isFn(controllers[index].onunload)) { if (controllers[index] && typeof controllers[index].onunload == sFn) {
var event = { var event = {
preventDefault: function() {isPrevented = true} preventDefault: function() {isPrevented = true}
} }
@ -541,7 +548,7 @@ Mithril = m = new function app(window, undefined) {
m.route = function() { m.route = function() {
//m.route() //m.route()
if (arguments.length === 0) return currentRoute if (arguments.length === 0) return currentRoute
else if (arguments.length === 3 && isStr(arguments[1])) { else if (arguments.length === 3 && type.call(arguments[1]) == sStr) {
var root = arguments[0], defaultRoute = arguments[1], router = arguments[2] var root = arguments[0], defaultRoute = arguments[1], router = arguments[2]
redirect = function(source) { redirect = function(source) {
var path = currentRoute = normalizeRoute(source) var path = currentRoute = normalizeRoute(source)
@ -568,9 +575,9 @@ Mithril = m = new function app(window, undefined) {
element.addEventListener("click", routeUnobtrusive) element.addEventListener("click", routeUnobtrusive)
} }
//m.route(route) //m.route(route)
else if (isStr(arguments[0])) { else if (type.call(arguments[0]) == sStr) {
currentRoute = arguments[0] currentRoute = arguments[0]
var querystring = isObj(arguments[1]) ? buildQueryString(arguments[1]) : null var querystring = arguments[1] != null && type.call(arguments[1]) == sObj ? buildQueryString(arguments[1]) : null
if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring
var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true
@ -633,7 +640,7 @@ Mithril = m = new function app(window, undefined) {
var str = [] var str = []
for(var prop in object) { for(var prop in object) {
var key = prefix ? prefix + "[" + prop + "]" : prop, value = object[prop] var key = prefix ? prefix + "[" + prop + "]" : prop, value = object[prop]
str.push(isObj(value) ? buildQueryString(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) str.push(value != null && type.call(value) == sObj ? buildQueryString(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value))
} }
return str.join("&") return str.join("&")
} }
@ -719,7 +726,7 @@ Mithril = m = new function app(window, undefined) {
} }
function thennable(then, successCallback, failureCallback, notThennableCallback) { function thennable(then, successCallback, failureCallback, notThennableCallback) {
if ((isObj(promiseValue) || isFn(promiseValue)) && isFn(then)) { if (((promiseValue != null && type.call(promiseValue) == sObj) || typeof promiseValue == sFn) && typeof then == sFn) {
try { try {
// count protects against abuse calls from spec checker // count protects against abuse calls from spec checker
var count = 0 var count = 0
@ -763,10 +770,10 @@ Mithril = m = new function app(window, undefined) {
fire() fire()
}, function() { }, function() {
try { try {
if (state == RESOLVING && isFn(successCallback)) { if (state == RESOLVING && typeof successCallback == sFn) {
promiseValue = successCallback(promiseValue) promiseValue = successCallback(promiseValue)
} }
else if (state == REJECTING && isFn(failureCallback)) { else if (state == REJECTING && typeof failureCallback == "function") {
promiseValue = failureCallback(promiseValue) promiseValue = failureCallback(promiseValue)
state = RESOLVING state = RESOLVING
} }
@ -792,7 +799,7 @@ Mithril = m = new function app(window, undefined) {
} }
} }
m.deferred.onerror = function(e) { m.deferred.onerror = function(e) {
if (type(e) == "[object Error]" && !e.constructor.toString().match(/ Error/)) throw e if (type.call(e) == "[object Error]" && !e.constructor.toString().match(/ Error/)) throw e
} }
m.sync = function(args) { m.sync = function(args) {
@ -880,12 +887,12 @@ Mithril = m = new function app(window, undefined) {
if (options.deserialize == JSON.parse) { if (options.deserialize == JSON.parse) {
xhr.setRequestHeader("Accept", "application/json, text/*"); xhr.setRequestHeader("Accept", "application/json, text/*");
} }
if (isFn(options.config)) { if (typeof options.config == sFn) {
var maybeXhr = options.config(xhr, options) var maybeXhr = options.config(xhr, options)
if (maybeXhr != null) xhr = maybeXhr if (maybeXhr != null) xhr = maybeXhr
} }
if (options.data && (!isStr(options.data) && options.data.constructor != window.FormData)) throw "Request data should be either be a string or FormData. Check the `serialize` option in `m.request`" if (options.data && (type.call(options.data) != sStr && options.data.constructor != window.FormData)) throw "Request data should be either be a string or FormData. Check the `serialize` option in `m.request`"
xhr.send(options.method == "GET" || !options.data ? "" : options.data) xhr.send(options.method == "GET" || !options.data ? "" : options.data)
return xhr return xhr
} }
@ -928,7 +935,7 @@ Mithril = m = new function app(window, undefined) {
var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity
var response = unwrap(deserialize(extract(e.target, xhrOptions))) var response = unwrap(deserialize(extract(e.target, xhrOptions)))
if (e.type == "load") { if (e.type == "load") {
if (isArr(response) && xhrOptions.type) { if (type.call(response) == sArr && xhrOptions.type) {
for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i]) for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i])
} }
else if (xhrOptions.type) response = new xhrOptions.type(response) else if (xhrOptions.type) response = new xhrOptions.type(response)