函數(shù)與作用域

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

函數(shù)聲明語法:function functionName(arg0,arg1,arg2){ //函數(shù)體 }
函數(shù)表達式語法:var function = function(arg0,arg1,arg2){ //函數(shù)體 }

區(qū)別:
使用function關鍵字可以聲明一個函數(shù)棍弄,它的特征是函數(shù)聲明提升疟游,執(zhí)行代碼前會先讀取函數(shù)聲明,即函數(shù)聲明不必放在調用的前面蛮原,它可以放在當前作用域任何位置另绩;函數(shù)表達式在使用前必須先賦值,所以聲明必須放在調用前面蹦漠,不然瀏覽器解析代碼時會認為函數(shù)還不存在而拋出錯誤车海,理解函數(shù)提升的關鍵就是理解函數(shù)聲明與函數(shù)表達式之間的區(qū)別。

  • 函數(shù)聲明示例
//函數(shù)聲明
function sayHi(){
  console.log('hi');
}
//函數(shù)調用
sayHi();

或者

//函數(shù)調用
sayHi();
//函數(shù)聲明
function sayHi(){
  console.log('hi');
}
  • 函數(shù)表達式示例
//函數(shù)聲明
var sayHi = function(){
  console.log('Hi!');
};
//函數(shù)調用
sayHi();

以下是錯誤做法

//函數(shù)調用
sayHi();
//函數(shù)聲明
var sayHi = function(){
  console.log('Hi!');
};
會拋出錯誤:函數(shù)還不存在

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

變量聲明前置與函數(shù)聲明前置

arguments 是什么棵红?

arguments是一個類數(shù)組對象,除了length屬性外沒有任何數(shù)組屬性嫂便,是所有函數(shù)中可用的局部變量忆绰,僅在函數(shù)內部有效。

  • 可通過arguments對象來訪問函數(shù)的參數(shù)列表翰灾,使用方括號語法訪問參數(shù)列表的每一個元素稚茅,第一個條目的索引為0,即第一項為arguments[0]咽块,第二項為arguments[1]欺税,以此類推。
  • 通過訪問arguments對象的length屬性可以確定有多少個參數(shù)傳遞給了函數(shù)亭罪。
  • arguments對象中的值可以被重寫并會自動反映到對應的命名參數(shù)歼秽,所以值與對應命名參數(shù)的值保持同步。如果只傳入了一個參數(shù)箩祥,那么為arguments[1]設置的值不會反映到命名參數(shù)中肆氓,因為arguments對象的長度由傳入的參數(shù)個數(shù)決定,而不由定義函數(shù)時的命名參數(shù)的個數(shù)決定盲泛。
  • 在嚴格模式下重寫arguments的值會導致語法錯誤,代碼不會執(zhí)行柑营。

使用場景:調用一個函數(shù)時村视,當這個函數(shù)的參數(shù)數(shù)量比它顯式聲明的參數(shù)數(shù)量更多時,就可以使用 arguments 對象奶赔。

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

  • 概念:函數(shù)重載指同一函數(shù)名對應著多個函數(shù)的實現(xiàn)杠氢。即每種實現(xiàn)對應一個函數(shù)體,這些函數(shù)名字相同绞旅,但參數(shù)類型或個數(shù)或順序不同温艇。
  • 函數(shù)重載主要是為了解決兩個問題:
    • 可變參數(shù)類型
    • 可變參數(shù)個數(shù)
    • 可變參數(shù)順序
  • 基本設計原則:當兩個函數(shù)除了參數(shù)類型和參數(shù)個數(shù)不同以外其他功能完全相同時勺爱,利用函數(shù)重載;兩個函數(shù)功能不同時不應使用重載卫旱,而應使用一個名字不同的函數(shù)绣否。

js是弱類型語言,參數(shù)不是固定的某個類型,所以在js中沒有重載跪呈,同名函數(shù)后面的會覆蓋前面的耗绿。但我們也可以實現(xiàn)重載所需要的功能。
實現(xiàn):
寫一個函數(shù)误阻,在函數(shù)體內針對不同的參數(shù)調用執(zhí)行不同的邏輯。

function printPeopleInfo(name,age,sex){
    if(name){
        console.log(name);
    }
    if(age){
        console.log(age);
    }
    if(sex){
        console.log(sex);
    }
}
printPeopleInfo("dot",23);  //dot 23
printPeopleInfo("dot","female",23);  //dot female 23
function add(){
    var num=0;
    for(var i=0;i<arguments.length;i++){
        num+=arguments[i];
    }
    console.log(num);
}
add(1);  //1
add(1,2,3);  //6

注意:始終記住函數(shù)名只是一個指向函數(shù)對象的指針儒洛,并不會與某個函數(shù)綁定狼速。

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

  • 立即執(zhí)行函數(shù)表達式:
    縮寫IIFE恼蓬,是一種利用javascript函數(shù)生成新作用域的編程方法僵芹,也叫自執(zhí)行函數(shù)。
  • 作用:
    • 令函數(shù)中聲明的變量繞過js的變量置頂聲明規(guī)則
    • 避免新的變量被解釋成全局變量或函數(shù)名占用全局變量名的情況
    • 在禁止訪問函數(shù)內變量聲明的情況下允許外部對函數(shù)的調用
  • 實現(xiàn):因js里的()里不能包含語句荷辕,所以解析器會將()里的代碼解析成function表達式并立即執(zhí)行攀痊。
// 以下都能實現(xiàn)立即執(zhí)行
(function(){ /* code */ }());
(function(){ /* code */ })();
// function前加一元運算符也可實現(xiàn)苟径,advance-task2我有提到過
!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();

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

  • 方法一:
var factorial = (function f(n){
    if (n <= 0){
        return 1;
    } else {
        return n * f(n-1);
    }
});
factorial(5);  //120
  • 方法二:
function factorial(n){
  if(n === 1) {
    return 1;
  }
  return n * factorial(n-1);
}
factorial(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, '男');
getInfo('小谷', 3);
getInfo('男'); 

輸出分別為:

name: 饑人谷
age: 2
sex: 男
["饑人谷",2,"男"]
name valley
name: 饑人谷
age: 3
sex: undefined
["小谷",3]
name valley
name: 男
age: undefined
sex: undefined
["男"]
name valley

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

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

如下代碼的輸出是险污?為什么

    console.log(a);  //undefined,因為變量a聲明提升并賦值為undefined拯腮,先讀取變量聲明
    var a = 1;
    console.log(b);  //拋出ReferenceError:b is not defined蚁飒,因為b沒有聲明

如下代碼的輸出是?為什么

    sayName('world');
    sayAge(10);
    function sayName(name){
        console.log('hello ', name);  //hello world琼懊,因為sayName函數(shù)聲明提升
    }
    var sayAge = function(age){
        console.log(age);  //拋出TypeError: sayAge is not a function,因為sayAge是函數(shù)表達式启妹,使用前必須賦值削祈,而聲明放在了調用的后面,此時函數(shù)還不存在咙崎,所以會報錯
    };

擴充——執(zhí)行環(huán)境與作用域

理解了這兩個知識點才能做以下四題

執(zhí)行環(huán)境與環(huán)境對象:

為了保證指令的順序吨拍,在運行時需要一個容器,把一系列函數(shù)伊滋、表達式队秩、語句(統(tǒng)稱為腳本)包起來馍资。而腳本之間也具有相關性,實現(xiàn)一個功能所需要的腳本鸟蟹,又往往再構成一個集合建钥。
比如一個汽車工廠,有一個子工廠專門生產(chǎn)螺釘熊经。整個汽車工廠就是一個大容器镐依,螺釘工廠就是一個小容器,其中生產(chǎn)螺帽的車間構成一個腳本集合,生產(chǎn)螺栓的車間構成一個腳本集合秋秤。
這樣的一個集合就是腳本的執(zhí)行環(huán)境脚翘,而一個腳本執(zhí)行環(huán)境的直接容器就是其環(huán)境對象来农。
上例中崇堰,車間就是執(zhí)行環(huán)境,螺釘工廠就是車間的環(huán)境對象繁莹。
可見執(zhí)行環(huán)境特幔、環(huán)境對象的概念是相對、有精度和角度的薄风。
換個角度拍嵌,螺絲工廠也是一個執(zhí)行環(huán)境横辆,汽車工廠也是螺絲工廠的環(huán)境對象。
在JS中逆粹,通常以對象作為環(huán)境對象炫惩,具體運行腳本的函數(shù)為執(zhí)行環(huán)境。
如上所言蹋绽,注意以下兩點:

  • 函數(shù)本身作為特殊的對象筋蓖,換個角度也可以充當環(huán)境對象:
function a(){};
a.color = "red";
a.getColor = function(){
    console.log(this.color);    //red
    console.log(this);   //function a(){};
};
a.getColor();
  • 由于函數(shù)的打包性,就像一個車間可以搬到不同的工廠蚣抗,其環(huán)境對象就要看具體情況而言:
function a(){console.log(this.name)};
var object = {
    name: 'Amily'
}
var name = 'Shaw';
a();    //Shaw
a.apply(object);    //Amily

執(zhí)行環(huán)境通過棧來保存翰铡,所以也叫環(huán)境棧,每當調用一個函數(shù)就向棧中推入該函數(shù)的執(zhí)行環(huán)境锭魔,函數(shù)執(zhí)行之后棧將其環(huán)境彈出迷捧,把控制權返回給之前的執(zhí)行環(huán)境,棧底是全局執(zhí)行環(huán)境笙蒙。

作用域鏈:

腳本執(zhí)行時膛堤,肯定需要使用變量,但有些變量不是在該執(zhí)行環(huán)境內定義的绿渣。此時需要一個機制燕耿,使得一個執(zhí)行環(huán)境中的腳本能拿到另一個執(zhí)行環(huán)境中的變量誉帅。這個機制必須有規(guī)矩,明確哪些變量能跨環(huán)境拿档插,哪些不能亚再。這個機制就是作用域鏈。
作用域鏈由一連串的變量構成则剃,這些變量不是雜亂無章的如捅,而是根據(jù)自身所在的執(zhí)行環(huán)境,由內層環(huán)境到外層環(huán)境己肮,按順序鏈下去的〗呀#考慮到有同級的執(zhí)行環(huán)境戈稿,數(shù)據(jù)結構上更像一棵樹鞍盗。
內部環(huán)境可通過作用域鏈訪問所有外部環(huán)境跳昼,但外部環(huán)境不能訪問內部環(huán)境中的任何變量與函數(shù),同級執(zhí)行環(huán)境間沒法直接訪問對方的變量敷存,除非 return 到下一鏈中堪伍,例如:

var a = function(){
    var name = 'Amily';
}
var b = function(){
    alert(name);    //undefined
}

通過try-catch語句和with語句可在作用域鏈前端臨時添加變量對象,此時涮俄,同名變量以給定的變量對象中的變量優(yōu)先尸闸。

注意:簡單理解棧和作用域鏈吮廉,棧就像一個桶,window是最先推到棧底的宙址,接著推入次外層變量對象踪旷,一直到最內層變量對象,即最內層在上舀患,從作用域的角度來看气破,查找作用域就是在棧里從上往下查找的過程聊浅,全局執(zhí)行環(huán)境的變量對象始終都是作用域鏈中的最后一個對象。

var color = "blue";
function changeColor(){
        var anotherColor = "red";
        function swapColors(){
                var tempColor = anotherColor;
                anotherColor = color;
                color = tempColor;
                // 這里可以訪問color、anotherColor低匙、tempColor
        }
        // 這里可以訪問color旷痕、anotherColor
}
changeColor();
// 這里只能訪問color
變量作用域

變量作用域相對于某個具體變量而言,指能夠通過作用域鏈拿到該變量的所有執(zhí)行環(huán)境顽冶。

垃圾收集

JS有自動垃圾收集機制欺抗,分別是標記清除和引用計數(shù)。
前者通過執(zhí)行環(huán)境來標記强重,后者通過地址的引用次數(shù)來標記,兩者都是在無法訪問后间景,被作為垃圾清除佃声。
在大型應用中,通常通過將無用的變量設置為 null 來進行手動垃圾清除倘要。

明確了以上概念圾亏,再來看以下四題會容易很多~

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

var x = 10;
bar() ;
function foo() {
  console.log(x);
}
function bar(){
  var x = 30;
  foo();
}
  • 輸出結果:10 undefined
  • 查找過程:不會寫查找過程偽代碼但能理解。
    • 函數(shù)最終結果與在哪執(zhí)行無關封拧,只與初始所在環(huán)境有關志鹃,foo函數(shù)未執(zhí)行時要得到x的值,首先在自身作用域中查找有無x變量哮缺,沒有找到即向外層作用域弄跌,本例中為全局作用域中查找,注意同級函數(shù)間并不能訪問對方的作用域這一點尝苇。繼續(xù)剛才的步驟铛只,foo在全局中找到了x,所以即使foo函數(shù)身處bar函數(shù)中糠溜,取得的也是全局作用域中x的值淳玩,也就是10.
    • undefined的出現(xiàn)可以理解為一個來自控制臺的調戲~
      細心的話會發(fā)現(xiàn),寫一個函數(shù)return的時候非竿,控制臺會出現(xiàn)最后一行變量或表達式的值蜕着,return表示不背鍋,一切都是console.log自作主張打印的結果红柱,忽略它就好承匣,如果沒有return一個函數(shù),直接console.log一下锤悄,控制臺也會多打一個undefined韧骗,因為函數(shù)沒有return就會返回一個undefined,而這個undefined就會被控制臺打印出來零聚,注意這是個坑袍暴,所以以下都不寫出undefined些侍。

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

var x = 10;
bar() ;
function bar(){
  var x = 30;
  function foo(){
    console.log(x) ;
  }
  foo();
}   
  • 輸出結果:30
  • 查找過程:調用foo函數(shù)時,foo函數(shù)先在自身作用域中查找變量x政模,沒有找到于是依次向外層查找岗宣,在bar函數(shù)中找到了x值為30,不再繼續(xù)向外查找淋样,控制臺打印30耗式。

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

var x = 10;
bar() 
function bar(){
  var x = 30;
  (function (){
    console.log(x)
  })()
}
  • 輸出結果:30
  • 查找過程:函數(shù)bar里存在一個立即執(zhí)行函數(shù),立即執(zhí)行函數(shù)在自身作用域沒找到x變量于是繼續(xù)向外層查找习蓬,找到x=30纽什,打印30.

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

var a = 1;

function fn(){
  console.log(a) //(1)
  var a = 5
  console.log(a) //(2)
  a++
  var a
  fn3() //(3)
  fn2() //(4)
  console.log(a) //(5)

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

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

fn()
console.log(a) //(6)
  • 輸出結果:從上到下依次是undefined躲叼、5、1企巢、6枫慷、20、200
  • 查找過程:
    • 調用函數(shù)fn浪规,進入fn的執(zhí)行環(huán)境或听,var a變量聲明前置并初始化a為undefined,所以(1)處打印undefined笋婿;
    • 給a賦值5誉裆,所以(2)處打印5;
    • 繼續(xù)執(zhí)行a++缸濒,得到a為6留到下次使用
    • 繼續(xù)執(zhí)行函數(shù)fn3()足丢,進入fn3作用域,fn3內沒有聲明變量a于是向外層查找庇配,而fn(3)所處作用域為全局作用域斩跌,無論它在哪執(zhí)行,永遠都是打印的全局變量1捞慌,所以(3)處打印1耀鸦,繼續(xù)向下讀取a=200,fn3不存在函數(shù)嵌套的情況啸澡,里面的a也未被聲明袖订,所以a是一個全局變量,全局中聲明了變量a嗅虏,于是替換全局變量a的值1為200洛姑,所以(6)處打印200;
    • 接著調用fn2函數(shù)旋恼,進入fn2執(zhí)行環(huán)境吏口,沒有找到變量a奄容,于是向外層函數(shù)fn中查找,得到了上上一步中的值6产徊,所以(4)處打印6昂勒,繼續(xù)向下讀取a=20,a未被聲明于是會逐級向上查找變量a舟铜,在父函數(shù)fn中找到了變量a并賦值20戈盈,所以(5)處打印20。

分析:fn3改變了全局變量a的值谆刨,fn2改變了fn中局部變量a的值塘娶。

注意!H病刁岸!

mdn
阮一峰

mdn和阮一峰的教程中指出,所有未聲明的變量都是全局的她我。
這句話不太準確虹曙,因為沒有考慮到函數(shù)嵌套的情況

在未聲明變量的情況下番舆,子函數(shù)是可以引用父函數(shù)內的變量的酝碳,它從子函數(shù)向父函數(shù)一層層向上查找,一直找到全局作用域恨狈,找到了變量疏哗,就使用那個作用域所在的變量,如果一直沒找到禾怠,就會在全局創(chuàng)建一個(非嚴格模式下)返奉。

你不知道的javascript(上)

參考資料:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市刃宵,隨后出現(xiàn)的幾起案子衡瓶,更是在濱河造成了極大的恐慌,老刑警劉巖牲证,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哮针,死亡現(xiàn)場離奇詭異,居然都是意外死亡坦袍,警方通過查閱死者的電腦和手機十厢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捂齐,“玉大人蛮放,你說我怎么就攤上這事〉煲耍” “怎么了包颁?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵瞻想,是天一觀的道長。 經(jīng)常有香客問我娩嚼,道長蘑险,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任岳悟,我火速辦了婚禮佃迄,結果婚禮上,老公的妹妹穿的比我還像新娘贵少。我一直安慰自己呵俏,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布滔灶。 她就那樣靜靜地躺著普碎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪录平。 梳的紋絲不亂的頭發(fā)上随常,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音萄涯,去河邊找鬼。 笑死唆鸡,一個胖子當著我的面吹牛涝影,可吹牛的內容都是我干的。 我是一名探鬼主播争占,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼燃逻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了臂痕?” 一聲冷哼從身側響起伯襟,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎握童,沒想到半個月后姆怪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡澡绩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年稽揭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肥卡。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡溪掀,死狀恐怖,靈堂內的尸體忽然破棺而出步鉴,到底是詐尸還是另有隱情揪胃,我是刑警寧澤璃哟,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站喊递,受9級特大地震影響随闪,放射性物質發(fā)生泄漏。R本人自食惡果不足惜册舞,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一蕴掏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧调鲸,春花似錦盛杰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至于微,卻和暖如春逗嫡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背株依。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工驱证, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人恋腕。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓抹锄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親荠藤。 傳聞我的和親對象是個殘疾皇子伙单,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容

  • 函數(shù)聲明和函數(shù)表達式有什么區(qū)別 JavaScript 中需要創(chuàng)建函數(shù)的話,有兩種方法:函數(shù)聲明哈肖、函數(shù)表達式吻育,各自寫...
    蕭雪圣閱讀 956評論 2 2
  • 1.函數(shù)聲明和函數(shù)表達式有什么區(qū)別 函數(shù)就是一段可以反復調用的代碼塊。函數(shù)還能接受輸入的參數(shù)淤井,不同的參數(shù)會返回不同...
    徐國軍_plus閱讀 475評論 0 0
  • 1.函數(shù)聲明和函數(shù)表達式有什么區(qū)別 function命令聲明的代碼區(qū)塊布疼,就是一個函數(shù)。function命令后面是函...
    饑人谷_Leon閱讀 283評論 0 0
  • 1.函數(shù)聲明和函數(shù)表達式有什么區(qū)別 函數(shù)聲明 代碼執(zhí)行時函數(shù)聲明會被提升到最前執(zhí)行庄吼,所以函數(shù)的調用與函數(shù)聲明的順序...
    Feiyu_有貓病閱讀 379評論 0 0
  • 【韓喜文2017.09.16星期六】 好展館讓天下沒有賣不出去的產(chǎn)品 好展館讓天下沒有不能傳承的文化 日精...
    韓喜文閱讀 103評論 0 0