2017-9-24

1. boolean構(gòu)造函數(shù)與邏輯或運算符(||)

如果boolean構(gòu)造函數(shù)的參數(shù)不是一個布爾值,參數(shù)會被轉(zhuǎn)換為布爾值。

如果參數(shù)為以下情況時:

  • 0
  • -0
  • false
  • NaN
  • undefined
  • null
  • ''(空字符串)

生成的boolean對象的值為false
除此以外的任何參數(shù)都會創(chuàng)建值為true的boolean對象。

注意不要將原始值true false,和值為true false的boolean對象相混淆。

例如一個名為a,值為false的boolean對象實際上擁有如下屬性:

 {
__proto__:Boolean,
[[PrimitiveValue]]:false
}

因此:

a === false; //false
a == false; //true

另外注意 //TODO

new Boolean('') === false //false
Boolean('') === false //true
邏輯或運算符(||)

邏輯運算符的操作數(shù)如果不是boolean類型,會先被轉(zhuǎn)換成boolean數(shù)。
過程與boolean構(gòu)造函數(shù)轉(zhuǎn)換參數(shù)時相似。

因此在定義一個函數(shù)時,可以利用(||)代替if運算符給可選參數(shù)設定默認值,例如:

function fun(o, /* optional */a) {
  if (a === undefined) {a = [];}
...
};

用與運算代替

function fun(o, /*optional */a) {
  a = a || [];
...
};

但實際上第二種寫法存在缺陷,當傳入的值本身就是false時,傳入的值卻被認為是無效的,變量被設為了默認值。

在ES6中可以直接為變量設定默認值了,例如:

function fun(o, a = []) {
};

所以TMD寫了這么一長串其實除了幫自己鞏固了一遍以外完全沒有什么卵用


2. 預定義與詞法作用域

先來看一段代碼
var a = 'global';
var getValue = function () {
  console.log(a);  //輸出undefined
  var a = 'local';
  console.log(a);  //輸出local
};
getValue();

如果不了解js解析器的預定義行為,以及js的詞法作用域,可能會覺得第一個console.log(a);將會輸出'global'。

下面來解釋js解析器的預定義

在上述代碼中getValue()的詞法作用域就是它定義時的這一段語句(從var getValue = ...console.log(a); };)。

在這段代碼中有一個對函數(shù)內(nèi)局部變量a的聲明,js解析器提前對var定義的變量進行了初始化,但是沒有賦值。
即實際上調(diào)用getValue()時作用域中存在一個a變量,它的值為undefined。
第一個console.log()執(zhí)行時,會現(xiàn)在當前作用域里尋找a。
a已經(jīng)被js解析器提前初始化卻沒有賦值,所以找到了一個變量a = undefined;。
因此第一個console.log()輸出undefined

如果當前作用域內(nèi)沒有所需要的變量(在本例子中是變量a),將會一直在作用域鏈上一級中尋找這個變量,直到找到該變量,或者抵達最外層作用域為止。關于詞法作用域?qū)⒃谙鹿?jié)詳細介紹。

所以如果想讓console.log()輸出'global',應該:

var a = 'global';
var getValue = function () {
  console.log(a);  //輸出'global'
};
getValue();

本節(jié)參考:


3. 詞法作用域(lexical scoping)與閉包(closure)

首先為了讓我們更加清楚詞法作用域的概念,這里引入動態(tài)作用域的概念來進行對比
  • 詞法作用域就是作用域在定義階段已經(jīng)決定好了,是由代碼中變量和塊作用域?qū)懺谀睦餂Q定的。
    無論函數(shù)在哪里,通過什么方式被調(diào)用,它的詞法作用域都只由函數(shù)被聲明時所處的位置決定。
    作用域和作用域鏈不會再發(fā)生變化。
  • 動態(tài)作用域并不關心函數(shù)和作用域是如何、在何處聲明的,只關心它們從何處調(diào)用。動態(tài)作用域的
    作用域鏈是基于調(diào)用棧的,而不是由代碼中的作用域嵌套的位置。

接下來請看一個例子:

var a = 2;
function foo() {
    console.log( a );
}
function bar() {
    var a = 3;
    foo();
}
bar();
  • 如果這段代碼處于詞法作用域中,變量a首先在foo()函數(shù)中查找,沒有找到,于是沿著作用域鏈往上級作用域繼續(xù)查找,找到了值為2的變量a,控制臺輸出2

  • 如果這段代碼處于動態(tài)作用域中,變量a首先在foo()函數(shù)中查找,沒有找到,于是沿著調(diào)用棧找到調(diào)用foo()的函數(shù)bar(),在bar()中尋找變量a,找到了值為3的變量a,控制臺輸出3。

