• js 深度合并两个对象


    起因

    今天使用 vue 开发组件的时候,使用到了 echart 。
    我遇到的问题就是,我有一个基础样式,是以对象形式保存的,名称是baseStyle。这个组件对外透露一个 style 的props,类型也规定为对象,默认值为空对象。
    然后我希望这两个对象合并在一起,形成的样式为总的样式,冲突的以 style 为主。也就是说,在我有自定义样式的需求的时候,我能改变样式,比如:

    // 基础样式
    let baseStyle={
        series:[
            {
                name:"选择",
                data:[1,2,3]
            }
        ]
    }
    // 外界参数
    let style={
        series:[
            {
                name:"我",
            }
        ]
    }
    // 我希望的最终的样式
    let ans={
        series:[
            {
                name:"我",
                data:[1,2,3]
            }
        ]
    }
    

    寻求解决

    一开始是使用的Object.assign(),发现这种方案是浅复制,同名的对象key会直接覆盖掉,这不是我想要的结果。
    那就只能自己手把手的写个合并函数,开始想的是递归,然后分类处理,但是问题来了:
    踩了很多坑,比如使用typeof判断类型,我懵了,没想到数组也是object,但是函数就是function,我寻思 type 不就是类型的意思吗?(好吧就是我懒,很少用到这个关键字。我还以为会出现array类型,好吧是我typescript用多了)
    然后复制是可以复制,但是数组变成了对象的形式。

    后来我在网上寻找思路,我搜索了:js 对象深度复制。
    搜索后期间试验了几个方案,都不是很理想。
    最后在思否:一个关于对象深合并的问题?上看到了解决方案,叫去看JQuery的extend源代码,我一看,我擦,这判断类型把我整的一愣一愣的(我就是今天在判断类型上吃了亏),太牛叉了。
    但是呢,JQuery 对数组的处理,是采用合并,而不是和对象一样,相同的位置进行覆盖。覆盖的意思就是我开头的代码,数组相同位置的对象,其相同位置也能覆盖。
    我以我的需求进行了部分改写。

    解决代码

    /**
     * 深度合并代码,思路来自 zepto.js 源代码
     * 切记不要对象递归引用,否则会陷入递归跳不出来,导致堆栈溢出
     * 作用是会合并 target 和 other 对应位置的值,冲突的会保留 target 的值
     */
    function deepMerge(target:any,other:any){
        const targetToString=Object.prototype.toString.call(target);
        const otherToString=Object.prototype.toString.call(target);
        if(targetToString==="[object Object]" && otherToString==="[object Object]"){
            for(let [key,val] of Object.entries(other)){
                if(!target[key]){
                    target[key]=val;
                }else{
                    target[key]=deepMerge(target[key],val);
                }
            }
        }else if(targetToString==="[object Array]" && otherToString==="[object Array]"){
            for(let [key,val] of Object.entries(other)){
                if(target[key]){
                    target[key]=deepMerge(target[key],val);
                }else{
                    target.push(val);
                }
            }
        }
        return target;
    }
    

    总结

    代码的主要问题是判断类型,使用typeof是万万不行的,你会发现对 null、数组、对象使用,得到的结果是一致的:

    例子 使用typeof得到的字符串
    null "object"
    [] "object"
    {} "object"
    function(){} "function"
    1 "number"
    "" "string"
    true "boolean"
    undefined "undefined"
    Symbol(1) "symbol"

    instanceof 也是不行,因为它会从原型链上寻找,从而导致很多时候得到的结果不符合人意。
    完美的方案就是Object.prototype.toString.call()

    例子 使用Object.prototype.toString.call()得到的字符串
    null "[object Null]"
    [] "[object Array]"
    {} "[object Object]"
    function(){} "[object Function]"
    1 "[object Number]"
    "" "[object String]"
    true "[object Boolean]"
    undefined "[object Undefined]"
    Symbol(1) "[object Symbol]"

    你可以能会好奇,变量自带的toString()可以使用吗?答案是不可以的,在ECMAObject.prototype.toString()中的解释如下:

    Object.prototype.toString()
    When the toString method is called, the following steps are taken:

    1. Get the [[Class]] property of this object.
    2. Compute a string value by concatenating the three strings “[object “, Result (1), and “]”.
    3. Return Result (2)

    也就是说,Object.prototype.toString()调用时,它会以调用者本身的[[Class]][1]属性,拼接成"[object " + [[Class]] + "]"的形式返回。
    但是toString()是可以覆写的,每个常见的 Object 子类都进行了相应的改写,比如数组调用时:

    [].toString();
    // ''
    // 一个空字符串
    
    (function(){}).toString();
    // 'function(){}'
    
    1..toString();
    // '1'
    // 数组是基本类型,这里会把数字包装成 Number 类型再进行调用,而 Number 就是对象
    
    

    注意第三个例子, 1 后面是两个点,原因在这:唯一数字类型:number


    1. 在 JS 中,[[*]]这种以双括号包裹的格式的属性,你无法用代码直接访问,因为这是给 JS 引擎使用的。我猜测应该来自 java 语言,因为 JVM 每加载一个类,都会生成对应的Class类。这个Class会记载加载的类的相关信息,比如这个类的函数、函数参数、属性等等。 ↩︎

  • 相关阅读:
    MySQL5.6 单列、多列索引以及IN语句的优化(翻译)
    curl
    HTML meta
    access limit
    document、location、body 属性方法
    ASP对Excel的基本操作
    IE6/IE7/FF(火狐Firefox)及其他浏览器的兼容性通用解决方法
    CSS兼容IE与Firefox要点分析
    PHP函数
    vs2005 修改新增文件时的默认编码方式(转)
  • 原文地址:https://www.cnblogs.com/panshaojun/p/16040628.html
Copyright © 2020-2023  润新知