聊一聊JavaScript中的严格模式与相关的‘坑’

ECMAScript5中的严格模式(‘ use strict’)是该语言的一个受限子集,修正了JavaScript这门语言的一些缺陷,并提供了健壮的查错功能和安全机制。JavaScript中的严格模式分为两个级别,全局和函数级,取决于‘use strict’指令的位置。

      在JavaScript中存在一些‘很差的东西’(在浏览器环境中),好比全局变量自动成为全局对象window的属性,给未声明的变量赋值全局做用域中自动声明一个同名变量,以及函数级做用域中undefined重写的问题等等。而严格模式修正了一些问题,也引起了一些奇怪的问题。前端

先说一下严格模式与非严格模式的区别:数组


  • 严格模式下禁止使用with语句

       with语句通常是下面这种形式:浏览器

with( obj ) 
statement复制代码

一开始设计with的目的可能就是为了简化代码编写。以下:安全

// 初始化对象
var obj = {
    a = 1,
    b = 2,
    c = 3
}
// 修改对象属性
obj.a = 2;
obj.b = 3; 
obj.c = 4;

// 等同于下面的写法
with( obj ) {
    a = 2;
    b = 3;
    c = 4;
}复制代码

with(obj)能够在代码块中的变量解析时将obj对象添加到做用域链的最前端,当代码块中的变量进行解析,根据变量的解析规则会首先查找obj对象中是否存在同名属性。可是这种破坏词法做用域的手段不只影响代码的执行速度,还会产生意想不到的结果。bash

var obj = {};

with( obj ) {
    a = 2;
} 复制代码

这段代码中的with语句并不会为obj建立一个a属性,在代码快中的a变量进行解析时并未在obj对象中找到同名属性。这时候变量解析会继续沿着做用域链向上查找,如果找到了同名变量a,就修改它的值(但若是这个变量是const声明的,你懂的)。若是未找到同名变量a,在非严格模式下就会建立一个全局变量a并赋值为2。这是多么糟糕的事!!!该禁!app


  • 严格模式下限制eval( )的超能力

eval( )一种能够改变JavaScript词法做用域的神奇函数函数

// 超能力一 访问并拥有更改当前做用域内变量或函数的能力
var a = 2;
eval( 'a = 3' );
console.log( a ); // 3

// 超能力二  拥有在当前做用域建立变量或函数的能力。
eval( 'var b = 6' );
console.log( b ); // 6

// 别名eval;
var otherEval = eval;
复制代码

这里说一下若是将eval赋值给一个变量在ES3中规定的是会报错,ES5中不会报错可是限制了别名eval的能力,ES5规定别名eval不具备读、写、定义函数做用域内变量和函数的能力,它只能 读、写、定义全局做用域中的变量和函数。ui

在严格模式下eval()扩号中的内容会建立专属于eval的词法做用域(即便被限制了仍是这么牛逼)这个做用域和函数级做用域同样,能够经过做用域链访问并修改外部做用域的变量和函数,可是eval内部建立的变量和函数外部是访问不了的。因此eval内部建立的变量和函数就不会暴露到eval所处的做用域中了。this


  • 严格模式下全部的变量都须要先声明再赋值,若是为一个未声明的变量赋值会报错

在非严格模式下会建立一个全局变量并自动成为window的属性。这带来的后果就是屏蔽了window对象上的同名属性。在这里说一下,不论是否是严格模式,全局环境中var声明的变量都会成为window对象的属性(并且是不可配置的,不能经过delete删除)。spa

以下图:



  • 严格模式下修改对象只读属性和为不可拓展的对象建立属性都会报错。

在非严格模式下会失败并不会报错。因为这个缘由我说一下关于JavaScript的另外一个‘坑’—— undefined。undefined JavaScript的基本数据类型之一,并且只有undefined这一个值。用来做为已声明但未赋值的变量的值。下面细数一下它的‘坑’。

var a;
console.log( typeof a ) // undefined
console.log( typeof b ) // undefined复制代码

这个也不算坑,有人认为这是typeof的一种安全防范机制。在《你不知道的JavaScript中》一书中做者认为使用typeof 操做符检测未声明变量应返回undeclared。之因此提这个是提醒本身下面这个知识点。

console.log( typeof b );  //  报错
let b; or const b = someValue;复制代码

这时候typeof的安全防范机制就敌不过temporal dead zone (let,const声明块级绑定带来的暂时性死区)了。这个暂时不说,今天写笔记也不是为了说这个。下面说一下undefined真正的‘坑’

首先明确一件事,做为和null这个基友同样只有惟一值的数据类型,undefined居然不是关键字,只是一个全局变量(致使了它面临被屏蔽的危险),人家null就是关键字。幸亏ES5给了一些补救措施,在ES5中undefined做为window对象的一个只读属性,唉,这就有点变化了,最起码在全局环境中声明同名变量屏蔽不了它了。


// 非严格模式
var undefined = 2;
console.log( undefinded );  // undefinded

// 注意若是是let或const声明,会报重复声明的错误。由于同一做用域下let const 不容许重复声明。
let undefined = 2; or const undefined = 2; 

// 严格模式

var undefined = 2;
console.log( undefined ); // error复制代码

这个看起来是否是合理多了。防止了undefined被同名变量覆盖。可是仍是有意外的。。。看下面:

