Tìm hiểu về Strict Mode trong Javascript
"use strict"; Chắc hẳn bạn đã bắt gặp dòng lệnh trên khi làm việc với JavaScript rồi chứ nhỉ? Đó là nội dung thường xuyên xuất hiện trong hầu hết các thư viện Javascript hiện đại. Vậy "use strict"; là gì, nó có ảnh hưởng gì đến code của bạn, và liệu bạn có nên sử dụng nó? Bài viết này ...
"use strict";
Chắc hẳn bạn đã bắt gặp dòng lệnh trên khi làm việc với JavaScript rồi chứ nhỉ? Đó là nội dung thường xuyên xuất hiện trong hầu hết các thư viện Javascript hiện đại.
Vậy "use strict"; là gì, nó có ảnh hưởng gì đến code của bạn, và liệu bạn có nên sử dụng nó?
Bài viết này sẽ đem đến cho bạn câu trả lời về những vấn đề đó, thông qua việc giới thiệu về một tính năng mới được bổ sung vào từ phiên bản ECMAScript 5, đó là Strict Mode.
Trước tiên, hãy cùng tản mạn một chút vậy. Tại sao ở trên thì lúc đầu mình nhắc đến Javascript, sau đó thì lại là nhắc đến tính năng của ECMAScript?
Thực tế ECMAScript chỉ là một đặc tả kỹ thuật của một dạng scripting language, được thiết kế bởi tổ chức Ecma International. Có nhiều implementation của đặc tả này, trong đó phổ biến nhất chính là JavaScript (ngoài ra có thể kể đến JScript của Microsoft, hay Action Script của Adobe Systems)
Như vậy có thể hiểu đơn giản rằng ECMAScript là những miêu tả lý thuyết trên giấy, còn Javascript là ngôn ngữ lập trình được xây dựng dựa trên những miêu tả đó. Dĩ nhiên là Javascript cũng mang trong mình những tính năng bổ sung so với những đặc tả của ECMAScript, khiến nó cũng có phần khác biệt với những ngôn ngữ khác cũng được xây dựng dựa trên ECMAScript.
Lịch sử phát triển của ECMAScript có thể tóm tắt lại như sau:
- 6/1997: Ra mắt phiên bản thứ nhất.
- 6/1998: Ra mắt phiên bản thứ hai.
- 12/1999: Ra mắt phiên bản thứ ba. Phiên bản thứ tư sau một thời gian draft cuối cùng đã không được ra mắt.
- 12/2009: Ra mắt phiên bản thứ năm, ES5. Đây chính là phiên bản đánh dấu sự xuất hiện của Strict Mode
- 6/2011: Ra mắt phiên bản 5.1.
- 6/2015: Ra mắt phiên bản thứ sáu, với cái tên chính thức là ECMAScript 2015, tuy nhiên vẫn hay được gọi dưới cái tên ES6.
- Phiên bản thứ bẩy, ES7, tính đến thời điểm thực hiện bài viết này (3/2016) vẫn đang trong giai đoạn draft.
Như vậy, nhìn vào quá trình phát triển ở trên thì ta có thể thấy Strict Mode mới chỉ được giới thiệu trong thời gian gần đây. Một điều nữa cũng cần phải nhắc đến là kể từ lúc phiên bản chính thức của ECMAScript được ra mắt cho đến lúc các bạn có thể sử dụng được nó trên trình duyệt là cả một quãng thời gian dài. Phải mất nhiều thời gian để các trình duyệt có thể support được hầu hết các tính năng của ES5.1.
ES6 ra đời được hơn nửa năm nay nhưng nhiều tính năng mới mà nó đem lại cũng chưa hoạt động trên nhiều trình duyệt. Mà đấy là mình đang nhắc đến những trình duyệt hiện đại như Chrome, Firefox đấy nhé, còn tính trên những phiên bản cũ, hay trên IE thì bạn nên xác định luôn là (rip) khá nhiều features =))
Bạn có thể theo dõi những tính năng nào của ECMAScript 5 hay 6 đã được support được bao nhiêu phần ở đây
Strict hiểu đơn giản theo nghĩa tiếng Việt là "nghiêm ngặt, nghiêm khắc". Strict Mode là một quy mẫu nghiêm khắc của Javascript. Nếu như coi việc viết code bình thường là Normal Mode, thì Strict Mode sẽ có thêm nhiều quy định khác so với Normal Mode. Việc đó khiến cho một thao tác vốn bình thường có thể chạy ngon lành trở nên lỗi, và throw ra errors.
Nhìn chung, Strict được tạo ra nhằm:
- Ngăn chặn sử dụng, và throw errors khi người lập trình thực hiện những xử lý được coi là unsafe, những xử lý mà có thể là ngoài ý muốn.
- Vô hiệu hoá các tính năng có thể gây nhầm lẫn, hoặc không nên được sử dụng.
- Ngăn chặn sử dụng một số từ mà có thể sẽ được sử dụng làm keywork trong tương lai.
Strict Mode có nhiều hạn chế hơn so với normal mode. Với việc tuân theo những quy định đó, bạn sẽ làm cho code Javascript của mình trở nên sáng sủa, dễ đọc hơn, cũng như ít vướng phải những lỗi không mong muốn.
Khi đọc đến đây, chắc hẳn bạn sẽ đặt câu hỏi rằng, vậy sau người ta không thay đổi hẳn phần đặc tả của ECMAScript liên quan đến những gì được đề cập trong Strict Mode đi, mà lại sinh ra thêm cái Strict Mode làm gì cho nó rắc rối?
Có lẽ một phần là để đảm bảo phần nào tính backward compatible giữa ES5 và phiên bản trước đó là ES3, phần nữa là để ECMAScript vẫn giữ được tính đơn giản, mềm dẻo từ trước, chứ không phải bị giới hạn bởi những quy tắc cứng nhắc mới được thêm vào. Từ đó ECMAScript sẽ trở nên dễ dàng tiếp cận hơn đối với những người mới làm quen.
Tuy nhiên khi đã qua giai đoạn "làm quen" rồi, thì bạn cần thay đổi. Đó là lúc bạn cần đến Strict Mode.
Để sử dụng Strict Mode trong code của mình, bạn có thể dùng đoạn text là "use strict";.
Lúc đầu nhìn qua mình cũng thấy nó khá là dị. Bởi như bạn thấy, đó là một ... string. =))
Việc định nghĩa như vậy được gọi dưới một cái tên là directive prologue.
Bạn có thể đặt đoạn text đó ở đầu file, hay ở đầu phần thân của một hàm. Việc khai báo "use strict"; ở đâu sẽ có quyết định phạm vi ảnh hưởng của Strict Mode.
Nếu bạn đặt "use strict"; ở đầu file, phạm vi của Strict Mode sẽ là toàn bộ file đó.
"use strict"; function foo(){ var bar = 0; return bar; } // Uncaught ReferenceError: bar is not defined bar = 1;
Nếu bạn đặt "use strict"; ở đầu phần thân hàm của một function, Strict Mode sẽ được áp dụng cho chỉ mình function đó.
function foo(){ "use strict"; // Uncaught ReferenceError: bar is not defined bar = 0; return bar; } // This will run normally bar = 1;
Trong phần này, chúng ta hãy cùng tìm hiểu về những quy định mới trong Strict Mode, những điều bạn sẽ không thể làm một khi đã sử dụng "use strict";!
Không thể sử dụng một biến mà không khái báo
Trong javascript, bạn có thể sử dụng từ khoá var, và mới nhất ở ES6 thì là cả let để khai báo biến. Tuy nhiên bạn vẫn có thể sử dụng được một biến mà không đến var hay let.
Ví dụ như đoạn code sau vẫn chạy tốt:
var foo = 1; console.log(foo); bar = 2; console.log(bar);
Sở dĩ không có lỗi gì xảy ra là bởi nếu không sử dụng từ khoá var, ta sẽ coi biến được sử dụng là biến Global. Ta có thể nhận thấy sự khác biệt giữa sử dụng var với không sử dụng var thông qua ví dụ sau đây:
// Global variable function foo() { bar = 1; } foo(); console.log(bar) // 1 // Local variable function foo() { var bar = 1; // bar chỉ có hiệu lực bên trong function console.log(bar); // 1 } foo(); console.log(bar); // ReferenceError: bar is not defined
Như bạn thấy, bình thường, nếu bạn có "quên" (hay cố tình "quên") không sử dụng var (hay let) để khai báo biến thì đoạn javascript của bạn vẫn sẽ chạy bình thường.
Tuy nhiên, ở Strict Mode, thì bạn sẽ không được phép làm như vậy nữa. Hãy để ý lại ví dụ ở phần hướng dẫn sử dụng Strict Mode thì bạn sẽ thấy.
Việc không cho phép sử dụng một biến chưa được khai báo (bằng từ khoá var, hoặc let) sẽ giúp bạn:
- Hạn chế trường hợp bạn gõ nhầm tên biến gây ra lỗi về mặt logic. Như trước đây, bạn khai báo một biến var aVariable, nhưng sau đó khi dùng bạn lại gõ thành là anVariable = 1 thì nó vẫn cứ chạy, và bạn sẽ phải ngồi tìm xem tại sao chương trình lại chạy không theo ý muốn =)) Với Strict Mode, sẽ có lỗi được báo khi bạn "gõ nhầm" tên biến :v
- Hạn chế được việc sử dụng biến Global ngoài ý muốn. Sử dụng biến Global thường không phải là một practice tốt. Trong hầu hết các trường hợp bạn không cần và không nên sử dụng biến Global.
Báo lỗi ở những assignments vốn không thể thực hiện
Bình thường, với một property của object mà có writable là false thì đương nhiên bạn vẫn sẽ không thể ghi đè dữ liệu lên thuộc tính đó. Nhưng vấn đề là code vẫn cứ chạy. Còn trong Strict Mode, sẽ có lỗi được thông báo.
Tương tự, ta cũng sẽ gặp phải lỗi khi cố gắng ghi đè lên một object chỉ có getter mà không có setter (2 khái niệm mới trong ES6), hay khi tạo thêm một property mới từ một object được config không thể extend.
// Without Strict Mode NaN = "lol"; // Nothing happen var obj = {}; Object.defineProperty(obj, 'prop', {value: 1, writable:false}); obj.prop; // => 2 obj.prop = 10; obj.prop; // => 2 // With Strict Mode "use strict"; NaN = "wtf"; // TypeError var obj = {}; Object.defineProperty(obj, 'prop', {value: 1, writable:false}); obj.prop; // => 2 obj.prop = 10; // Uncaught TypeError: Cannot assign to read only property 'prop' of object #<Object> // Assignment to a getter-only property var obj = { get x() { return 1; } }; obj.x = 2; // Uncaught TypeError: Cannot set property x of #<Object> which has only a getter // Assignment to a new property on a non-extensible object var fixedObj = {}; Object.preventExtensions(fixedObj); fixedObj.newProp = "new value"; // Uncaught TypeError: Can't add property newProp, object is not extensible
Báo lỗi khi delete những thứ không thể xoá
Sẽ có lỗi khi bạn thực hiện thao tác xoá biến, hàm, hay argument.
Ngoài ra, cũng sẽ có lỗi khi bạn cố tình xoá được một property của object không thể configurable.
"use strict"; var foo = 1; function bar() {}; delete foo; // Uncaught SyntaxError: Delete of an unqualified identifier in Strict Mode. delete bar; var obj = {}; Object.defineProperty(obj, "baz", { value: 1, configurable: false }); delete obj.baz; // Uncaught TypeError: Cannot delete property 'baz' of #<Object>
Xin được chú ý rằng ở normal mode, bạn cũng sẽ không thể thực hiện được các thao tác trên. Tuy nhiên code vẫn sẽ chạy bình thường mà không có lỗi gì được báo ra.
Các tham số của một hàm không được phép trùng nhau
Nếu parameters của một function bị trùng tên nhau, thì sẽ có lỗi được báo.
"use strict"; function foo(bar, baz, bar) { // Uncaught SyntaxError: Duplicate parameter name not allowed in this context } foo(1, 2, 3);
Việc báo error này sẽ giúp bạn tránh được lỗi không đáng có nếu chẳng may viết nhầm tên biến. (Bởi việc có 2 parameter trùng tên có lẽ chỉ xảy ra do lỗi mistype của bạn thôi, chứ hầu như chẳng bao giờ ta lại cần đến một trường hợp như vậy cả.)
Còn ở mode bình thường, bạn sẽ có các parameters bị trùng tên khi khai báo function. Và đương nhiên, giá trị của cái đằng sau sẽ đè lên cái trước đó.
Không sử dụng được cách viết số thuộc hệ bát phân với tiền tố là 0
Bình thường, nếu một số được bắt đầu bằng 0, thì javascript sẽ hiểu đó là hệ cơ số 8. Ví dụ 010 === 8 sẽ trả về giá trị là true.
Tuy nhiên, không phải ai cũng biết đến điều đó, và cách viết 010 có thể sẽ khiến nhiều người vẫn hiểu đó là 10. Chính vì thế ở Strict Mode, nó đã bị coi là một lỗi cú pháp.
"use strict"; var foo = 010; // Uncaught SyntaxError: Octal literals are not allowed in Strict Mode.
Tuy nhiên, ở ES6, bạn lại có thể dùng cách viết trên, với tiền tố là 0o. Tức dù là trong Strict Mode, nhưng var foo = 0o10 vẫn là một cách viết hợp lệ, và 0o10 === 8 sẽ trả ra kết quả là true.
Không thể sử dụng with
with là một câu lệnh nguy hiểm, có thể gây ra nhầm lẫn trong nhiều trường hợp. Chính vì thế, trong Strict Mode, nó bị loại bỏ hoàn toàn, và nếu bạn cố tình sử dụng with, bạn sẽ gặp lỗi Syntax Error.
"use strict"; var foo = 1; var bar = {foo: 2} with (bar) { console.log(foo); // Bạn sẽ gặp khó khăn trong việc xác định foo ở đây là biến, hay là thuộc tính của bar. }
Không sử dụng được biến được khai báo bên trong eval
Bình thường, nếu trong hàm eval của bạn có khai báo biến thì scope của biến đó sẽ là Global, hoặc là bên trong function, nơi mà eval được gọi.
Tuy nhiên, ở Strict Mode, khi đã kết thúc eval, bạn sẽ không thể sử dụng được biến nữa.Global
// Non-Strict Mode eval("var foo = 1"); foo // 1 // Strict Mode "use strict"; eval("var foo = 1"); foo // Uncaught ReferenceError: foo is not defined
Không thể sử dụng eval và arguments như là một identifier
Trong Strict Mode, bạn sẽ không thể sử dụng được eval và arguments như là một tên biến, thên function, hay tên paramenter ... Mọi cố gắng để sử dụng 2 từ khoá đó cho mục đích như trên đều sẽ tạo ra Syntax Error.
"use strict"; var eval = 1; // Syntax Error function arguments() { }; var foo = function eval() { }; function bar(eval) { };
Thay đổi cách thức hoạt động của this trong một số trường hợp
Trong Strict Mode thì
- this sẽ không bị ép thành object nữa.
function foo() { return this; } // Non-Strict Mode foo.call(1) === 1; // false. // Bởi foo.call(1) sẽ trả ra giá trị là một object, tương đương với `new Number(1)` // Strict Mode foo.call(1) === 1; // true
- this sẽ không còn bị chuyển thành Global object (window) nếu nó là null hay undefined
function foo() { return this; } // Non-Strict Mode foo() === window; // true foo.apply(undefined) === window; // true // Strict Mode foo() === undefined; // true foo.bind(null)() === null; // true
Lại tản mạn một chút ^^! Ta có thể dựa trên tính chất này để check xem code hiện có đang được chạy trong Strict Mode hay không, chẳng hạn như bằng cách tạo ra một hàm như sau:
function isStrictMode() { return (typeof this === "undefined"); }
Hạn chế sử dụng các property caller, callee và arguments trong một số trường hợp
Ở Strict Mode, bạn sẽ không thể gọi ra .caller hay .arguments từ tên hàm, hay cũng không thể gọi arguments.callee.
function foo(bar, baz) { "use strict"; // Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on Strict Mode // functions or the arguments objects for calls to them console.log(arguments.callee); console.log(foo.caller); console.log(foo.arguments); }
Không thể định nghĩa function bên trong một statement hay một block
Strict Mode chỉ cho phép bạn định nghĩa một function ở ngoài cùng của file script, hay ngay bên trong một function khác. Bạn sẽ không thể định nghĩa một function bên trong một hàm if, hàm for, hay một block {}.
"use strict"; function foo() { function bar() { }; // OK } if (aVariable) { var baz = function () { return true }; // OK } { function qux() { return true }; // SyntaxError }
Không thể sử dụng một số từ khoá được "giữ chỗ" trước cho những phiên bản ES sau này
Việc ECMAScript phát triển để phù hợp với những yêu cầu của một ngôn ngữ hiện đại là điều tất yếu. Thế nên ngay từ ES5, với Strict Mode, một cái tên "có thể sẽ được sử dụng" trong tương lai với tư cách là keyword đã không thể sử dụng như là identifier được nữa.
Đó là những từ:
- implements
- interface
- let
- package
- private
- protected
- public
- static
- yield
"use strict"; // Uncaught SyntaxError: Unexpected Strict Mode reserved word var let = 1; function public() { };
Strict Mode được tạo ra để giúp bạn tránh được một số lỗi không đáng có khi làm việc với Javascript, dễ dàng viết code Javascript an toàn hơn, cũng như giúp phong cách code của bạn trở nên mạch lạc, dễ đọc hơn.
Mặc dù khi làm việc với Strict Mode, ban đầu bạn có thể sẽ phải gặp nhiều khó khăn khi một số tính năng không thể sử dụng được nữa, nhưng quả thực thì những gì trở thành lỗi ở Strict Mode đều là những thứ bạn không nên phạm phải kể cả khi viết code ở non-strict Mode.
Nếu bạn hỏi có nên sử dụng Strict Mode không thì theo mình, câu trả lời đơn giản là: CÓ!
Mọi browser hiện đại ngày nay đều đã support Strict Mode, giờ thì đến lượt bạn, hãy bắt tay vào làm việc với nó luôn và ngay nhé