通過上述例子明確地展現(xiàn)出了兩種作用域的區(qū)別,為了方便大家記憶,簡而言之:

  • 詞法作用域在定義時確定。
  • 動態(tài)作用域在運行時確定。
JavaScript中的作用域
  1. 特別注意JavaScript中沒有塊級作用域(實際上是ES5中沒有,ES6已經(jīng)支持),只有函數(shù)可以限定一個變量的作用域。
  2. 根據(jù)詞法作用域的作用域鏈的特點,子作用域可以訪問父作用域。

接下來看一道習題,思考10秒再看答案:

if(! "a" in window) {
    var a = "233";
}
console.log(a);

正確答案是輸出undefined。
因為var a = "233";這條語句雖然在if語句的大括號中,但JavaScript中沒有塊級作用域,因此這條語句實際上聲明了一個處于全局作用域中的變量。由于JS解析器的預定義特點,變量a被提前聲明,但由于不滿足if語句的條件,沒有進行賦值,因此變量a的值為undefined,所以控制臺輸出undefined。

接下來討論閉包

假設我們現(xiàn)在需要一個計數(shù)器函數(shù)counter,每次只需要調(diào)用它,計數(shù)器就加一:

var a = 0;
var counter = function () {
  return a++;
};  

現(xiàn)在我們每次執(zhí)行counter(),就可以為計數(shù)器加一了。

counter(); // a: 1
counter(); // a: 2
counter(); // a: 3

但是在實際開發(fā)中會遇到一些問題,比如在另一個地方有一個函數(shù)fun

var fun = function () {
  a *= a;
}; 
...
...
fun(); // a: 9糟糕!不小心修改了a。

在實際開發(fā)中萬一遇到這種問題會非常麻煩,特別是已經(jīng)開發(fā)了一段時間,有些變量名你已經(jīng)忘記了的時候。
那對于這種幾乎專用的變量,我們能不能想辦法把它們藏起來,現(xiàn)在來修改一下counter函數(shù)

var counter = function () {
  var a = 0; //不再在全局聲明變量a,在counter的作用域里面聲明,保護他!
  return a++;
};

好,我們成功地保護了變量a!這下外部的其他函數(shù)沒法對它進行操作了。
不過等等,現(xiàn)在這樣每次counter執(zhí)行完,里面的變量不會再被其他地方引用,
就被垃圾回收機制回收了,再次執(zhí)行的時候,變量又會變回默認值,沒有辦法計數(shù)了。
我們得想一個辦法讓變量a不會被回收:

var counter = function () {
  var a = 0;
  var foo = function() {
    return a++; //foo可以訪問到counter的作用域,可以訪問到a。
  };
  foo();
};

現(xiàn)在foo已經(jīng)引用了a,但是foo被回收的時候,a也會被回收了,仍然沒有解決問題。

var counter = function () {
  var a = 0;
  return function() { //我們讓counter返回下面這個匿名函數(shù)。
    return a++; //這個匿名函數(shù)可以訪問到counter的作用域,可以訪問到a。
  };
};
var counter1 = counter();讓一個全局變量引用counter的返回值,即剛才的匿名函數(shù)。

好了,現(xiàn)在形成了這樣的一個關系:
counter1->匿名函數(shù)->變量a
由于counter1是在全局聲明的,他的生命周期直到整個程序才結(jié)束。
現(xiàn)在我們既可以長久的保存變量a又不會造成全局污染。
最后我們得到的這一段代碼,就是一個閉包。

總結(jié)一下,閉包就是一個可以訪問其他函數(shù)作用域的函數(shù),并且能保持那個作用域里的變量不被釋放,
可以重復使用,并且只能由調(diào)用閉包者來訪問。

所以剛才var counter1 = counter();這一步是形成閉包最重要的一步。

閉包的特點:
  • 不容易被釋放
  • 占用更多內(nèi)存
使用閉包的場景:
  • 使用閉包可以在JavaScript中模擬塊級作用域
  • 閉包可以用于在對象中創(chuàng)建私有變量

這是我對閉包最簡單的理解,當然閉包還有其更深層次的理解,需要了解JS的執(zhí)行環(huán)境(execution context)、活動對象(activation object)、垃圾回收(garbege collection)以及作用域(scope)和作用域鏈(scope chain)的運行機制。

本節(jié)參考:


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容