文章出處

看完了javaScript數據類型表達式與運算符相關知識后以為可以對JavaScript筆試題牛刀小試一把了,沒想到有一次次的死在逗號,冒號和括號上,不得已再看看這幾個符號吧。

逗號

逗號我們常見的用法就是在連續聲明一些變量的時候,可以少些很多var

var a=1,
    b=2,
    c=3;

方法參數我們使用逗號隔開,對象屬性也是逗號隔開

function fbn(name,title){}
var person={
    name:"Byron",
    age:"24"
};

然而我們也會遇到這樣的問題,在賦值表達式中出現的逗號

var a=(1,2,3);

表達式與運算符中提到過逗號運算符就是對應這種情況,這時表達式計算結果是最后一個子表達式結果,也就是3。千萬不要誤會前面的子表達式不會執行,每個子表達式都會執行,只不過“返回值”是最后一個表達式結果。

var a,b;
a=(b=1,2);
console.log(a);//2
console.log(b);//1

冒號

?:運算符

var p=gender ? 'male':female;

對象字面量

var obj={
    name:"Byron",
    age:24
};

switch語句

switch(t){
    case 1:
        console.log('xxx');
        break;
    case 2:
        console.log('ooo');
        break;
}

相信這是大家所熟知的用法了,那么我們可以個題目

x:y:z:1,2,3;

上面運算會不會報錯?不報錯運算結果是什么?很多同學初次看到這個會很驚訝,覺得肯定會出錯,但結果卻是3,來看看為什么

其實冒號還有個作用:聲明label,JavaScript中語句可以有個標簽前綴,我們稱之為標記語句,break或continue可以和標記的語句結合使用,控制流程。如果標簽有重復,就會出錯。我們上面的語句可以翻譯成這樣

x:
 y:
  z:1,2,3

這樣我們結合剛才說的逗號的知識就能明白為什么結果是3了,很多優化建議都是不提倡使用標簽的,有沒有想起C語言的goto,使用了標簽控制流程,使程序相當難讀懂。

var x=1;
foo:{
    x=2;
    break foo;
    x=3;
}
console.log(x);

大括號

對象直接量聲明

var obj={
    name:"Byron",
    age:24
};

整條語句使賦值語句,右值部分十個表達式,通過直接量構造出一個對象

函數聲明或者函數直接量

function fn1(){ 
    //....
}  
var fn2=function(){
    //...
};

相信這種用法不閉多說什么了

組織復合語句

with(obj){
    //...
}
for(){
 //...
}
if(){
    //...
}else{
    //...
}

大括號沒有帶來塊級作用域

熟悉JavaScript的同學肯定對這點兒已經熟知了,大括號雖然能夠組織復雜的語句等,是指算是同一“塊”,with甚至提供了相近的功能,但遺憾的使JavaScript只有函數作用域,沒有塊作用域,再JavaScript中下面做法會聲明全局變量,這個小小的知識點往往引英雄競折腰

  1. 在function外使用聲明變量(無論是否使用var)
  2. 在function內不是用var 聲明變量
  3. 直接賦值于window屬性
var a=2;
function fn(){
    b=3;
    window.c=4;
}

除了這三種剩下的就是function范圍內的局部變量了,在很多JavaScript規范中都有提到,盡量提早聲明變量正是由于其沒有塊作用域

function fn(n){
    if(n>1){
        var a=n;
    }else{
        var b=n;
    }
    console.log(a);
}

這樣的代碼在很多語言中有語法錯誤,因為if和else的大括號有塊作用域,變量a、b在自己對應塊作用域中,出了塊就訪問不到了。但在JavaScript中,沒有塊作用域,所以我們在if、else內聲明的變量console.log依然能夠訪問,這確實是糟糕的設計,為了減少錯誤可能,盡量把變量聲明提前。

很多筆試題目正是針對這方面知識出題的

{a:1};
var x={a:1};
{a:1,b:2};
var y={a:1,b:2};

親自試試是不是發現很驚訝,我們分析一下

{a:1} JavaScript有傳說中的“語句優先”,也就是當大括號既可以被理解為復合語句塊也可以被理解為對象直接量的時候,JavaScript會將其理解為復合語句快。{a:1}其實就是 a: 1,想想冒號的作用是不是知道為什么返回值是1了。

var x={a:1} 當{a:1} 作為右值出現的時候,明顯就不是語句,而是直接量表達式了,所以把大括號當作對象直接量語法處理,結果是個對象。

{a:1,b:2}; 看了上面這個就簡單了,可以翻譯為:a:1,b:2 結合逗號和冒號作用,結果似乎顯而易見了,就是2嘛。然而其實報錯了,這是為什么?在逗號運算符后面必須是表達式,而標簽語句十個label statement,是條語句,所以就報錯了。

了解了這些知識我們再來試幾個題目(看答案在控制臺上,不要試圖alert)

