阅读 939

前端JavaScript '严格模式' 的详细总结

在阅读jQuery源码的时候,发现jQuery用到的是'use strict'严格模式。因此就去查阅了一些资料来补充自己对严格模式的认识。下面是参考【javascript高级程序设计(第三版)】的内容。

1.严格模式(strict mode)

1.1 什么是严格模式

  ‘严格模式’最早引入是ECMAScript5。通过严格模式,可以在函数内部选择进行较为严格的全局或局部的错误条件检测。

【补充】ECMAScript各版本的划分,推荐:阮一峰老师中ECMAScript的历史介绍

1.2 使用严格模式的好处

可以提早知道代码中存在的错误,及早捕获一些可能导致编程错误的ECMAScript行为。

1.3 支持严格模式的浏览器

理解严格模式的规则非常重要,支持严格模式的浏览器有

  • IE 10+
  • Firefox 4+
  • Safari 5.1+
  • Chrome

2. 如何使用严格模式

当我们想要让自己编写的代码处在严格模式的时候,可以使用严格模式的编译指示(pragma),也就是一个不会赋给任何变量的字符串:'use strict'这就话需要放在想要处于严格模式的首句。

这种语法可以向后兼容那些不支持严格模式的javascript引擎。

  • 支持严格模式的引擎会启动这种模式
  • 不支持该模式的引擎就当遇到一个未赋值的字符串字面量,会忽略这个编译指示

如果在全局作用域中给出的这个编译指示,则整个脚本都将使用严格模式。也就是这个编译指示放在哪里,只要有javascript代码,就会让其处于严格模式。

在函数中使用严格模式。把'use strict'放在首位,就可以对整个函数其作用

function fn(){
  'use strict'
  //要实现的代码
}
复制代码

【补充】字面量: 百度百科的解释
通俗理解:let a = 1;这个等号左边的叫做变量,等号右边的就叫做字面量

3. 页面中使用严格模式与不使用严格模式的区别

3.1 变量

在严格模式下,什么时候创建变量以及怎么创建变量都是有限制的。

3.1.1 不允许意外创建全局变量

未声明变量在严格模式非严格模式下的区别:

  • 非严格模式:即使message前面没有var关键字,也没有定义为全局对象的属性;也可以将它创建为全局变量
  • 严格模式 :如果给一个没有声明的变量赋值或者变量名拼写错误。那代码在执行的时候就会抛出ReferenceError这个错误
message = 'Hello World!'
console.log(window.message)  //'Hello World!'
复制代码
'use strict'
message = 'Hello World!'
console.log(window.message);  //Uncaught ReferenceError: message2 is not defined
复制代码

3.1.2 不能对变量调用delete操作符

  • 非严格模式:允许使用delete删除元素,但会静态默认失败(也就是删除不了,但是不会报错,哈哈)
  • 严格模式:删除变量也会导致错误
var color = 'red';
delete color;
console.log(color); //'red'
复制代码
'use strict'
var color = 'red';
delete color;
console.log(color); //Uncaught SyntaxError: Delete of an unqualified identifier in strict mode. 
复制代码

3.1.3 对变量名的限制

不能使用 implementsinterfaceletpackageprivateprotectedpublicstaticyield 作为变量名。这些都是保留字,将来的 ECMAScript 版本中可能会用到它们。

  • 非严格模式:使用上面的关键字不会报错,但是最好不要用(变量的命名规范中有一条就是不能使用JS中的关键字和保留字)
  • 严格模式下:用以上标识符作为变量名会导致语法错误。
var implements=1;
console.log(implements); //1
复制代码
'use strict'
var implements=1;
console.log(implements);//Uncaught SyntaxError: Unexpected strict mode reserved word
复制代码

3.2 对象

在严格模式下操作对象比在非严格模式下更容易导致错误。

  • 非严格模式下会静默失败的情形
  • 在严格模式下就会抛出错误

因此,在开发中使用严格模式会加大早发现错误的可能性。

3.2.1 在下列情形下操作对象的属性会导致错误:

  • 为只读属性赋值会抛出 TypeError
  • 对不可配置的(nonconfigurable)的属性使用 delete 操作符会抛出 TypeError
  • 为不可扩展的(nonextensible)的对象添加属性会抛出 TypeError
"use strict";

// 给不可写属性赋值
var obj1 = {};
Object.defineProperty(obj1, "x", { value42writablefalse });
obj1.x = 9// 抛出TypeError错误

