From ea2aaf55d341cc4a46c26497cb5992769560f7e4 Mon Sep 17 00:00:00 2001 From: David Duponchel Date: Thu, 9 May 2013 18:11:13 +0200 Subject: [PATCH 1/9] Lazy decompress data This commit aims to be as lazy as possible : When reading a file, we now don't decompress the content, we just keep a reference to the original compressed file and an offset. If the user accesses a file, we will decompress it and replace the content (so we don't have to decompress it again). When generating a zip, if a file has not been decompressed we check if we can reuse the compressed content. This unfortunately means that we won't be backward compatible : the data attribute may not be calculated yet. Worse, the data now can be a string, an array or a UInt8Array. The user must use the getters ! The interface for compression/decompression has also changed : we now specify the input type for each operation. This has been tested in IE 6 -> 10, firefox, chrome, opera. If anyone has an apple product with safari, he's welcome to test :) --- index.html | 44 ++- jszip-deflate.js | 4 +- jszip-inflate.js | 4 +- jszip-load.js | 342 ++++++++++++----- jszip.js | 731 +++++++++++++++++++++++++++---------- test/browser-test-utils.js | 6 +- test/test.js | 254 ++++++++----- 7 files changed, 987 insertions(+), 398 deletions(-) diff --git a/index.html b/index.html index 42277580..71b04dd1 100644 --- a/index.html +++ b/index.html @@ -280,7 +280,6 @@

file(name)

The file if any, null otherwise. The file has the following structure :
@@ -489,6 +489,8 @@

load(data, options)

  • base64 (boolean) true if the data is base64 encoded, false for binary. Default : false.
  • checkCRC32 (boolean) true if the read data should be checked against its CRC32. Default : false.
  • +
  • optimizedBinaryString (boolean), default false. Set it to true if (and only if) the input is a string and + has already been prepared with a 0xFF mask.
Returns :
@@ -548,11 +550,13 @@

JSZip.support

Loading zip files, limitations

+

Not supported features

All the features of zip files are not supported. Classic zip files will work but encrypted zip, multi-volume, etc are not supported and the load() method will throw an Error.

+

ZIP64 and 32bit integers

ZIP64 files can be loaded, but only if the zip file is not "too big". ZIP64 uses 64bits integers but Javascript represents all numbers as @@ -564,17 +568,30 @@

Loading zip files, limitations

So if all the 64bits integers can fit into 32 bits integers, everything will be fine. If it's not the case, you will have other problems anyway (see next limitation).

+

Performance issues

An other limitation comes from the browser (and the machine running the browser). - A compressed zip file of 10M is common and easily opened by desktop application, - but not in a browser. The processing of such a beast is likely to be painful : - the browser will eat hundreds of megabytes while using CPU like never.
- If you use an old browser, things will be worse. For example, IE6 and IE7 are - quite slow to to execute the unit tests, and they completely freeze as soon as - they try to handle larger files.
- Conclusion : reading small files is OK, reading others is not. + A compressed zip file of 10MB is "easily" opened by firefox/chrome/opera/IE10 but + will crash older IE. Also keep in mind that strings in javascript are encoded in UTF-16 : + a 10MB ascii text file will take 20MB of memory.

+ If you're having performance issues, please consider the following : +

    +
  • Don't use IE < 10. Everything is better with WebGL support.
  • +
  • + If you load the file from an ajax call, ask your XHR an ArrayBuffer. + Loading a string is asking for troubles. +
  • +
  • + If you want to get the content of an ASCII file as a string, consider using + asBinary() instead of asText(). The transformation + "binary string" -> "utf string" is a consuming process. +
  • +
+

+

The output zip will differ from the input zip

+

Reading and generating a zip file won't give you back the same file. Some data are discarded (file metadata) and other are added (subfolders).

