全文共5131字,预计学习时长15分钟
来源:unsplash
JavaScript过去常常由于语法的不严谨,被人们所诟病。于是后来在ECMAscript 5添加了第二种运行模式:“严格模式”。一旦使用严格模式,那么一些“不严谨”的语法代码将不再被支持。
严格模式在现代JavaScript中同样是很重要的一部分。在这个模式中,可以让人选择是否使用更受限制的JavaScript语法。
JavaScript严格模式中的语义和原来“草率模式”的不一样。在之前的模式中,“草率模式”下,语法松散,如果代码中出现了错误也不会有任何提示。这就意味着这些错误经常会被无视,代码可能会运行出完全无法预测的结果。
严格模式对JavaScript的语义做出了一些调整,消灭了错误,这样代码就能没有错误地运行。
严格模式还能指出阻止JavaScript引擎自动优化的错误。此外,它也禁用了一些可能会在未来版本被定义的特征。
无论是单个函数或者整个脚本,都可以运用严格模式。但严格模式不能仅仅只对括号里的语句或者代码生效。如果要在脚本中使用严格模式,需要在脚本文件的第一行添加 "usestrict", 或者'use strict'。
如果有些脚本中使用了严格模式,而有些没有,那么那些使用了严格模式的脚本可能就会和没使用严格模式的脚本合并。
这两种不同脚本的合并意味着没有使用严格模式的代码会在严格模式下执行。反之亦然,所以最好不要把它们混在一起。
严格模式还能运用到函数中。要实现这一点,只需在函数第一行加上"usestrict"或者'use strict',严格模式就能应用到整个函数,包括该函数内的嵌套函数。
比如说:
const strictFunction= ()=>{
'use strict';
const nestedFunction = ()=>{
// this function also uses strictmode
}
}
ES2015里引入的JavaScript模块,已经自动使用了严格模式,所以不需要其他的语句来开启严格模式。
严格模式下的变化
使用严格模式的代码,其语法和运行时间会被改变。代码中的错误会转化为程序运行时出现的语法错误。特定变量的计算方式,eval函数和arguments对象都将得到简化,以及在未来的ES规范中可能实现的更改。
直接报错
代码中的错误会转化为程序运行时的语法错误。虽然这些代码能在非严格模式下运行,但严格模式会限制错误语法的使用,不会放任代码带着错误运行。
创建全局变量必须使用var,let,或者const。因此缺少这些关键词声明的变量将无法使用。比如说,下列代码会报出ReferenceError:
'use strict';
badVariable = 1;
上述代码在严格模式中无法运行,因为它会在严格模式关闭的时候创建一个名为badVariable的全局变量。而严格模式会阻止这种行为,防止误建全局变量。
现在,所有无声故障都将报错。这包括之前被无视的所有无效语法。
例如,无法为启用了严格模式的只读变量(例如,arguments、NaN或者eval)赋值。
任何对像不可写全局属性这样的只读属性的赋值,只读属性的赋值以及不可扩展对象的赋值,在严格模式下都会出现异常。
以下代码在严格模式下会报错:
'use strict';
let undefined = 5;
let Infinity = 5;
let obj = {};
Object.defineProperty(obj, 'foo', { value: 1, writable: false });
obj.foo = 1
let obj2 = { get foo() { return 17; } };
obj2.foo = 2
let fixedObj = {};
Object.preventExtensions(fixedObj);
fixed.bar= 1;
运行上述代码,系统会提示 TypeError.undefined和Infinity都是不可写的全局变量。obj代表不可写的属性。
Obj2的foo具有只读属性,因此不能赋值。通过Object.preventExtensions方法,可以防止fixedObj添加更多属性。
另外,严格模式下,删除不可删除的属性,系统会报出TypeError。比如说:
'use strict';
delete Array.prototype
运行上述代码,系统会提示TypeError.
严格模式下,同一对象不能有重名的属性,所以下列代码会出现语法错误:
'use strict';
let o = { a: 1, a: 2 };
严格模式要求函数参数名都是唯一的。在严格模式未开启的情况下,如果两个参数都被命名为one,那么在传入参数时,后被定义的那个参数会被默认为是参数的值。
但是,严格模式下,函数不能有重名的参数,所以下列代码因出现语法错误而无法运行:
const multiply = (x, x, y) =>x*x*y;
严格模式禁止八进制表示法。虽然这并不是规范,但浏览器中,整数的第一位如果是0,表示这是八进制数。
这就会使开发者有点摸不着头脑,因为他们可能会觉得前缀0完全没有意义。所以严格模式下禁止八进制表示法,并且会报出语法错误。
严格模式还禁止使用使优化过程变得更加困难的语法。严格模式要求变量的储存位置与优化前的位置相同。所以要注意不要出现会阻止优化的语法。
with语句就是一个例子。如果使用with语句,它会阻止JavaScript解释器知道被引用的变量或属性,因为很可能在with语句的内部或外部具有相同名称的变量。
如果使用下列代码:
let x = 1;
with (obj) {
x;
}
那么,如果with语句里面有一个x,JavaScript也无法判定with语句指的是x变量还是obj,obj.x的属性。
由此可见,x变量的储存位置是很模糊的。所以严格模式不允许使用with语句。如果在严格模式中输入下列语句:
'use strict';
let x = 1;
with (obj) {
x;
}
上述代码会报语法错误。
严格模式,在eval语句中变量声明也不被允许。
比如说,正常模式下,eval语句('let x')会在该语句中声明变量x。这样一来,人们可以在字符串中隐藏变量声明,但这些字符串可能会覆盖eval语句之外的同一变量声明。
为避免这种情况,严格模式不允许在传递给eval语句的字符串参数中进行变量声明。
严格模式还会禁止删除普通变量名称,所以下列代码也会报语法错误:
'use strict';
let x;
delete x;
禁止无效句法
严格模式中不允许出现eval和arguments的无效句法。
来源:unsplash
这意味着不允许对其执行任何操作,例如为它们赋新值或将它们用作变量,函数或函数中参数的名称。
下列是eval 和arguments无效用法的一些例子:
'use strict';
eval = 1;
arguments++;
arguments--;
++eval;
eval--;
let obj = { set p(arguments) { } };
let eval;
try { } catch (arguments) { }
try { } catch (eval) { }
function x(eval) { }
function arguments() { }
let y = function eval() { };
let eval = ()=>{ };
let f = new Function('arguments', "'use strict'; return 1;");
严格模式也不允许为arguments对象创建别名,并通过这个别名设置新值。
正常模式下,如果函数的第一个参数是a,设置a的值也设置了arguments[0]。在严格模式下,arguments对象将始终拥有调用该函数所使用的参数列表。
比如说,如果有:
const fn = function(a) {
'use strict';
a = 2;
return [a, arguments[0]];
}console.log(fn(1))
然后我们应该可以看到[2,1]已记录。这是因为将a设定为2的同时,未将arguments[0]也设置为2。
性能优化
此外,严格模式不支持arguments.callee。正常模式下,它所做的就是返回arguments.callee所在的被调用函数的名称。
它阻止了诸如内联函数之类的优化,因为arguments.callee要求,如果访问了arguments.callee,则未对内联函数的引用可用。因此,在严格模式下,arguments.callee会引发TypeError。
使用严格模式时,不会始终强制性地将this定义为对象。 但如果一个函数的“this”是通过call,apply或bind与任何非对象类型(例如未定义(undefined),空(null),数值(number),布尔值(boolean)等的原始类型)进行绑定的,那么必须强制将this定义为对象。
如果是在非对象类型中使用this,则全局window对象将取代this。这意味着将全局对象暴露给正在被调用的函数,并且this被绑定到了非对象类型。
比如说,如果运行下列代码:
'use strict';function fn() {
return this;
}
console.log(fn() === undefined);
console.log(fn.call(2) === 2);
console.log(fn.apply(null) === null);
console.log(fn.call(undefined) === undefined);
console.log(fn.bind(true)() === true);
所有控制台日志(console logs) 都会显示true,因为当将this更改为非对象类型时,该函数内部的this不会自动转换为全局对象window。
修复安全漏洞
在严格模式下,不允许公开该函数的caller和arguments,因为caller可能会公开一个函数,该函数会调用含caller属性的函数。
arguments具有在调用函数时传递的参数。 例如,假设有一个名为fn的函数,则可以通过fn.caller查看调用fn的函数,并通过fn.arguments看到在调用fn时传递给fn的参数。
这是一个潜在的安全漏洞,但是可以通过禁止使用函数的这两个属性进行修复。
function secretFunction() {
'use strict';
secretFunction.caller;
secretFunction.arguments;
}
function restrictedRunner() {
return secretFunction();
}
restrictedRunner();
在上述例子中,无法在严格模式下访问secretFunction.caller和secretFunction.arguments,因为人们可能会利用它来获取函数的调用堆栈。如果运行上述代码,可能会触发TypeError。
来源:unsplash
在未来JavaScript版本中,成为限制关键词的标识符将不再允许用于变量和属性名称的标识符。
下列关键词不会被用到代码中作为标识符。
Implements, interface, let, package, private, protected, public, static和yield。
在ES2015和之后的版本,这些都是保留字了,所以它们绝对不能在非严格模式下被用来命名变量和对象属性。
严格模式成为标准已经有些年头了。现在大部分浏览器都能兼容,可能只有IE这种比较老的浏览器会出现兼容方面的问题。
其他浏览器基本都能支持严格模式。所以,该模式应当用来规避错误和安全隐患,比如暴露堆叠,或在eval中声明新变量。
此外,严格模式能消除正常模式下未被发现的错误,保证代码能没有错误地运行。它还能指出阻碍JavaScript引擎进行优化的错误。
此外,它也禁用了一些可能会在未来版本中定义的特征。
这就是为什么JavaScript需要严格模式,小伙伴们都懂了吗?
留言 点赞 关注
我们一起分享AI学习与发展的干货
欢迎关注全平台AI垂类自媒体 “读芯术”
(添加小编微信:dxsxbb,加入读者圈,一起讨论最新鲜的人工智能科技哦~)