Có cách nào để Object.freeze () một JavaScript Date không?


76

Theo tài liệu MDNObject.freeze() :

Các Object.freeze()phương pháp đóng băng một đối tượng: đó là, ngăn chặn các thuộc tính mới được thêm vào nó; ngăn các thuộc tính hiện có bị xóa; và ngăn các thuộc tính hiện có hoặc khả năng liệt kê, khả năng cấu hình hoặc khả năng ghi của chúng bị thay đổi. Về bản chất, đối tượng được tạo ra một cách hiệu quả là bất biến. Phương thức trả về đối tượng đang bị đóng băng.

Tôi đã mong đợi rằng việc đóng băng cuộc gọi vào một ngày sẽ ngăn chặn những thay đổi đối với ngày đó, nhưng nó có vẻ như không hoạt động. Đây là những gì tôi đang làm (chạy 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)

Tôi đã mong đợi cuộc gọi setTimethất bại hoặc không làm gì cả. Bất kỳ ý tưởng làm thế nào để đóng băng một ngày?


Chúng tôi đang tải cấu hình được mã hóa JSON từ một tệp và muốn đảm bảo rằng không có phần nào khác của ứng dụng vô tình thực hiện thay đổi đối với cấu hình này. Vì vậy, chúng tôi đang gọi đệ quy Object.freeze()trên toàn bộ đối tượng. Nó có vẻ hoạt động, ngoại trừ vấn đề Ngày tháng vui nhộn này.
Andrew Eisenberg

1
lưu trữ ngày tháng dưới dạng một chuỗi hoặc dấu thời gian và chuyển đổi nó thành đối tượng ngày tháng trước khi sử dụng hoặc bạn có thể tạo một getter cho đối tượng chuỗi đó sẽ trả về ngày tháng. như vậy bạn sẽ không được sử dụng đối tượng ngày ở tất cả, vì vậy wouldnt này yêu cầu đóng băng ngày
Sachin

Hãy xem xét tất cả các hàm là bất biến khi chúng trả về một đối tượng mới. Không chắc điều này sẽ tương tác như thế nào với freeze ().
Owen Beresford

Đây là lý do tôi thích tính bất biến theo mặc định.
Ben Leggiero

1
@ BenC.R.Leggiero Đồng ý. Ngày tháng có thể thay đổi là một lỗ hổng cơ bản của cả Java và JavaScript. Nó không gây ra vấn đề gì.
Andrew Eisenberg

Câu trả lời:


62

Có cách nào để Object.freeze () một JavaScript Date không?

Tôi không nghĩ vậy. Tuy nhiên, bạn có thể đến gần , hãy xem dưới dòng bên dưới. Nhưng trước tiên hãy xem tại sao chỉ Object.freezekhông hoạt động.

Tôi đã mong đợi rằng việc đóng băng cuộc gọi vào một ngày sẽ ngăn chặn những thay đổi đối với ngày đó ...

Nó sẽ nếu Date sử dụng một thuộc tính đối tượng để giữ giá trị thời gian bên trong của nó, nhưng nó không. Thay vào đó, nó sử dụng một [[DateValue]] khe cắm bên trong . Các vị trí bên trong không phải là thuộc tính:

Các khe bên trong tương ứng với trạng thái bên trong được liên kết với các đối tượng và được sử dụng bởi các thuật toán đặc tả ECMAScript khác nhau. Khe bên trong không phải là thuộc tính đối tượng ...

Vì vậy, việc đóng băng đối tượng không có bất kỳ ảnh hưởng nào đến khả năng biến đổi [[DateValue]]khe bên trong của nó .


Bạn có thể đóng băng một Datehoặc hiệu quả bằng cách nào: Thay thế tất cả các phương thức đột biến của nó bằng các hàm no-op (hoặc các hàm gây ra lỗi) và sau đó thay freezethế nó. Nhưng theo quan sát của zzzzBov (một điều tuyệt vời!) , Điều đó không ngăn cản ai đó thực hiện Date.prototype.setTime.call(d, 0)(trong một nỗ lực có chủ ý để đi xung quanh đối tượng bị đóng băng hoặc như một sản phẩm phụ của một số mã phức tạp mà họ đang sử dụng). Vì vậy, nó gần , nhưng không có xì gà.

Đây là một ví dụ (Tôi đang sử dụng các tính năng của ES2015 ở đây, vì tôi thấy điều đó lettrong mã của bạn, vì vậy bạn sẽ cần một trình duyệt gần đây để chạy nó; nhưng điều này cũng có thể được thực hiện với các tính năng chỉ dành cho ES5):

Tôi nghĩ rằng tất cả các phương pháp đột biến đều Datebắt đầu set, nhưng nếu không, thật dễ dàng để điều chỉnh những điều trên.


1
Tôi ước tôi có thể ủng hộ điều này hai lần. Thật là thú vị.
Mike Cluck

2
Ha! You can freeze a Date, or effectively so anyway: Replace all its mutator methods with no-ops and then freeze it.Ý tưởng tuyệt vời!
Andrew Eisenberg

