JS函數(shù)與作用域

函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別

  • JavaScript 中需要創(chuàng)建函數(shù)的話,有兩種方法:函數(shù)聲明、函數(shù)表達(dá)式,各自寫法如下:
    // 方法一:函數(shù)聲明
    function foo() {}
    
    // 方法二:函數(shù)表達(dá)式
    var foo = function () {};
    
  • 另外還有一種自執(zhí)行函數(shù)表達(dá)式,主要用于創(chuàng)建一個新的作用域,在此作用域內(nèi)聲明的變量不會和其它作用域內(nèi)的變量沖突或混淆,大多是以匿名函數(shù)方式存在,且立即自動執(zhí)行:
(function () {
    // var x = ...
})();

此種自執(zhí)行函數(shù)表達(dá)式歸類于以上兩種方法的第二種,也算是函數(shù)表達(dá)式。

方法一和方法二都創(chuàng)建了一個函數(shù),且命名為 foo,但是二者還是有區(qū)別的。JavaScript 解釋器中存在一種變量聲明被 提升(hoisting) 的機(jī)制,也就 是說變量(函數(shù))的聲明會被提升到作用域的最前面,即使寫代碼的時候是寫在最后面,也還是會被 提升 至最前面。

** 例如以下代碼段:**

alert(foo); // function foo() {}
alert(bar); // undefined
function foo() {}
var bar = function bar_fn() {};
alert(foo); // function foo() {}
alert(bar); // function bar_fn() {}

輸出結(jié)果分別是function foo() {}、undefinedfunction foo() {}function bar_fn() {}。

可以看到 foo 的聲明是寫在 alert 之后,仍然可以被正確調(diào)用,因?yàn)?JavaScript 解釋器會將其提升到 alert 前面,而以函數(shù)表達(dá)式創(chuàng)建的函數(shù) bar則不享受此待遇。

那么bar 究竟有沒有被提升呢,其實(shí)用 var聲明的變量都會被提升,只不過是被先賦值為 undefined 罷了,所以第二個 alert 彈出了 undefined。

所以,JavaScript 引擎執(zhí)行以上代碼的順序可能是這樣的:

  1. 創(chuàng)建變量 foobar,并將它們都賦值為 undefined。
  2. 創(chuàng)建函數(shù) foo 的函數(shù)體,并將其賦值給變量 foo。
  3. 執(zhí)行前面的兩個 alert
  4. 創(chuàng)建函數(shù) bar_fn,并將其賦值給 bar。
  5. 執(zhí)行后面的兩個 alert

注:

嚴(yán)格地說,再 JavaScript 中創(chuàng)建函數(shù)的話,還有另外一種方法,稱為“函數(shù)構(gòu)造法”:

var foo = Function('alert("hi!");');
var foo = new Function('alert("hi!");'); // 等同于上面一行

new function是可以傳參數(shù)的。
比如

var sum= new  Function("a", "b", "c", "return a+b+c");
sum(1,2,3)//6

什么是變量的聲明前置?什么是函數(shù)的聲明前置

先來看一個例子

fn1();   // 輸出:我是函數(shù)聲明
fn2();   //  報錯
console.log(a); // 輸出:undefined

function fn1() {
    console.log( "我是函數(shù)聲明" );
}

var fn2 = function() {
    console.log( "我是函數(shù)表達(dá)式" );
}

var a = 20 

因?yàn)镴S對使函數(shù)聲明前置,所以fn1()在函數(shù)聲明前執(zhí)行仍然可以得到正確答案,而函數(shù)表達(dá)式fn2則報錯,
為什么?我們先要弄清楚:

JS解釋器如何找到我們定義的函數(shù)和變量?
通過 變量對象(Variable Object, VO)來獲取。VO是一個抽象概念的“對象”,它用于存儲執(zhí)行上下文中的:1. 變量;2. 聲明;3. 函數(shù)參數(shù)

函數(shù)的VO分為兩個階段——變量初始化代碼執(zhí)行。在變量初始化階段,VO按照如下順序填充:
1. 函數(shù)參數(shù)(若未傳入,初始化該參數(shù)值為undefined)
2. 函數(shù)聲明(若發(fā)生命名沖突,會覆蓋)
3. 變量聲明(初始化變量值為undefined,若發(fā)生命名沖突,則忽略)

注意:函數(shù)表達(dá)式與變量初始化無關(guān)。

在變量初始化階段,需要先對arguments變量進(jìn)行初始化(激活對象,AO),再把函數(shù)體內(nèi)的變量聲明與函數(shù)聲明存儲在AO內(nèi),VO(functionContext) === AO。

