前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript 严格模式

JavaScript 严格模式

作者头像
多云转晴
发布2020-02-17 16:12:02
9220
发布2020-02-17 16:12:02
举报
文章被收录于专栏:webTowerwebTower

严格模式是 ECMAScript5ES5)发布的语言新特性。使用严格模式可以限制 JavaScript 的一些语言特性,使用严格模式可以去除在书写代码时的一些“骚操作”(有些特性在严格模式下是不可用的),使代码更严谨整洁。

严格模式对正常的 JavaScript 语义做了一些更改:

  • 严格模式通过抛出错误来消除了一些原有静默错误;
  • 严格模式修复了一些导致 JavaScript引擎难以执行优化的缺陷:有时候,相同的代码,严格模式可以比非严格模式下运行得更快;
  • 严格模式禁用了在ECMAScript的未来版本中可能会定义的一些语法;

使用严格模式

使用严格模式也很简单,只要在 js 文件中写入 "use strict" 这一行文字即可。写了之后,该字符串所在的作用域中的代码就会遵循严格模式。

比如:

代码语言:javascript
复制
"use strict"

function aa(){
    // ...
}
// ....

use strict 可以写在文件的许多位置,下面说一下在不同位置它起到的作用与作用范围。

  1. 将该字符串写在文件最顶部,这时整个文件的代码都会进入严格模式;
  2. 将字符放在函数顶部,那么严格模式只会给这个函数开启严格模式;
代码语言:javascript
复制
function strict(a,b){
    "use strict";   // 给 strict 开启严格模式
    return a + b;
}
  1. 不要将 use strict 单独放在 {} 中,在这样的上下文中这么做是没有效果的。

比如下面的代码,其实并没有开启严格模式:

代码语言:javascript
复制
{ "use strict" };

// some code ...

这是因为 {} 相当于一个作用域,上面相当于在一个作用域中使用严格模式,作用域外面的代码是不受约束的。因此可以看出,严格模式只对它所在的作用域中的代码有效(而且是它下方的代码)。

一个函数中的内容({}里的)就是一个作用域,ES6 类里的内容也是一个作用域;for 循环中也是一个作用域。

  1. 当打包文件时(多个文件打包成一个),你最好将所有的文件(打包之前的)都设置成严格模式或非严格模式,因为如果不这样做,打包后的文件可能并不是严格模式,可能会背离你的目的。或者将严格模式定义在函数中,这样或许能避免模式冲突。

严格模式中的变化

首先看几个例子,在不使用严格模式下运行代码是什么结果,而使用了严格模式又是什么结果。

1. 变量声明

考虑下面代码,运行后会出现什么结果:

代码语言:javascript
复制
"use strict";

a = 123;
console.log(a);

运行后会发现报错(a is not defined),这是因为 在严格模式下你必须声明变量,然后再赋值

这样做的好处是,当你想使用一个局部变量时,却没有声明它,这时如果不使用严格模式,改变量默认会挂载到上层作用域中。有了严格模式可以帮助你检查这样的错误,同时也可以提高编码规范。

2. 相同的形参变量

考虑下面的代码:

代码语言:javascript
复制
function loose(a,a){
    return a + a;
}

console.log(loose(2,4));

结果可能出乎意料,并没有报错,而且输出的是 8。只是因为在正常模式下,最后一个重名参数名会掩盖之前的重名参数。因此这个函数其实只有一个形参 a,4 才是它真正的实参。

在严格模式下这是会报错的。

3. arguments

arguments 是存储函数形参的类数组,还有一个特殊的函数: arguments.callee,它可以用于引用该函数的函数体内当前正在执行的函数。arguments.callee() 相当于执行函数体本身,当你想对匿名函数进行递归调用时就可以使用 callee 函数。

比如下面的代码:

代码语言:javascript
复制
[1,2,3,4,5].map(function (n) {
    // 使用 callee 实现递归
    return !(n > 1) ? 1 : arguments.callee(n - 1) * n;
    // [1, 2, 6, 24, 120]
});

而在严格模式下,arguments.callee 是不能使用的(会报错)。那又如何实现上面的递归呢?可以使用具名回调函数:

代码语言:javascript
复制
"use strict";
// 使用具名函数
var arr = [1, 2, 3, 4, 5].map(function fn(n) {
    return !(n > 1) ? 1 : fn(n - 1) * n;
});

当然你也可以直接将 fn 函数直接写成一个独立的函数,然后传入 map 中即可:[1,2,3,4,5].map(fn);

考虑下面的代码:

代码语言:javascript
复制
function loose(a,b,c){
    a = 100;
    console.log(arguments);
}

loose("a","b");

打印后会发现,a 的值已经变成 100。在严格模式下函数形参是只读的,修改了形参的值并不对报错,当然值还是原来的值,并没有被你修改掉。如果你真的有这个需求,可以声明一个变量,然后将形参赋给改变量。或者使用 arguments[0] = 100 的方式去修改,在严格模式下通过 arguments 下标的方式是可以修改传入的值的。

4. this 指向

考虑下面两个函数打印的 this 值:

代码语言:javascript
复制
function fn1(){
    console.log(this);
}

function fn2(){
    "use strict";   // 启用严格模式
    console.log(this);
}