// 给只读属性赋值
var obj2 = { get x() { return 17; } };
obj2.x = 5// 抛出TypeError错误

// 给不可扩展对象的新属性赋值
var fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = "ohai"// 抛出TypeError错误
复制代码

3.2.2 使用对象的另一个限制与通过对象字面量声明对象有关。在使用对象字面量时,属性名必须唯一。

这个问题在ECMAScript6中已经不复存在,也就是使用严格模式时,和非严格模式一样,都是最后那个值,不会报错

  • 非严格模式:没有错误,以第二个属性为准
  • 严格模式:抛出语法错误
"use strict";
var person = { 
  name"Nicholas"
  name"Greg" 
}; 
复制代码

这里的对象 person 有两个属性,都叫 name。在非严格模式下,person 对象的 name 属性值是第二个,而在严格模式下,这样的代码会导致语法错误。

3.3 函数

3.3.1 命名函数的参数必须唯一

  • 非严格模式:没有错误,只能访问第二个参数。通过参数名只能访问第二个参数,要想访问第一个就需要通过arguments对象
  • 严格模式:抛出语法错误
function sum(num,num){
  //函数体
  return num + num; // 要想访问第一个参数:return arguments[0] + num;
}
console.log(sum(10,20)); // 40
复制代码
'use strict';
function sum(num,num){
  //函数体
  return num + num;
}
console.log(sum(10,20)); // Uncaught SyntaxError: Duplicate parameter name not allowed in this context
复制代码

3.3.2 修改命名空间参数的值与arguments的映射关系

  • 非严格模式:修改会反映到arguments
  • 严格模式:修改不会反映到arguments
function sum(num){
   num=20;
   console.log(arguments[0]); //20
   console.log(num);  //20
}
sum(10);
复制代码
'use strict';
function sum(num){
   num=20;
   console.log(arguments[0]); //10
   console.log(num);  //20
}
sum(10);
复制代码

  以上代码中,函数sum()只有一个命名参数 num。调用这个函数时传入了一个参数10, 这个值赋给了num。而在函数内部,num被改为20。在非严格模式下,这个修改也会改变 arguments[0]的值,但在严格模式下,arguments[0]的值仍然是传入的值。

3.3.3 淘汰了 arguments.callee 和 arguments.caller

  • 非严格模式下:这两个属性一个引用函数本身,一个引用调用函数。
  • 严格模式下:访问哪个属性都会抛出 TypeError。