根據(jù) VO數(shù)據(jù) 填充順序看以下例子

var x = 10;
bar();

function foo() {
    console.log(x);
}

function bar() {
   var x = 30;
    foo();  //得到什么? 
}

/*
1. globalContext = {
    AO: {
          x: 10
          foo: function
          bar: function 
    }
    Scope: null
}

// 聲明 foo 時 得到下面
foo.[ [scope] ] = globalContext.AO
// 聲明 bar 時 得到下面
bar.[ [scope] ] = globalContext.AO

// 當(dāng)調(diào)用bar(),進(jìn)入bar 的執(zhí)行上下文

2. barContext = {
    AO: {
         x: 30
    }
    Scope = bar.[ [scope] ]    // globalContext.AO
}

//當(dāng)調(diào)用 foo() 時,先從 foo 執(zhí)行上下文中的 AO里找,找不到再從 foo 的 [[scope]]里,找到后即調(diào)用
因?yàn)樵趂oo執(zhí)行上下文中找不到x,所以直接進(jìn)入foo的scope里面找,即globalContext.AO

3. fooContext =  {
    AO: {
          
    }
    Scope = foo.[ [scope] ]  // globalContext.AO
}

*/

globalContext = {
    AO: {
          x: 10
          foo: function
          bar: function 
    }
    Scope: null

所以console.log(x) 是 10

arguments 是什么

  • arguments 是一個類似數(shù)組的對象, 對應(yīng)于傳遞給函數(shù)的參數(shù)。
  • arguments.length表示的是實(shí)際上向函數(shù)傳入了多少個參數(shù)。
  • arguments對象是所有函數(shù)中可用的局部變量。你可以使用arguments對象在函數(shù)中引用函數(shù)的參數(shù)。此對象包含傳遞給函數(shù)的每個參數(shù)的條目,第一個條目的索引從0開始。例如,如果一個函數(shù)傳遞了三個參數(shù),你可以參考它們?nèi)缦拢?/li>

arguments[0]
arguments[1]
arguments[2]

  • 在函數(shù)內(nèi)部,你可以使用arguments對象獲取到該函數(shù)的所有傳入?yún)?shù)
Paste_Image.png

函數(shù)的"重載"怎樣實(shí)現(xiàn)

首先想聲明下,什么是函數(shù)重載,javascript中不存在函數(shù)重載的概念,(其實(shí)是個偽命題)但一個函數(shù)通過不同參數(shù)列表(arguments)來實(shí)現(xiàn)各個功能,我們都叫函數(shù)重載,這就是牛逼閃閃的 JavaScript 函數(shù)重載

/*
    * 傳統(tǒng)方法一
    * */
    var overrideMethod = function () {
        switch (arguments.length) {
            case 0 :
                console.log("class no body");
                break;
            case 1 :
                console.log("class has one student");
                break;
            default :
                console.log("class has more students");
        }
    }
    overrideMethod("test","blue"); //class has more students
    overrideMethod("test-blue"); //class has one student
    overrideMethod(); //class no body
    /*
    * 我們希望對象Company擁有一個find方法,當(dāng)不傳任何參數(shù)時,
    * 就會把Company.names里面的所有元素返回來;
    * 因?yàn)閒ind方法是根據(jù)參數(shù)的個數(shù)不同而執(zhí)行不同的操作的,
    * 所以,需要有一個overrideCompanyFind方法,能夠如下的為Company添加find的重載:
    * */
    var company = {
        names : ["baron" , "Andy" ,"Lily" , "Blures"],
        find : function () {
            return this.names.length
        }
    };
    var overrideCompanyFind = function (object , method , cb) {
        var oldMethod = object[method];
        //給object 重新賦予新的方法
        object[method] = function () {
            if (cb.length == arguments.length) {
               return cb.apply(this,arguments)
            }else if(typeof oldMethod== 'function'){
               return oldMethod.apply(this,arguments)
            }
        };
    };
    overrideCompanyFind(company,'find',function (name , name2) {
        return this.names
    });
    overrideCompanyFind(company,'find',function (name) {
        return name + '的位置是' + this.names.indexOf(name) + '排'
    });
    console.log(company.find()); //4
    console.log(company.find('Lily')); // Lily的位置是2排
    console.log(company.find('Lily','baron')); //["baron", "Andy", "Lily", "Blures"]

立即執(zhí)行函數(shù)表達(dá)式是什么?有什么作用

**立即調(diào)用函數(shù)表達(dá)式可以令其函數(shù)中聲明的變量繞過JavaScript的變量置頂聲明規(guī)則,還可以避免新的變量被解釋成全域變量或函數(shù)名占用全域變量名的情況。與此同時它能在禁止訪問函數(shù)內(nèi)聲明變量的情況下允許外部對函數(shù)的調(diào)用。有時,這種編程方法也被叫做“自執(zhí)行(匿名)函數(shù)”,但“立即調(diào)用函數(shù)表達(dá)式”是語義上最準(zhǔn)確的術(shù)語。 **

// 下面2個括弧()都會立即執(zhí)行

(function () { /* code */ } ()); // 推薦使用這個
(function () { /* code */ })(); // 但是這個也是可以用的

// 由于括弧()和JS的&&,異或,逗號等操作符是在函數(shù)表達(dá)式和函數(shù)聲明上消除歧義的
// 所以一旦解析器知道其中一個已經(jīng)是表達(dá)式了,其它的也都默認(rèn)為表達(dá)式了
// 不過,請注意下一章節(jié)的內(nèi)容解釋

var i = function () { return 10; } ();
true && function () { /* code */ } ();
0, function () { /* code */ } ();

// 如果你不在意返回值,或者不怕難以閱讀
// 你甚至可以在function前面加一元操作符號

!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();

// 還有一個情況,使用new關(guān)鍵字,也可以用,但我不確定它的效率
// http://twitter.com/kuvos/status/18209252090847232

new function () { /* code */ }
new function () { /* code */ } () // 如果需要傳遞參數(shù),只需要加上括弧()

有什么用?

javascript中沒用私有作用域的概念,如果在多人開發(fā)的項目上,你在全局或局部作用域中聲明了一些變量,可能會被其他人不小心用同名的變量給覆蓋掉,根據(jù)javascript函數(shù)作用域鏈的特性,可以使用這種技術(shù)可以模仿一個私有作用域,用匿名函數(shù)作為一個“容器”,“容器”內(nèi)部可以訪問外部的變量,而外部環(huán)境不能訪問“容器”內(nèi)部的變量,所以( function(){…} )()內(nèi)部定義的變量不會和外部的變量發(fā)生沖突,俗稱“匿名包裹器”或“命名空間”。


求n!,用遞歸來實(shí)現(xiàn)

function fn(n){
    if(n===1){
         return 1;
    }
   alert( n * fn(n-1));
}
fn(5);

以下代碼輸出什么?

function getInfo(name, age, sex){
        console.log('name:',name);
        console.log('age:', age);
        console.log('sex:', sex);
        console.log(arguments);
        arguments[0] = 'valley';
        console.log('name', name);
    }

getInfo('饑人谷', 2, '男');  //  name: 饑人谷    age:2    sex:男    ["饑人谷", 2, "男"]    name valley
getInfo('小谷', 3);   // name: 小谷    age:3    sex:undefined    ["小谷", 3]    name valley
getInfo('男');   // name: 男    age:undefined    sex:undefined    ["男"]    name valley

寫一個函數(shù),返回參數(shù)的平方和?

   function sumOfSquares(){
        var sum = 0;
       for (var i=0; i<arguments.length; i++) {
              sum +=  arguments[i] * arguments[i];
       }
       console.log(sum);
   }
   var result = sumOfSquares(2,3,4)
   var result2 = sumOfSquares(1,3)
   console.log(result)  //29
   console.log(result)  //10

如下代碼的輸出?為什么

    console.log(a);    //  undefined
    var a = 1;       
    console.log(b);     // 報錯

JS變量聲明會被 提升 所以console.log(a) var a 會提前,但是還沒被賦值,所以輸出undefined
console.log(b) 因?yàn)樽兞?code>b沒有被聲明,所以報錯

如下代碼的輸出?為什么

    sayName('world');     // hello world
    sayAge(10);          // 報錯
    function sayName(name){
        console.log('hello ', name);
    }
    var sayAge = function(age){
        console.log(age);
    };

sayName('world');根據(jù)函數(shù)聲明會被提升,所以輸出hello world。sayAge(10),報錯是因?yàn)?code>var sayAge = function(age){}是一個函數(shù)表達(dá)式,聲明var sayAge時,還不是一個函數(shù), sayAge(10) 調(diào)用在聲明前,所以報錯。

如下代碼輸出什么? 寫出作用域鏈查找過程偽代碼

var x = 10
bar() 
function foo() {
  console.log(x)
}
function bar(){
  var x = 30
  foo()
}           //  輸出 10

1. globalContext = {
    AO: {
       x: 10
       foo: funciton
       bar: funciton
    }
    Scope: null
}

