Создайте постоянную/неизменяемую копию любого объекта (включая глубокие свойства)

Как я могу создать неизменяемую/неизменяемую версию объекта в JavaScript, свойства которого не могут быть изменены? Это также должно относиться к свойствам любых под-объектов и т.д.

Все методы, с которыми мне приходилось сталкиваться (Object.defineProperty, Object.freeze и т.д.) работают только для свойств верхнего уровня объекта, но не для под-объектов.

(Возможный прецедент: после создания/модификации объекта типа settings или configuration в определенном модуле, вам нужно подвергнуть его остальным программным модулям в неизменяемой форме.)

Ответ 1

Это решение, с которым я придумал, подумав. Хорошо работает для моих нужд, поэтому я решил поделиться им с QNA. Предлагайте любые улучшения/проблемы, если вы их найдете.

/**
 * Make the the specified object (deeply) immutable or "read-only", so that none of its
 * properties (or sub-properties) can be modified. The converted object is returned.
 * @param {object} obj Input object
 */
makeImmutable: function makeImmutable (obj) {
    if ((typeof obj === "object" && obj !== null) ||
        (Array.isArray? Array.isArray(obj): obj instanceof Array) ||
        (typeof obj === "function")) {

        Object.freeze(obj);

        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                makeImmutable(obj[key]);
            }
        }
    }
    return obj;
}

EDIT: Упрощен код. Также корректно обрабатывает массивы.

Ответ 2

Заинтриговано этим решением.

Вот фрагмент кода с примером:

/**
 * Make the the specified object (deeply) immutable or "read-only", so that none of its
 * properties (or sub-properties) can be modified. The converted object is returned.
 * @param {object} obj Input object
 */
makeImmutable: function makeImmutable(obj) {
  if ((typeof obj === "object" && obj !== null) ||
    (Array.isArray ? Array.isArray(obj) : obj instanceof Array) ||
    (typeof obj === "function")) {

    Object.freeze(obj);

    for (var key in obj) {
      if (obj.hasOwnProperty(key)) {
        makeImmutable(obj[key]);
      }
    }
  }
  return obj;
}

var newObj = {
  thisArrayOfObjects: [{
    propertyOne: 'value1',
    propertyTwo: 'value2'
  }]
};
newObj.thisArrayOfObjects.push({
  propertyBefore: 'before3'
});


console.log('newObj', newObj);
$('#viewer').append('newObj: ' + JSON.stringify(newObj));

makeImmutable(newObj);

console.log('imutable', newObj);
$('#viewer').append('<br/><br/>imutable: ' + JSON.stringify(newObj));
try {
  newObj.thisArrayOfObjects.push({
    propertyThree: 'value3'
  });
} catch (e) {
  $('#viewer').append('<br/><br/>immutable error: ' + e.message);
  console.log('immutable error:', e.message);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="viewer" />

Ответ 3

Отличные ответы. Этот использует lodash:

var _ = require('lodash');

Чтобы сделать объект неизменным:

/**
 * Makes an Object immutable by (deep) freezing all own peoperties.
 * @param {*} obj - Object to make immutable.
 * @returns {*} The input Object.
 */
function deepFreeze(obj) {
    if (_.isObject(obj) || _.isArray(obj) || _.isFunction(obj)) {
        Object.freeze(obj);
        _.forOwn(obj, deepFreeze);
    }
    return obj;
}

Чтобы сделать неизменяемый клон:

var frozenClone = deepFreeze(_.cloneDeep(obj));