【问题标题】:Dynamically set property of nested object动态设置嵌套对象的属性
【发布时间】:2013-09-27 00:44:34
【问题描述】:

我有一个对象,它可以是任意数量的深度并且可以具有任何现有属性。 例如:

var obj = {
    db: {
        mongodb: {
            host: 'localhost'
        }
    }
};

我想像这样设置(或覆盖)属性:

set('db.mongodb.user', 'root');
// or:
set('foo.bar', 'baz');

属性字符串可以有任何深度,值可以是任何类型/事物。
如果属性键已经存在,则不需要合并作为值的对象和数组。

前面的例子会产生以下对象:

var obj = {
    db: {
        mongodb: {
            host: 'localhost',
            user: 'root'
        }
    },
    foo: {
        bar: baz
    }
};

如何实现这样的功能?

【问题讨论】:

标签: javascript ecmascript-5


【解决方案1】:

这个函数,使用你指定的参数,应该添加/更新obj容器中的数据。请注意,您需要跟踪 obj 架构中的哪些元素是容器,哪些是值(字符串、整数等),否则您将开始抛出异常。

obj = {};  // global object

function set(path, value) {
    var schema = obj;  // a moving reference to internal objects within obj
    var pList = path.split('.');
    var len = pList.length;
    for(var i = 0; i < len-1; i++) {
        var elem = pList[i];
        if( !schema[elem] ) schema[elem] = {}
        schema = schema[elem];
    }

    schema[pList[len-1]] = value;
}

set('mongo.db.user', 'root');

【讨论】:

  • @bpmason1 你能解释一下为什么你到处都使用var schema = obj 而不是obj 吗?
  • @sman591 schema 是一个指针,它沿着schema = schema[elem] 的路径向下移动。所以在for循环之后,schema[pList[len - 1]]指向obj中的mongo.db.user。
  • 这解决了我的问题,谢谢,在 MDN 文档中找不到这个。但我还有一个疑问,如果赋值运算符给出了对内部对象的引用,那么如何从 object1 中创建一个单独的 object2,以便对 object2 所做的更改不会反映在 object1 上。
  • @Onix 您可以为此使用 lodash cloneDeep 函数。
  • @Onix const clone = JSON.parse(JSON.stringify(obj))
【解决方案2】:

如果您只需要更改更深层次的嵌套对象,那么另一种方法可能是引用该对象。由于 JS 对象由它们的引用处理,因此您可以创建对您具有字符串键访问权限的对象的引用。

例子:

// The object we want to modify:
var obj = {
    db: {
        mongodb: {
            host: 'localhost',
            user: 'root'
        }
    },
    foo: {
        bar: baz
    }
};

var key1 = 'mongodb';
var key2 = 'host';

var myRef = obj.db[key1]; //this creates a reference to obj.db['mongodb']

myRef[key2] = 'my new string';

// The object now looks like:
var obj = {
    db: {
        mongodb: {
            host: 'my new string',
            user: 'root'
        }
    },
    foo: {
        bar: baz
    }
};