//聲明 foo() 時 得到
foo.[ [scope] ] = globalContext.AO
//聲明 bar() 時 得到
bar.[ [scope] ] = globalContext.AO
// 當(dāng)調(diào)用bar(),進(jìn)入bar 的執(zhí)行上下文
2. barContext = {
    AO: {
      x: 30
    }
   Scope: bar.[ [scope] ]
}
//當(dāng)調(diào)用 foo() 時,先從 bar 執(zhí)行上下文中的 AO里找,找不到再從 bar 的 [[scope]]里,找到后即調(diào)用
  fooContext = {
    AO: {
       
    } 
   Scope: foo.[ [scope] ]
}

所以console.log(x) 得到10

如下代碼輸出什么? 寫出作用域鏈查找過程偽代碼

var x = 10;
bar() 
function bar(){
  var x = 30;
  function foo(){
    console.log(x) 
  }
  foo();
}

以下代碼輸出什么? 寫出作用域鏈的查找過程偽代碼

var x = 10;
bar() 
function bar(){
  var x = 30;
  (function (){
    console.log(x)
  })()
}

1. globalContext = {
   AO: {
      x: 10
      bar: funciton
   }
   Scope: null
}

bar.[ [scope] ] = globalContext.AO

2. barContext = {
    AO: {
      x: 30
    }
   Scope: bar.[ [scope] ]
 }

聲明x,x賦值10 > 執(zhí)行bar() > 聲明x,x賦值30 > 立即執(zhí)行匿名函數(shù) > console.log(x) > 到bar上下文AO中找到x = 30 ,所以console.log(x) 得到30
 
// 



以下代碼輸出什么? 寫出作用域鏈查找過程偽代碼

var a = 1;

function fn(){
  console.log(a)
  var a = 5
  console.log(a)
  a++
  var a
  fn3()
  fn2()
  console.log(a)

  function fn2(){
    console.log(a)
    a = 20
  }
}

function fn3(){
  console.log(a)
  a = 200
}

fn()
console.log(a)

1. globalContext = {
    AO: {
       a: 1
       fn: funciton
       fn3: funciton
    }
   Scope:null
}
// fn.[ [scope] ] = globalContext.AO   
// fn3.[ [scope] ] = globalContext.AO

2. fnContext = {
    AO: {
       a: undefine
       fn3: funciton
       fn2: funciton
    }
   Scope: fn.[ [scope] ]    // fn.[ [scope] ] = globalContext,AO
}
}

fn2Context {
     AO: {
        
     } 
    Scope: fn2.[ [scope] ]    // fn2.[ [scope] ] = fnContext,AO
}

fn3Context {
     AO: {
     }
    Scope: fn3.[ [scope] ]    // fn3.[ [scope] ] = globalContext,AO
}

執(zhí)行fn() > console.log(a) 此時 a = undefined;  > console.log(a) 此時 a = 5 > a++ 此時 a = 6 > 執(zhí)行fn3() , console.log(a) 找到globalContext.AO里 a = 1;  a = 200 此時globalContext.AO里 a = 200   >  執(zhí)行fn2() , console.log(a) 找到fnContext.AO里 a = 6, 然后a = 20 此時 fnContext.AO里 a = 20;
> 執(zhí)行 console.log(a) 此時 a = 20    >   最后 執(zhí)行 console.log(a)  得到 a = 200

結(jié)果  undefined > 5 > 1 > 6 > 20 > 200
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內(nèi)容

  • 函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別? 函數(shù)聲明和函數(shù)表達(dá)式是EMACScript規(guī)定的兩種不同的聲明函數(shù)的方法。1.函...
    LeeoZz閱讀 353評論 0 1
  • JavaScript中的函數(shù)運(yùn)行在它們被定義的作用域里,而不是它們被執(zhí)行的作用域里。 函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)...
    畢子歌閱讀 406評論 0 0
  • 1.函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別 函數(shù)聲明可以看作是函數(shù)的初始化,我們將給函數(shù)傳參并建立函數(shù)體的表達(dá)式,當(dāng)我門建...
    高進(jìn)哥哥閱讀 221評論 0 0
  • 1.函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別 函數(shù)聲明 代碼執(zhí)行時函數(shù)聲明會被提升到最前執(zhí)行,所以函數(shù)的調(diào)用與函數(shù)聲明的順序...
    Feiyu_有貓病閱讀 397評論 0 0
  • 去感覺而不是想象,去描述而不是評價。 很多時候,我們所看到的事情,只能看到我們想看到的部分,因?yàn)樵谟^察時加入了太多...
    無間行者lee閱讀 280評論 0 1