/** * @name CeL function for compatibility * @fileoverview 本檔案包含了 new ECMAScript standard 標準已規定,但先前版本未具備的內建物件功能;以及相容性 test * 專用的 functions。
* ES6 shim / polyfill
* 部分標準功能已經包含於 ce.js。 * * @see see Set, Map @ _structure/dependency_chain.js * * 注意: 本檔案可能會被省略執行,因此不應有標準之外的設定,應將之放置於 data.native。 * * More examples: see /_test suite/test.js * * @since * @see https://www.audero.it/blog/2016/12/05/monkey-patching-javascript/ * @see https://tc39.es/proposal-collection-methods/ * @see https://github.com/tc39/proposals * @see https://github.com/Financial-Times/polyfill-library/tree/master/polyfills * @see Version Information (Windows Scripting - * JScript) http://espadrine.github.io/New-In-A-Spec/es2017/ */ 'use strict'; // 'use asm'; // -------------------------------------------------------------------------------------------- typeof CeL === 'function' && CeL.run({ // module name name : 'data.code.compatibility', // This module should NOT require other modules! // nothing required. // 本 module 為許多 module 所用,應盡可能勿 requiring 其他 module。 // require : '', // 設定不匯出的子函式。 // no_extend : '*', // 為了方便格式化程式碼,因此將 module 函式主體另外抽出。 code : module_code }); function module_code(library_namespace) { /** * null module constructor * * @class 標準已規定,但先前版本未具備的功能;以及相容性 test 專用的 functions。 */ var _// JSDT:_module_ = function() { // null module constructor }; /** * for JSDT: 有 prototype 才會將之當作 Class */ _// JSDT:_module_ .prototype = {}; // cache var Array_slice = Array.prototype.slice, // cache set_method = library_namespace.set_method, /** * The index return when not found.
* 未發現之index。未找到時的 index。基本上與程式碼設計合一,僅表示名義,不可更改。(=== -1) * * @type {Number} * @constant */ NOT_FOUND = ''.indexOf('_'); // ----------------------------------------------------------------------------------------------------------------------------------------------------------// /** * TODO: * * http://www.comsharp.com/GetKnowledge/zh-CN/It_News_K875.aspx
* 8進制數字表示被禁止, 010 代表 10 而不是 8 * * http://jquerymobile.com/gbs/ */ var main_version = 0, full_version = ''; // for IE/NS only if (typeof window !== 'undefined' && window.ScriptEngine) { library_namespace.debug(library_namespace .is_type(ScriptEngineMajorVersion), 2); main_version = window.ScriptEngineMajorVersion() + '.' + window.ScriptEngineMinorVersion(); full_version = window.ScriptEngine() + ' ' + main_version + '.' + window.ScriptEngineBuildVersion(); main_version = Number(main_version); } else if (false // java test: 加了下面這段在 FF3 會召喚出 java! IE中沒有java object. // old: object, new: function (?) // && (typeof java == 'function' || typeof java == 'object') && java ) { library_namespace.debug("Today is " + java.text.SimpleDateFormat("EEEE-MMMM-dd-yyyy").format( new java.util.Date())); if (main_version = java.lang.System.getProperty('os.name') + ' ' + java.lang.System.getProperty('os.version') + ' ' + java.lang.System.getProperty('os.arch')) full_version = main_version; else main_version = 0; } if (full_version) library_namespace.debug('Script engine: ' + full_version); /** * 版本檢查. * * @param {Number}version * 最低 version */ function check_version(version) { if (!library_namespace.is_digits(version) || version < 5) version = 5; if (typeof WScript !== 'undefined' && WScript.Version < version) { // WScript.FullName, WScript.Path var Locale = library_namespace.env.locale, promptTitle = Locale == 0x411 ? 'アップグレードしませんか?' : '請升級', promptC = Locale == 0x411 ? "今使ってる " + WScript.Name + " のバージョンは古過ぎるから、\nMicrosoft Windows スクリプト テクノロジ Web サイトより\nバージョン " + WScript.Version + " から " + version + " 以上にアップグレードしましょう。" : "正使用的 " + WScript.Name + " 版本過舊,\n請至 Microsoft Windows 網站將版本由 " + WScript.Version + " 升級到 " + version + " 以上。", url = /* Locale==0x411? */"http://www.microsoft.com/japan/developer/scripting/default.htm"; if (1 == WScript.Popup(promptC, 0, promptTitle, 1 + 48)) WshShell.Run(url); WScript.Quit(1); } } // ----------------------------------------------------------------------------------------------------------------------------------------------------------// // global object var globalThis = library_namespace.env.global; if (false && (!globalThis.global || globalThis.global !== globalThis)) { // Object.defineProperty() defined in base.js Object.defineProperty(globalThis, 'global', { configurable : true, enumerable : false, value : globalThis, writable : false }); } if (!globalThis.globalThis || globalThis.globalThis !== globalThis) { // e.g., node-v10.19.0 // Object.defineProperty() defined in base.js Object.defineProperty(globalThis, 'globalThis', { configurable : true, enumerable : false, value : globalThis, writable : false }); } set_method(globalThis, { encodeURI : escape, decodeURI : unescape, encodeURIComponent : encodeURI, decodeURIComponent : decodeURI, isNaN : function(value) { // parseFloat(value) // var a = typeof value == 'number' ? value : parseInt(value); // alert(typeof a + ',' + a + ',' + (a === a)); /** * 變數可以與其本身比較。如果比較結果不相等,則它會是 NaN。原因是 NaN 是唯一與其本身不相等的值。 * * A reliable way for ECMAScript code to test if a value X is a NaN * is an expression of the form X !== X. The result will be true if * and only if X is a NaN. */ // return /* typeof value=='number'&& */a != a; value = Number(value); return value !== value; }, // isFinite(null) === true isFinite : function isFinite(value) { // https://tc39.es/ecma262/#sec-isfinite-number return Number.isFinite(Number(value)); } }, null); // ----------------------------------------------------------------------------------------------------------------------------------------------------------// // Object.* if (typeof Object.freeze === 'function') try { // https://github.com/es-shims/es5-shim/blob/master/es5-sham.js Object.freeze(function() { }); } catch (e) { var Object_freeze = Object.freeze; Object.freeze = function freeze_Object(object) { return typeof object == 'function' ? Object_freeze(object) : object; }; } if (!Object.setPrototypeOf) { var Object_getPrototypeOf, Object_setPrototypeOf; // test prototype chain // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain if (typeof {}.__proto__ === 'object') { // http://ejohn.org/blog/objectgetprototypeof/ // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/GetPrototypeOf // http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/ Object_getPrototypeOf = function(object) { return object.__proto__; }; Object_setPrototypeOf = function(object, prototype) { object.__proto__ = prototype; return object; }; set_method(Object, { getPrototypeOf : Object_getPrototypeOf, setPrototypeOf : Object_setPrototypeOf }); } else if ({}.constructor && {}.constructor.prototype) { Object_getPrototypeOf = function(object) { return object.constructor.prototype; }; Object_setPrototypeOf = function(object, prototype) { object.constructor.prototype = prototype; return object; }; set_method(Object, { getPrototypeOf : Object_getPrototypeOf, setPrototypeOf : Object_setPrototypeOf }); } } // IE 8, JScript 5.8.23141 中,DOM 可能沒有 .hasOwnProperty()。 var hasOwnProperty = Object.prototype.hasOwnProperty // Object.getOwnPropertyDescriptor(object, 'property') || function hasOwnProperty(key) { try { // Object.getPrototypeOf() 返回給定對象的原型。 var prototype = Object.getPrototypeOf(this); return (key in this) // && (!(key in prototype) || this[key] !== prototype[key]); } catch (e) { // TODO: handle exception } }; // Object.keys(): get Object keys, 列出對象中所有可以枚舉的屬性 (Enumerable Only) // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys // 可用來防止 .prototype 帶來之 properties。e.g., @ IE // cf. Object.getOwnPropertyNames() 會列出對象中所有可枚舉以及不可枚舉的屬性 (enumerable or // non-enumerable) function Object_keys(object) { var keys = []; try { for ( var key in object) { if (Object.hasOwn(object, key)) keys.push(key); } } catch (e) { // TODO: handle exception } return keys; } /** * @deprecatred */ function getPropertyNames() { return Object.keys(this); } function getOwnPropertyDescriptor(object, property) { if (Object.hasOwn(object, property)) { return { configurable : true, enumerable : true, value : object[property], writable : true }; } } function getOwnPropertyDescriptors(object) { var descriptors = Object.create(null); // for...in 循環也枚舉原型鏈中的屬性 for ( var property in object) { var descriptor = Object.getOwnPropertyDescriptor(object, property); if (descriptor) descriptors[property] = descriptor; } return descriptors; } // https://tc39.github.io/ecma262/#sec-object.values // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values // http://www.2ality.com/2015/11/stage3-object-entries.html // Object.values() function Object_values(object) { var values = []; for (var keys = Object.keys(object), index = 0, length = keys.length; index < length; index++) { values.push(object[keys[index]]); } return values; return Object.keys(object).map(function(key) { return object[key]; }); } // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries // Object.entries() function Object_entries(object) { var entries = []; for (var keys = Object.keys(object), index = 0, length = keys.length; index < length; index++) { var key = keys[index]; entries.push([ key, object[key] ]); } return entries; return Object.keys(object).map(function(key) { // [ key, value ] return [ key, object[key] ]; }); } // Object.fromEntries() function fromEntries(iterable) { var object = Object.create(null); iterable.forEach(function(pair) { // pair = [ key, value ] object[pair[0]] = pair[1]; }); return object; } set_method(Object, { // 鎖定物件。 // Object.seal() seal : function seal(object) { // 無法以舊的語法實現。 return object; }, // Object.isSealed() isSealed : function isSealed(object) { // 若欲更嚴謹些,可依照經驗法則,避開無法測試的 write-only 物件、或一改變就會產生後續影響的 object, // 並對 object 作變更測試,確保 object 真的具有不可變更之性質。 return false; }, // Object.preventExtensions() preventExtensions : function preventExtensions(object) { // 無法以舊的語法實現。 return object; }, // Object.isExtensible() isExtensible : function isExtensible(object) { return true; }, // Object.freeze() freeze : function freeze(object) { // 無法以舊的語法實現。 return object; }, // Object.isFrozen() isFrozen : function isFrozen(object) { // 無法以舊的語法實現。 return false; }, // Object.is(): Return SameValue(value_1, value_2). // 以 SameValue Algorithm 判斷。 is : function is(value_1, value_2) { return value_1 === value_2 ? value_1 !== 0 // check +0 and -0 || 1 / value_1 // 為 JsDoc 換行。 === 1 / value_2 // check NaN. May use Number.isNaN() as well. : value_1 !== value_1 && value_2 !== value_2; }, // Object.fromEntries() fromEntries : fromEntries, // Object.keys(): get Object keys, 列出對象中所有可以枚舉的屬性 (enumerable only) keys : Object_keys, values : Object_values, entries : Object_entries, // Object.hasOwn(object, property) // https://github.com/tc39/proposal-accessible-object-hasownproperty hasOwn : function hasOwn(object, property) { return hasOwnProperty.call(object, property); }, // Object.getOwnPropertyDescriptor() getOwnPropertyDescriptor : getOwnPropertyDescriptor, // Object.getOwnPropertyDescriptors() getOwnPropertyDescriptors : getOwnPropertyDescriptors, // Object.getOwnPropertyNames() // 會列出對象中所有可枚舉以及不可枚舉的屬性 (enumerable or non-enumerable) // 列出的比 Object.keys() 多 getOwnPropertyNames : Object_keys }); // 會造成幾乎每個使用 for(.. in Object),而不是使用 Object.keys() 的,都出現問題。 if (false) set_method(Object.prototype, { // getPropertyNames : getPropertyNames, hasOwnProperty : hasOwnProperty }); // ----------------------------------------------------------------------------------------------------------------------------------------------------------// // Array.* // Array.prototype.at(), String.prototype.at(), typed_array.at() // https://github.com/tc39/proposal-relative-indexing-method#polyfill function get_item_at(index) { index = ToInteger(index); var length = this.length; // Allow negative indexing from the end if (index < 0) index += length; // incase (a=[])[-3]=3;a.at(-3); if (0 <= index && index < length) { // typeof this === 'object' @ HTA (HTML Application) @ Windows 10 // return this instanceof String ? this.charAt(index) : this[index]; return this.charAt ? this.charAt(index) : this[index]; } } // 稀疏矩陣 (sparse matrix) 用的 Array.prototype.some() // 要到 index > 1e7 比較感覺得出來。 // e.g., // a=[];a[1e7]=2;alert(a.some(function(v){return v===2})); // a=[];a[1e7]=2;alert(a.sparse_some(function(v){return v===2})); function sparse_some(callback, thisArg) { for ( var index in this) if (!isNaN(index)) if (thisArg ? callback.call(thisArg, this[index], index, this) // 不採用 .call() 以加速執行。 : callback(this[index], index, this)) return true; return false; } // 稀疏矩陣 (sparse matrix) 用的 Array.prototype.every() // 要到 index > 1e7 比較感覺得出來。 function sparse_every(callback, thisArg) { for ( var index in this) if (!isNaN(index)) if (!(thisArg ? callback .call(thisArg, this[index], index, this) // 不採用 .call() 以加速執行。 : callback(this[index], index, this))) return false; return true; } // 測試 for ( ... in array ) 時,會依順序提供 index。 if (typeof Array.prototype.some !== 'function') (function() { var array = [], result = []; array[4] = 4; array[2] = 2; array[0] = 0; for ( var index in array) if (!isNaN(index)) result.push(index); // CeL.log(result.join()); if (library_namespace.env.sequenced_Array = result.join() === '0,2,4') set_method(Array.prototype, { sparse_some : sparse_some, sparse_every : sparse_every }); })(); set_method(Array, { // Array.of() of : function of() { return Array_slice.call(arguments); } }); function normalize_position(position, length) { return position < 0 && (position += length) < 0 ? 0 // : (position |= 0) > length ? length : position; } // Array.prototype.copyWithin(target, start[, end = this.length]) function copyWithin(target, start, end) { var length = this.length; start = normalize_position(start, length); end = normalize_position(end || length, length); target = normalize_position(target, length); if (target < start || end <= target) while (start < end) this[target++] = this[start++]; else if (target !== start) { if (length < target + start) { end -= target - start; target = length; } else target += start; // 反向(後→前) copy,確保 copy from 不曾被本操作汙染過。 while (start < end) this[--target] = this[--end]; } return this; } // https://github.com/tc39/proposal-flatMap function flat(depth) { if (depth === undefined) depth = 1; else depth |= 0; var array = []; function push_item(_this, _depth) { _depth--; _this.forEach(function(item) { if (_depth >= 0 && Array.isArray(item)) push_item(item, _depth); else array.push(item); }); } push_item(this, depth); return array; } function flatMap(mapperFunction, thisArg) { if (false) { return this.map( thisArg ? mapperFunction.bind(thisArg) : mapperFunction) .flat(); } var array = []; this.forEach(function(item) { item = thisArg ? mapperFunction.call(thisArg, item) : mapperFunction(item); if (Array.isArray(item)) item.forEach(function(_item) { array.push(_item); }); else array.push(item); }); return array; } set_method(Array.prototype, { /** Array.prototype.copyWithin(target, start [ , end ]) */ copyWithin : copyWithin, // https://github.com/tc39/proposal-relative-indexing-method#polyfill at : get_item_at, // Array.prototype.includes() // part of the Harmony (ECMAScript 7) proposal. // http://www.2ality.com/2016/01/ecmascript-2016.html includes : function Array_includes(search_target, position) { if (search_target === search_target) return this.indexOf(search_target, position) !== NOT_FOUND; // assert: search_target is NaN var length = this.length; // position = normalize_position(position, length); if (position < 0 && (position += length) < 0) position = 0; else if ((position |= 0) > length) position = length; for (; position < length; position++) if (this[position] !== this[position]) return true; return false; }, // Array.prototype.reduce() reduce : function reduce(callbackfn/* , initialValue */) { var index = 0, length = this.length, value; if (arguments.length > 1) value = arguments[1]; else // initialValue for (; index < length; index++) if (index in this) { value = this[index++]; break; } for (; index < length; index++) if (index in this) value = callbackfn(value, this[index], index, this); return value; }, // Array.prototype.reduceRight() reduceRight : function reduceRight(callbackfn // , initialValue ) { var index = this.length, value; if (arguments.length > 1) value = arguments[1]; else // initialValue while (index-- > 0) if (index in this) { value = this[index]; break; } while (index-- > 0) if (index in this) value = callbackfn(value, this[index], index, this); return value; }, // Array.prototype.entries() entries : function entries() { // new Array_Iterator(array, use value) return new library_namespace.Array_Iterator(this); }, // Array.prototype.values() values : function values() { // new Array_Iterator(array, use value) return new library_namespace.Array_Iterator(this, true); }, // Array.prototype.keys() keys : function Array_keys() { var keys = []; for ( var key in this) if (/^\d+$/.test(key)) keys.push(key | 0); library_namespace.debug('keys: ' + keys, 5, // 'Array.prototype.keys'); return new library_namespace.Array_Iterator(keys, true); }, // Array.prototype.findIndex() findIndex : function findIndex(predicate, thisArg) { for (var index = 0, length = this.length; index < length; index++) if (index in this) if (thisArg ? predicate.call(thisArg, this[index], index, this) // 不採用 .call() 以加速執行。 : predicate(this[index], index, this)) return index; return NOT_FOUND; }, // Array.prototype.find() find : function find(predicate, thisArg) { var index = this.findIndex(predicate, thisArg); if (index !== NOT_FOUND) return this[index]; // return undefined; }, // https://github.com/tc39/proposal-array-find-from-last // Array.prototype.findLastIndex() findLastIndex : function findLastIndex(predicate, thisArg) { for (var index = this.length; index > 0;) if (--index in this) if (thisArg ? predicate.call(thisArg, this[index], index, this) // 不採用 .call() 以加速執行。 : predicate(this[index], index, this)) return index; return NOT_FOUND; }, // Array.prototype.findLast() findLast : function findLast(predicate, thisArg) { var index = this.findLastIndex(predicate, thisArg); if (index !== NOT_FOUND) return this[index]; // return undefined; }, // Array.prototype.some() some : function some(callback, thisArg) { for (var index = 0, length = this.length; index < length; index++) if (index in this) if (thisArg ? callback.call(thisArg, this[index], index, this) // 不採用 .call() 以加速執行。 : callback(this[index], index, this)) return true; return false; }, // Array.prototype.every() every : function every(callback, thisArg) { for (var index = 0, length = this.length; index < length; index++) if (index in this) if (!(thisArg ? callback.call(thisArg, this[index], index, this) // 不採用 .call() 以加速執行。 : callback(this[index], index, this))) return false; return true; }, // Array.prototype.map() map : function map(callback, thisArg) { var result = []; this.forEach(function() { result.push(callback.apply(this, arguments)); }, thisArg); return result; }, // Array.prototype.filter() filter : function map(callback, thisArg) { var result = []; this.forEach(function(value) { if (callback.apply(this, arguments)) result.push(value); }, thisArg); return result; }, // Array.prototype.flat flat : flat, // Array.prototype.flatMap flatMap : flatMap, // Array.prototype.indexOf ( searchElement [ , fromIndex ] ) indexOf : function indexOf(searchElement, fromIndex) { fromIndex |= 0; var length = this.length; if (fromIndex < 0 && (fromIndex += length) < 0) fromIndex = 0; for (; fromIndex < length; fromIndex++) if (searchElement === this[fromIndex]) return fromIndex; return NOT_FOUND; }, // Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] ) lastIndexOf : function lastIndexOf(searchElement, fromIndex) { var length = this.length; if (isNaN(fromIndex)) fromIndex = length - 1; else if ((fromIndex |= 0) < 0) fromIndex += length; else fromIndex = Math.min(fromIndex, length - 1); for (; 0 <= fromIndex; fromIndex--) if (searchElement === this[fromIndex]) return fromIndex; return NOT_FOUND; }, // Array.prototype.fill() fill : function fill(value, start, end) { // Array.prototype.fill() 只會作用於 0~原先的 length 範圍內! if (isNaN(end) || this.length < (end |= 0)) end = this.length; else if (end < 0) end += this.length; for (var index = start || 0; index < end;) this[index++] = value; return this; }, /** * 對於舊版沒有 Array.push() 等函數時之判別及處置。
* 不能用t=this.valueOf(); ... this.push(t); */ // Array.prototype.push() push : function push() { var i = 0, l = arguments.length, w = this.length; // 在 FF3 僅用 this[this.length]=o; 效率略好於 Array.push(), // 但 Chrome 6 相反。 for (; i < l; i++) this[w++] = arguments[i]; return w; }, // Array.prototype.pop() pop : function pop() { // 不能用 return this[--this.length]; var l = this.length, v; if (l) { v = this[l]; this.length--; } return v; }, // Array.prototype.shift() shift : function shift() { var v = this[0]; // ECMAScript 不允許設定 this= this.value = this.slice(1); return v; }, // Array.prototype.unshift() unshift : function unshift() { // ECMAScript 不允許設定 this = this.value = Array_slice.call(arguments).concat(this); return this.length; } }, null); // ----------------------------------------------------------------------------------------------------------------------------------------------------------// // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays // It's hard to fully simulate typed arrays. // 只能把原先即存在的功能加強到可用。 var typed_arrays = []; if (typeof Uint32Array === 'function') (function() { var Array_prototype = Array.prototype, // TODO: .slice() and others typed_array_methods = 'copyWithin,entries,every,fill,filter,find,findIndex,forEach,includes,indexOf,join,keys,lastIndexOf,map,reduce,reduceRight,reverse,set,slice,some,subarray,values,at' .split(','); 'Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array' // .split(',').forEach(function(typed_array) { try { typed_array = eval(typed_array); } catch (e) { library_namespace.warn('Not exist: ' + typed_array); return; } var prototype = typed_array.prototype; typed_array_methods.forEach(function(method) { if (!(method in typed_array.prototype)) Object.defineProperty(prototype, method, { value : Array_prototype[method] }); }); typed_arrays.push(typed_array); }); })(); if (typed_arrays.length === 0) typed_arrays = null; _.typed_arrays = typed_arrays; // ----------------------------------------------------------------------------------------------------------------------------------------------------------// // Number.* // 1. === 1.0 function ToNumber(value) { // Number(value) return +value; } // cf. value | 0 /** ((Number.MAX_SAFE_INTEGER / 4) | 0) < 0, 0 < ((Number.MAX_SAFE_INTEGER / 5) | 0) */ function ToInteger(value) { return Math.trunc(value) || 0; // return value >> 0; // https://tc39.es/ecma262/#sec-tointeger value = Number(value); return value > 0 ? Math.floor(value) : value < 0 ? -Math.floor(value) : 0; } // Number.isNaN() // http://wiki.ecmascript.org/doku.php?id=harmony:number.isnan function is_NaN(value) { return typeof value === 'number' && // isNaN(value) value !== value; } // calculatable /** * 取得最小最低可做除法計算數值。回傳值為邊界,已不可再做操作。但可做乘法操作。 * * @param [base_integral] * @returns {Number} */ function dividable_minimum(base_integral, return_last) { if (!base_integral || isNaN(base_integral)) base_integral = 1; var last = 1, min; // 預防 min 變成0,因此設置前一步 last。 while (base_integral !== base_integral + (min = last / 2)) last = min; return !return_last && min || last; } // calculatable /** * 取得最大可做加法計算數值。回傳值為邊界,已不可再做加法操作。但可做減法操作。 * * @param [base_integral] * @returns {Integer} */ function addable_maximum(base_integral) { if (!base_integral || isNaN(base_integral)) base_integral = 1; var max = 1, test; while ((max *= 2) < (test = max + base_integral) && max === test - base_integral) ; return max; } var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || addable_maximum() - 1; // Number.isSafeInteger() function isSafeInteger(number) { return typeof number === 'number' // && number <= MAX_SAFE_INTEGER && -MAX_SAFE_INTEGER <= number && Math.abs(number) <= MAX_SAFE_INTEGER // 在範圍外的,常常 % 1 亦為 0。 && 0 === number % 1 // && Math.floor(number) === number ; } set_method(Number, { /** * The value of Number.MAX_SAFE_INTEGER is the largest integer value * that can be represented as a Number value without losing precision, * which is 9007199254740991 (2^53-1). */ MAX_SAFE_INTEGER : MAX_SAFE_INTEGER, /** * The value of Number.MIN_SAFE_INTEGER is -9007199254740991 * (-(2^53-1)). */ MIN_SAFE_INTEGER : -MAX_SAFE_INTEGER, /** * The value of Number.EPSILON is the difference between 1 and the * smallest value greater than 1 that is representable as a Number * value, which is approximately 2.2204460492503130808472633361816 x * 10-16. */ EPSILON : dividable_minimum(0, 1) }, 'number'); set_method(Number, { isSafeInteger : isSafeInteger, /** * Number.toInteger() is obsolete. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toInteger * Number.toInteger was part of the draft ECMAScript 6 specification, * but has been removed on August 23, 2013 in Draft Rev 17. */ // toInteger: ToInteger, /** * Number.isInteger()
* cf. .is_digits() */ isInteger : function isInteger(number) { // https://tc39.es/ecma262/#sec-number.isinteger return Number.isFinite(number) // Math.floor(Infinity) === Infinity && ToInteger(number) === number; }, parseFloat : parseFloat, parseInt : parseInt, /** * Number.isNaN() */ isNaN : is_NaN, isFinite : function isFinite(value) { return typeof value === 'number' && !isNaN(value) && value !== Infinity && value !== -Infinity; } }); // ----------------------------------------------------------------------------------------------------------------------------------------------------------// // Math.* // https://rwaldron.github.io/proposal-math-extensions/ // 32 bits var BITS = 1; while (1 !== 1 << BITS) BITS <<= 1; // assert: BITS === 32 // Math.clz32() // TODO: 增進效率。 function clz32(value) { // ToUint32() ?? value >>>= 0; // console.log(value + ' = ' + value.toString(2) + ' (2)'); // binary search: 計算數字本身具有的 bits. for (var min = 0, MAX = BITS, zeros;;) { zeros = (min + MAX) >> 1; // console.log(min + ' - ' + zeros + ' - ' + MAX); if (0 === value >> zeros) if (MAX === zeros) break; else MAX = zeros; else if (min === zeros) break; else min = zeros; } return BITS - MAX; } // 分界 var Math_hypot_up_boundary = Math.sqrt(Number.MAX_SAFE_INTEGER) / 2 | 0, // Math_hypot_down_boundary = Math.sqrt(Number.MIN_VALUE); // Math.hypot(value_1 , value_2, value_3 = 0) // TODO: 增進效率。 // http://en.wikipedia.org/wiki/Hypot function hypot(value_1, value_2, value_3) { var r, MAX = Math.max(value_1 = Math.abs(value_1), // 轉正 value_2 = Math.abs(value_2), // value_3 = value_3 === undefined ? 0 : Math.abs(value_3)); if (!MAX || !Number.isFinite(MAX)) return MAX; if (MAX < Math_hypot_up_boundary // avoid underflow && Math_hypot_down_boundary < Math.min(value_1, value_2, value_3) // avoid overflow, minimise rounding errors (預防本該為整數的出現小數). && Number.isFinite(r = value_1 * value_1 + value_2 * value_2 + value_3 * value_3)) return Math.sqrt(r); return MAX * Math.sqrt((value_1 ? (value_1 /= MAX) * value_1 : 0) + (value_2 ? (value_2 /= MAX) * value_2 : 0) + (value_3 ? (value_3 /= MAX) * value_3 : 0)); } set_method(Math, { LN2 : Math.log(2), LN10 : Math.log(10) }, 'number'); var expm1_error = 1e-5; set_method(Math, { expm1 : function expm1(value) { // If x is −0, the result is −0. if (!value) return value; // 在這範圍外不會有誤差。 // x = 1e-5; Math.expm1(x) === Math.exp(x) - 1 if (Math.abs(value) > expm1_error) return Math.exp(value) - 1; // 提高精準度。 // x = 1e-6; Math.expm1(x) > Math.exp(x) - 1 // http://www.wolframalpha.com/input/?i=e^x-1 // Taylor series: x+x^2/2+x^3/6+x^4/24+x^5/120+O(x^6) var delta = value, index = 1, result = value; // 頂多跑個 2~3 次就該結束了。 while (result + (delta *= value / ++index) !== result) result += delta; return result; }, // hyperbolic functions // https://en.wikipedia.org/wiki/Hyperbolic_function sinh : function sinh(value) { // If x is −0, the result is −0. return value ? Math.abs(value) > expm1_error // ? (Math.exp(value) - Math.exp(-value)) / 2 // : (Math.expm1(value) - Math.expm1(-value)) / 2 // : value; }, cosh : function cosh(value) { // If x is −0, the result is −0. return value ? (value = Math.abs(value)) < 19 // ? (Math.exp(value) + Math.exp(-value)) / 2 // value = 19; // Math.exp(value) + Math.exp(-value) === Math.exp(value); : Math.exp(value) / 2 : value; }, tanh : function tanh(value) { if (!value) // If x is −0, the result is −0. return value; // value = 19; // Math.exp(value) + Math.exp(-value) === Math.exp(value); if (Math.abs(value) > 19) // If x is +∞, the result is +1. return value > 0 ? 1 : -1; var e = Math.exp(value), me = Math.exp(-value); return (Math.abs(value) < expm1_error // 提高精準度。 ? Math.expm1(value) - Math.expm1(-value) : e - me) / (e + me); }, // https://en.wikipedia.org/wiki/Hyperbolic_function#Inverse_functions_as_logarithms // inverse hyperbolic function // https://en.wikipedia.org/wiki/Inverse_hyperbolic_function asinh : function asinh(value) { // If x is −0, the result is −0. if (!value) return value; // http://www.wolframalpha.com/input/?i=asinh+x var v = Math.abs(value); v = Math.log(v + Math.sqrt(v * v + 1)); return value < 0 ? -v : v; }, acosh : function acosh(value) { // http://www.wolframalpha.com/input/?i=acosh+x return Math.log(value + Math.sqrt(value * value - 1)); }, atanh : function atanh(value) { // If x is −0, the result is −0. return value ? Math.log((1 + value) / // 為 JsDoc 換行。 (1 - value)) / 2 : value; }, // Math.log2() log2 : function log2(value) { // return Math.log(value) / Math.LN2; return Math.log(value) * Math.LOG2E; }, // Math.log10() log10 : function log10(value) { // 採用 /Math.LN10 會造成 Math.log10(1e-12) !== -12 // return Math.log(value) / Math.LN10; return Math.log(value) * Math.LOG10E; }, log1p : function log1p(value) { // If x is −0, the result is −0. return value ? Math.log(1 + value) : value; }, hypot : hypot, cbrt : function cbrt(value) { // If x is −0, the result is −0. return value ? Math.pow(value, 1 / 3) : value; }, clz32 : clz32, // Math.trunc() trunc : function trunc(value) { // value >= 0 ? Math.floor(value) : Math.ceil(value) return value > 0 ? Math.floor(value) // : value < 0 ? Math.ceil(value) // : value === 0 ? value : isNaN(value) ? NaN // null, true, false, .. : value | 0; }, sign : function sign(value) { // If x is −0, the result is −0. return 0 < value ? 1 : value < 0 ? -1 : value; } }); // ----------------------------------------------------------------------------------------------------------------------------------------------------------// // RegExp.* // 注意: 因為要設定額外功能,RegExp.prototype.flags 放在 data.native // ----------------------------------------------------------------------------------------------------------------------------------------------------------// // String.* /** * bug fix (workaround) for String.prototype.split(): * * @see https://github.com/es-shims/es5-shim/blob/master/es5-shim.js * http://blog.stevenlevithan.com/archives/cross-browser-split * http://blog.stevenlevithan.com/archives/fixing-javascript-regexp * * @since 2010/1/1 19:03:40 2015/1/28 17:43:0 refactoring 重構 * * @example // More examples: see /_test suite/test.js * */ if ( // [ "", "" ] @ IE8, [ "", "_", "" ] @ firefox 38+ '_'.split(/(_)/)[1] !== '_' // 理論上 '.'.split(/\./).length 應該是 2,但 IE 5–8 中卻為 0! // 用 .split('.') 倒是 OK. // || '.'.split(/\./).length === 0 ) (function() { var native_String_split = // 增加可以管控與回復的手段,預防有時需要回到原有行為。 library_namespace.native_String_split = String.prototype.split; // The length property of the split method is 2. (String.prototype.split = function(separator, limit) { if (!library_namespace.is_RegExp(separator)) return native_String_split.apply(this, arguments); // 不改變 separator 本身,加上 .global flag。 separator = new RegExp(separator.source, (separator.global ? '' : 'g') // should use: RegExp_flags(separator) + ('' + separator).match(/[^\/]*$/)[0]); var matched, result = [], last_index = 0; if (0 <= limit) // ToLength(), ToUint32() limit >>>= 0; else // Math.pow(2, 32) - 1 limit = -1 >>> 0; while (result.length < limit && last_index < this.length) { matched = separator.exec(this); if (!matched) { if (false) library_namespace.debug('push (last) [' + this.slice(last_index) + ']'); result.push(this.slice(last_index)); break; } if (false) { library_namespace.warn('index: ' + last_index + '-' + matched.index + '-' + separator.lastIndex + ' (' + matched[0].length + ')' + ' [' + this.slice(0, last_index) + '' + this.slice(last_index, separator.lastIndex) + '' + this.slice(separator.lastIndex) + ']'); library_namespace.log('matched 1 [' + matched.join(';') + ']'); } if (false && library_namespace.show_value) { library_namespace.show_value(matched, 'matched'); library_namespace.show_value(result.slice(), 'result'); } // 當有東西時才登記。 if (last_index < matched.index || matched[0]) { result.push(this.slice(last_index, matched.index)); if (false) library_namespace.debug('push [' + this.slice(last_index, matched.index) + ']'); if (matched.length > 1 && matched.index < this.length) Array.prototype.push .apply(result, matched.slice(1)); // IE 中,匹配到 null string 時,lastIndex 會自動 +1。 // /(,?)/g.exec('1') 之 lastIndex = 1, last_index = matched.index + matched[0].length; if (false) library_namespace.debug('last_index: [' + last_index + ']'); if (last_index === this.length) { if (matched[0]) { if (false) library_namespace.debug('push null (last)'); result.push(''); } break; } } else if (false) library_namespace.debug('Skip this.'); if (separator.lastIndex === matched.index) // 避免無窮迴圈。 separator.lastIndex++; if (false && library_namespace.show_value) { library_namespace .info('lastIndex: ' + separator.lastIndex + ', next from: [' + this.slice(0, separator.lastIndex) + '|' + this.slice(separator.lastIndex) + ']'); library_namespace.log('matched 2 [' + matched.join(';') + ']'); library_namespace.log('result [' + result.join(';') + ']'); } } return result; })[library_namespace.env.not_native_keyword] = true; })(); // String.prototype.repeat() // in VB: String(count, this) // “x” operator @ perl // http://wiki.ecmascript.org/doku.php?id=harmony:string.prototype.repeat function repeat(count) { var result = [], /** * The repeat function is intentionally generic * https://mail.mozilla.org/pipermail/es-discuss/2011-January/012538.html * Trivia: ""+obj is not the same thing as ToString(obj). They differ if * obj has a .valueOf method. */ piece = '' + this; if (!piece || isNaN(count) || (count = Math.floor(count)) < 1) return ''; // https://mail.mozilla.org/pipermail/es-discuss/2011-January/012525.html // If ToUint32(`amount) is not equal to `amount, throw a RangeError. // isFinite() if (count >>> 0 !== count) throw new Error("invalid repeat argument"); // http://stackoverflow.com/questions/202605/repeat-string-javascript // 此法較 (new Array( count + 1 ).join(this)) 稍快。 while (true) { library_namespace.debug('left: ' + count, 3, 'String.prototype.repeat'); if (count & 1) result.push(piece); if (count >>>= 1) piece += piece; else break; } return result.join(''); } /** * 2010/6/1 test time: ' fhdgjk lh gjkl ;sfdf d hf gj ' .replace(/^\s+|\s+$/g, '') ~< .replace(/\s+$|^\s+/g, '') < .replace(/^\s+/, '').replace(/\s+$/, '') ~< .replace(/\s+$/, '').replace(/^\s+/, '') */ /** * 去除首尾空白。去除前後空白。去頭去尾。去掉 string 前後 space. * * @param {String} * string input string * @return {String} 轉換過的 string * @since 2006/10/27 16:36 * @see from lib/perl/BaseF.pm (or program/database/BaseF.pm)
* function strip() @ Prototype JavaScript framework * * String.prototype.trim() * http://stackoverflow.com/questions/1418050/string-strip-for-javascript * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim * http://blog.stevenlevithan.com/archives/faster-trim-javascript * * @_memberOf _module_ */ function trim() { // The repeat function is intentionally generic var string = String(this) // https://blog.stevenlevithan.com/archives/faster-trim-javascript .replace(/^\s\s*/, ''); var PATTERN_SPACE = /\s/, index = string.length; while (PATTERN_SPACE.test(string.charAt(--index))) ; return string.slice(0, index + 1); // .replace(/\s+$|^\s+/g, ''); // .replace(/^\s+|\s+$/g, ''); // The definition of white space is the union of WhiteSpace and // LineTerminator. // .replace(/[\s\n]+$|^[\s\n]+/g, ''); } // String.prototype.trimStart(), String.prototype.trimLeft() var trimStart = String.prototype.trimLeft || function trimStart() { return String(this).replace(/^[\s\n]+/, ''); }; // String.prototype.trimEnd(), String.prototype.trimRight() var trimEnd = String.prototype.trimRight || function trimEnd() { return String(this).replace(/[\s\n]+$/, ''); }; // --------------------------------------------------------------------------- // http://tc39.github.io/proposal-string-pad-start-end/ // https://github.com/tc39/proposal-string-pad-start-end function pad_String(/* targetLength */maxLength, /* padString */ fillString) { if (!(this.length < maxLength)) { return ''; } if (fillString === undefined) { fillString = ' '; } else if (!(fillString = String(fillString))) { return ''; } maxLength -= this.length; if (fillString.length < maxLength) { fillString = fillString.repeat(Math.ceil(maxLength / fillString.length)); } return fillString.length === maxLength ? fillString // assert: fillString.length > maxLength : fillString.slice(0, maxLength); } // --------------------------------------------------------------------------- // String.prototype.startsWith() function startsWith(searchString, position) { if (library_namespace.is_RegExp(searchString)) throw new Error( "Invalid type: searchString can't be a Regular Expression"); searchString = String(searchString); if (!position || !(position |= 0) || position < 0) return this.lastIndexOf(searchString, 0) === 0; return searchString === this.substr(position, searchString.length); } // String.prototype.endsWith() function endsWith(searchString, endPosition) { if (library_namespace.is_RegExp(searchString)) throw new Error( "Invalid type: searchString can't be a Regular Expression"); searchString = String(searchString); var is_tail = endPosition === undefined || (endPosition |= 0) === this.length, // position = (is_tail ? this.length : endPosition) - searchString.length; return position >= 0 && (is_tail ? this.indexOf(searchString, position) === position : searchString === this.substr(position, searchString.length)); } // String.prototype.codePointAt(position) // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt function codePointAt(index) { var _1 = this.charCodeAt(index), _2; if (index + 1 < this.length // check high surrogate && _1 >= 0xD800 && _1 <= 0xDBFF // check low surrogate && (_2 = this.charCodeAt(index + 1)) >= 0xDC00 && _2 <= 0xDFFF) { return (_1 - 0xD800) * 0x400 + _2 - 0xDC00 + 0x10000; } return _1; } // String.fromCodePoint(...codePoints) // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint function fromCodePoint() { var result = ''; for (var index = 0, length = arguments.length; index < length; index++) { var char_code = +arguments[index]; if (char_code < 0x10000) { result += String.fromCharCode(char_code); } else { char_code -= 0x10000; result += String.fromCharCode((char_code >> 10) + 0xD800, (char_code % 0x400) + 0xDC00); } } return result; } // ------------------------------------------ // @see CeL.data.native.RegExp_flags() var has_RegExp_flags = /./g.flags === 'g'; // Generator function var has_Generator; (function() { var g, i, l; try { eval('g = function*() { yield 2;yield 1; }; l = []; for(i of g()) l.push(i); l = l.join();'); has_Generator = l === '2,1'; } catch (e) { } })(); /** * return a new RegExp instance with global flag */ function new_global_RegExp(regexp) { return library_namespace.is_RegExp(regexp) // do NOT chancg regexp.lastIndex ? new RegExp(regexp.source, has_RegExp_flags ? regexp.global ? regexp.flags : regexp.flags + 'g' : regexp.ignoreCase ? 'ig' : 'g') // : new RegExp(regexp, 'g'); } _.new_global_RegExp = new_global_RegExp; // String.prototype.matchAll() // http://2ality.com/2018/02/string-prototype-matchall.html // https://tc39.github.io/proposal-string-matchall/ // let all_matched = [...string.matchAll(regExp)].map(mapfn); // let all_matched = Array.from(string.matchAll(regExp), mapfn); var matchAll; if (has_Generator) { // e.g., node.ja 11.9.0, Chrome/61.0.3163.100 Electron/2.0.9 // TODO: returns an RegExpStringIterator // String.prototype.matchAll 調用 RegExp.prototype[Symbol.matchAll] eval('matchAll = function* matchAll(regexp) { const is_global = !library_namespace.is_RegExp(regexp) || regexp.global; regexp = new_global_RegExp(regexp); let matched; if (is_global) { while (matched = regexp.exec(this)) { yield matched; } } else { throw new TypeError("String.prototype.matchAll called with a non-global RegExp argument"); if (matched = regexp.exec(this)) { yield matched; } } }'); } // ------------------------------------------ set_method(String.prototype, { repeat : repeat, trim : trim, trimStart : trimStart, trimEnd : trimEnd, // String.prototype.padStart() padStart : function padStart(maxLength, fillString) { return pad_String.call(this, maxLength, fillString) + this; }, // String.prototype.padEnd() padEnd : function padEnd(maxLength, fillString) { return this + pad_String.call(this, maxLength, fillString); }, // String.prototype.includes() includes : function includes(searchString, position) { return this.indexOf(searchString, position) !== NOT_FOUND; }, startsWith : startsWith, endsWith : endsWith, matchAll : matchAll || function matchAll_array(regexp) { /* const */var is_global = !library_namespace.is_RegExp(regexp) // || regexp.global; /* const */regexp = new_global_RegExp(regexp); var matched, list = []; if (is_global) { while (matched = regexp.exec(this)) { // yield matched; list.push(matched); } } else if (matched = regexp.exec(this)) { // e.g., 'a1b2A3B4a5cc'.matchAll(/(a)(.)/ig) list.push(matched); } return list; }, at : get_item_at, codePointAt : codePointAt }); set_method(String, { fromCodePoint : fromCodePoint }); // ----------------------------------------------------------------------------------------------------------------------------------------------------------// // Date.* function pad_00(integer) { return integer < 10 ? '0' + integer : integer; } // Date.prototype.toISOString() for IE8 // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString function toISOString() { var year = this.getUTCFullYear(), m = year < 0; if (m) year = -year; if (year < 1000) year = (year < 100 ? year < 10 ? '000' : '00' : '0') + year; if (m) year = '-' + year; m = this.getUTCMilliseconds(); if (m < 100) m = (m < 10 ? '00' : '0') + m; return year + '-' + pad_00(this.getUTCMonth() + 1) + '-' + pad_00(this.getUTCDate()) + 'T' + pad_00(this.getUTCHours()) + ':' + pad_00(this.getUTCMinutes()) + ':' + pad_00(this.getUTCSeconds()) + '.' + m + 'Z'; } set_method(Date, { // Date.UTC() UTC : function UTC(year, month, day, hour, minute, second, // millisecond) { var date = new Date(year || 0, month || 0, isNaN(day) ? 1 : day, hour || 0, minute || 0, second || 0, millisecond || 0); return date.getTime() - 60 * 1000 * date.getTimezoneOffset(); }, // Date.now() now : function now() { return new Date().getTime(); }, // Date.parse() parse : function parse(string) { return new Date(string).getTime(); } }); set_method(Date.prototype, { // Date.prototype.toJSON() toJSON : function toJSON() { return this.toISOString(); }, // Date.prototype.toISOString() toISOString : toISOString }); // ----------------------------------------------------------------------------------------------------------------------------------------------------------// // JSON.* /** * 2008/12/21 18:53:42
* value to json
* JavaScript Object Notation ECMA-262 3rd Edition * * http://stackoverflow.com/questions/1500745/how-to-pass-parameters-in-eval-in-an-object-form * json={name:'~',values:..,description:'~'}
* window[json.name].apply(null, json.values) * * usage:
* json(value) * * parse: // 字串的前後記得要加上刮號 (),這是用來告知 Javascript Interpreter 這是個物件描述,不是要執行的 statement。 data=eval('('+data+')'); eval('data='+data); * * TODO: * * useObj
* 加入function object成員,.prototype可用with()。加入函數相依性(dependency) * * array用name: (function(){ var o; o = [...]; var i, v = {...}; for(i in v) o[i] = v[i]; return o; })() * * recursion 循環參照 (function(){ var o; o = {a:[]}; o['b'] = [o['a']], o['a'].push([o['b']]); return o; })() * * BUG:
* function 之名稱被清除掉了,這可能會產生問題! (function(){ var f=function(){...}; f.a=...; f.b=...; f.prototype={ a:..., b:... } return f; })() * * test recursion 循環參照:
* function 之名稱被清除掉了,這可能會產生問題! (function(){ var o=[], _1=[o]; o.push(_1); return o; })(); var a=[], b; a.push(b=[a]); json(a); */ // json[generateCode.dLK]='qNum,dQuote'; /** * 須判別來源是否為 String or Number! * * @deprecated 改用 window.JSON, jQuery.parseJSON. * @param val * @param name * @param type * type==2: inside object, treat undefined as '' * @returns */ function json(val, name, type) { var _f = json, expA = [], expC = [], vType = typeof val, addE = function( o, l, n) { if (l) { o = _f(o, 0, 2); n = typeof n == 'undefined' || n === '' ? '' : (/^(\d{1,8})?(\.\d{1,8})?$/.test(n) || /^[a-z_][a-z_\d]{0,30}$/i.test(n) ? n : dQuote(n)) + ':' + _f.separator; expA.push(n, o[1]); // expC.push(_f.indentString+n+o[0].join(_f.line_separator+_f.indentString)+','); o = o[0]; o[0] = n + (typeof o[0] == 'undefined' ? '' : o[0]); o[o.length - 1] += ','; for (var i = 0; i < o.length; i++) o[i] = _f.indentString + (typeof o[i] == 'undefined' ? '' : o[i]); expC = expC.concat(o); } else expA.push(o), expC.push(o); } // 去掉最後一組的 ',' 並作結。 , closeB = function(c) { var v = expC.at(-1); if (v.charAt(v.length - 1) == ',') expC[expC.length - 1] = v.slice(0, v.length - 1); addE(c); }; switch (vType) { case 'number': // http://msdn2.microsoft.com/zh-tw/library/y382995a(VS.80).aspx // isFinite(value) ? String(value) var k = 0, m = 'MAX_VALUE,MIN_VALUE,NEGATIVE_INFINITY,POSITIVE_INFINITY,NaN' .split(','), t = 0; if (val === NaN || val === Infinity || val === -Infinity) t = '' + val; else for (; k < m.length; k++) if (val === Number[m[k]]) { t = 'Number.' + m[k]; break; } if (!t) { // http://msdn2.microsoft.com/zh-tw/library/shydc6ax(VS.80).aspx for (k = 0, m = 'E,LN10,LN2,LOG10E,LOG2E,PI,SQRT1_2,SQRT2' .split(','); k < m.length; k++) if (val === Math[m[k]]) { t = 'Math.' + m[k]; break; } if (!t) if (k = ('' + val).match(/^(-?\d*[1-9])(0{3,})$/)) t = k[1] + 'e' + k[2].length; else { // 有理數判別 k = qNum(val); // 小數不以分數顯示. m==1:非分數 m = k[1]; while (m % 2 == 0) m /= 2; while (m % 5 == 0) m /= 5; t = k[2] == 0 && m != 1 ? k[0] + '/' + k[1] : // TODO: 加速(?) (t = Math.floor(val)) == val && ('' + t).length > (t = '0x' + val.toString(16)).length ? t : val; } } addE(t); break; case 'null': addE('' + val); break; case 'boolean': addE(val); break; case 'string': addE(dQuote(val)); break; case 'undefined': addE(type == 2 ? '' : 'undefined'); break; case 'function': // 加入function // object成員,.prototype可用with()。加入函數相依性(dependency) var toS, f; // 這在多執行緒有機會出問題! if (typeof val.toString != 'undefined') { toS = val.toString; delete val.toString; } f = '' + val; if (typeof toS != 'undefined') val.toString = toS; // function 才會產生 \r\n 問題,所以先處理掉 f = f.replace(/\r?\n/g, _f.line_separator); var r = /^function\s+([^(\s]+)/, m = f.match(r), t; if (m) m = m[1], addE('// function [' + m + ']'), t = f.replace(r, 'function' + _f.separator); if (m && t.indexOf(m) !== NOT_FOUND) library_namespace.error('function [' + m + '] 之名稱被清除掉了,這可能會產生問題!'); addE(t || f); // UNDO break; case 'object': try { if (val === null) { addE('' + val); break; } var c = val.constructor; if (c === RegExp) { addE(val); break; } // typeof val.getTime=='function' if (c == Date || vType == 'date') { // 與 now 相隔過短(<1e7, 約3h)視為 now。 // 但若是 new Date()+3 之類的會出現誤差! addE('new Date' // date 被當作 object + ((val - new Date) > 1e7 ? '(' + val.getTime() + ')' : '')); break; } if (('' + c).indexOf('Error') !== NOT_FOUND) { addE('new Error' + (val.number || val.description ? '(' + (val.number || '') + (val.description ? (val.number ? ',' : '') + dQuote(val.description) : '') + ')' : '')); break; } var useObj = 0; if (c == Array) { var i, l = 0; if (!_f.forceArray) for (i in val) if (isNaN(i)) { useObj = 1; break; } else l++; if (_f.forceArray || !useObj && l > val.length * .8) { addE('['); for (i = 0; i < val.length; i++) addE(val[i], 1); closeB(']'); break; } else useObj = 1; } if (useObj || c == Object) {// instanceof addE('{'); for ( var i in val) addE(val[i], 1, i); closeB('}'); break; } addE(dQuote(val)); break; } catch (e) { if (28 == (e.number & 0xFFFF)) alert('json: Too much recursion?\n循環參照?'); return; } case 'unknown': // sometimes we have this kind of type default: alert('Unknown type: [' + vType + '] (constructor: ' + val.constructor + '), please contract me!\n' + val); break; // alert(vType); } return type ? [ expC, expA ] : expC.join(_f.line_separator); } // dependency List Key json.dL = 'dependencyList'; json.forceArray = 1; json.indentString = ' '; json.line_separator = '\n'; json.separator = ' '; // -------------------------------------------- // JSON polyfill // 2021: library_namespace.parse_JSON() @ _structure/module.js function JSON_parse(text, reviver) { function parse_next_value() { /** * [... {... "... 00000 null */ var matched = text.match(/^\s*([\[{"]|null|\d+)/); if (!matched) { // SyntaxError return; } text = text.slice(/* matched.index(=0) + */matched[0].length); if (matched[1] === 'null' || /^\d/.test(matched[1])) { return matched[1] === 'null' ? null : +matched[1]; } // https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/JSON if (matched[1] === '"') { matched = text.match(/^(?:\\"|[^"])*"/); text = text.slice(matched[0].length); return matched[0].slice(0, -1) // .replace(/\\u([0-9a-f]{4})/ig, function(code) { return String.fromCharCode(parseInt(code, 16)); }).replace(/\\([\s\S]|$)/g, function(escape_char) { // /\\(["\\\/\b\f\t\r\n])/g var escaped = { '"' : '"', '\\' : '\\', b : '\b', f : '\f', t : '\t', r : '\r', n : '\n' }[escape_char]; if (!escaped) { throw new SyntaxError('Unexpected token \\' // Invalid JSON + escape_char + ' in JSON'); } return escaped }); } if (matched[1] === '[') { var array = []; matched = text.match(/^\s*\]/); if (matched) { // "{}" text = text.slice(matched[0].length); return array; } while (true) { var next_value = parse_next_value(); if (next_value === undefined) return; array.push(next_value); matched = text.match(/^\s*([,\]])/); if (!matched) { // SyntaxError return; } text = text.slice(matched[0].length); if (matched[1] === ']') { break; } } return array; } // assert: matched[1] === '{' var object = {}; matched = text.match(/^\s*}/); if (matched) { // "{}" text = text.slice(matched[0].length); return object; } while (true) { var next_key = parse_next_value(); if (typeof next_key !== 'string') { throw new SyntaxError('Unexpected token in JSON: ' + next_key + '; ' + text); } matched = text.match(/^\s*:/); if (!matched) { // SyntaxError return; } text = text.slice(matched[0].length); var next_value = parse_next_value(); if (next_value === undefined) return; matched = text.match(/^\s*([,}])/); if (!matched) { // SyntaxError return; } object[next_key] = next_value; text = text.slice(matched[0].length); if (matched[1] === '}') { break; } } return object; } text = String(text); var result = parse_next_value(); if (text.trim()) throw new SyntaxError('Unexpected token in JSON: ' + text); return result; } function JSON_stringify(value, replacer, space) { var object_Set = new Set; if (!space) { space = ''; } else if (typeof space === 'number') { if (space > 10) space = 10; else if (space < 1) space = 0; space = ' '.repeat(space); } else { space = String(space).slice(0, 10); } function stringify(object, /* 累積的空格 */_spaces, key, obj) { if (object && typeof object.toJSON === 'function') { // e.g., {Date} object = object.toJSON(key); } if (replacer) object = replacer.call(obj, key, value); if (typeof object === 'string') return '"' + object.replace(/["\\\/\b\f\t\r\n]/g, '\\"') + '"'; if (!object || typeof object !== 'object') { if (object === undefined) { if (!key) return; object = null; } object = String(object); return object; } var next_spaces = _spaces + space; var output = []; if (Array.isArray(object)) { for (var index = 0; index < object.length; index++) { var value = object[index]; if (object_Set.has(value)) { throw new Error( // Too much recursion?\n循環參照? 'Converting circular structure to JSON: [' + index + ']'); } if (value === undefined) value = null; output.push('\n' + next_spaces + stringify(value, next_spaces, key, object)); } return '[' + output.join(',') + (space ? '\n' + _spaces : '') + ']'; } var keys = Object.keys(object); for (var index = 0; index < keys.length; index++) { var key = keys[index], value = object[key]; if (object_Set.has(value)) { throw new Error('Converting circular structure to JSON: .' + key); } if (value === undefined) continue; output.push('\n' + next_spaces + stringify(key, next_spaces) + (space ? ': ' : ':') + stringify(value, next_spaces, key, object)); } return '{' + output.join(',') + (space ? '\n' + _spaces : '') + '}'; } return stringify(value, ''); } if (!globalThis.JSON) { globalThis.JSON = { parse : JSON_parse, stringify : JSON_stringify }; } // -------------------------------------------- // for old node.js function Buffer_from(source, encoding) { return new Buffer(source, encoding); } if (library_namespace.platform.nodejs) { if (!Buffer.from) { // For Node.js v5.11.1 and below // e.g., node.js v0.10.25 Buffer.from = Buffer_from } if (!Buffer.allocUnsafe) { Buffer.allocUnsafe = Buffer_from; } if (!Buffer.prototype.forEach) { Buffer.prototype.forEach = Array.prototype.forEach; } } // ----------------------------------------------------------------------------------------------------------------------------------------------------------// /** * Promise polyfill * * https://babeljs.io/repl Promise test cases: promise = new Promise(function(r,R){r()}) r(value) fulfilled (p=new Promise(function(r,R){r( 2 )}));console.log(p) Promise {: 2} r(promise) promise→p (p=new Promise(function(r,R){r( Promise.resolve(2) )}));console.log(p) p: Promise {} → Promise {: 2} (p=new Promise(function(r,R){r( Promise.reject(2) )})).then(null,function(){});console.log(p) p: Promise {} → Promise {: 2} (q=(p=new Promise(function(r,R){r( Promise.reject(2) )})).then(3,4)).then(5,function(v){console.log(v)});console.log(p);console.log(q) 2 p: Promise {} → Promise {: 2} p=new Promise(function(r,R){r(Promise.resolve(1));throw 2});console.log(p) p: Promise {} → Promise {: 1} r(thenable) thenable→p (p=new Promise(function(r,R){r({then:function(r,R){r(4)}})}));console.log(p) Promise {} → Promise {: 4} (p=new Promise(function(r,R){r({then:function(r,R){console.log(this);R(4)}})})).then(null,function(){}); this: {then: ƒ} p → Promise {: 4} R(value) rejected R(promise) (p=new Promise(function(r,R){R( Promise.resolve(2) )})).then(null,function(){});console.log(p) Promise {: Promise} R(thenable) promise.then(r_,R_) promise: pending, fulfilled, rejected r_ return value value→return (p=new Promise(function(r){throw 2})).then(null,function(){});console.log(p) Promise {: 2} r_ return promise p→promise→return p=Promise.resolve(2).then(function(){return Promise.resolve(4)});console.log(p) p: Promise {} → Promise {: 4} (p=Promise.reject(3)).then(null,function(){});(q=Promise.resolve(2).then(function(){return p})).then(null,function(){});console.log(p);console.log(q) Promise {: 3} ; Promise {} → Promise {: 3} ; Promise {: 3} p=Promise.resolve(2).then(function(){return new Promise(function(r){setTimeout(function(){r(4)},2);})});console.log(p) p: Promise {} → Promise {: 4} p=Promise.resolve(2).then(function(){return new Promise(function(){})});console.log(p) Promise {} Promise.resolve().then(function(){return Promise.reject(3)}).then(null,function(){});console.log(p) Promise {} p=Promise.resolve().then(function(){return new Promise(function(r){setTimeout(function(){console.log(4);r(4)},500)}).then(function(v){console.log(++v);return v})}).then(function(v){console.log(++v);return v},function(){});console.log(p) p: Promise {} 4 5 6 p → Promise {: 6} r_ return thenable p→thenable→returned p=Promise.resolve(2).then(function(){return {then:function(r,R){r(4)}}});console.log(p) p: Promise {} → Promise {: 4} (p=Promise.resolve(2).then(function(){return {then:function(r,R){R(4)}}})).then(null,function(){}); chrome: Promise {: undefined} or firefox: Promise { : "pending" } p: Promise {} → Promise {: 4} p=Promise.resolve(2).then(function(){return {then:function(r,R){setTimeout(function(){r(4)},2);}}});console.log(p) p: Promise {} → Promise {: 4} R_ return value (p=new Promise(function(r,R){R(2)})).then(null,function(){});console.log(p) Promise {: 2} R_ return promise (p=new Promise(function(r,R){R(Promise.resolve(2))})).then(null,function(){});console.log(p) Promise {: Promise} (q=Promise.reject(2)).then(null,function(){});(p=new Promise(function(r,R){R(q)})).then(null,function(){});console.log(p) Promise {: Promise} R_ return thenable (p=new Promise(function(r,R){R({then:function(r,R){R(2)}})})).then(null,function(){});console.log(p) Promise {: {…}} Promise.resolve(v) v: value: tested above p=Promise.resolve('v');console.log(p) p=Promise.resolve(2).then(function(v){console.log(v);return 3}).then(function(v){console.log(v);return 3});console.log(p) Promise {} 2 3 v: promise (q=Promise.resolve(2)).then(null,function(){});p=Promise.resolve(q);console.log(p) Promise {: 2} (q=Promise.reject(2)).then(null,function(){});p=Promise.resolve(q);console.log(p) Promise {: 2} v: thenable q={then:function(r,R){r(2)}};p=Promise.resolve(q);console.log(p) p: Promise {} → Promise {: 2} q={then:function(r,R){R(2)}};(p=Promise.resolve(q)).then(null,function(){});console.log(p) p: Promise {} → Promise {: 2} q={b:2,then:function(r){console.log(this.b);r(2)}};p=Promise.resolve(q);console.log(p) q={b:2,then:function(r){console.log(this.b);throw 2}};(p=Promise.resolve(q)).then(null,function(){});console.log(p) Promise.reject(v) v: value: tested above (p=Promise.reject('v')).then(null,function(){});console.log(p) p=Promise.resolve(1);console.log(p);(q=Promise.reject(p)).then(null,function(){});console.log(p!==q) Promise {: 1} console.log(p!==q): true (p=Promise.reject(1)).then(null,function(){});(q=Promise.reject(p)).then(null,function(){});console.log(p!==q) console.log(p!==q): true v: promise (p=Promise.reject(Promise.resolve(2))).then(null,function(){});console.log(p) Promise {: Promise} (q=Promise.resolve(2)).then(null,function(){});(p=Promise.reject(q)).then(null,function(){});console.log(p) Promise {: Promise} (q=Promise.reject(2)).then(null,function(){});(p=Promise.reject(q)).then(null,function(){});console.log(p) Promise {: Promise} v: thenable q={then:function(r,R){R(2)}};(p=Promise.reject(q)).then(null,function(){});console.log(p) Promise {: {…}} Promise.all ( iterable ) iterable: value: (p=Promise.all([])).then(function(L){console.log(Array.isArray(L)+','+L.length)});console.log(p) p: Promise {: Array(0)} true,0 Promise.all(1).then(function(L){console.log('_'+L)},function(x){console.log('!'+x.message)}) x: TypeError: number 1 is not iterable (cannot read property Symbol(Symbol.iterator)) iterable: promise, thenable: Promise.all([2,1,Promise.reject(8)]).then(function(){},function(R){console.log(R)}) 8 l=[3,'a',new Promise(function(r,R){setTimeout(function(){r(4)},8)}),9,{then:3},{then:function(r,R){return r(7)}},Promise.resolve(3),[2,3],null,undefined];Promise.all(l).then(function(L){console.log(L);}) Promise {} (10) [3, "a", 4, 9, {…}, 7, 3, Array(2), null, undefined] Promise.race ( iterable ) Promise.race([new Promise(function(r,R){setTimeout(function(){console.log(500);r(3)},500)}),new Promise(function(r,R){setTimeout(function(){console.log(5);r(1)},5)}),Promise.resolve(6),new Promise(function(r,R){setTimeout(function(){console.log(400);r(7)},400)})]).then(function(r){console.log('** '+r)}) → 6 promise.finally(onFinally) Promise_finally(onFinally) onFinally: value p=Promise.resolve(2)['finally'](3).then(function(v){console.log(v);return 4});console.log(p) 2 p: Promise {} → Promise {: 4} onFinally: function return value p=Promise.resolve(2).then(function(v){console.log(v);return 3})['finally'](function(v){console.log('_'+v);return 4}).then(function(v){console.log(v);return 5});console.log(p) p: Promise {} 2 _undefined 3 p → Promise {: 5} p=Promise.reject(2).then(function(v){console.log(v);return 3})['finally'](function(v){console.log('_'+v);return 4}).then(function(v){console.log(v);return 5}).then(null,function(v){console.log(v);return 6});console.log(p) p: Promise {} _undefined 2 p → Promise {: 6} onFinally: function return promise, thenable: p=Promise.resolve(2).then(function(v){console.log(v);return 3})['finally'](function(v){console.log('_'+v);return Promise.resolve(3)}).then(function(v){console.log(v);return 5});console.log(p) p: Promise {} 2 _undefined 3 p → Promise {: 5} // -------------------------------------------------------------------------------------- var v='';(p=new Promise(function(r,R){r(1);throw '0'})) .then(function(x){v+=x;console.log(v);return 2}) .then(function(x){v+=x;return 3}) .then(function(x){v+=x;throw '4'},function(y){v+=y;return 0}) .then(function(x){v+=x;throw 0},function(y){v+=y;return 5}) .then(function(x){v+=x;return 6}) .then(function(x){v+=x;console.log(v==="_123456")});v='_';console.log(p) console.log(v) var v='';(p=new Promise(function(r,R){r(Promise.resolve(1));r(0);throw '2'})) .then(function(x){v+=x;console.log(v);return 2}) .then(function(x){v+=x;return 3}) .then(function(x){v+=x;throw '4'},function(y){v+=y;return 0}) .then(function(x){v+=x;throw 0},function(y){v+=y;return 5}) .then(function(x){v+=x;return 6}) .then(function(x){v+=x;console.log(v==="_123456")});v='_';console.log(p) console.log(v) (q=Promise.reject(2).then(function(){})).then(function(){},function(y){}) // console.log(q): Promise {: 2} var v='';(p=new Promise(function(r,R){R(1);r(0)})) .then(function(x){v+=x;console.log('!!');return 0}) .then(function(x){v+=x;return 0}) .then(function(x){v+=x;throw '0'},function(y){console.log('y='+y);v+=y;return 2}) .then(function(x){v+=x;throw '3'},function(y){v+=y;return 0}) .then(function(x){v+=x;return 0}) .then(function(x){v+=x;},function(x){v+=x;console.log(v);console.log(v==="_123")});v='_'; var q=null,r='';Promise.resolve() .then(function(x){r+=x;return 1}) .then(function(x){r+=x;q=new Promise(function(r,R){setTimeout(r,8)});return q}) .then(function(x){r+=x;return 2}) .then(function(x){r+=x; q .then(function(x){r+=x;return 3}) .then(function(x){r+=x;return 5}) .then(function(x){r+=x;return 7}) .then(function(x){r+=x;}); return '_'}) .then(function(x){r+=x;return 4}) .then(function(x){r+=x;return 6}) .then(function(x){r+=x;return 8}) .then(function(x){r+=x;console.log(r==="undefined1undefined2undefined_345678")}) var q=null,r='';Promise.resolve() .then(function(x){r+='_';return 1}) .then(function(x){r+=x;q=new Promise(function(r,R){setTimeout(function(){R(2)},8)});q.then(function(x){r+=x;return 0},function(y){r+=2&&y;});return q}) .then(function(x){r+=x;return 0}) .then(function(x){r+=x; q .then(function(x){r+=x;return 0},function(y){r+=y;return 0}) .then(function(x){r+=x;return 0}) .then(function(x){r+=x;return 0}) .then(function(x){r+=x;}); return 0}) .then(function(x){r+=x;return 0},function(y){r+=3;return 4}) .then(function(x){r+=x;return 5}) .then(function(x){r+=x;return 6}) .then(function(x){r+=x;console.log(r==="_123456")}) var q=null,r='';Promise.resolve() .then(function(x){r+='_';return 1}) .then(function(x){r+=x;q=new Promise(function(r,R){setTimeout(function(){R(2)},8)});q.then(function(x){r+=x;return 0},function(y){r+=2&&y;});return q}) .then(function(x){r+=x;return 0},function(y){r+=3;return 4}) //.then(function(x){r+='^^^';return 4}) .then(function(x){r+=4&&x; console.log(q) q // Promise {: 2} .then(function(x){r+=x;return 0},function(y){r+=5;return 7}) .then(function(x){r+=x;return 9}) .then(function(x){r+=x;return 'b'}) .then(function(x){r+=x;}); return 6}) .then(function(x){r+=6&&x;return 8},function(y){r+=y;return 0}) .then(function(x){r+=x;return 'a'}) .then(function(x){r+=x;return 'c'}) .then(function(x){r+=x;console.log(r==="_123456789abc")}) r='';(q=new Promise(function(r,R){setTimeout(function(){R(1)},5000)})) .then(function(x){r+=x;return 0},function(y){r+=1&&y;return 2}) .then(function(x){r+=2&&x;return 3}) .then(function(x){r+=3&&x;console.log(r==='123')}) r='';(q=new Promise(function(r,R){setTimeout(function(){R(1)},50)})) .then(function(x){r+=x;return 0},function(y){r+=1&&y;return 2}) .then(function(x){r+=2&&x;return 3}) //console.log(r): '12' // // another: q.then(function(x){r+=3&&x;console.log(r==='123')}) // Uncaught (in promise) 1 // r==='12' q=new Promise(function(r,R){setTimeout(R,8)}) s=q.then(function(x){},function(y){}) console.log([s!==q,s,q]) // s: resolved, q: rejected. then方法執行完會另外產生一個新的 Promise 物件。 // https://eyesofkids.gitbooks.io/javascript-start-es6-promise/content/contents/then_adv.html test of Promise.prototype.then(): this[KEY_STATE] === PromiseState_pending p=new Promise(function(r,R){setTimeout(function(){r(9)},8)}); p.then(function(a){r+=1}); p.then(function(a){r+=2}); p.then(function(a){r+=3}); console.log(p);r=''; // console.log(r==='123'); p=new Promise(function(r,R){setTimeout(function(){R(9)},8)}); p.then(null,function(a){r+=1}); p.then(null,function(a){r+=2}); p.then(null,function(a){r+=3}); console.log(p);r=''; // console.log(r==='123'); p=new Promise(function(r,R){setTimeout(function(){r(9)},8)});p.then(123).then(function(x){console.log(x===9);}); * * @since 2018/9/16 7:37:10 */ // Promise 標準規格書/定義 // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-promise-objects // https://promisesaplus.com/ // https://github.com/promises-aplus/promises-spec // https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate // http://azu.github.io/promises-book/ // 實作 Promise polyfill // https://github.com/stefanpenner/es6-promise/tree/master/lib/es6-promise // https://github.com/taylorhakes/promise-polyfill/blob/master/dist/polyfill.js // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-enqueuejob var EnqueueJob = library_namespace.platform.nodejs && process.nextTick // https://github.com/stefanpenner/es6-promise/blob/master/lib/es6-promise/asap.js // node version 0.10.x displays a deprecation warning when nextTick is used // recursively // see https://github.com/cujojs/when/issues/410 for details ? function(job) { return process.nextTick(job); } // https://github.com/cssmagic/ChangeLog/issues/3 // setImmediate() 會調度宏微任務而不是微任務,可能導致不一樣的調度結果。 : typeof setImmediate === 'function' ? setImmediate : function(job) { setTimeout(job, 1); }, // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-properties-of-promise-instances // const PromiseState_pending = 0, PromiseState_fulfilled = 1, PromiseState_rejected = -1, // const KEY_STATE = '_state', KEY_VALUE = '_value', KEY_HANDLED = '_handled', KEY_REACTIONS = '_reactions', // KEY_DEPEND_ON = KEY_VALUE; 因為沒有衝突,可重複利用 KEY_VALUE。 // 應對: @see TriggerPromiseReactions(promise) KEY_DEPEND_ON = '_depend_on' && KEY_VALUE; // @private // assert: won't throw function PerformPromiseThen(promise, reaction, rejected, value) { if (typeof reaction !== 'function') { (rejected ? RejectPromise : FulfillPromise)(promise, value); return; } /** * (p=Promise.reject(2).then(function(){},function(v){return Promise.resolve(2)})).then(null,function(){}); Promise {} → Promise {: 2} */ try { value = reaction(value); FulfillPromise(promise, value); } catch (e) { RejectPromise(promise, e); } } // @private // assert: won't throw // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-triggerpromisereactions function TriggerPromiseReactions(promise) { var value = promise[KEY_VALUE], rejected = promise[KEY_STATE] === PromiseState_rejected, reactions = promise[KEY_REACTIONS]; if (!reactions) { // TriggerPromiseReactions() already executed /** p=new Promise(function(r,R){r(Promise.resolve(1));throw 2});console.log(p) */ return; } // free delete promise[KEY_REACTIONS]; if (false && KEY_VALUE !== KEY_DEPEND_ON) delete promise[KEY_DEPEND_ON]; // assert: (`value` is not thenable || rejected) === true EnqueueJob(function() { if (rejected && !promise[KEY_HANDLED]) { // assert: no onRejected reaction // Promise.reject(2).then(function(v){}).catch(function(v){}) // HostPromiseRejectionTracker ( promise, operation ) // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-host-promise-rejection-tracker throw new Error('Uncaught (in promise): ' + value); } library_namespace.debug('reactions of ' + promise + ': ' + reactions.join(';'), 8, 'TriggerPromiseReactions'); // PromiseReactionJob ( reaction, argument ) // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-promisereactionjob reactions.forEach(function(reaction) { if (Array.isArray(reaction)) { // @see // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-promisereaction-records // reaction = [ promise, onFulfilled, onRejected ] PerformPromiseThen(reaction[0], reaction[rejected ? 2 : 1], rejected, value); } else { // assert: IsPromise(reaction) (rejected ? RejectPromise : FulfillPromise) (reaction, value); } }); }); } // @private // assert: won't throw // Promise Resolve Functions, resolve `promise` with `result`. // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-promise-resolve-functions // https://promisesaplus.com/#the-promise-resolution-procedure function FulfillPromise(promise, result, no_enqueue) { // test if promise is settled / alreadyResolved if (promise[KEY_STATE] !== PromiseState_pending) { // p=new Promise(function(r,R){setTimeout(function(){r(1);r(p)},1)}) return; } function executing_job(job) { if (no_enqueue) job(); else EnqueueJob(job); } var then; try { if (promise === result) { // selfResolutionError /** (p=new Promise(function(r,R){setTimeout(function(){r(p)},1)})).then(null,function(){}) */ // Chaining cycle detected for promise # throw new TypeError('A promise cannot be resolved with itself.'); } then = get_then_of_thenable(result); if (!then) { // p=new Promise(function(r,R){r({then:2})}) promise[KEY_STATE] = PromiseState_fulfilled; promise[KEY_VALUE] = result; TriggerPromiseReactions(promise); } else { if (IsPromise(result)) { /** * // won't throw HostPromiseRejectionTracker: (p=new Promise(function(r,R){r( Promise.reject(2) )})).then(null,function(){});console.log(p) q=Promise.reject(2);(p=new Promise(function(r,R){r( q )})).then(null,function(){});console.log(p) Promise.resolve().then(function(){return Promise.reject(3)}).then(null,function(){}); Promise.resolve().then(function(){return new Promise(function(r,R){R(3)})}).then(null,function(){}); */ library_namespace.debug('handled: ' + result, 8, 'FulfillPromise'); result[KEY_HANDLED] = true; } executing_job(function() { /** * old runtime environment may not has * Function.prototype.bind(), so we use anonymous functions * instead of FulfillPromise.bind() to improve performance. */ function onFulfilled(value) { if (!called) { called = true; FulfillPromise(promise, value, no_enqueue); } } function onRejected(value) { if (!called) { called = true; RejectPromise(promise, value); } } var called; try { if (IsPromise(result)) { /** * p=new Promise(function(r){setTimeout(function(){r(Promise.resolve(2))},1)}) */ if (result[KEY_STATE] === PromiseState_pending) { promise[KEY_DEPEND_ON] = result; result[KEY_REACTIONS].push(promise); } else { // copy result → promise promise[KEY_STATE] = result[KEY_STATE]; promise[KEY_VALUE] = result[KEY_VALUE]; // assert: promise[KEY_STATE] !== // PromiseState_pending TriggerPromiseReactions(promise); } } else { /** * is_thenable(result) p=new Promise(function(r,R){r({then:function(){}})}) t={};t.then=function(r){r(2)};p=new Promise(function(r,R){r(t)}) t=function(){};t.then=function(r){r(2)};p=new Promise(function(r,R){r(t)}) */ then.call(result, onFulfilled, onRejected); } } catch (e) { onRejected(e); } }); } } catch (e) { executing_job(function() { RejectPromise(promise, e); }); } } // @private // assert: won't throw // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-promise-reject-functions function RejectPromise(promise, reason) { // test if promise is settled / alreadyResolved if (promise[KEY_STATE] !== PromiseState_pending) { return; } // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-rejectpromise // p=new Promise(function(r,R){setTimeout(function(){R(p)},1)}) // p=new Promise(function(r,R){R(function(){throw p})}) // p=new Promise(function(r,R){R({then:function(){throw p}})}) promise[KEY_STATE] = PromiseState_rejected; promise[KEY_VALUE] = reason; EnqueueJob(function() { // (q=(p=Promise.reject(2)).then(3,4)).then(null,function(){});console.log(p);console.log(q); TriggerPromiseReactions(promise); }); } // @private // is thenable object, thenable 物件 function get_then_of_thenable(value) { // `value &&`: for null if (value && (typeof value === 'function' || typeof value === 'object')) { var then = value.then; return typeof then === 'function' && then; } } // @see is_thenable() @ base.js // cf. Promise.isPromise() function is_thenable(value) { return !!get_then_of_thenable(value); // old style return value && typeof value.then === 'function'; } // library_namespace.is_thenable = is_thenable; // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-ispromise // https://stackoverflow.com/questions/27746304/how-do-i-tell-if-an-object-is-a-promise // test if is Promise 物件, Promise.isPromise(), // `require('util').types.isPromise()` function IsPromise(value) { return value instanceof Promise; return Promise.resolve(value) == value; return value && value.constructor === Promise; return value && Object.prototype.toString.call(value) === "[object Promise]"; // NG: only thenable return is_thenable(value); // assert: Should NOT access value.then // https://promisesaplus.com/#point-75 // NG: var then = get_then_of_thenable(value), constructor; return then && typeof (constructor = value.constructor) === 'function' && typeof constructor.resolve === 'function' && typeof constructor.reject === 'function' // ECMA-262, 9th edition 標準 // && typeof constructor.all === 'function' // && typeof constructor.race === 'function' && then; } // -------------------------------------------------------- // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-promise-executor function Promise(executor) { if (!(this instanceof Promise)) // calling a builtin Promise constructor without new is forbidden throw new TypeError((typeof this) + ' is not a promise'); if (typeof executor !== 'function') // calling a builtin Promise constructor without new is forbidden throw new TypeError('Promise resolver ' + executor + ' is not a function'); // private: // [[PromiseState]] this[KEY_STATE] = PromiseState_pending; // [[PromiseResult]] // this[KEY_VALUE] = undefined; // [[PromiseIsHandled]] // this[KEY_HANDLED] = false; // 因為本promise擱置而擱置的{Promise} or 函數。 // subscribers, deferreds // [[PromiseFulfillReactions]] queue // [[PromiseRejectReactions]] queue this[KEY_REACTIONS] = []; var promise = this; // forced to convert to thenable FulfillPromise(promise, 'then' in executor ? executor : { then : executor }, /* no_enqueue */!('then' in executor)); } // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-promise.prototype.then // .then()會另外產生一個新的promise物件。 NewPromiseCapability() function then(onFulfilled, onRejected) { // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-performpromisethen if (typeof onFulfilled !== 'function') onFulfilled = undefined; if (typeof onRejected !== 'function') onRejected = undefined; if (onRejected) { var promise = this; do { /** * if promise[KEY_HANDLED] is already `true`, it means there are * more than one handler.
* e.g., * (q=Promise.reject(2)).then(null,function h1(){});q.then(null,function h2(){}); */ promise[KEY_HANDLED] = true; } while (IsPromise(promise = promise[KEY_DEPEND_ON])); } // 回傳另一個被擱置的 Promise 物件 // NewPromiseCapability(C) var promise = new this.constructor(library_namespace.null_function); promise[KEY_DEPEND_ON] = this; if (this[KEY_STATE] === PromiseState_pending) { // test case: see "test of Promise.prototype.then()" above /** * 等待本promise解決了再處理。
* p=Promise.resolve(2);q=p.then();p!==q * * @see https://www.ecma-international.org/ecma-262/9.0/index.html#sec-promisereaction-records */ this[KEY_REACTIONS].push(onFulfilled || onRejected // reaction = [ promise, onFulfilled, onRejected ] ? [ promise, onFulfilled, onRejected ] : promise); } else { // assert: `this` promise is settled, solved. /** * p=Promise.resolve().then(function(){}) p=Promise.resolve().then(function(a){return {a:1,b:2}}) p=Promise.resolve().then(function(a){return function(a){return a}}) q={b:2,then:5};p=Promise.resolve(2).then(a=>{return q});console.log(p); (p=Promise.reject(2)).catch(a=>0);(q=Promise.resolve(1).then(a=>p)).catch(a=>0);p!==q q=new Promise(r=>setTimeout(r,500));p=Promise.resolve(2).then(a=>{return q});console.log(p); p=new Promise((r,R)=>{setTimeout(function(){r(9)},8)});p.then(p);p.then(p); value is thenable object / function q={then:r=>r(1)};p=Promise.resolve(2).then(a=>{return q}) q={b:2,then:r=>{console.log(this);r(this.b)}};p=Promise.resolve(2).then(a=>{return q});console.log(p); (q=(p=Promise.reject(2)).then(3,4)).then(null,function(){});console.log(p);console.log(q); (q=Promise.reject(2).then(function(){})).then(function(){},function(y){}) */ var rejected = this[KEY_STATE] === PromiseState_rejected, value = this[KEY_VALUE]; EnqueueJob(function() { PerformPromiseThen(promise, rejected ? onRejected : onFulfilled, rejected, value); }); } return promise; } // -------------------------------------------------------- Promise.resolve = function resolve(result) { if (IsPromise(result) && result[KEY_STATE] === PromiseState_fulfilled) // p=Promise.resolve(1);q=Promise.resolve(p);console.log(p===q) return result; // NewPromiseCapability(C) // https://www.ecma-international.org/ecma-262/9.0/index.html#sec-newpromisecapability var promise = new this(library_namespace.null_function); /** * (q=new Promise(function(r,R){setTimeout(function(){R(2)},80)}));(p=Promise.resolve(q)).then(function(x){console.log('*'+x)},function(y){console.log('_'+y)});console.log(p) */ if (IsPromise(result) && result[KEY_STATE] !== PromiseState_pending) { // Promise.resolve() 基本上盡可能早點得到一個確定的 Promise 物件,並且不會改變傳入的引數。 // copy result → promise promise[KEY_STATE] = result[KEY_STATE]; promise[KEY_VALUE] = result[KEY_VALUE]; /** * // won't throw HostPromiseRejectionTracker: (q=Promise.reject(2)).then(null,function(){});p=Promise.resolve(q);console.log(p) (q=new Promise(function(r,R){R(2)})).then(null,function(){});p=Promise.resolve(q);console.log(p) */ // will throw HostPromiseRejectionTracker: // Promise.resolve(Promise.reject(2)) /** * // throw: (q=Promise.reject(2)); // no throw: console.log(q);p=Promise.resolve(q);console.log(p) // no throw: (q=new Promise(function(r,R){R(2)})).then(null,function(){}); // throw: p=new Promise(function(r){r(q)});console.log(p) // throw: (q=Promise.reject(2)); // throw: console.log(q);p=new Promise(function(r,R){r(q)});console.log(p) */ promise[KEY_HANDLED] = true; } else { FulfillPromise(promise, result); } return promise; }; Promise.reject = function reject(reason) { // NewPromiseCapability(C) var promise = new this(library_namespace.null_function); RejectPromise(promise, reason); return promise; }; // @private function GetIterator(iterable) { if (typeof iterable === 'string') { // return Array.from(iterable); return iterable.split(''); } if (!Array.isArray(iterable)) { // e.g., {Number|Object|Function|Null|Undefined} throw new TypeError('Argument of Promise.all: {' + (typeof iterable) + '} ' + iterable + ' is not iterable'); } return iterable; } /** * var promise1 = Promise.resolve(3); var promise2 = new Promise(function(resolve, reject) { setTimeout(function() { reject('foo'); }, 100); }); var promises = [ promise1, promise2 ]; Promise.allSettled(promises).then(function(results) { results.forEach(function(result) { console.log([ result.status, result.value, result.reason ].join(', ')); }); }); Promise.all(promises).then(function(results) { results.forEach(function(result) { console.log(result.join(', ')); }); }); */ function for_all_promises(iterable, onFulfilled, onRejected) { // -iterable.length: be sure `remaining` is negative before // iterable.forEach() completed var remaining = -iterable.length, result_list = []; function fill(value, index) { // CeL.log([ remaining, index, value ].join(', ')); result_list[index] = value; if (--remaining === 0) { // CeL.log('result_list: ' + result_list); onFulfilled(result_list); } } GetIterator(iterable).forEach(function(item, index) { var then = get_then_of_thenable(item); if (!then) { // Not a .then() object result_list[index] = item; return; } function reject(reason) { if (onRejected) { // Promise.all() onRejected(reason); } else { // Promise.allSettled() fill({ status : "rejected", reason : reason }, index); } } try { then.call(item, function(result) { fill(onRejected ? result : { status : "fulfilled", value : result }, index); }, reject); } catch (e) { reject(e); } remaining++; }); remaining += iterable.length; if (remaining === 0) { // No .then() object onFulfilled(result_list); } } // https://eyesofkids.gitbooks.io/javascript-start-es6-promise/content/contents/promise_all_n_race.html // Promises/A+並沒有關於Promise.reject或Promise.resolve的定義,它們是ES6 Promise標準中的實作。 // 如果參數中 promise 有一個失敗(rejected),此實例回調失敗(reject) Promise.all = function all(iterable) { return new this(function(onFulfilled, onRejected) { for_all_promises(iterable, onFulfilled, onRejected); }); }; // 拿到每個Promise的狀態 不管其是否處理成功 Promise.allSettled = function allSettled(iterable) { return new this(function(onFulfilled/* , onRejected */) { for_all_promises(iterable, onFulfilled); }); }; // 如果參數中某個promise解決或拒絕,返回的promise就會解決或拒絕。 Promise.race = function race(iterable) { return new this(function(onFulfilled, onRejected) { GetIterator(iterable) // won't skip anyone. Using forEach for sparse array .forEach(function(item) { try { var then = get_then_of_thenable(item); if (then) { then.call(item, onFulfilled, onRejected); } else { onFulfilled(item); } } catch (e) { onRejected(e); } }); }); }; // https://sung.codes/blog/2019/05/18/promise-race-vs-promise-any-and-promise-all-vs-promise-allsettled/ Promise.any = function any(iterable) { return new this(function(onFulfilled, onRejected) { var remaining = 0, waiting; GetIterator(iterable) // won't skip anyone. Using forEach for sparse array .forEach(function(item) { try { var then = get_then_of_thenable(item); if (then) { remaining++; then.call(item, function(item) { remaining--; some_fulfilled = true; onFulfilled(item); }, function(item) { // TODO: check what to onRejected() // TODO: Using AggregateError object if (--remaining === 0 && waiting) onRejected(item); }); } else { onFulfilled(item); } } catch (e) { // ignores rejections. Ignore error } }); if (remaining > 0) waiting = true; else onFulfilled(); }); }; // -------------------------------------------------------- // https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally function Promise_finally(onFinally) { if (typeof onFinally !== 'function') // p=Promise.resolve(2)['finally'](3).then(function(v){console.log(v)});console.log(p) return this.then(); if (false) library_namespace.debug('creating onFinally step: ' + onFinally, 0, 'Promise_finally'); var _Promise = this.constructor || globalThis.Promise; // onFinally won't get any arguments return this.then(function(value) { return _Promise.resolve(onFinally()).then(function() { // inherit value return value; }); }, function(value) { return _Promise.resolve(onFinally()).then(function() { // return _Promise.reject(value); throw value; }); }); } // https://github.com/tc39/proposal-promise-try // a syntactic sugar function Promise_try(executor) { return new this(function(resolve/* , reject */) { return resolve(executor()); }); } // Promise.allSettled() // // Cannot catch Promise.allSettled(). Only use Using Promise.allSettled() // when you do not care errors. Or using `Promise.all().then(, cacher)` // before Promise.allSettled() to catch error. // // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled // https://github.com/es-shims/Promise.allSettled/blob/master/implementation.js function Promise_allSettled(iterable) { function onFulfilled(result) { return { status : "fulfilled", value : result }; } function onRejected(reason) { return { status : "rejected", reason : reason }; } var _Promise = this; iterable = Array.from(iterable, function(promise) { if (!is_thenable(promise)) promise = _Promise.resolve(promise); return promise.then(onFulfilled, onRejected); }); return _Promise.all(iterable); } // -------------------------------------------------------- function Promise_toString() { return 'Promise {[' + (this[KEY_STATE] === PromiseState_pending ? 'pending' : this[KEY_STATE] === PromiseState_rejected ? 'rejected' : 'fulfilled') + ']: ' + this[KEY_VALUE] + '}'; } // Do not set `Promise.prototype={...}`, // so we can use `new this.constructor()` Object.assign(Promise.prototype, { // for debug only // toString : Promise_toString, then : then, // a syntactic sugar // caught 'catch' : function Promise_catch(onRejected) { return this.then(undefined, onRejected); } }); set_method(globalThis, { Promise : Promise }, 'function'); set_method(globalThis.Promise, { 'try' : Promise_try, allSettled : Promise_allSettled }, 'function'); set_method(globalThis.Promise.prototype, { // finale 'finally' : Promise_finally }, 'function'); // -------------------------------------------------------- if (typeof Symbol === 'function') { // Symbol.prototype.description if (!('description' in Symbol.prototype)) { // Object.getOwnPropertyDescriptor(Symbol.prototype, 'description'); Object.defineProperty(Symbol.prototype, 'description', { enumerable : false, configurable : true, get : function() { var matched = String(this).match(/^Symbol\(([\s\S]*)\)$/); return matched[1]; } }); } } // -------------------------------------------------------- /** * defective polyfill for W3C URL API, URLSearchParams() @ CeL.application.net. * * defective polyfill for W3C fetch API @ CeL.application.net.Ajax. */ // ----------------------------------------------------------------------------------------------------------------------------------------------------------// return (_// JSDT:_module_ ); }