【讨论】:

    【解决方案3】:

    另一种方法是使用递归来挖掘对象:

    (function(root){
    
      function NestedSetterAndGetter(){
        function setValueByArray(obj, parts, value){
    
          if(!parts){
            throw 'No parts array passed in';
          }
    
          if(parts.length === 0){
            throw 'parts should never have a length of 0';
          }
    
          if(parts.length === 1){
            obj[parts[0]] = value;
          } else {
            var next = parts.shift();
    
            if(!obj[next]){
              obj[next] = {};
            }
            setValueByArray(obj[next], parts, value);
          }
        }
    
        function getValueByArray(obj, parts, value){
    
          if(!parts) {
            return null;
          }
    
          if(parts.length === 1){
            return obj[parts[0]];
          } else {
            var next = parts.shift();
    
            if(!obj[next]){
              return null;
            }
            return getValueByArray(obj[next], parts, value);
          }
        }
    
        this.set = function(obj, path, value) {
          setValueByArray(obj, path.split('.'), value);
        };
    
        this.get = function(obj, path){
          return getValueByArray(obj, path.split('.'));
        };
    
      }
      root.NestedSetterAndGetter = NestedSetterAndGetter;
    
    })(this);
    
    var setter = new this.NestedSetterAndGetter();
    
    var o = {};
    setter.set(o, 'a.b.c', 'apple');
    console.log(o); //=> { a: { b: { c: 'apple'}}}
    
    var z = { a: { b: { c: { d: 'test' } } } };
    setter.set(z, 'a.b.c', {dd: 'zzz'}); 
    
    console.log(JSON.stringify(z)); //=> {"a":{"b":{"c":{"dd":"zzz"}}}}
    console.log(JSON.stringify(setter.get(z, 'a.b.c'))); //=> {"dd":"zzz"}
    console.log(JSON.stringify(setter.get(z, 'a.b'))); //=> {"c":{"dd":"zzz"}}
    

    【讨论】:

      【解决方案4】:

      Lodash 有一个名为 update 的方法,可以满足您的需求。

      该方法接收以下参数:

      1. 要更新的对象
      2. 要更新的属性路径(属性可以深度嵌套)
      3. 返回要更新的值的函数(将原始值作为参数)

      在您的示例中,它看起来像这样:

      _.update(obj, 'db.mongodb.user', function(originalValue) {
        return 'root'
      })
      

      【讨论】:

        【解决方案5】:

        Lodash 有一个_.set() 方法。

        _.set(obj, 'db.mongodb.user', 'root');
        _.set(obj, 'foo.bar', 'baz');
        

        【讨论】:

        • 也可以用来设置key的值吗?如果是的话,你能分享一个例子吗?谢谢
        • 这很好,但是您将如何跟踪/确定路径?
        • @aheuermann 我有几级嵌套数组,如果是多级嵌套对象数组,如何设置属性
        • lodash set 也接受一个数组作为路径,例如_.set(obj, ['db', 'mongodb', 'user'], 'root');
        • 请注意,当密钥的一部分包含像“foo.bar.350350”这样的数字时,这将无法按预期工作。相反,它将创建 350350 个空元素!
        【解决方案6】:

        有点晚了,但这里有一个非图书馆的、更简单的答案:

        /**
         * Dynamically sets a deeply nested value in an object.
         * Optionally "bores" a path to it if its undefined.
         * @function
         * @param {!object} obj  - The object which contains the value you want to change/set.
         * @param {!array} path  - The array representation of path to the value you want to change/set.
         * @param {!mixed} value - The value you want to set it to.
         * @param {boolean} setrecursively - If true, will set value of non-existing path as well.
         */
        function setDeep(obj, path, value, setrecursively = false) {
            path.reduce((a, b, level) => {
                if (setrecursively && typeof a[b] === "undefined" && level !== path.length){
                    a[b] = {};
                    return a[b];
                }
        
                if (level === path.length){
                    a[b] = value;
                    return value;
                } 
                return a[b];
            }, obj);
        }
        

        我制作的这个功能可以完全满足你的需要,而且还能做更多。

        假设我们要更改深度嵌套在该对象中的目标值:

        let myObj = {
            level1: {
                level2: {
                   target: 1
               }
            }
        }
        

        所以我们会这样调用我们的函数:

        setDeep(myObj, ["level1", "level2", "target1"], 3);
        

        将导致:

        我的对象 = { 1级: { 级别2:{ 目标:3 } } }

        将 set recursive 标志设置为 true 将设置对象(如果它们不存在)。

        setDeep(myObj, ["new", "path", "target"], 3, true);
        

        将导致:

        obj = myObj = {
            new: {
                 path: {
                     target: 3
                 }
            },
            level1: {
                level2: {
                   target: 3
               }
            }
        }
        

        【讨论】:

        • 用过这段代码,简洁明了。我没有计算level,而是使用reduce 的第三个参数。
        • 我认为level需要+1或path.length -1
        • 不执行归约时不应使用归约。
        • @McTrafik 你应该改用什么
        • 一个循环。 reduce 函数只是 for 循环的糖语法,带有适用于归约的累加器。看到类似这样的内容:medium.com/winnintech/… 并且此代码不会累积任何内容,也不会执行归约,因此这里的 reduce 调用是对模式的滥用。
        【解决方案7】:

        受@bpmason1 的回答启发:

        function leaf(obj, path, value) {
          const pList = path.split('.');
          const key = pList.pop();
          const pointer = pList.reduce((accumulator, currentValue) => {
            if (accumulator[currentValue] === undefined) accumulator[currentValue] = {};
            return accumulator[currentValue];
          }, obj);
          pointer[key] = value;
          return obj;
        }
        

        例子:

        const obj = {
          boats: {
            m1: 'lady blue'
          }
        };
        leaf(obj, 'boats.m1', 'lady blue II');
        leaf(obj, 'boats.m2', 'lady bird');
        console.log(obj); // { boats: { m1: 'lady blue II', m2: 'lady bird' } }
        

        【讨论】:

          【解决方案8】:

          我们可以使用递归函数:

          /**
           * Sets a value of nested key string descriptor inside a Object.
           * It changes the passed object.
           * Ex:
           *    let obj = {a: {b:{c:'initial'}}}
           *    setNestedKey(obj, ['a', 'b', 'c'], 'changed-value')
           *    assert(obj === {a: {b:{c:'changed-value'}}})
           *
           * @param {[Object]} obj   Object to set the nested key
           * @param {[Array]} path  An array to describe the path(Ex: ['a', 'b', 'c'])
           * @param {[Object]} value Any value
           */
          export const setNestedKey = (obj, path, value) => {
            if (path.length === 1) {
              obj[path] = value
              return
            }
            return setNestedKey(obj[path[0]], path.slice(1), value)
          }
          

          更简单!

          【讨论】:

          • 看起来不错!只需要检查 obj 参数以确保它不是假的,如果链下的任何道具都不存在,则会抛出错误。
          • 你可以使用 path.slice(1);
          • 优秀的答案,简洁明了的解决方案。
          • 我相信 if 语句应该是 obj[path[0]] = value;,因为 path 始终是 string[] 类型,即使只剩下 1 个字符串。
          • Javascript 对象应该使用obj[['a']] = 'new value' 工作。检查代码:jsfiddle.net/upsdne03
          【解决方案9】:

          ES6 也有一个很酷的方法来做到这一点,使用 Computed Property NameRest Parameter

          const obj = {
            levelOne: {
              levelTwo: {
                levelThree: "Set this one!"
              }
            }
          }
          
          const updatedObj = {
            ...obj,
            levelOne: {
              ...obj.levelOne,
              levelTwo: {
                ...obj.levelOne.levelTwo,
                levelThree: "I am now updated!"
              }
            }
          }
          

          如果levelThree 是动态属性,即设置levelTwo 中的任何属性,您可以使用[propertyName]: "I am now updated!",其中propertyName 包含levelTwo 中的属性名称。

          【讨论】:

            【解决方案10】:

            我只是用ES6+递归写了一个小函数来达到目的。

            updateObjProp = (obj, value, propPath) => {
                const [head, ...rest] = propPath.split('.');
            
                !rest.length
                    ? obj[head] = value
                    : this.updateObjProp(obj[head], value, rest.join('.'));
            }
            
            const user = {profile: {name: 'foo'}};
            updateObjProp(user, 'fooChanged', 'profile.name');
            

            我在响应更新状态时经常使用它,它对我来说效果很好。

            【讨论】:

            • 这很方便,我必须在 proPath 上放置一个 toString() 以使其与嵌套属性一起使用,但之后效果很好。 const [head, ...rest] = propPath.toString().split('.');
            • @user738048 @Bruno-Joaquim this.updateStateProp(obj[head], value, rest); 应该是 this.updateStateProp(obj[head], value, rest.join());
            【解决方案11】:

            我创建了gist,用于根据正确答案通过字符串设置和获取 obj 值。您可以下载它或将其用作 npm/yarn 包。

            // yarn add gist:5ceba1081bbf0162b98860b34a511a92
            // npm install gist:5ceba1081bbf0162b98860b34a511a92
            export const DeepObject = {
              set: setDeep,
              get: getDeep
            };
            
            // https://*.com/a/6491621
            function getDeep(obj: Object, path: string) {
              path = path.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
              path = path.replace(/^\./, '');           // strip a leading dot
              const a = path.split('.');
              for (let i = 0, l = a.length; i < l; ++i) {
                const n = a[i];
                if (n in obj) {
                  obj = obj[n];
                } else {
                  return;
                }
              }
            
              return obj;
            }
            
            // https://*.com/a/18937118
            function setDeep(obj: Object, path: string, value: any) {
              let schema = obj;  // a moving reference to internal objects within obj
              const pList = path.split('.');
              const len = pList.length;
              for (let i = 0; i < len - 1; i++) {
                const elem = pList[i];
                if (!schema[elem]) {
                  schema[elem] = {};
                }
                schema = schema[elem];
              }
            
              schema[pList[len - 1]] = value;
            }
            
            // Usage
            // import {DeepObject} from 'somePath'
            //
            // const obj = {
            //   a: 4,
            //   b: {
            //     c: {
            //       d: 2
            //     }
            //   }
            // };
            //
            // DeepObject.set(obj, 'b.c.d', 10); // sets obj.b.c.d to 10
            // console.log(DeepObject.get(obj, 'b.c.d')); // returns 10
            

            【讨论】:

              【解决方案12】:

              如果您想要一个需要先前属性存在的函数,那么您可以使用类似这样的东西,它还会返回一个标志,说明它是否设法找到并设置了嵌套属性。

              function set(obj, path, value) {
                  var parts = (path || '').split('.');
                  // using 'every' so we can return a flag stating whether we managed to set the value.
                  return parts.every((p, i) => {
                      if (!obj) return false; // cancel early as we havent found a nested prop.
                      if (i === parts.length - 1){ // we're at the final part of the path.
                          obj[parts[i]] = value;          
                      }else{
                          obj = obj[parts[i]]; // overwrite the functions reference of the object with the nested one.            
                      }   
                      return true;        
                  });
              }
              

              【讨论】:

                【解决方案13】:

                JQuery 有一个扩展方法:

                https://api.jquery.com/jquery.extend/

                只需将覆盖作为对象传递,它将合并两者。

                【讨论】:

                  【解决方案14】:

                  我需要实现同样的目标,但在 Node.js 中...... 所以,我发现了这个不错的模块:https://www.npmjs.com/package/nested-property

                  例子:

                  var mod = require("nested-property");
                  var obj = {
                    a: {
                      b: {
                        c: {
                          d: 5
                        }
                      }
                    }
                  };
                  console.log(mod.get(obj, "a.b.c.d"));
                  mod.set(obj, "a.b.c.d", 6);
                  console.log(mod.get(obj, "a.b.c.d"));
                  

                  【讨论】:

                  • 如何解决复杂的嵌套对象。 ``` const x = { '一': 1, '二': 2, '三': { '一': 1, '二': 2, '三': [ { '一': 1 }, { '一': '一' }, { '一': '我' } ] }, '四': [0, 1, 2] }; console.log(np.get(x, 'three.three[0].one')); ```
                  【解决方案15】:

                  受 ClojureScript 的 assoc-in (https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/core.cljs#L5280) 启发,使用递归:

                  /**
                   * Associate value (v) in object/array (m) at key/index (k).
                   * If m is falsy, use new object.
                   * Returns the updated object/array.
                   */
                  function assoc(m, k, v) {
                      m = (m || {});
                      m[k] = v;
                      return m;
                  }
                  
                  /**
                   * Associate value (v) in nested object/array (m) using sequence of keys (ks)
                   * to identify the path to the nested key/index.
                   * If one of the values in the nested object/array doesn't exist, it adds
                   * a new object.
                   */
                  function assoc_in(m={}, [k, ...ks], v) {
                      return ks.length ? assoc(m, k, assoc_in(m[k], ks, v)) : assoc(m, k, v);
                  }
                  
                  /**
                   * Associate value (v) in nested object/array (m) using key string notation (s)
                   * (e.g. "k1.k2").
                   */
                  function set(m, s, v) {
                      ks = s.split(".");
                      return assoc_in(m, ks, v);
                  }
                  

                  注意:

                  使用提供的实现,

                  assoc_in({"a": 1}, ["a", "b"], 2) 
                  

                  返回

                  {"a": 1}
                  

                  我希望它在这种情况下引发错误。如果需要,您可以在 assoc 中添加检查以验证 m 是对象还是数组,否则会引发错误。

                  【讨论】:

                    【解决方案16】:

                    我试着写了这个简单的设置方法,它可能对某人有帮助!

                    function set(obj, key, value) {
                     let keys = key.split('.');
                     if(keys.length<2){ obj[key] = value; return obj; }
                    
                     let lastKey = keys.pop();
                    
                     let fun = `obj.${keys.join('.')} = {${lastKey}: '${value}'};`;
                     return new Function(fun)();
                    }
                    
                    var obj = {
                    "hello": {
                        "world": "test"
                    }
                    };
                    
                    set(obj, "hello.world", 'test updated'); 
                    console.log(obj);
                    
                    set(obj, "hello.world.again", 'hello again'); 
                    console.log(obj);
                    
                    set(obj, "hello.world.again.onece_again", 'hello once again');
                    console.log(obj);

                    【讨论】:

                      【解决方案17】:

                      我使用纯 es6 和不会改变原始对象的递归提出了自己的解决方案。

                      const setNestedProp = (obj = {}, [first, ...rest] , value) => ({
                        ...obj,
                        [first]: rest.length
                          ? setNestedProp(obj[first], rest, value)
                          : value
                      });
                      
                      const result = setNestedProp({}, ["first", "second", "a"], 
                      "foo");
                      const result2 = setNestedProp(result, ["first", "second", "b"], "bar");
                      
                      console.log(result);
                      console.log(result2);

                      【讨论】:

                      • 您可以通过使用默认值声明“obj”来消除第一个 if 块 setNestedProp = (obj = {}, keys, value) => {
                      • 不错。回顾也可以在原地解构 keys 参数并节省另一行代码
                      • 现在基本上是一个班轮?
                      【解决方案18】:
                      const set = (o, path, value) => {
                          const props = path.split('.');
                          const prop = props.shift()
                          if (props.length === 0) {
                              o[prop] = value
                          } else {
                              o[prop] = o[prop] ?? {}
                              set(o[prop], props.join('.'), value)
                          }
                      }
                      

                      【讨论】:

                        【解决方案19】:

                        迟到 - 这是一个普通的 js 函数,它接受路径作为参数并返回修改后的对象/json

                        let orig_json = {
                          string: "Hi",
                          number: 0,
                          boolean: false,
                          object: {
                            subString: "Hello",
                            subNumber: 1,
                            subBoolean: true,
                            subObject: {
                              subSubString: "Hello World"
                            },
                            subArray: ["-1", "-2", "-3"]
                          },
                          array: ["1", "2", "3"]
                        }
                        
                        function changeValue(obj_path, value, json) {
                          let keys = obj_path.split(".")
                          let obj = { ...json },
                            tmpobj = {},
                            prevobj = {}
                          for (let x = keys.length - 1; x >= 0; x--) {
                            if (x == 0) {
                              obj[keys[0]] = tmpobj
                            } else {
                              let toeval = 'json.' + keys.slice(0, x).join('.');
                              prevobj = { ...tmpobj
                              }
                              tmpobj = eval(toeval);
                              if (x == keys.length - 1) tmpobj[keys[x]] = value
                              else {
                                tmpobj[keys[x]] = prevobj
                              }
                            }
                          }
                          return obj
                        }
                        
                        let newjson = changeValue("object.subObject.subSubString", "Goodbye world", orig_json);
                        console.log(newjson)

                        【讨论】:

                          【解决方案20】:

                          添加或覆盖属性的另一种解决方案:

                          function propertySetter(property, value) {
                            const sampleObject = {
                              string: "Hi",
                              number: 0,
                              boolean: false,
                              object: {
                                subString: "Hello",
                                subNumber: 1,
                                subBoolean: true,
                                subObject: {
                                  subSubString: "Hello World",
                                },
                                subArray: ["-1", "-2", "-3"],
                              },
                              array: ["1", "2", "3"],
                            };
                          
                            const keys = property.split(".");
                            const propertyName = keys.pop();
                            let propertyParent = sampleObject;
                            while (keys.length > 0) {
                              const key = keys.shift();
                              if (!(key in propertyParent)) {
                                propertyParent[key] = {};
                              }
                              propertyParent = propertyParent[key];
                            }
                            propertyParent[propertyName] = value;
                            return sampleObject;
                          }
                          
                          console.log(propertySetter("object.subObject.anotherSubString", "Hello you"));
                          
                          console.log(propertySetter("object.subObject.subSubString", "Hello Earth"));
                          
                          console.log(propertySetter("object.subObject.nextSubString.subSubSubString", "Helloooo"));

                          【讨论】:

                            【解决方案21】:

                            扩展@bpmason1 提供的已接受答案,以支持字符串路径中的数组,例如字符串路径可以是'db.mongodb.users[0].name''db.mongodb.users[1].name'

                            它将设置属性值,如果不存在,将被创建。

                            var obj = {};
                            
                            function set(path, value) {
                              var schema = obj;
                              var keysList = path.split('.');
                              var len = keysList.length;
                              for (var i = 0; i < len - 1; i++) {
                                var key = keysList[i];
                                // checking if key represents an array element e.g. users[0]
                                if (key.includes('[')) {
                                  //getting propertyName 'users' form key 'users[0]'
                                  var propertyName = key.substr(0, key.length - key.substr(key.indexOf("["), key.length - key.indexOf("[")).length);
                                  if (!schema[propertyName]) {
                                    schema[propertyName] = [];
                                  }
                                  // schema['users'][getting index 0 from 'users[0]']
                                  if (!schema[propertyName][parseInt(key.substr(key.indexOf("[") + 1, key.indexOf("]") - key.indexOf("[") - 1))]) {
                                    // if it doesn't exist create and initialise it
                                    schema = schema[propertyName][parseInt(key.substr(key.indexOf("[") + 1, key.indexOf("]") - key.indexOf("[") - 1))] = {};
                                  } else {
                                    schema = schema[propertyName][parseInt(key.substr(key.indexOf("[") + 1, key.indexOf("]") - key.indexOf("[") - 1))];
                                  }
                                  continue;
                                }
                                if (!schema[key]) {
                                  schema[key] = {};
                                }
                                schema = schema[key];
                              } //loop ends
                              // if last key is array element
                              if (keysList[len - 1].includes('[')) {
                                //getting propertyName 'users' form key 'users[0]'
                                var propertyName = keysList[len - 1].substr(0, keysList[len - 1].length - keysList[len - 1].substr(keysList[len - 1].indexOf("["), keysList[len - 1].length - keysList[len - 1].indexOf("[")).length);
                                if (!schema[propertyName]) {
                                  schema[propertyName] = [];
                                }
                                // schema[users][0] = value;
                                schema[propertyName][parseInt(keysList[len - 1].substr(keysList[len - 1].indexOf("[") + 1, keysList[len - 1].indexOf("]") - keysList[len - 1].indexOf("[") - 1))] = value;
                              } else {
                                schema[keysList[len - 1]] = value;
                              }
                            }
                            
                            // will create if not exist
                            set("mongo.db.users[0].name.firstname", "hii0");
                            set("mongo.db.users[1].name.firstname", "hii1");
                            set("mongo.db.users[2].name", {
                              "firstname": "hii2"
                            });
                            set("mongo.db.other", "xx");
                            console.log(obj);
                            
                            // will set if exist
                            set("mongo.db.other", "yy");
                            console.log(obj);

                            【讨论】:

                              【解决方案22】:

                              灵感来自 ImmutableJS 的 setIn 方法,它永远不会改变原始的。 这适用于混合数组和对象嵌套值。

                              function setIn(obj = {}, [prop, ...rest], value) {
                                  const newObj = Array.isArray(obj) ? [...obj] : {...obj};
                                  newObj[prop] = rest.length ? setIn(obj[prop], rest, value) : value;
                                  return newObj;
                              }
                              
                              var obj = {
                                a: {
                                  b: {
                                    c: [
                                      {d: 5}
                                    ]
                                  }
                                }
                              };
                              
                              const newObj = setIn(obj, ["a", "b", "c", 0, "x"], "new");
                              
                              //obj === {a: {b: {c: [{d: 5}]}}}
                              //newObj === {a: {b: {c: [{d: 5, x: "new"}]}}}
                              

                              【讨论】:

                                【解决方案23】:

                                如果您想深入更新或插入对象 试试这个:-

                                 let init = {
                                       abc: {
                                           c: {1: 2, 3: 5, 0: {l: 3}},
                                           d: 100
                                       }
                                    }
                                    Object.prototype.deepUpdate = function(update){
                                       let key = Object.keys(update);
                                       key.forEach((k) => {
                                           if(typeof update[key] == "object"){
                                              this[k].deepUpdate(update[key], this[k])
                                           }
                                           else 
                                           this[k] = update[k]
                                       })
                                    }
                                
                                    init.deepUpdate({abc: {c: {l: 10}}})
                                    console.log(init)
                                

                                但要确保它会改变原来的对象,你可以让它不改变原来的对象:

                                JSON.parse(JSON.stringify(init)).deepUpdate({abc: {c: {l: 10}}})
                                

                                【讨论】:

                                  【解决方案24】:

                                  这是使用 ES 12 的解决方案

                                  function set(obj = {}, key, val) {
                                    const keys = key.split('.')
                                    const last = keys.pop()
                                    keys.reduce((o, k) => o[k] ??= {}, obj)[last] = val
                                  }
                                  

                                  (对于旧版本的javascript,你可以在reduce中做o[k] || o[k] = {}

                                  首先,我们将keys 设置为除最后一个键之外的所有内容的数组。

                                  然后在reduce中,累加器深入obj 每次,如果该键的值未定义,则将其初始化为一个空对象。

                                  最后,我们将最后一个键的值设置为val

                                  【讨论】:

                                    【解决方案25】:

                                    改进 bpmason1 的回答: - 添加一个 get() 函数。 - 不需要定义全局存储对象 - 可以从同一个域 iFrame 访问

                                    function set(path, value) 
                                    {
                                      var schema = parent.document;
                                      path="data."+path;
                                      var pList = path.split('.');
                                      var len = pList.length;
                                      for(var i = 0; i < len-1; i++) 
                                      {
                                        if(!schema[pList[i]]) 
                                          schema[pList[i]] = {}
                                        schema = schema[pList[i]];
                                      }
                                      schema[pList[len-1]] = value;
                                    }
                                    
                                    function get(path) 
                                    {
                                      path="data."+path;
                                      var schema=parent.document;
                                      var pList = path.split('.');
                                      for(var i = 0; i < pList.length; i++) 
                                        schema = schema[pList[i]];
                                      return schema;
                                    }
                                    
                                    set('mongo.db.user', 'root');
                                    set('mongo.db.name', 'glen');
                                    
                                    console.log(get('mongo.db.name'));  //prints 'glen'
                                    

                                    【讨论】: