Чи є спосіб Object.freeze () дати JavaScript?


76

Відповідно до документації MDNObject.freeze() :

Object.freeze()Метод заморожує об'єкт: тобто, запобігає нові властивості від додавання до нього; запобігає видаленню існуючих властивостей; і запобігає зміні існуючих властивостей або їх перелічуваності, конфігурації або запису, По суті, об'єкт робиться фактично незмінним. Метод повертає заморожений об'єкт.

Я очікував, що виклик заморозки на дату запобіжить змінам цієї дати, але, схоже, це не працює. Ось що я роблю (запустивши Node.js v5.3.0):

let d = new Date()
Object.freeze(d)
d.setTime(0)
console.log(d) // Wed Dec 31 1969 16:00:00 GMT-0800 (PST)

Я б очікував, що заклик setTimeабо провалиться, або нічого не зробить. Будь-які ідеї, як заморозити побачення?


Ми завантажуємо кодовану конфігурацію JSON із файлу і хочемо переконатися, що жодна інша частина програми випадково не вносить зміни до цієї конфігурації. Отже, ми рекурсивно закликаємо Object.freeze()весь об’єкт. Здається, це працює, за винятком цієї непомітної проблеми з датою.
Ендрю Айзенберг,

1
збережіть дату як рядок або позначку часу та перетворіть її в об’єкт дати перед використанням, або ви можете створити геттер для цього об’єкта рядка, який поверне дату. таким чином ви взагалі не будете використовувати об'єкт дати, тому для цього не потрібно буде заморожувати дату
Сачин

Розглянемо всі функції, які незмінні, оскільки вони повертають новий об’єкт. Не впевнений, як це може взаємодіяти з freeze ().
Оуен Бересфорд,

Ось чому я насолоджуюсь незмінністю за замовчуванням.
Ben Leggiero

1
@ BenC.R.Leggiero Погодився. Змінювані дати є основною вадою як Java, так і JavaScript. Це не викликає кінця проблем.
Ендрю Айзенберг,

Відповіді:


62

Чи є спосіб Object.freeze () дати JavaScript?

Я не думаю. Ви можете наблизитися , однак, дивіться під рядком нижче. Але спочатку подивимося, чому просто Object.freezeне працює.

Я очікував, що виклик заморозки на дату запобіжить змінам цієї дати ...

Було б , якщо Date використовується властивість об'єкта , щоб тримати його внутрішнє значення часу, але це не так. Натомість використовується [[DateValue]] внутрішній слот . Внутрішні слоти не є властивостями:

Внутрішні слоти відповідають внутрішньому стану, який пов'язаний з об'єктами і використовується різними алгоритмами специфікації ECMAScript. Внутрішні слоти не є властивостями об’єкта ...

Отже, заморожування об’єкта ніяк не впливає на його здатність мутувати його [[DateValue]]внутрішній слот.