function factorial(num{
   if (num <= 1) {
      return 1;
   } else {
      return num * arguments.callee(num - 1)
   }
}
var result = factorial(5);
console.log(result); //120 =>5*4*3*2*1
复制代码
'use strict';
function factorial(num{
   if (num <= 1) {
      return 1;
   } else {
      return num * arguments.callee(num - 1)
   }
}
var result = factorial(5);
console.log(result); //120 =>5*4*3*2*1
复制代码

【补充】arguments.callee是已经被废弃的,属性用在函数执行的时候调用自身(可以理解为递归的思想)。虽然arguments.caller 已经不可使用了,但是你还可以使用 Function.caller

3.3.4 函数名的限制

与变量类似,严格模式对函数名也做出了限制,不允许用 implementsinterfaceletpackageprivateprotectedpublicstaticyield 作为函数名。

3.3.5 在 if / for 语句中声明函数会导致语法错误:

  • 非严格模式:将函数提升到 if / for 语句外部
  • 严格模式:抛出语法错误
if (true) {
   function f(console.log(1); }
}
f(); //函数执行输出1

for (var i = 0; i < 5; i++) {
   function f2({console.log(1); }
   f2();  //输出5次1
}
f2();  //输出1
复制代码
"use strict";
if (true) {
   function f(console.log(1); }
}
f(); //Uncaught ReferenceError: f is not defined  at

for (var i = 0; i < 5; i++) {
   function f2({console.log(1); }
   f2();  //输出5次1
}
f2();  //Uncaught ReferenceError: f2 is not defined at
复制代码

3.4 eval()

饱受诟病的 eval()函数在严格模式下也得到了提升。最大的变化就是它在包含上下文中不再创建变量或函数。

3.4.1 使用 eval()创建变量

  • 非严格模式:弹出对话框显示 10
  • 严格模式:调用 alert(x)时会抛出 ReferenceError
function doSomething()
  eval("var x=10"); 
  alert(x); 
}
doSomething();  //函数执行会弹出10
复制代码
'use strict';
function doSomething()
  eval("var x=10"); 
  alert(x); 
}
doSomething(); //函数执行报错:Uncaught ReferenceError: x is not defined at doSomething
复制代码

如果是在非严格模式下,以上代码会在函数 doSomething()中创建一个局部变量 x,然后 alert()还会显示该变量的值。但在严格模式下,在 doSomething()函数中调用 eval()不会创建变量 x,因此调用 alert()会导致抛出 ReferenceError,因为 x 没有定义。

3.4.2

可以在 eval()中声明变量和函数,但这些变量或函数只能在被求值的特殊作用域中有效,随后就将被销毁。因此,以下代码可以运行,没有问题:

"use strict"
var result = eval("var x=10, y=11; x+y"); 
alert(result); //21 
复制代码

这里在 eval()中声明了变量 xy,然后将它们加在一起,返回了它们的和。于是,result 变量的值是 21,即 xy相加的结果。而在调用alert()时,尽管 xy 已经不存在了,result变量的值仍然是有效的。

3.5 eval 与 arguments

严格模式已经明确禁止使用evalarguments作为标识符,也不允许读写它们的值。

3.5.1 把 eval 和 arguments 作为变量引用

  • 非严格模式:没问题,不出错
  • 严格模式:抛出语法错误
var eval = 10
var arguments = "Hello world!";
console.log(eval,arguments); //10 "Hello world!"
复制代码
'use strict'
var eval = 10
var arguments = "Hello world!";
console.log(eval,arguments); //Uncaught SyntaxError: Unexpected eval or arguments in strict mode
复制代码

3.5.2 在非严格模式下,可以重写 eval,也可以给 arguments 赋值。但在严格模式下,这样做会导致语法错误。不能将它们用作标识符,意味着以下几种使用方式都会抛出语法错误:

  • 使用 var 声明;
  • 赋予另一个值;
  • 尝试修改包含的值,如使用++;
  • 用作函数名;
  • 用作命名的函数参数;
  • 在 try-catch 语句中用作例外名。

3.6 抑制this

JavaScript 中一个最大的安全问题,也是最容易让人迷茫的地方,就是在某些情况下如何抑制 this的值。

  • 非严格模式下使用函数的 apply()或 call()方法时,null 或 undefined 值会被转换为全局对象。
  • 严格模式下,函数的 this 值始终是指定的值,无论指定的是什么值。

3.6.1 访问属性

  • 非严格模式:访问全局属性
  • 严格模式:抛出错误,因为 this 的值为 null
var color = "red";
function displayColor({
   console.log(this.color);
}
displayColor.call(null); //'red'
复制代码
'use strict';
var color = "red";
function displayColor({
   console.log(this.color);
}
displayColor.call(null); //Uncaught TypeError: Cannot read property 'color' of null at displayColor
复制代码

以上代码向displayColor.call()中传入了null,如果在是非严格模式下,这意味着函数的this值是全局对象。结果就是输出"red"。而在严格模式下,这个函数的 this的值是 null,因此在访问 null的属性时就会抛出错误。

3.7 其他变化

3.7.1 with 的语句用法

首先是抛弃了 with 语句。

  • 非严格模式下: with语句能够改变解析标识符的路径,允许
  • 严格模式下:with 被简化掉了,抛出语法
with (location) {
  console.log(href);//http://127.0.0.1:5501/%E7%AC%AC%E5%85%AD%E5%91%A8/%E7%AC%AC%E4%BA%8C%E5%A4%A9/%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F.html
}  
复制代码
'use strict'
with (location) {
  console.log(href); //Uncaught SyntaxError: Strict mode code may not include a with statement

复制代码

因此,在严格模式下使用 with会导致语法错误

3.7.2 使用八进制字面量

  • 非严格模式:值为 8
  • 严格模式:抛出语法错误
var value = 010;
console.log(value);//8
复制代码
'use strict'
var value = 010;
console.log(value);//Uncaught SyntaxError: Octal literals are not allowed in strict mode.
复制代码

4. 参考书籍/网址

   本文参考了《javascript高级程序设计(第三版)》 以及 MDN网站。把内容进行了总结,每一个的不同都使用代码的形式展示了出来,希望对广大朋友们有帮助。如果感觉本文不错,可以点赞支持一下。可以关注一下

文章分类
前端
文章标签