From 6c7595530dbee6b3fa9d5c8304468ae41be71875 Mon Sep 17 00:00:00 2001 From: David Duponchel Date: Sat, 11 May 2013 10:37:39 +0200 Subject: [PATCH 4/9] Tests : better display of unexpected errors. --- test/test.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/test.js b/test/test.js index 07c6eb08..64972966 100644 --- a/test/test.js +++ b/test/test.js @@ -40,9 +40,11 @@ function testZipFile(testName, zipName, testFunction) { start(); refZips[zipName] = file; testFunction.call(this, file); - }, function(errorMessage){ - start(); - ok(false, "Ajax error for " + zipName + " : " + this.status); + }, function(error){ + if (QUnit.config.semaphore) { + start(); + } + ok(false, error); }); } }); From 7f6387fea5d3724c246d5c4f738cb1db0691f4ce Mon Sep 17 00:00:00 2001 From: David Duponchel Date: Sat, 11 May 2013 12:36:13 +0200 Subject: [PATCH 5/9] inflate/deflate : v0.1.6 --- jszip-deflate.js | 59 +++++++++++++++++++++++++----------------------- jszip-inflate.js | 59 +++++++++++++++++++++++++----------------------- 2 files changed, 62 insertions(+), 56 deletions(-) diff --git a/jszip-deflate.js b/jszip-deflate.js index dc93ff42..e8ff0b4a 100644 --- a/jszip-deflate.js +++ b/jszip-deflate.js @@ -1,15 +1,15 @@ "use strict"; (function () { - if(!JSZip) { - throw "JSZip not defined"; - } - var context = {}; - (function () { + if(!JSZip) { + throw "JSZip not defined"; + } + var context = {}; + (function () { - // https://github.com/imaya/zlib.js - // commit 4630e858caf90dbe9ca68ef44866eaf286cf7d8a - // file bin/deflate.min.js + // https://github.com/imaya/zlib.js + // tag 0.1.6 + // file bin/deflate.min.js /** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';var n=void 0,u=!0,aa=this;function ba(e,d){var c=e.split("."),f=aa;!(c[0]in f)&&f.execScript&&f.execScript("var "+c[0]);for(var a;c.length&&(a=c.shift());)!c.length&&d!==n?f[a]=d:f=f[a]?f[a]:f[a]={}};var C="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array;function K(e,d){this.index="number"===typeof d?d:0;this.d=0;this.buffer=e instanceof(C?Uint8Array:Array)?e:new (C?Uint8Array:Array)(32768);if(2*this.buffer.length<=this.index)throw Error("invalid index");this.buffer.length<=this.index&&ca(this)}function ca(e){var d=e.buffer,c,f=d.length,a=new (C?Uint8Array:Array)(f<<1);if(C)a.set(d);else for(c=0;c>>8&255]<<16|L[e>>>16&255]<<8|L[e>>>24&255])>>32-d:L[e]>>8-d);if(8>d+b)k=k<>d-m-1&1,8===++b&&(b=0,f[a++]=L[k],k=0,a===f.length&&(f=ca(this)));f[a]=k;this.buffer=f;this.d=b;this.index=a};K.prototype.finish=function(){var e=this.buffer,d=this.index,c;0>>=1}return d};ba("Zlib.RawDeflate",ka);ba("Zlib.RawDeflate.prototype.compress",ka.prototype.h);var Ka={NONE:0,FIXED:1,DYNAMIC:ma},V,La,$,Ma;if(Object.keys)V=Object.keys(Ka);else for(La in V=[],$=0,Ka)V[$++]=La;$=0;for(Ma=V.length;$a&&(a=c[n]),c[n]>=1;for(t=m;td&&(this.b.length=d),c=this.b);return this.buffer=c};q("Zlib.RawInflate",v);q("Zlib.RawInflate.prototype.decompress",v.prototype.t);var T={ADAPTIVE:w,BLOCK:x},U,V,W,X;if(Object.keys)U=Object.keys(T);else for(V in U=[],W=0,T)U[W++]=V;W=0;for(X=U.length;W Date: Sat, 11 May 2013 12:24:45 +0200 Subject: [PATCH 6/9] optimize memory consumption instead of cpu consumption str += 'char' is faster than array.push && array.join but the difference is not big. String are immutable so the concatenation will create n(n-1)/2 objects, so a memory consumption in O(n^2). The array join is in O(n). When working with large files (hundreds of Mb), O(n^2) is clearly not a good idea. Also, use TextDecoder if available to boost perfs. --- jszip.js | 107 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 38 deletions(-) diff --git a/jszip.js b/jszip.js index 05478e2f..cdd74a68 100644 --- a/jszip.js +++ b/jszip.js @@ -66,24 +66,32 @@ JSZip.prototype = (function () { * @param {function} filter a function String -> String, applied if not null on the result. * @return {String} the string representing this._data. */ - var dataToString = function (filter) { + var dataToString = function (asUTF8) { var result = this._data; if (result === null || typeof result === "undefined") { return ""; } if (result instanceof JSZip.CompressedObject) { - result = JSZip.utils.transformTo("string", result.getContent()); - this._data = result; + result = result.getContent(); this.options.binary = true; this.options.base64 = false; - } else { - result = JSZip.utils.transformTo("string", result); + this._data = result; } + // if the data is a base64 string, we decode it before checking the encoding ! if (this.options.base64) { result = JSZip.base64.decode(result); } - if (filter) { - result = filter(result); + if (asUTF8 && this.options.binary) { + // JSZip.prototype.utf8decode supports arrays as input + // skip to array => string step, utf8decode will do it. + result = JSZip.prototype.utf8decode(result); + } else { + // no utf8 transformation, do the array => string step. + result = JSZip.utils.transformTo("string", result); + } + + if (!asUTF8 && !this.options.binary) { + result = JSZip.prototype.utf8encode(result); } return result; }; @@ -106,14 +114,14 @@ JSZip.prototype = (function () { * @return {string} the UTF8 string. */ asText : function () { - return dataToString.call(this, this.options.binary ? JSZip.prototype.utf8decode : null); + return dataToString.call(this, true); }, /** * Returns the binary content. * @return {string} the content as binary. */ asBinary : function () { - return dataToString.call(this, !this.options.binary ? JSZip.prototype.utf8encode : null); + return dataToString.call(this, false); }, /** * Returns the content as an Uint8Array. @@ -236,7 +244,7 @@ JSZip.prototype = (function () { // special case : it's way easier to work with Uint8Array than with ArrayBuffer if (dataType === "arraybuffer") { - data = new Uint8Array(data); + data = JSZip.utils.transformTo("uint8array", data); } } @@ -802,57 +810,65 @@ JSZip.prototype = (function () { * http://www.webtoolkit.info/javascript-utf8.html */ utf8encode : function (string) { - var utftext = ""; + var result = []; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { - utftext += String.fromCharCode(c); + result.push(String.fromCharCode(c)); } else if ((c > 127) && (c < 2048)) { - utftext += String.fromCharCode((c >> 6) | 192); - utftext += String.fromCharCode((c & 63) | 128); + result.push(String.fromCharCode((c >> 6) | 192)); + result.push(String.fromCharCode((c & 63) | 128)); } else { - utftext += String.fromCharCode((c >> 12) | 224); - utftext += String.fromCharCode(((c >> 6) & 63) | 128); - utftext += String.fromCharCode((c & 63) | 128); + result.push(String.fromCharCode((c >> 12) | 224)); + result.push(String.fromCharCode(((c >> 6) & 63) | 128)); + result.push(String.fromCharCode((c & 63) | 128)); } } - return utftext; + return result.join(""); }, /** * http://www.webtoolkit.info/javascript-utf8.html */ - utf8decode : function (utftext) { - var string = ""; + utf8decode : function (input) { + var result = []; + var type = JSZip.utils.getTypeOf(input); + var isArray = type !== "string"; var i = 0; var c = 0, c1 = 0, c2 = 0, c3 = 0; - while ( i < utftext.length ) { + // check if we can use the TextDecoder API + // see http://encoding.spec.whatwg.org/#api + if (JSZip.support.uint8array && type === 'uint8array' && typeof TextDecoder === "function") { + return TextDecoder("utf-8").decode(input); + } + + while ( i < input.length ) { - c = utftext.charCodeAt(i); + c = isArray ? input[i] : input.charCodeAt(i); if (c < 128) { - string += String.fromCharCode(c); + result.push(String.fromCharCode(c)); i++; } else if ((c > 191) && (c < 224)) { - c2 = utftext.charCodeAt(i+1); - string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + c2 = isArray ? input[i+1] : input.charCodeAt(i+1); + result.push(String.fromCharCode(((c & 31) << 6) | (c2 & 63))); i += 2; } else { - c2 = utftext.charCodeAt(i+1); - c3 = utftext.charCodeAt(i+2); - string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + c2 = isArray ? input[i+1] : input.charCodeAt(i+1); + c3 = isArray ? input[i+2] : input.charCodeAt(i+2); + result.push(String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63))); i += 3; } } - return string; + return result.join(""); } }; }()); @@ -1029,16 +1045,31 @@ JSZip.support = { * @return {String} the result. */ function arrayLikeToString(array) { - var result = ""; - for(var i = 0; i < array.length; i++) { - // yup, String.fromCharCode.apply(null, array); is waaaaaay faster - // see http://jsperf.com/converting-a-uint8array-to-a-string/2 - // but... the stack is limited ! - // maybe http://jsperf.com/arraybuffer-to-string-apply-performance/2 ? - result += String.fromCharCode(array[i]); + // Performances notes : + // -------------------- + // String.fromCharCode.apply(null, array) is the fastest, see + // see http://jsperf.com/converting-a-uint8array-to-a-string/2 + // but the stack is limited (and we can get huge arrays !). + // + // result += String.fromCharCode(array[i]); generate too many strings ! + // + // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2 + var chunk = 65536; + var result = [], len = array.length, type = JSZip.utils.getTypeOf(array), k = 0; + + while (k < len && chunk > 1) { + try { + if (type === "array") { + result.push(String.fromCharCode.apply(null, array.slice(k, Math.max(k + chunk, len)))); + } else { + result.push(String.fromCharCode.apply(null, array.subarray(k, k + chunk))); + } + k += chunk; + } catch (e) { + chunk = Math.floor(chunk / 2); + } } - - return result; + return result.join(""); }; /** From 54dcb7e3bc87877b5bc8fe8cf645da75af2f5fe6 Mon Sep 17 00:00:00 2001 From: David Duponchel Date: Fri, 17 May 2013 20:50:28 +0200 Subject: [PATCH 7/9] remove an old file --- test/crcPerf.html | 89 ----------------------------------------------- 1 file changed, 89 deletions(-) delete mode 100644 test/crcPerf.html diff --git a/test/crcPerf.html b/test/crcPerf.html deleted file mode 100644 index 65091b8a..00000000 --- a/test/crcPerf.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - -CRC Performance Testing - - - - - - - - - - - - -

Please wait while tests complete

-
-

Original CRC Method Performance (10000 loops)

-
- ... Waiting ... -
-
-
-

New CRC Method Performance (10000 loops)

-
- ... Waiting ... -
-
- - - - From a98f7ab2d8f17d7feb1f75b8558a09ccd0d63b1a Mon Sep 17 00:00:00 2001 From: David Duponchel Date: Sat, 25 May 2013 19:16:23 +0200 Subject: [PATCH 8/9] Avoid if possible strings manipulation. Working with strings consumes a lot of resources. For example, the transformation utf8 string -> binary string is faster with the path utf8 string -> Uint8Array (via TextEncoder) -> binary string. --- jszip-load.js | 3 +- jszip.js | 99 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/jszip-load.js b/jszip-load.js index 7173aeb0..fbebe8e2 100644 --- a/jszip-load.js +++ b/jszip-load.js @@ -632,7 +632,8 @@ Dual licenced under the MIT license or GPLv3. See LICENSE.markdown. } }, prepareReader : function (data) { - if (JSZip.utils.getTypeOf(data) === "string") { + var type = JSZip.utils.getTypeOf(data); + if (type === "string" && !JSZip.support.uint8array) { this.reader = new StringReader(data, this.loadOptions.optimizedBinaryString); } else { this.reader = new Uint8ArrayReader(JSZip.utils.transformTo("uint8array", data)); diff --git a/jszip.js b/jszip.js index cdd74a68..821eccfb 100644 --- a/jszip.js +++ b/jszip.js @@ -61,22 +61,61 @@ JSZip.defaults = { JSZip.prototype = (function () { + /** + * Returns the raw data of a ZipObject, decompress the content if necessary. + * @param {ZipObject} file the file to use. + * @return {String|ArrayBuffer|Uint8Array} the data. + */ + var getRawData = function (file) { + if (file._data instanceof JSZip.CompressedObject) { + file._data = file._data.getContent(); + file.options.binary = true; + file.options.base64 = false; + + if (JSZip.utils.getTypeOf(file._data) === "uint8array") { + var copy = file._data; + // when reading an arraybuffer, the CompressedObject mechanism will keep it and subarray() a Uint8Array. + // if we request a file in the same format, we might get the same Uint8Array or its ArrayBuffer (the original zip file). + file._data = new Uint8Array(copy.length); + // with an empty Uint8Array, Opera fails with a "Offset larger than array size" + if (copy.length !== 0) { + file._data.set(copy, 0); + } + } + } + return file._data; + }; + + /** + * Returns the data of a ZipObject in a binary form. If the content is an unicode string, encode it. + * @param {ZipObject} file the file to use. + * @return {String|ArrayBuffer|Uint8Array} the data. + */ + var getBinaryData = function (file) { + var result = getRawData(file), type = JSZip.utils.getTypeOf(result); + if (type === "string") { + if (!file.options.binary) { + // unicode text ! + // unicode string => binary string is a painful process, check if we can avoid it. + if (JSZip.support.uint8array && typeof TextEncoder === "function") { + return TextEncoder("utf-8").encode(result); + } + } + return file.asBinary(); + } + return result; + } + /** * Transform this._data into a string. * @param {function} filter a function String -> String, applied if not null on the result. * @return {String} the string representing this._data. */ var dataToString = function (asUTF8) { - var result = this._data; + var result = getRawData(this); if (result === null || typeof result === "undefined") { return ""; } - if (result instanceof JSZip.CompressedObject) { - result = result.getContent(); - this.options.binary = true; - this.options.base64 = false; - this._data = result; - } // if the data is a base64 string, we decode it before checking the encoding ! if (this.options.base64) { result = JSZip.base64.decode(result); @@ -99,7 +138,7 @@ JSZip.prototype = (function () { * A simple object representing a file in the zip file. * @constructor * @param {string} name the name of the file - * @param {string} data the data + * @param {String|ArrayBuffer|Uint8Array} data the data * @param {Object} options the options of the file */ var ZipObject = function (name, data, options) { @@ -128,22 +167,7 @@ JSZip.prototype = (function () { * @return {Uint8Array} the content as an Uint8Array. */ asUint8Array : function () { - var result = this._data; - if (result instanceof JSZip.CompressedObject) { - result = JSZip.utils.transformTo("uint8array", result.getContent()); - // when reading an arraybuffer, the CompressedObject mechanism will keep it and subarray() a Uint8Array. - // if we request a file in the same format, we might get the same Uint8Array or its ArrayBuffer (the original zip file). - this._data = new Uint8Array(result.length); - // with an empty Uint8Array, Opera fails with a "Offset larger than array size" - if (result.length !== 0) { - this._data.set(result, 0); - } - // new result ! - var result = this._data; - } - if (JSZip.utils.getTypeOf(this._data) === "string") { - result = this.asBinary(); - } + var result = getBinaryData(this); return JSZip.utils.transformTo("uint8array", result); }, /** @@ -312,7 +336,7 @@ JSZip.prototype = (function () { } } else { // have uncompressed data - content = JSZip.utils.getTypeOf(file._data) === "string" ? file.asBinary() : file._data; + content = getBinaryData(file); if (!content || content.length === 0 || file.options.dir) { compression = JSZip.compressions['STORE']; content = ""; @@ -688,7 +712,7 @@ JSZip.prototype = (function () { case "arraybuffer" : return JSZip.utils.transformTo(options.type.toLowerCase(), zip); case "blob" : - return JSZip.utils.arrayBuffer2Blob(zip); + return JSZip.utils.arrayBuffer2Blob(JSZip.utils.transformTo("arraybuffer", zip)); // case "zip is a string" case "base64" : @@ -810,6 +834,13 @@ JSZip.prototype = (function () { * http://www.webtoolkit.info/javascript-utf8.html */ utf8encode : function (string) { + // TextEncoder + Uint8Array to binary string is faster than checking every bytes. + // http://jsperf.com/utf8encode-vs-textencoder + if (JSZip.support.uint8array && typeof TextEncoder === "function") { + var u8 = TextEncoder("utf-8").encode(string); + return JSZip.utils.transformTo("string", u8); + } + var result = []; for (var n = 0; n < string.length; n++) { @@ -844,8 +875,10 @@ JSZip.prototype = (function () { // check if we can use the TextDecoder API // see http://encoding.spec.whatwg.org/#api - if (JSZip.support.uint8array && type === 'uint8array' && typeof TextDecoder === "function") { - return TextDecoder("utf-8").decode(input); + if (JSZip.support.uint8array && typeof TextDecoder === "function") { + return TextDecoder("utf-8").decode( + JSZip.utils.transformTo("uint8array", input) + ); } while ( i < input.length ) { @@ -980,7 +1013,7 @@ JSZip.support = { }, /** * Create a blob from the given ArrayBuffer. - * @param {string} buffer the buffer to transform. + * @param {ArrayBuffer} buffer the buffer to transform. * @return {Blob} the result. * @throws {Error} an Error if the browser doesn't support the requested feature. */ @@ -1034,7 +1067,7 @@ JSZip.support = { */ function stringToArrayLike(str, array) { for (var i = 0; i < str.length; ++i) { - array[i] = str.charCodeAt(i); + array[i] = str.charCodeAt(i) & 0xFF; } return array; }; @@ -1092,7 +1125,7 @@ JSZip.support = { transform["string"] = { "string" : identity, "array" : function (input) { - return stringToArrayLike(input, []); + return stringToArrayLike(input, new Array(input.length)); }, "arraybuffer" : function (input) { return transform["string"]["uint8array"](input).buffer; @@ -1120,7 +1153,7 @@ JSZip.support = { return arrayLikeToString(new Uint8Array(input)); }, "array" : function (input) { - return arrayLikeToArrayLike(new Uint8Array(input), []); + return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength)); }, "arraybuffer" : identity, "uint8array" : function (input) { @@ -1132,7 +1165,7 @@ JSZip.support = { transform["uint8array"] = { "string" : arrayLikeToString, "array" : function (input) { - return arrayLikeToArrayLike(input, []); + return arrayLikeToArrayLike(input, new Array(input.length)); }, "arraybuffer" : function (input) { return input.buffer; From 14217a0de3f31662ee99af820017592c627cd63e Mon Sep 17 00:00:00 2001 From: David Duponchel Date: Sun, 30 Jun 2013 15:59:33 +0200 Subject: [PATCH 9/9] performances : small gains on chrome See http://jsperf.com/array-direct-assignment-vs-push/31, direct assignment is faster than push. --- jszip.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/jszip.js b/jszip.js index 821eccfb..e902cd2f 100644 --- a/jszip.js +++ b/jszip.js @@ -834,28 +834,31 @@ JSZip.prototype = (function () { * http://www.webtoolkit.info/javascript-utf8.html */ utf8encode : function (string) { - // TextEncoder + Uint8Array to binary string is faster than checking every bytes. + // TextEncoder + Uint8Array to binary string is faster than checking every bytes on long strings. // http://jsperf.com/utf8encode-vs-textencoder + // On short strings (file names for example), the TextEncoder API is (currently) slower. if (JSZip.support.uint8array && typeof TextEncoder === "function") { var u8 = TextEncoder("utf-8").encode(string); return JSZip.utils.transformTo("string", u8); } - var result = []; + // array.join may be slower than string concatenation but generates less objects (less time spent garbage collecting). + // See also http://jsperf.com/array-direct-assignment-vs-push/31 + var result = [], resIndex = 0; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { - result.push(String.fromCharCode(c)); + result[resIndex++] = String.fromCharCode(c); } else if ((c > 127) && (c < 2048)) { - result.push(String.fromCharCode((c >> 6) | 192)); - result.push(String.fromCharCode((c & 63) | 128)); + result[resIndex++] = String.fromCharCode((c >> 6) | 192); + result[resIndex++] = String.fromCharCode((c & 63) | 128); } else { - result.push(String.fromCharCode((c >> 12) | 224)); - result.push(String.fromCharCode(((c >> 6) & 63) | 128)); - result.push(String.fromCharCode((c & 63) | 128)); + result[resIndex++] = String.fromCharCode((c >> 12) | 224); + result[resIndex++] = String.fromCharCode(((c >> 6) & 63) | 128); + result[resIndex++] = String.fromCharCode((c & 63) | 128); } } @@ -867,7 +870,7 @@ JSZip.prototype = (function () { * http://www.webtoolkit.info/javascript-utf8.html */ utf8decode : function (input) { - var result = []; + var result = [], resIndex = 0; var type = JSZip.utils.getTypeOf(input); var isArray = type !== "string"; var i = 0; @@ -886,16 +889,16 @@ JSZip.prototype = (function () { c = isArray ? input[i] : input.charCodeAt(i); if (c < 128) { - result.push(String.fromCharCode(c)); + result[resIndex++] = String.fromCharCode(c); i++; } else if ((c > 191) && (c < 224)) { c2 = isArray ? input[i+1] : input.charCodeAt(i+1); - result.push(String.fromCharCode(((c & 31) << 6) | (c2 & 63))); + result[resIndex++] = String.fromCharCode(((c & 31) << 6) | (c2 & 63)); i += 2; } else { c2 = isArray ? input[i+1] : input.charCodeAt(i+1); c3 = isArray ? input[i+2] : input.charCodeAt(i+2); - result.push(String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63))); + result[resIndex++] = String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); i += 3; }