// 不论是不是严格模式都会屏蔽
function show() {
    var undefined = 2
    console.log( undefined );
}

show();  // 2复制代码

这仍是被屏蔽了啊。。。这是由于治标不治本啊!在全局做用域中(非严格模式下)当咱们使用var声明或直接给变量undefined赋值,他会自动成为window的属性,由于undefined是只读属性,因此这个赋值就会失败,可是默认不报错。咱们使用let或const声明时又因为重复声明,会报重复声明的错误。在严格模式下var声明不只会失败,还会由于修改只读属性报错。这就是全局环境不能修改undefined值的缘由。

由于在函数级做用域中以上两种状况都不存在,undefined又不是关键字,能够用作标识符。因此当在函数内部undefined的值就能够被修改了。这时候若是想要一个可靠地undefined的值,一种方法是经过window对象访问,另外一种是使用void操做符。

  • 严格模式下函数做为函数调用this指向undefined

这句话读起来有点绕口,其实就是函数this的默认绑定问题。在非严格模式下,函数直接做为函数调用时this默认绑定报window对象上。这个特性能够用来检测当前环境是不是严格模式。

function isStrict() {
    return this === undefined;
}

var strictMode = isStrict(); // false复制代码

  • 严格模式下call和apply的调用的函数的this值就是其第一个参数

在非严格模式下若是传入call或apply的第一个参数是null或undefined,会默认其被window对象替代。若是是基本类型,则包装为对应的包装对象。

var a = 2;
function foo(b ) {
    console.log( this.a + b );
}

foo.call( null, 3 );  // 5

// 这里介绍一种安全的机制,即便不使用严格模式也很安全。
// 若是仅仅使用call或apply调用一个函数并不涉及this绑定时,
    能够给它传入一个彻底为‘空’的对象。
var empty = Object.create( {} );
Math.max.apply( empty, array );


复制代码

  • 严格模式函数的arguments对象仅包含传入实参的副本。以及禁止经过其caller和callee检测函数调用栈的能力

arguments这个对象谁用谁知道。函数重载啊,柯里化,经过caller属性进行函数递归(这个没人用吧哈哈),实现各类绑定的polyfill,简直是神器有木有(ES6使用rest参数代替)。之因此对其进行限制仍是由于他没法无天的能力。看看下面的代码:

function foo() {
    console.log( arguments[ 0 ] );
}
foo( 5 ); // 5

function foo( a ) {
    console.log( a + arguments[ 1 ] );
}

foo( 2, 3 ); // 5复制代码

这种能力在即便没有形参或者传入实参数量多于形参时咱们也能够获取实参。


arguments对象不只获取了实参还创建了与形参之间的关联(和形参指向同一个值得引用)

function foo( a ) {
    arguments[ 0 ] = 5;
    console.log( a );
}
foo( 2 ); // 5复制代码

可是这种关联只在传入对应实参后才会创建。因此当同时访问命名参数和与其对应的arguments类数组单元是就容易产生混乱

function foo( a ) {
    a = 5;
    console.log( arguments[ 0 ] );
    console.log( a );
}
foo();  // undefined  5复制代码


在ES6中函数使用默认参数时也会引发混乱

function foo( a = 2 ) {
    console.log( a );
    console.log( arguments[ 0 ] );
    console.log( arguments.length );
}

foo();  // 2 undefined 0复制代码

因此严格模式为了不这种状况出现,使arguments对象仅仅包含传入实参的副本,并不会与形参之间创建起关联

'use strick'

function foo( a ) {
    arguments[ 0 ] = 5;
    console.log( a );
    console.log( arguments[ 0 ] );
}

foo( 2 ); // 2  5
复制代码

所以若是咱们指望使用arguments对象这种神奇能力,就不要同时访问形参何其对应的arguments类数组单元。或者使用ES6的rest参数。

  • 严格模式下delete运算符后面跟非法操做数时这会报错,使用delete删除不可配置的属性时报错

在非严格模式下这两种行为仅仅是简单的返回false并不会报错。这是为了健壮查错机制。


  • 严格模式下函数声明中存在两个或两个以上个同名参数会报错

在非严格模式下最后一个同名形参覆盖前面全部的同名形参

function foo( a, a ) {
    console.log( a );
    console.log( arguments[ 0 ] );
    console.log( arguments[ 1 ] );
}
foo( 2 ); // undefined 2 undefined
复制代码


  • 在严格模式下不容许使用以0开头的八进制整数直接量

在非严格模式下不报错并将其转化为十进制。ES6中的八进制以0o开头,如:0o32


  • 在严格模式中eval,arguments当作关键字


  • 严格模式下对象直接量中定义两个或两以上的同名属性会报错(ES6中支持属性名重复定义)

因为ES6支持属性名重复定义,因此这个没啥用,可是咱们要注意以下:

var obj = { a: 1, a: 2, a: 3 };
    
for ( var key in obj ) {
    console.log( obj[ key ] );
}
// 3复制代码

从上面的代码看一看出仅保留最后一个建立的同名属性。


这就是我所理解的严格模式以及JavaScript中可爱的‘坑’。还有不少我还没学到,也没被‘坑’,或者说还未发觉。 本人水平有限,上面也只是本身在书上及实践中对JavaScript的理解。有什么问题欢迎你们指出,但愿在你们们的批评下取得进步。

相关文章
相关标签/搜索