Ви можете заморозити a Date, або ефективно в будь-якому випадку: Замініть усі його методи мутатора на функції no-op (або функції, що видають помилку), а потім freezeїї. Але , як зазначено на zzzzBov (хороший один!) , Що не заважає кому - то робити Date.prototype.setTime.call(d, 0)(у навмисній спробі обійти заморожений об'єкт, або як побічний продукт якого - то складного коду вони використовують). Отже, це близько , але сигари немає.

Ось приклад (я використовую тут функції ES2015, оскільки я це бачив letу вашому коді, тому для його запуску вам знадобиться нещодавній браузер; але це можна зробити і з функціями лише ES5):

Я думаю, що всі мутаторні методи Dateпочинають з set, але якщо не так, то це легко налаштувати вище.


1
Я хотів би, щоб я міг проголосувати за це двічі. Це дуже цікаво.
Mike Cluck

2
Ха! You can freeze a Date, or effectively so anyway: Replace all its mutator methods with no-ops and then freeze it.Чудова ідея!
Ендрю Айзенберг,

8
"Ви можете заморозити Date" - ... іш, просто пам'ятайте, що перезаписування функції без операції можна обійти, викликавши функцію з Date.prototypeтакої, що Date.prototype.setTime.call(frozenDate, 0), звичайно, на практиці це було б смішно, але місцеві тестування показують, що це працює принаймні в Chrome.
zzzzBov

2
@zzzzBov: Смішна річ? Можливо, але дуже, дуже корисний застереження .
TJ Crowder,

5
Може бути простіше просто використовувати valueOf/ timestamp і зробити це властивість незмінним - і просто перекинути його в новий Dateоб'єкт, як і коли це потрібно?
Емісар

8

З документів MDNObject.freeze (наголос на моєму):

Значення не можна змінювати для властивостей даних. Властивості аксесуара (геттери та сетери) працюють однаково (і все одно створюють ілюзію, що ви змінюєте значення). Зверніть увагу, що значення, які є об'єктами, все ще можуть бути змінені, якщо вони також не заморожені.

Метод об'єкта Date setTimeне змінює властивість об'єкта Date, тому він продовжує працювати, незважаючи на замороження екземпляра.


7

Це справді гарне запитання!

Відповідь TJ Crowder має відмінне рішення, але мене змусило задуматися: що ще ми можемо зробити? Як ми можемо обійти Date.prototype.setTime.call(yourFrozenDate)?

Перша спроба: "Обгортка"

Одним із прямих способів є надання AndrewDateфункції, яка обертає дату. У ньому є все, що має дата, за вирахуванням сетерів:

function AndrewDate(realDate) {
    var proto = Date.prototype;
    var propNames = Object.getOwnPropertyNames(proto)
        .filter(propName => !propName.startsWith('set'));

    return propNames.reduce((ret, propName) => {
        ret[propName] = proto[propName].bind(realDate);
        return ret;
    }, {});
}

var date = AndrewDate(new Date());
date.setMonth(2); // TypeError: d.setMonth is not a function

Це робить створення об’єкта, який має всі властивості, які має фактичний об’єкт дати та використовує Function.prototype.bindдля їх встановлення this.

Це не безглуздий спосіб збиратися навколо ключів, але, сподіваюся, ви бачите мій намір.

Але зачекайте ... дивлячись на це трохи далі тут і там, ми можемо побачити, що існує кращий спосіб зробити це.

2-а спроба: Довірені особи

function SuperAndrewDate(realDate) {
    return new Proxy(realDate, {
        get(target, prop) {
            if (!prop.startsWith('set')) {
                return Reflect.get(target, prop);
            }
        }
    });
}

var proxyDate = SuperAndrewDate(new Date());

І ми це вирішили!

...типу. Дивіться, Firefox на даний момент єдиний, хто реалізує проксі-сервери, і з якихось химерних причин об’єкти дати не можуть бути проксі-проксі. Крім того, ви помітите, що ви все ще можете робити такі речі, 'setDate' in proxyDateі ви побачите завершення роботи в консолі. Щоб подолати, що потрібно забезпечити більше пасток; в зокрема, has, enumerate, ownKeys, getOwnPropertyDescriptorі хто знає , які дивні випадки краю є!

... Отже, якщо подумати, ця відповідь майже безглузда. Але принаймні нам було весело, так?


Насправді Chrome (і NodeJS) реалізують проксі в стабільному режимі. Ви можете спокійно ним користуватися.
Бенджамін Груенбаум,

хороший :) BTW: чи є якась причина, чому ви використовуєте Reflect.get замість target [prop]?
Каміль Томшик,

@ KamilTomšík Симетрія! Кожна пастка проксі має свою реалізацію за замовчуванням у Reflect, тому при реалізації проксі-серверів та делегуванні дії за замовчуванням я люблю використовувати Reflect. Ніякої особливої ​​причини, крім цієї.
Зірак

6

Ви можете обернути його в структуру, подібну до класу, та визначити власні геттери та сетери, щоб запобігти небажаним змінам


1
Для мене це звучить як найпростіша відповідь
sricks

2

Боюсь, прийнята відповідь насправді хибна. Ви насправді можете заморозити екземпляр будь-якого об'єкта, включаючи екземпляр Date. На підтримку відповіді @zzzzBov , заморожування екземпляра об'єкта не означає, що стан об'єкта стає постійним.

Одним із способів довести, що Dateекземпляр справді заморожений, є наступні кроки:

var date = new Date();
date.x = 4;
console.log(date.x); // 4
Object.freeze(date);
date.x = 20; // this assignment fails silently, freezing has made property x to be non-writable
date.y = 5; // this also fails silently, freezing ensures you can't add new properties to an object
console.log(date.x); // 4, unchanged
console.log(date.y); // undefined

Але ви можете досягти такої поведінки, як я гадаю, ви бажаєте наступним чином:

var date = (function() {
    var actualDate = new Date();

    return Object.defineProperty({}, "value", {
        get: function() {
            return new Date(actualDate.getTime())
        },
        enumerable: true
    });
})();

console.log(date.value); // Fri Jan 29 2016 00:01:20 GMT+0100 (CET)
date.value.setTime(0);
console.log(date.value); // Fri Jan 29 2016 00:01:20 GMT+0100 (CET)
date.value = null;       // fails silently
console.log(date.value); // Fri Jan 29 2016 00:01:20 GMT+0100 (CET)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.