{foo:[1,2,3]}[0];
{a:1}+2;
2+{a:1};

不知道小伙伴兒們做對了沒有,這幾個題目核心一樣,大括號雖然看起來沒什么作用,但起到了語句分隔符作用,{foo:[1,2,3]}[0]可以理解為

{foo:[1,2,3]};
[0];

所以返回值是[0],同樣{a:1}+2變為

{a:1};
+2

但是!為什么2+{a:1}就不一樣了呢?這時加法運算符導致的,加號是左結合的,{}被解析為表達式(得是表達式相加嘛),根據數據類型中知識對象{a:1}轉換為NaN

小括號

在JavaScript中小括號有幾種用法

函數聲明或調用表達式參數表

這個好理解,函數定義的時候需要用小括號將其參數包裹,用逗號隔開,調用的時候也一樣

function fn(name,age){
    //...
}
fn('Byron',24);
var f=new fn('Byron',24)

與一些關鍵字組成條件語句

我們常見的if、switch、while中的小括號就是干這個用的

if(a>0){
    //...
}
while(i<len){
    //...
}
for(var i=0;i<len;i++){
    //...
}

分組運算符
分組運算符內部只能包含表達式,可以改變運算符優先級,舍棄一些可能的語法樹,最常見的

var x=(1+2)*3;

相信不用多解釋,很多同學會認為小括號有強制表達式運算的功能,其實這時片面的理解,這只是改變了運算符優先級,生成新的語法樹后的結果。

對于簡單的json字符串轉為對象的時候,因為瀏覽器兼容性原因,不能使用JSON對象,又懶得引入json2,所以就會用eval()處理,大概寫法這樣

var jsonStr=...;
var jsonObj=eval('(' + jsonStr + ')');

很多同學會問,為什么還要加上個小括號呢?像我們上面解釋的大括號的作用,json字符串 "{a:1,b:2}" 這樣的格式會被理解為語句,也就是傳說中的label statement,語法樹是這樣的

{
a:1,
b:2
}

上面提到過逗號運算符不能在label statement后面,所以會報錯,而加上括號后由于分組運算符只能包含表達式,所以{}變成直接量語法,這樣就是我們希望的內容了。

立即調用的函數表達式

再來回頭看看我們所謂的立即執行函數,一般有兩種寫法

(function(){})();
(function(){}());
!function(){}();

搜了很多資料,終于看到了靠譜解釋,總結一下,首先我們需要搞清楚函數表達式和函數聲明區別,ECMAScript規范中定義的相當模糊:

函數聲明必須帶有標示符(Identifier)(就是大家常說的函數名稱),而函數表達式則可以省略這個標示符:

  函數聲明:

  function 函數名稱 (參數:可選){ 函數體 }

  函數表達式:

  function 函數名稱(可選)(參數:可選){ 函數體 }

其實我們常用的區分方式是根據上下文,如果function fn(){}作為右值出現(賦值表達式右邊)那么就是表達式,否則就是函數聲明。有幾種看起來不常規的方式需要我們注意

new function fn(){}; //表達式,因為在new 表達式中
(function(){}());//表達式,在分組運算符中

這樣我們就能理解第二種寫法了,就是利用分組運算符改變了語法樹。同樣第三種寫法其實是利用了一元運算符后面跟表達式的原理,我們也可以寫成

+function(){}()
-function(){}()
~function(){}()

知乎上長天之云甚至寫出了這么多

( function() {}() );
( function() {} )();
[ function() {}() ];
////////////////////////////////
~ function() {}();
! function() {}();
+ function() {}();
- function() {}();
////////////////////////////////
delete function() {}();
typeof function() {}();
void function() {}();
new function() {}();
new function() {};
/////////////////////////////////
var f = function() {}();
/////////////////////////////////
1, function() {}();
1 ^ function() {}();
1 > function() {}();

所以我們應該稱立即執行函數為立即調用的函數表達式!

中括號

相對而言中括號是個最簡單的符號了,一般有幾種語義

***數組相關

我們知道數組可以通過中括號來直接量實例化

var a=[1,2,3];

***獲取對象屬性值

這也是很常見的用法

var a=[1,2,3];
var b={name:'Byron'};
a[2];
b['name'];

看幾個有意思的小題目

[1,2,3,4,5][0..toString.length];//0.等同于0.0
'foo'.split('') + [];

最后

不總結不知道,一總結下一跳啊,幾個小小的符號竟然這么百轉回腸,總結完之后才知道自己以前不了解這些知識,死在筆試題上一點兒都不冤,準備換工作的同學也了解一下以備萬一吧。

參考

Another JavaScript quiz

我是如何理解”Another JavaScript quiz”中的題目

JavaScript 匿名函數有哪幾種執行方式?

深入理解JavaScript系列(2):揭秘命名函數表達式


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()