聊一聊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的理解。有什麼問題歡迎你們指出,但願在你們們的批評下取得進步。

相關文章
相關標籤/搜索