8
"Bạn có thể đóng băng Date" - ... là, chỉ cần nhớ rằng việc ghi đè hàm bằng lệnh cấm có thể bị phá vỡ bằng cách gọi hàm từ hàm Date.prototypenhư vậy Date.prototype.setTime.call(frozenDate, 0), tất nhiên trong thực tế, điều này thật nực cười, nhưng thử nghiệm cục bộ cho thấy nó hoạt động ít nhất là trong Chrome.
zzzzBov

2
@zzzzBov: Điều vô lý để làm gì? Có thể, nhưng một cảnh báo rất, rất hữu ích .
TJ Crowder

5
Có thể đơn giản hơn chỉ cần sử dụng valueOf/ timestamp và hiển thị thuộc tính đó là bất biến - và chỉ cần ném nó vào một Dateđối tượng mới khi nào bạn cần?
Emissary

8

Từ tài liệu của MDN trênObject.freeze (tôi nhấn mạnh):

Không thể thay đổi giá trị cho các thuộc tính dữ liệu. Các thuộc tính của Accessor (getters và setters) hoạt động giống nhau (và vẫn tạo ra ảo giác rằng bạn đang thay đổi giá trị). Lưu ý rằng các giá trị là đối tượng vẫn có thể được sửa đổi, trừ khi chúng cũng bị đóng băng.

setTimePhương thức của đối tượng Date không thay đổi thuộc tính của đối tượng Date, vì vậy nó vẫn tiếp tục hoạt động, mặc dù đã đóng băng phiên bản.


7

Đây thực sự là một câu hỏi tốt!

Câu trả lời của TJ Crowder có một giải pháp tuyệt vời, nhưng nó khiến tôi phải suy nghĩ: Chúng ta có thể làm gì khác? Làm thế nào chúng ta có thể đi xung quanh Date.prototype.setTime.call(yourFrozenDate)?

Lần thử đầu tiên: "Wrapper"

Một cách trực tiếp là cung cấp một AndrewDatehàm kết thúc một ngày tháng. Nó có tất cả mọi thứ mà một ngày có trừ những người thiết lập:

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

Điều này làm là tạo một đối tượng có tất cả các thuộc tính mà một đối tượng ngày tháng thực tế có và sử dụng Function.prototype.bindđể thiết lập chúng this.

Đây không phải là một bằng chứng ngu ngốc để thu thập các phím, nhưng hy vọng bạn có thể thấy ý định của tôi.

Nhưng khoan đã ... nhìn xa hơn một chút ở đây và ở đó, chúng ta có thể thấy rằng có một cách tốt hơn để làm điều này.

Lần thử thứ 2: Proxy

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

var proxyDate = SuperAndrewDate(new Date());

Và chúng tôi đã giải quyết nó!

... đại loại. Hãy xem, Firefox là phiên bản duy nhất hiện nay cài đặt proxy và vì một số lý do kỳ lạ không thể proxy đối tượng date. Hơn nữa, bạn sẽ nhận thấy rằng bạn vẫn có thể làm những việc như thế 'setDate' in proxyDatevà bạn sẽ thấy các hoàn thành trong bảng điều khiển. Để vượt qua điều đó cần cung cấp nhiều bẫy hơn; Cụ thể, has, enumerate, ownKeys, getOwnPropertyDescriptorvà ai biết được những gì trường hợp cạnh lạ có!

... Vì vậy, suy nghĩ lại, câu trả lời này gần như vô nghĩa. Nhưng ít nhất chúng ta đã vui vẻ, phải không?


Trên thực tế, Chrome (và NodeJS) triển khai proxy ổn định. Bạn có thể yên tâm sử dụng nó.
Benjamin Gruenbaum

hay đấy :) BTW: có lý do gì khiến bạn sử dụng Reflect.get thay vì target [prop] không?
Kamil Tomšík

@ KamilTomšík Đối xứng! Mỗi bẫy Proxy có triển khai mặc định của nó trong Reflect, vì vậy khi triển khai proxy và ủy quyền hành động mặc định, tôi thích sử dụng Reflect. Không có lý do đặc biệt nào khác ngoài điều đó.
Zirak

6

Bạn có thể bọc nó trong một lớp như cấu trúc và xác định các bộ thu và bộ định tuyến tùy chỉnh để ngăn chặn sự thay đổi không mong muốn


1
Đây âm thanh như câu trả lời đơn giản nhất đối với tôi
sricks

2

Tôi e rằng câu trả lời được chấp nhận là thiếu sót. Bạn thực sự có thể đóng băng một thể hiện của bất kỳ đối tượng nào bao gồm cả một thể hiện của Date. Để hỗ trợ câu trả lời của @zzzzBov , việc đóng băng một thể hiện đối tượng không có nghĩa là trạng thái của đối tượng trở nên không đổi.

Một cách để chứng minh rằng một Datephiên bản thực sự bị đóng băng là làm theo các bước bên dưới:

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

Nhưng bạn có thể đạt được hành vi mà tôi cho rằng bạn mong muốn như sau:

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.