Tìm hiểu về Strict Mode trong Javascript

js copy.png

Mở đầu

"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.

ECMAScript, hm?

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

Vậy Strict Mode là gì?

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

Để 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;

Những hạn chế trong Strict Mode

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ó writablefalse 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 evalarguments như là một identifier

Trong Strict Mode, bạn sẽ không thể sử dụng được evalarguments 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, calleearguments 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() { };

Lời kết

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à: !

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é 😄

References