javascript通用方法封装

这篇博客主要介绍了JavaScript中的对象深拷贝,包括基本数据类型拷贝、对象拷贝、深拷贝以及多层对象拷贝的实现,特别讨论了原生方法的局限性和递归复制、JSON转换等解决方案。同时,博主分享了如何处理兼容iOS的时间字符串格式问题,以确保在不同平台上的正确运行。最后,提供了完整的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

工作上常用方法每次重写,便想封装一起,方便调用,也当作备忘录

时间字符串格式

js中的Date对象原生的方法里,都不适用实际需求:"2022-11-09 12:03:32"

let t = new Date();
console.log(t.toString());//Wed Nov 09 2022 12:14:09 GMT+0800 (中国标准时间)
console.log(t.toLocaleString());//2022/11/9 12:14:09

将斜杠"/"替换为"-",可以初步实现,其中月、日不满10没补0倒是能接受。

new Date().toLocaleString().replace(/\//g,"-");

replaceAll()方法有的地方不兼容,所以用replace()
其中toLocaleString()方法,显然是转为当地时间,官方将我们所在时区时间格式定为这样。在某些场景格式不对。

  • 其他时区运行该代码
  • 将浏览器信息设为其他时区
  • 其他接入js的API但时区设置为其他地方的。

可以自己写个自定义的方法,直接给到Date的原型链上:

/**
 增加Date自定义原型链
 给Date实例对象添加toStr()方法,返回年月日时分秒字符串,补0,如 `2022-08-08 17:12:00`
 */
Date.prototype.toStr = function () {
    // return this.toLocaleString().replace(/\//g,"-");//本地时间格式,CN时:`2022/8/11 09:22:08`
    let y = this.getFullYear();
    let M = this.getMonth() + 1;
    let d = this.getDate();
    let h = this.getHours();
    let m = this.getMinutes();
    let s = this.getSeconds();
    M = M > 9 ? M : ("0" + M);
    d = d > 9 ? d : ("0" + d);
    h = h > 9 ? h : ("0" + h);
    m = m > 9 ? m : ("0" + m);
    s = s > 9 ? s : ("0" + s);
    return y + "-" + M + "-" + d + " " + h + ":" + m + ":" + s;
}

引入该文件或插入该代码,在后面就能直接:Date().toStr(),方法名是自定义的,只要不重名即可。

对象深拷贝

javascript对象可分为:基本数据类型、引用数据类型。其中引用引用型数据,其变量指向的其实是引用目标的存储地址。由于js是弱数据类型语言,在打印的时候也看不出来。如果是其他编程语言的话,引用数据会直接显示引用地址,或者对象类。

基本数据类型拷贝

闲话少说,如果是基本数据类型,复制就直接复制了:

let a=1;
let b=a;//1

对象拷贝

如果是对象:

let obj1 = {
	a:1,
	b:2
}
let obj2 = obj1;

obj1被声明的时候,就会给一个存储空间,有自己的存储地址。比如abc@1234(随便写的,便于理解),那么obj2被赋值的时候,是把这个存储地址abc@1234obj2,那么每次使用它的时候,程序会根据该地址(好比X省X市X县X小区X号)找到该对象。

深拷贝

以上两段代码就是浅拷贝,使用简单,但有个问题,如果我是想复制一个完整的对象,另外使用呢?

let obj1 = {
	a:1,
	b:2
}
let obj2 = obj1;
obj1.a=33;
console.log(obj2.a);//33

浅拷贝只是复制了地址,相当于一个人多了个外号,人还是那个人。改变一个变量下面的属性,另一个变量也会跟着改变。
这时候就要用到深拷贝。

let obj1 = {
	a:1,
	b:2
}
let arr1=[1,2,3];

let obj2 = {...obj1}
let arr2 = [...arr1];

obj1.a="txt";
arr1.push("text");

console.log(obj2.a);//1
console.log(arr2);//[1,2,3]

多层对象拷贝

js原生的深拷贝能解决大部分问题,但如果是复杂结构的对象,就处理不了。

let obj1 = {
	a:1,
	b:{
		name:"test"
	}
}
let obj2 = {...obj1}
obj1.a=100;
console.log(obj2.a);//1

obj1.b.name="abc";
console.log(obj2.b.name);//"abc"

以上示例表明:原生方法的“深拷贝”其实也不够“深”,它最多只会给第一层的内容重新开辟存储空间,再下一层还是引用数据。
假如现在想要复制一个多层结构的引用型数据,进行另外的操作,不能让两变量互相影响,那么原生方法就不适用了。

递归复制

第一种方法就是:判断子属性是什么类型,若是基本数据类型,直接复制。若是引用数据类型,如果是数组,将数组遍历,递归。如果是普通对象,也是遍历属性名、属性值,再对属性值递归。如果是function(特殊的对象),就直接复制(这种情况更建议定义一个类)。
这种方法麻烦,而且递归有溢栈、死循环的风险。(主要是麻烦)。

JSON

将数据转JSON字符串,再转js对象,会完全开辟新的存储空间。

function copy (obj) {
    return JSON.parse(JSON.stringify(obj));
}

其他

有些插件会自带深拷贝的方法,如jQuery的$.extend()。请自行探究。
拷贝的目的就是:复制某变量里所有层级内容,且不能互相影响,如果真的结构过于复杂,更建议新定义一个class

兼容iOS的时间字符串

在windows系统上的浏览器中,new Date("2022-11-09 14:22:25")这种格式是可以正常运行的。但是在苹果手机上,只能new Date("2022/11/09 14:22:25"),它只识别/而不识别-(Windows、Android都可以同时识别-/),有时候后台给的数据就是这种格式的话,就会异常(isNaN)。
所以保险起见,多平台开发时,一律将-替换为/再转时间对象。

function dateByLocal(str){
	return new Date(str.replace(/-/g,"/"));
}

也可以写个继承类

class Date_ extends Date {
    constructor(param) {
        if (typeof param === "string") {
            param = param.replace(/-/g, "/");
        }
        super(param);
    }
}

在Windows测试的时候,可以把replace里的-改为~,然后直接调用console.log(new Date_("2022~11~09 14:52:04"))
可以先判断浏览器是否为苹果,再做相应兼容性处理。可参考这篇:JS判断是苹果系统(ios)还是安卓系统(Android)或者PC端

完整代码

在网上找的其他包括未确认的方法汇总如下。有些冗杂的方法有更好的写法,下面没更新,请自行选择。

/*
* 2022-05-19 11:23:00
* tzj
* 将全局通用方法写在该文件,统一标准,统一调用
* */

"use strict";//开启严格模式

/**
 * 自定义数据转字符串
 * 不建议使用`引用数据类型`的数据:function、object、array
 * */
function toStr(data) {
    switch (typeof data) {
        case "function":
            return data.toString();
        case "number":
            if (data === Infinity) return "∞";
            if (data === -Infinity) return "-∞";
            return isNaN(data) ? "" : data.toString();
        case "object":
            if (data === null) {
                return "";
            } else if (data instanceof Array) {
                return data.map(item => {
                        return toStr(item);
                    }
                ).toString();
            } else if (data instanceof Date) {
                return timeStr(data);
            } else if (data instanceof RegExp) {
                return data.toString();
            } else {
                let str = "";
                for (let key in data) {
                    str += key + ":" + toStr(data[key]) + ",";
                }
                return str;
            }
        case "string":
            return data;
        case "symbol":
            return data.description ? data.description : "";
            // break;
        case "undefined":
            return "";
        // 大精度整数 let bi=BigInt(1);或者let bi=1n;高精度只能强转低精度number
        case "bigint":
            data.toString();
            break;
        case "boolean":
            return data ? "1" : "0";
        default:
            return data.toString();
    }
}

// 修改原型链
Date.prototype.toStr =function(format){
    return timeStr(this,format);
}

/**
 时间转字符串
 - ***time*** 时间 String、Number、Date
 - ***format*** 要显示的格式 String
 - 时间错误,则取当前时间
 - 格式错误时,默认使用"yyyy-MM-dd hh:mm:ss"
 - y 年 2位或4位,不分大小写
 - M 月 MM补0
 - d 日 dd补0,不区分大小写
 - H 时 HH补0,大写24小时制,小写12小时制,Hh、hH为错误格式
 - m 分 mm补0
 - s 秒 ss补0,不区分大小写
 - 间隔内容仅为"年月日时分秒"或"-"、"/"、”:",可搭配使用
 */
function timeStr(time, format) {
    let t = isNaN(Date.parse(time)) ? new Date() : new Date(time);
    let reg = /([Yy]{4})([-年\/])(M{1,2})([-月\/])([dD]{1,2})(日?) ([Hh]{1,2})([::时])(m{1,2})([::分])([sS]{1,2})(秒?)/g;
    let txt = format ? format.match(reg).toString() : "YYYY-MM-DD HH:mm:SS";//默认值
    let arr = txt.split(reg);
    let y = t.getFullYear().toString();
    y = arr[1].match(/y{4}/i) ? y : y.slice(-2);
    let M = t.getMonth() + 1;
    M = setZero(M, arr[3] === "MM");
    let d = t.getDate();
    d = setZero(d, arr[5].match(/d{2}/i));
    let h = t.getHours();
    h = arr[7].match(/H+/) ? h : (h % 12);
    h = setZero(h, arr[7].match(/h{2}/i));
    let m = t.getMinutes();
    m = setZero(m, arr[9] === "mm");
    let s = t.getSeconds();
    s = setZero(s, arr[11].match(/s{2}/i));
    return y + arr[2] + M + arr[4] + d + arr[6] + " " + h + arr[8] + m + arr[10] + s + arr[12];
}

/**
 * 待完善:写个递归函数,多传一个位数number(设置上限,避免溢栈),可补0任意位数
 * */
function setZero(n, b) {
    return !b ? n : n > 9 ? n : ("0" + n);
}

/**
 * 返回时间差 时分秒string
 * @param time 结束时间
 * */
function countdown(time) {
    let t = new Date(time).getTime() - new Date().getTime();
    if (t <= 0) {
        return false;
    }
    let seconds = Math.floor(t / 1000);
    let h = Math.floor(seconds / 3600);
    h = h > 0 ? (h + ":") : "";
    let m = Math.floor(seconds % 3600 / 60);
    let s = seconds % 60;
    s = (s < 10 && m > 0) ? ("0" + s) : s;
    m = m > 0 ? (m + ":") : "";
    return h + m + s;
}

/**
 设置倒计时
 @param id 显示节点id
 @param title 前缀文字
 @param time  结束时间
 */
function setCountdown(id, title, time) {
    let box = $("#" + id);
    let inter = setInterval(() => {
        let t = countdown(time);
        if (!t) {
            clearInterval(inter);
            box.hide();
        } else {
            box.show();
            box.html(title + ` <span style="color: red">${t}</span>`);
        }
    }, 1000)
}

const IMG_TYPE = [".png", ".jpg", ".jpeg", ".gif"];

/**
 检测文件名后缀
 - 获取str=123.jpg中的`.jpg`,是否包含于数组arr中
 @return boolean
 */
function testFile(str, arr) {
    arr = arr ? arr : IMG_TYPE;
    let type = /(\.[a-zA-Z1-9]+)$/.exec(str)[0];
    if (!type) return false;
    return arr.indexOf(type.toLowerCase()) > -1;
}

/**
 * 1024制,计算文件大小
 * */
function getSize(n) {
    n = Number(n);
    n = isNaN(n) ? 0 : n;
    return n < 1024 ? "0Kb" : n < 1024 * 1024 ? ((n / 1024).toFixed(1) + "Kb") : ((n / 1024 / 1024).toFixed(1) + "M");
}

/*
* 重写new Date();兼容iOS  待完善
* */
// const _Date=Date;
// Date=function (){
//     if(arguments.length>0&& typeof arguments[0] === "string" && arguments[0].includes("-")){
//         arguments[0]=arguments[0].replace(/-/g,"/");
//     }
//     return new _Date(...arguments);
// }
// new Date("2022-11-23 11:11:23");

/*
* 设置table
* 没做异常判断,请严格根据格式传参
* */
let tableBox = {
    id: "",
    varList: [],
    /*
    * 初始化
    * @param id table节点id,元素初始结构,有table、thead、tbody
    * @param varList 字段显示内容,name(必需):字段名,title(必需):显示标题名,width:当前列的宽度,callback:回调方法
    * */
    init(id, varList) {
        this.id = id;
        this.varList = varList;
        let str = "";
        varList.forEach(item => {
            str += `
                <td style="${item.width ? ('width:' + item.width) : ''}">${item.title}</td>
            `
        })
        str = `<tr>${str}</tr>`;
        $("#" + id + " thead").html(str);
    },
    getList(data) {
        let str = "";
        data.forEach(item => {
            let trStr = "";
            this.varList.forEach(val => {
                let value = item[val.name];
                trStr += `
                    <td style="${val.width ? ('width:' + val.width) : ''}">${val.callback ? val.callback(value) : value ? value : ""}</td>
                `
            })
            str += `<tr>${trStr}</tr>`;
        })
        $("#" + this.id + " tbody").html(str);
    }
}

let local = {
    get(key) {
        if (!window.localStorage) {
            alert("该浏览器不支持localStorage!请升级浏览器!");
        } else {
            return window.localStorage.getItem(key);
        }
    },
    set(key, value) {
        if (!window.localStorage) {
            alert("该浏览器不支持localStorage!请升级浏览器!");
        } else {
            window.localStorage.setItem(key, value);
        }
    }
}

let session = {
    get(key) {
        if (!window.sessionStorage) {
            alert("该浏览器不支持sessionStorage!请升级浏览器!");
        } else {
            return window.sessionStorage.getItem(key);
        }
    },
    set(key, value) {
        if (!window.sessionStorage) {
            alert("该浏览器不支持sessionStorage!请升级浏览器!");
        } else {
            window.sessionStorage.setItem(key, value);
        }
    }
}

/** 以下是从网上找的,且未验证,有的会报错。*/

// 1) 滚动条:没整明白
// window.scrollTo(0,0);//置顶
// window.scrollTo(0,document.body.scrollHeight);//置底
// 水平,垂直,取值,赋值
function scrollTop(height = null) {
    let a = document.documentElement.scrollTop;
    let b = window.pageYOfset;
    let c = document.body.scrollTop;
    if (height === null) {
        return a || b || c;
    } else {
        if (a) {
            document.documentElement.scrollTop = height;
        } else if (b) {
            window.pageYOfset = height;
        } else if (c) {
            document.body.scrollTop = height;
        }
    }
}

// document.documentElement.scrollTop || document.body.scrollTop;


// 获取非行内样式兼容
function f1(obj, attr) {
    if (window.getComputedStyle) {
        return window.getComputedStyle(obj, false)[attr]
    } else {
        return obj.currentStyle.attr
    }
}

// 获取非行内样式(兼容问题)
function getStyle(obj, attr) {             //获取非行间样式,obj是对象,attr是值
    if (obj.currentStyle) {                //针对ie获取非行内样式
        return obj.currentStyle[attr];
    } else {
        return getComputedStyle(obj, false)[attr];   //针对非ie
    }
}

// 3) 网页可视区域兼容
window.innerHeight || document.documentElement.clientHeight

window.innerWidth || document.documentElement.clientWidth

// 4)事件对象兼容
// let e = e || window.event

// 5)阻止事件冒泡兼容
// e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true

// 6)阻止默认行为兼容
// e.preventDefult ? e.preventDefult() : e.returnValue = false

// 7)事件监听兼容
// obj.addEventListener("事件去掉on", function () {
// }, 布尔值)

// obj.attachEvent("事件带on", function () {
// })  //IE

// 8)事件目标对象兼容
// var t = event.target || event.srcElement;

// 10)获取键盘的按键
// IE8以及以下版本不兼容which
// var x = event.which || event.keyCode
// var x = e.which || e.keyCode

//处理innerText和textContent
function getInnerText(element) {
    //判断element是否支持innerText
    if (typeof element.innerText === 'string') {
        return element.innerText;
    } else {
        return element.textContent;
    }
}

//设置元素之间的内容
function setInnerText(element, content) {
    if (typeof element.innerText === 'string') {
        element.innerText = content;
    } else {
        element.textContent = content;
    }
}

//处理firstElementChild的兼容性
function getFirstElementChild(parent) {
    //如果当前浏览器支持firstElementChild
    if (parent.firstElementChild) {
        return parent.firstElementChild;
    }
    var node, nodes = parent.childNodes, i = 0;
    while (node = nodes[i++]) {
        if (node.nodeType === 1) {
            return node;
        }
    }
    return null;
}

//注册事件,处理兼容性问题
function addEventListener(element, eventName, callback) {
    if (element.addEventListener) {
        element.addEventListener(eventName, callback, false);
    } else if (element.attachEvent) {
        element.attachEvent('on' + eventName, callback);
    } else {
        element['on' + eventName] = callback;
    }
}

//移除事件,处理兼容性问题
function removeEventListener(element, eventName, callback) {
    if (element.removeEventListener) {
        element.removeEventListener(element, callback, false);
    } else if (element.detachEvent) {
        element.detachEvent('on' + eventName, callback);
    } else {
        element['on' + eventName] = null;
    }
}

//获取页面滚动的距离,并处理兼容性
function getScroll() {
    return {
        scrollTop: document.documentElement.scrollTop || document.body.scrollTop,
        scrollLeft: document.documentElement.scrollLeft || document.body.scrollLeft
    }
}

//获取鼠标在页面上的坐标  解决IE8的问题
function getPage(e) {
    return {
        pageX: e.clientX + getScroll().scrollLeft,
        pageY: e.clientY + getScroll().scrollTop
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

汤卓杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值