fn1();  fn2();

运行后会发现,fn2 的 this 是 undefined。这是因为在严格模式下通过 this 传递给一个函数的值不会被强制转换为一个对象;一个开启严格模式的函数,指定的 this 不再被封装为对象,而且如果没有指定 this 的话它值是 undefined,可以使用 call, apply 或者 bind 方法来指定一个确定的 this

比如下面的代码会全部返回 true。而如果是非严格模式,除了最后 obj 的那一个,其余都返回 false。这是因为非严格模式下,this 会被包装成一个对象。

代码语言:javascript
复制
"use strict";
function fun() { return this; }
console.log(fun() === undefined);
console.log(fun.call(2) === 2);
console.log(fun.apply(null) === null);
console.log(fun.call(undefined) === undefined);
console.log(fun.bind(true)() === true);

"use strict";
var obj = {
    // fn1 就在一个对象里,因此 this 指向 obj
    fn1(){
        return this;
    }
}
console.log(obj.fn1() === obj);     // true
对于箭头函数

箭头函数与普通函数略有不同,箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this。而且箭头函数还有以下特点:

  • 由于箭头函数没有自己的this指针,通过 call() apply()bind() 方法调用一个函数时,只能传递参数(不能绑定 this),他们的第一个参数会被忽略。
  • 严格模式中与 this 相关的规则都将被忽略。也就是说严格模式对箭头函数的 this 指向无效,顶层的箭头函数还是指向 window
  • 箭头函数不绑定 arguments。 比如下面的代码: const fn = (a,b,c,d) => arguments; // 会报错:arguments is not defined console.log(fn(1,2,3,4)); 因此,使用箭头函数可以接收上层普通函数的 arguments 参数。 function fn(a,b,c){ let func = () => { console.log(arguments); } func(); } fn(1,2,3); // 1,2,3
  • 箭头函数不能用作构造器,和 new 一起用会抛出错误,箭头函数也没有 prototype 属性。
  • yield 关键字通常不能在箭头函数中使用(除非是嵌套在允许使用的函数内)。因此,箭头函数不能用作函数生成器。

5. 静默失败以及禁用

使用严格模式会引起静默失败,静默就是有些操作是不能完成的,但运行代码不报错也没有任何效果。例如 Object.defineProperty 方法可以给对象的键设置一些属性,比如设置不可写属性:

代码语言:javascript
复制
var obj1 = {};
Object.defineProperty(obj1, "x", { value: 42, writable: false });
obj1.x = 9;

如果是非严格模式,运行代码并不会报错,而且 obj.x 的值也不能被改变。而如果开启了严格模式,就会出现错误。

类似的静默失败还有这么几个方法:

  • Object.preventExtensions(object) 给不可扩展对象的新属性赋值(例如:object.aa = 123);
  • Object.defineProperties() 在一个对象上定义新的属性或修改现有属性的状态(可以一次修改多个属性);
  • 试图删除不可删除的属性时会抛出异常。例如:delete Object.prototype
  • 不能将变量名命名为 eval 或者 arguments,严格模式下会报错;
  • 严格模式禁用 with 语句(一般也不用);
  • 严格模式下禁止使用 021 这种表示八进制(普通模式下表示八进制),可以使用 0o21 表示八进制;
  • 严格模式下禁止给基本类型设置属性(string,number,bigint,boolean,null,undefined,symbol),例如:true.a = 1"aa".a = 1(123).b = "aa",这些都会报错在严格模式下。
  • 严格模式禁止删除声明的变量。比如:var a; delete a; 会报错;
  • 在严格模式中一部分字符变成了保留的关键字。这些字符包括 implements, interface, let, package, private, protected, public, static 和 yield。在严格模式下,你不能再用这些名字作为变量名或者形参名。
  • 严格模式下不能在循环语句以及条件语句中声明函数;

6. eval 函数

eval 函数可以将字符串解析成 js 代码然后执行,因此 eval 很强大,严格模式对 eval 函数做了一些限制。

eval 会将传入的字符串执行,然后将返回(或者赋值)的变量返回。而且 "use strict" 严格模式标志可以写进 eval 函数中执行。

  • 严格模式下的 eval 不再为上层范围(包围 eval 代码块的范围)引入新变量。

比如下面的代码,如果不使用严格模式,变量 a 和 b 的值会相同。

代码语言:javascript
复制
var a = 123;

// var a = 12 然后将 a 赋给 b
var b = eval("var a = 12;a");

console.log(a,b);   // 12 12

如果使用了严格模式(var b = eval("'use strict';var a = 12;a");),则打印的变量 a 仍然是 123。

还有一种的情况:

代码语言:javascript
复制
strict2(eval,"Non-strict code.");

function strict2(f, str) {
    "use strict";
    // 没有直接调用 eval(...): 当且仅当 str 中的代码开启了严格模式时
    // 才会在严格模式下运行
    return f(str);
}

以上就是 JavaScript 的严格模式。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebTower 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用严格模式
  • 严格模式中的变化
    • 1. 变量声明
      • 2. 相同的形参变量
        • 3. arguments
          • 4. this 指向
            • 对于箭头函数
          • 5. 静默失败以及禁用
            • 6. eval 函数
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档