clone(obj, options) {
        let ret
        options = Object.assign({ functions: true, elements: true, events: true, exclude: [] }, options ?? {})
        if (Array.isArray(obj)) {
            ret = Array.from(obj)
            ret.forEach((value, ind) => {
                ret[ind] = this.clone(value, options)
            })
        } else if (this.isPlainObject(obj)) {
            ret = {}
            Object.assign(ret, obj)
            if (options.exclude) {
                options.exclude.forEach(key => { delete ret[key] }) // delete excluded keys
            }
            Object.keys(ret).forEach(key => {
                ret[key] = this.clone(ret[key], options)
                if (ret[key] === undefined) delete ret[key] // do not include undefined elements
            })
        } else {
            if ((obj instanceof Function && !options.functions)
                    || (obj instanceof Node && !options.elements)
                    || (obj instanceof Event && !options.events)
            ) {
                // do not include these objects, otherwise include them uncloned
            } else {
                // primitive variable or function, event, dom element, etc, -  all these are not cloned
                ret = obj
            }
        }
        return ret
    }
Deep copy of an object or an array. Function, events and HTML elements will not be cloned,
you can choose to include them or not, by default they are included.
You can also exclude certain elements from final object if used with options: { exclude }