文件上传
axios二次封装
let instance = axios.create();
instance.defaults.baseURL = 'http://127.0.0.1:8888';
instance.defaults.headers['Content-Type'] = 'multipart/form-data';
instance.defaults.transformRequest = (data, headers) => {
const contentType = headers['Content-Type'];
if (contentType === "application/x-www-form-urlencoded") return Qs.stringify(data);
return data
}
instance.interceptors.response.use(response => {
return response.data;
})
前端页面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>珠峰在线Web高级课</title>
<link rel="stylesheet" href="css/reset.min.css">
<link rel="stylesheet" href="css/upload.css">
<style>
html,
body {
overflow-x: hidden;
}
.container {
padding: 20px 100px;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.container .item h3 {
line-height: 40px;
}
</style>
</head>
<body>
...
<script src="node_modules/axios/dist/axios.min.js"></script>
<script src="node_modules/qs/dist/qs.js"></script>
<script src="node_modules/spark-md5/spark-md5.min.js"></script>
<script src="js/utils.min.js"></script>
<script src="js/instance.js"></script>
<script src="js/upload.js"></script>
</body>
</html>
基于FORM-DATA实现文件上传
<div class="item">
<h3>单一文件上传「FORM-DATA」</h3>
<section class="upload_box" id="upload1">
<input type="file" class="upload_inp" accept=".png,.jpg,.jpeg">
<div class="upload_button_box">
<button type="button" class="upload_button select">选择文件</button>
<button type="button" class="upload_button upload">上传到服务器</button>
</div>
<div class="upload_tip">只能上传 PNG/JPG/JPEG 格式图片,且大小不能超过2MB</div>
<ul class="upload_list">
</ul>
</section>
</div>
(function () {
let upload = document.querySelector("#upload1"),
upload_inp = upload.querySelector(".upload_inp"),
upload_button_select = upload.querySelector(".upload_button.select"),
upload_button_upload = upload.querySelector('.upload_button.upload'),
upload_tip = upload.querySelector('.upload_tip'),
upload_list = upload.querySelector('.upload_list');
let _file = null;
upload_button_select.addEventListener('click', () => {
if (upload_button_select.classList.contains('disable') || upload_button_upload.classList.contains('loading')) return;
upload_inp.click();
})
upload_inp.addEventListener('change', () => {
let file = upload_inp.files[0];
if (!file) return;
if (2 * 1024 * 1024 < file.size) {
alert('上传的文件不能超过2MB~~');
return;
}
_file = file;
upload_tip.style.display = 'none';
upload_list.style.display = 'block';
upload_list.innerHTML = `<li>
<span>文件:${file.name}</span>
<span><em>移除</em></span>
</li>`;
})
const clearHandle = () => {
_file = null;
upload_tip.style.display = 'block';
upload_list.style.display = 'none';
upload_list.innerHTML = '';
upload_inp.value = ''
}
upload_list.addEventListener('click', (ev) => {
console.log(ev.target);
let target = ev.target;
if (target.tagName == "EM") clearHandle();
return
})
const changeDisable = flag => {
if (flag) {
upload_button_select.classList.add('disable');
upload_button_upload.classList.add('loading');
return;
}
upload_button_select.classList.remove('disable');
upload_button_upload.classList.remove('loading');
}
upload_button_upload.addEventListener('click', () => {
if (upload_button_upload.classList.contains('disable') || upload_button_upload.classList.contains('loading')) return;
if (!_file) {
alert('请您先选择要上传的文件~~');
return;
}
changeDisable(true);
let formData = new FormData();
formData.append('file', _file);
formData.append('filename', _file.name);
instance.post('/upload_single', formData).then(data => {
if (+data.code == 0) {
alert(`文件已经上传成功~~,您可以基于 ${data.servicePath} 访问这个资源~~`);
return;
}
return Promise.reject(data.codeText)
}).catch(reason => {
alert('文件上传失败,请您稍后再试~~');
}).finally(() => {
clearHandle();
changeDisable(false);
})
})
})();
基于BASE64实现文件上传
<div class="item">
<h3>单一文件上传「BASE64」,只适合图片</h3>
<section class="upload_box" id="upload2">
<input type="file" class="upload_inp" accept=".jpg,.jpeg,.png">
<div class="upload_button_box">
<button type="button" class="upload_button select">上传图片</button>
</div>
<div class="upload_tip">只能上传jpg/png格式图片,且大小不能超过2mb</div>
</section>
</div>
(function () {
let upload = document.querySelector("#upload2"),
upload_inp = upload.querySelector(".upload_inp"),
upload_button_select = upload.querySelector(".upload_button.select");
const checkIsDisable = element => {
let classList = element.classList;
return classList.contains('disable') || classList.contains('loading');
}
upload_button_select.addEventListener('click', function () {
if (checkIsDisable(this)) return;
upload_inp.click();
})
const changeBASE64 = file => {
return new Promise(resolve => {
let fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = ev => {
resolve(ev.target.result);
}
})
}
upload_inp.addEventListener('change', async () => {
let file = upload_inp.files[0],
BASE64,
data;
if (!file) return;
if (2 * 1024 * 1024 < file.size) {
alert('上传的文件不能超过2MB~~');
return;
}
upload_button_select.classList.add('loading');
BASE64 = await changeBASE64(file);
try {
data = await instance.post('/upload_single_base64', {
file: encodeURIComponent(BASE64),
filename: file.name
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
if (+data.code == 0) {
alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 地址去访问~~`);
return;
}
throw data.codeText;
} catch (err) {
alert('很遗憾,文件上传失败,请您稍后再试~~');
} finally {
upload_button_select.classList.remove('loading');
upload_inp.value = '';
}
})
})();
文件缩略图 & 自动生成名字
<div class="item">
<h3>单一文件上传「缩略图处理」</h3>
<section class="upload_box" id="upload3">
<input type="file" class="upload_inp" accept=".jpg,.jpeg,.png">
<div class="upload_button_box">
<button type="button" class="upload_button select">选择文件</button>
<button type="button" class="upload_button upload">上传到服务器</button>
</div>
<div class="upload_abbre">
<img src="" alt="">
</div>
</section>
</div>
(function () {
let upload = document.querySelector("#upload3"),
upload_inp = upload.querySelector(".upload_inp"),
upload_button_select = upload.querySelector(".upload_button.select"),
upload_button_upload = upload.querySelector('.upload_button.upload'),
upload_abbre = upload.querySelector('.upload_abbre'),
upload_abbre_img = upload_abbre.querySelector('img');
let _file = null;
const checkIsDisable = element => {
let classList = element.classList;
return classList.contains('disable') || classList.contains('loading');
}
const changeBASE64 = file => {
return new Promise(resolve => {
let fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = ev => {
resolve(ev.target.result)
}
})
}
const changeBuffer = file => {
return new Promise(resolve => {
let fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
fileReader.onload = ev => {
let buffer = ev.target.result,
spark = new SparkMD5.ArrayBuffer(),
HASH,
suffix;
spark.append(buffer);
HASH = spark.end();
suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)[1];
resolve({
buffer,
HASH,
suffix,
filename: `${HASH}.${suffix}`
});
}
})
}
upload_button_select.addEventListener('click', function () {
if (checkIsDisable(this)) return;
upload_inp.click();
})
upload_inp.addEventListener('change', async () => {
let file = upload_inp.files[0],
BASE64;
if (!file) return;
_file = file;
upload_button_select.classList.add('disable');
BASE64 = await changeBASE64(file);
upload_abbre.style.display = 'block';
upload_abbre_img.src = BASE64;
upload_button_select.classList.remove('disable');
})
const changeDisable = flag => {
if (flag) {
upload_button_select.classList.add('disable');
upload_button_upload.classList.add('loading');
return;
}
upload_button_select.classList.remove('disable');
upload_button_upload.classList.remove('loading');
}
upload_button_upload.addEventListener('click', async function () {
if (changeDisable(this)) return;
if (!_file) {
alert('请您先选择要上传的文件~~');
return;
}
changeDisable(true);
let { filename } = await changeBuffer(_file);
let formData = new FormData();
formData.append('file', _file);
formData.append('filename', filename);
instance.post('/upload_single_name', formData).then(data => {
if (+data.code == 0) {
alert(`文件已经上传成功~~,您可以基于 ${data.servicePath} 访问这个资源~~`);
return;
}
return Promise.reject(data.codeText)
}).catch(reason => {
alert('文件上传失败,请您稍后再试~~');
}).finally(() => {
changeDisable(false);
upload_abbre.style.display = 'none';
upload_abbre_img.src = '';
_file = null;
})
})
})();
进度管控
<div class="item">
<h3>单一文件上传「进度管控」</h3>
<section class="upload_box" id="upload4">
<input type="file" class="upload_inp">
<div class="upload_button_box">
<button type="button" class="upload_button select">上传文件</button>
</div>
<div class="upload_progress">
<div class="value"></div>
</div>
</section>
</div>
(function () {
let upload = document.querySelector('#upload4'),
upload_inp = upload.querySelector('.upload_inp'),
upload_button_select = upload.querySelector('.upload_button.select'),
upload_progress = upload.querySelector('.upload_progress'),
upload_progress_value = upload_progress.querySelector('.value');
const checkIsDisable = element => {
let classList = element.classList;
return classList.contains('disable') || classList.contains('loading');
}
upload_button_select.addEventListener('click', function () {
if (checkIsDisable(this)) return;
upload_inp.click();
})
upload_inp.addEventListener('change', async function () {
let file = upload_inp.files[0],
data;
if (!file) return;
upload_button_select.classList.add('loading');
try {
let formData = new FormData();
formData.append('file', file);
formData.append('filename', file.name);
data = await instance.post('/upload_single', formData, {
onUploadProgress(ev) {
let { loaded, total } = ev;
upload_progress.style.display = 'block';
upload_progress_value.style.width = `${loaded / total * 100}%`;
}
})
if (+data.code === 0) {
upload_progress_value.style.width = `100%`;
await delay(300);
alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 访问该文件~~`);
return;
}
throw data.codeText;
} catch (err) {
alert('很遗憾,文件上传失败,请您稍后再试~~');
} finally {
upload_button_select.classList.remove('loading');
upload_progress.style.display = 'none';
upload_progress_value.style.width = `0%`;
}
})
})();
多文件上传
<div class="item">
<h3>多文件上传</h3>
<section class="upload_box" id="upload5">
<input type="file" class="upload_inp" multiple>
<div class="upload_button_box">
<button type="button" class="upload_button select">选择文件</button>
<button type="button" class="upload_button upload">上传到服务器</button>
</div>
<ul class="upload_list">
</ul>
</section>
</div>
(function () {
let upload = document.querySelector('#upload5'),
upload_inp = upload.querySelector('.upload_inp'),
upload_button_select = upload.querySelector('.upload_button.select'),
upload_button_upload = upload.querySelector('.upload_button.upload'),
upload_list = upload.querySelector('.upload_list');
let _files = [];
upload_button_select.addEventListener('click', function () {
if (checkIsDisable(this)) return;
upload_inp.click();
})
const createRandom = () => {
let ran = Math.random() * new Date();
return ran.toString(16).replace('.', '');
};
upload_inp.addEventListener('change', async function () {
_files = Array.from(upload_inp.files);
if (_files.length === 0) return;
_files = _files.map(file => {
return {
file,
filename: file.name,
key: createRandom()
};
});
let str = ``;
_files.forEach((item, index) => {
str += `<li key="${item.key}">
<span>文件${index + 1}:${item.filename}</span>
<span><em>移除</em></span>
</li>`;
});
upload_list.innerHTML = str;
upload_list.style.display = 'block';
})
upload_list.addEventListener('click', ev => {
let target = ev.target,
curLi = null,
key;
if (target.tagName == 'EM') {
curLi = target.parentNode.parentNode;
if (!curLi) return;
upload_list.removeChild(curLi);
key = curLi.getAttribute('key');
_files = _files.filter(item => item.key !== key);
if (_files.length === 0) {
upload_list.style.display = 'none';
}
}
})
const checkIsDisable = element => {
let classList = element.classList;
return classList.contains('disable') || classList.contains('loading');
};
const changeDisable = flag => {
if (flag) {
upload_button_select.classList.add('disable');
upload_button_upload.classList.add('loading');
return;
}
upload_button_select.classList.remove('disable');
upload_button_upload.classList.remove('loading');
};
upload_button_upload.addEventListener('click', async function () {
if (checkIsDisable(this)) return;
if (_files.length === 0) {
alert('请您先选择要上传的文件~~');
return;
}
changeDisable(true);
let upload_list_arr = Array.from(upload_list.querySelectorAll('li'));
_files = _files.map(item => {
let fm = new FormData,
curLi = upload_list_arr.find(liBox => liBox.getAttribute('key') === item.key),
curSpan = curLi ? curLi.querySelector('span:nth-last-child(1)') : null;
fm.append('file', item.file);
fm.append('filename', item.filename);
return instance.post('/upload_single', fm, {
onUploadProgress(ev) {
if (curSpan) {
curSpan.innerHTML = `${(ev.loaded / ev.total * 100).toFixed(2)}%`;
}
}
}).then(data => {
if (+data.code === 0) {
if (curSpan) {
curSpan.innerHTML = `100%`;
}
return;
}
return Promise.reject();
})
})
const clearHandle = () => {
changeDisable(false);
_files = [];
upload_list.innerHTML = '';
upload_list.style.display = 'none';
}
Promise.all(_file).then(() => {
setTimeout(() => {
alert('恭喜您,所有文件都上传成功~~');
clearHandle()
}, 300);
}).catch(() => {
alert('很遗憾,上传过程中出现问题,请您稍后再试~~');
clearHandle()
}).finally(() => {
})
})
})();
拖拽上传
<div class="item">
<h3>拖拽上传</h3>
<section class="upload_box" id="upload6">
<input type="file" class="upload_inp">
<div class="upload_drag">
<i class="icon"></i>
<span class="text">将文件拖到此处,或<a href="javascript:;" class="upload_submit">点击上传</a></span>
</div>
<div class="upload_mark">正在上传中,请稍等...</div>
</section>
</div>
(function () {
let upload = document.querySelector('#upload6'),
upload_inp = upload.querySelector('.upload_inp'),
upload_submit = upload.querySelector('.upload_submit'),
upload_mark = upload.querySelector('.upload_mark');
let isRun = false;
upload_submit.addEventListener('click', function () {
upload_inp.click();
});
const uploadFile = async file => {
if (isRun) return;
isRun = true;
upload_mark.style.display = 'block';
try {
let fm = new FormData,
data;
fm.append('file', file);
fm.append('filename', file.name);
data = await instance.post('/upload_single', fm);
if (+data.code === 0) {
alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 访问该文件~~`);
return;
}
throw data.codeText;
} catch (err) {
alert(`很遗憾,文件上传失败,请您稍后再试~~`);
} finally {
upload_mark.style.display = 'none';
isRun = false;
}
};
upload_inp.addEventListener('change', function () {
let file = upload_inp.files[0];
if (!file) return;
uploadFile(file);
});
upload.addEventListener('dragover', function (ev) {
ev.preventDefault();
});
upload.addEventListener('drop', function (ev) {
ev.preventDefault();
let file = ev.dataTransfer.files[0];
if (!file) return;
uploadFile(file);
});
})();
大文件上传
<div class="container">
<div class="item">
<h3>大文件上传</h3>
<section class="upload_box" id="upload7">
<input type="file" class="upload_inp">
<div class="upload_button_box">
<button class="upload_button select">上传图片</button>
</div>
<div class="upload_progress">
<div class="value"></div>
</div>
</section>
</div>
</div>
(function () {
let upload = document.querySelector('#upload7'),
upload_inp = upload.querySelector('.upload_inp'),
upload_button_select = upload.querySelector('.upload_button.select'),
upload_progress = upload.querySelector('.upload_progress'),
upload_progress_value = upload_progress.querySelector('.value');
const checkIsDisable = element => {
let classList = element.classList;
return classList.contains('disable') || classList.contains('loading');
};
const changeBuffer = file => {
return new Promise(resolve => {
let fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
fileReader.onload = ev => {
let buffer = ev.target.result,
spark = new SparkMD5.ArrayBuffer(),
HASH,
suffix;
spark.append(buffer);
HASH = spark.end();
suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)[1];
resolve({
buffer,
HASH,
suffix,
filename: `${HASH}.${suffix}`
});
};
});
};
upload_button_select.addEventListener('click', function () {
if (checkIsDisable(this)) return;
upload_inp.click();
});
upload_inp.addEventListener('change', async function () {
let file = upload_inp.files[0];
if (!file) return;
upload_button_select.classList.add('loading');
upload_progress.style.display = 'block';
let already = {},
data = null,
{
HASH,
suffix
} = await changeBuffer(file);
try {
data = await instance.get('/upload_already', {
params: {
HASH
}
});
if (+data.code === 0) {
already = data.fileList;
}
} catch (err) { }
let max = 1024 * 100,
count = Math.ceil(file.size / max),
index = 0,
chunks = [];
if (count > 100) {
max = file.size / 100;
count = 100;
}
while (index < count) {
chunks.push({
file: file.slice(index * max, (index + 1) * max),
filename: `${HASH}_${index + 1}.${suffix}`
})
index++;
}
index = 0;
const clear = () => {
upload_button_select.classList.remove('loading');
upload_progress.style.display = 'none';
upload_progress_value.style.width = '0%';
};
const complete = async () => {
index++;
upload_progress_value.style.width = `${index / count * 100}%`;
if (index < count) return;
upload_progress_value.style.width = `100%`;
try {
data = await instance.post('/upload_merge', {
HASH,
count
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
if (+data.code === 0) {
await delay(300)
alert(`恭喜您,文件上传成功,您可以基于 ${data.servicePath} 访问该文件~~`);
clear();
return;
}
throw data.codeText;
} catch (error) {
alert('切片合并失败,请您稍后再试~~');
clear();
}
}
chunks.forEach(chunk => {
if (already.length > 0 && already.includes(chunk.filename)) {
complete();
return;
}
let fm = new FormData;
fm.append('file', chunk.file);
fm.append('filename', chunk.filename);
instance.post('/upload_chunk', fm).then(data => {
if (+data.code === 0) {
complete();
return;
}
return Promise.reject(data.codeText);
}).catch(() => {
alert('当前切片上传失败,请您稍后再试~~');
clear();
});
})
})
})();
Utils
(function () {
"use strict";
const getProto = Object.getPrototypeOf,
class2type = {},
toString = class2type.toString,
hasOwn = class2type.hasOwnProperty;
const isFunction = function isFunction(obj) {
return typeof obj === "function" && typeof obj.nodeType !== "number" &&
typeof obj.item !== "function";
};
const isWindow = function isWindow(obj) {
return obj != null && obj === obj.window;
};
const toType = function toType(obj) {
let reg = /^\[object (.+)\]$/;
if (obj == null) return obj + "";
return typeof obj === "object" || typeof obj === "function" ?
reg.exec(toString.call(obj))[1].toLowerCase() :
typeof obj;
};
const isPlainObject = function isPlainObject(obj) {
let proto, Ctor;
if (!obj || toString.call(obj) !== "[object Object]") return false;
proto = getProto(obj);
if (!proto) return true;
Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
return typeof Ctor === "function" && Ctor === Object;
};
const isEmptyObject = function isEmptyObject(obj) {
if (obj == null || !/^(object|function)$/.test(typeof obj)) return false;
let keys = Object.getOwnPropertyNames(obj);
if (typeof Symbol !== "undefined") keys = keys.concat(Object.getOwnPropertySymbols(obj));
return keys.length === 0;
};
const isArrayLike = function isArrayLike(obj) {
let length = !!obj && "length" in obj && obj.length,
type = toType(obj);
if (isFunction(obj) || isWindow(obj)) return false;
return type === "array" || length === 0 ||
typeof length === "number" && length > 0 && (length - 1) in obj;
};
const isNumeric = function isNumeric(obj) {
let type = typeof (obj);
return (type === "number") || type === "string" && !isNaN(obj)
}
const clearTimer = function clearTimer(timer) {
if (timer !== null) clearTimeout(timer);
return null;
};
const debounce = function debounce(func, wait, immediate) {
if (typeof func !== "function") throw new TypeError("func is not a function!");
if (typeof wait === "boolean") immediate = wait;
if (typeof wait !== "number") wait = 300;
if (typeof immediate !== "boolean") immediate = false;
let timer = null;
return function operate(...params) {
let now = !timer && immediate;
timer = clearTimer(timer);
timer = setTimeout(() => {
timer = clearTimer(timer);
if (!immediate) func.apply(this, params);
}, wait);
if (now) return func.apply(this, params);
};
};
const throttle = function throttle(func, wait) {
if (typeof func !== "function") throw new TypeError("func is not a function!");
if (typeof wait !== "number") wait = 300;
let timer = null,
previous = 0;
return function operate(...params) {
let now = +new Date(),
remaining = wait - (now - pervious);
if (remaining <= 0) {
timer = clearTimer(timer);
previous = +new Date();
return func.apply(this, params);
}
if (!timer) {
timer = setTimeout(() => {
timer = clearTimer(timer);
previous = +new Date();
func.apply(this, params);
}, remaining);
}
};
};
const each = function each(obj, callback) {
if (obj == null || !/^object$/.test(typeof obj)) throw new TypeError("obj must be an object/array/likeArray");
if (typeof callback !== "function") throw new TypeError("callback is not a function");
let item, keys, key;
if (isArrayLike(obj)) {
for (let i = 0; i < obj.length; i++) {
item = obj[i];
if (callback.call(item, item, i) === false) break;
}
} else {
keys = Object.getOwnPropertyNames(obj);
if (typeof Symbol !== "undefined") keys = keys.concat(Object.getOwnPropertySymbols(obj));
for (let i = 0; i < keys.length; i++) {
key = keys[i];
item = obj[key];
if (callback.call(item, item, key) === false) break;
}
}
return obj;
};
const clone = function clone(obj, deep, exist) {
if (obj == null) return obj;
if (typeof deep !== "boolean") deep = false;
let ctor = obj.constructor,
type = toType(obj),
isArray = Array.isArray(obj),
isObject = isPlainObject(obj),
result;
if (/^(regexp|date)$/i.test(type)) return new ctor(obj);
if (/^(error)$/i.test(type)) return new ctor(obj.message);
if (typeof obj === "function") {
return function (...params) {
return obj.call(this, ...params);
};
}
if (!isArray && !isObject) return obj;
if (!Array.isArray(exist)) exist = [];
if (exist.indexOf(obj) > -1) return obj;
exist.push(obj);
result = new ctor();
each(obj, (value, key) => {
if (deep) {
result[key] = clone(value, deep, exist);
return;
}
result[key] = value;
});
return result;
};
const merge = function merge() {
let options,
target = arguments[0] || {},
i = 1,
length = arguments.length,
exist = arguments[length - 1],
deep = false;
if (typeof target === "boolean") {
deep = target;
target = arguments[i] || {};
i++;
}
if (target == null || (typeof target !== "object" && !isFunction(target))) target = {};
Array.isArray(exist) && exist.isExist ? length-- : (exist = [], exist.isExist = true);
for (; i < length; i++) {
options = arguments[i];
if (options == null) continue;
if (exist.indexOf(options) > -1) return options;
exist.push(options);
each(options, (copy, name) => {
let copyIsArray = Array.isArray(copy),
copyIsObject = isPlainObject(copy),
src = target[name];
if (deep && copy && (copyIsArray || copyIsObject)) {
if (copyIsArray && !Array.isArray(src)) src = [];
if (copyIsObject && !isPlainObject(src)) src = {};
target[name] = merge(deep, src, copy, exist);
} else if (copy !== undefined) {
target[name] = copy;
}
});
}
return target;
};
const utils = {
version: '1.0.0',
debounce,
throttle,
isFunction,
isWindow,
toType,
isPlainObject,
isEmptyObject,
isArrayLike,
isNumeric,
each,
clone,
merge
};
if (typeof module === "object" && typeof module.exports === "object") module.exports = utils;
if (typeof window !== "undefined") window.utils = utils;
})();