@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
152 lines (131 loc) • 3.75 kB
JavaScript
/**
* Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
* Node module: @schukai/monster
*
* This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
* The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
*
* For those who do not wish to adhere to the AGPLv3, a commercial license is available.
* Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
* For more information about purchasing a commercial license, please contact schukai GmbH.
*
* SPDX-License-Identifier: AGPL-3.0
*/
import { getGlobal } from "../types/global.mjs";
import {
isArray,
isFunction,
isObject,
isPrimitive,
isProxy,
} from "../types/is.mjs";
import { typeOf } from "../types/typeof.mjs";
import { validateObject } from "../types/validate.mjs";
export { clone };
/**
* With this function, objects can be cloned.
* The entire object tree is run through.
*
* Proxy, Element, HTMLDocument and DocumentFragment instances are not cloned.
* Global objects such as windows are also not cloned,
*
* If an object has a method `getClone()`, this method is used to create the clone.
*
* @param {*} obj object to be cloned
* @return {*}
* @license AGPLv3
* @since 1.0.0
* @copyright schukai GmbH
* @throws {Error} unable to clone obj! its type isn't supported.
*/
function clone(obj) {
// typeof null results in 'object'. https://2ality.com/2013/10/typeof-null.html
if (null === obj) {
return obj;
}
// Handle the two simple types, null and undefined
if (isPrimitive(obj)) {
return obj;
}
// Handle the two simple types, null and undefined
if (isFunction(obj)) {
return obj;
}
// Handle Array
if (isArray(obj)) {
const copy = [];
for (let i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}
if (isObject(obj)) {
// Handle Date
if (obj instanceof Date) {
const copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle URL
if (obj instanceof URL) {
return new URL(obj.toString());
}
/** Do not clone DOM nodes */
if (typeof Element !== "undefined" && obj instanceof Element) return obj;
if (typeof Document !== "undefined" && obj instanceof Document) return obj;
if (
typeof DocumentFragment !== "undefined" &&
obj instanceof DocumentFragment
)
return obj;
/** Do not clone global objects */
if (obj === getGlobal()) return obj;
if (typeof window !== "undefined" && obj === window) return obj;
if (typeof document !== "undefined" && obj === document) return obj;
if (typeof navigator !== "undefined" && obj === navigator) return obj;
if (typeof JSON !== "undefined" && obj === JSON) return obj;
if (isProxy(obj)) return obj; // Handle Proxy-Object
return cloneObject(obj);
}
throw new Error("unable to clone obj! its type isn't supported.");
}
/**
*
* @param {object} obj
* @return {object}
* @private
*/
function cloneObject(obj) {
validateObject(obj);
const fkt = obj?.["constructor"];
/** Object has clone method */
if (typeOf(fkt) === "function") {
const prototype = fkt?.prototype;
if (typeof prototype === "object") {
if (
prototype.hasOwnProperty("getClone") &&
typeOf(obj.getClone) === "function"
) {
return obj.getClone();
}
}
}
let copy = {};
if (
typeof obj.constructor === "function" &&
typeof obj.constructor.call === "function"
) {
copy = new obj.constructor();
}
for (const key in obj) {
if (!obj.hasOwnProperty(key)) {
continue;
}
if (isPrimitive(obj[key])) {
copy[key] = obj[key];
continue;
}
copy[key] = clone(obj[key]);
}
return copy;
}