一顷编、函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別 (*)
主要區(qū)別函數(shù)聲明會有聲明提升堕义,而函數(shù)表達(dá)式的規(guī)則跟變量一樣上陕。
例:
源碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<script>
foo1(4);
foo2(3);
var foo2 = function(a){console.log(a)}
function foo1(i){console.log(i)}
</script>
</body>
</html>
相當(dāng)于:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<script>
var foo2;//----變量提升
function foo1(i){console.log(i)};//---函數(shù)聲明提升
foo1(4);//----可執(zhí)行該函數(shù)
foo2(3);//----會報錯扬跋,因為是從上到下的順序的
foo2 = function(a){console.log(a)}
</script>
</body>
</html>
二阶捆、什么是變量的聲明前置?什么是函數(shù)的聲明前置 (**)
具體例子可以看第一道題已經(jīng)po出demo钦听;
JavaScript引擎的工作方式是洒试,先解析代碼,獲取所有被聲明的變量朴上,然后再一行一行地運(yùn)行垒棋。這造成的結(jié)果,就是所有的變量的聲明語句痪宰,都會被提升到代碼的頭部叼架,這就叫做變量提升(hoisting)畔裕。
函數(shù)的聲明前置就是把整個函數(shù)提升到當(dāng)前作用域的最前面(位于前置的變量聲明后面)。
三乖订、arguments 是什么 (*)
arguments 是一個類數(shù)組對象扮饶。代表傳給一個function的參數(shù)列表。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<script>
function demo() {
for(var i=0; i<arguments.length; i++)
console.log(arguments[i]);
console.log(arguments.length);
}
demo("a",2,3,4,5);
</script>
</body>
</html>
四乍构、函數(shù)的重載怎樣實現(xiàn) (**)
什么是重載函數(shù)允許在同一范圍中聲明幾個功能類似的同名函數(shù)甜无,但是這些同名函數(shù)的形式參數(shù)必須不同,這就是重載函數(shù)哥遮。
重載函數(shù)的作用重載函數(shù)常用來實現(xiàn)功能類似而所處理的數(shù)據(jù)類型不同的問題岂丘。
JS的函數(shù)重載的實現(xiàn)在面向?qū)ο笳Z言里重載是很重要的一個特性,而JavaScript這個自稱面向?qū)ο蟮恼Z言是沒有直接提供重載眠饮。那么JavaScript是怎么實現(xiàn)(準(zhǔn)確地講應(yīng)該叫“模擬”)的呢奥帘?
利用arguments實現(xiàn)重載arguments是JavaScript里的一個內(nèi)置對象,包含了調(diào)用者傳遞的實際參數(shù)君仆,而調(diào)用時只它和數(shù)組一樣有個length屬性翩概;我們暫且把它當(dāng)“數(shù)組”來理解吧,我們根據(jù)該數(shù)組的長度以及其元素的類型來選擇不同的實現(xiàn)返咱,從而模擬了重載钥庇。
在JavaScript中沒有函數(shù)重載的概念,函數(shù)通過名字確定唯一性咖摹,參數(shù)不同也被認(rèn)為是相同的函數(shù)评姨,后面的覆蓋前面的。所以我們這里通過arguments實現(xiàn)重載:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<script>
function demo() {
for(var i=0; i<arguments.length; i++)
console.log(arguments[i]);
}
demo(2,3,4,5);
demo("a","b");
</script>
</body>
</html>
五萤晴、立即執(zhí)行函數(shù)表達(dá)式是什么吐句?有什么作用 (***)
- 立即執(zhí)行函數(shù)表達(dá)式(IIFE——Immediately Invoked Function Expression),是將函數(shù)的定義放在一個圓括號里店读,讓JavaScript引擎將其理解為一個表達(dá)式嗦枢,再在函數(shù)的定義后面加一個(),以達(dá)到定義函數(shù)后立即調(diào)用該函數(shù)的效果屯断。下面兩種形式在功能上是一致的:
(function(){ /* code */ }()); //立即執(zhí)行函數(shù)表達(dá)式
(function(){ /* code */ })(); //立即執(zhí)行函數(shù)表達(dá)式
- 立即執(zhí)行函數(shù)表達(dá)式的作用
- 可以創(chuàng)建匿名函數(shù)文虏,避免污染全局變量
- 防止其他js文件出現(xiàn)與本文件的函數(shù)名重名情況,那么函數(shù)內(nèi)部定義的 同一個變量a有可能被污染;
- 形成一個單獨的作用域殖演,可以封裝一些外部無法讀取的私有變量
// 寫法一
var tmp = newData;
processData(tmp);
storeData(tmp);//
寫法二(function (){
var tmp = newData;
processData(tmp);
storeData(tmp);
}());
上面代碼中氧秘,寫法二比寫法一更好,因為完全避免了污染全局變量
六趴久、什么是函數(shù)的作用域鏈 (****)
解釋:
當(dāng) Javascript 代碼需要查詢一個變量 x 的時候丸相,會有一個被稱為「變量名解析」的過程。它會首先檢查作用域鏈的第一個對象彼棍,如果這個對象包含名為 x 的屬性灭忠,那么就采用這個屬性的值膳算,否則,會繼續(xù)檢查第二個對象更舞,依此類推畦幢。當(dāng)檢查到最后一個對象的時候仍然沒有相應(yīng)的屬性,則這個變量會被認(rèn)定為是「未定義」的缆蝉。
舉例:
<script type="text/javascript">
var rain = 1;
function rainman(){
var man = 2;
function inner(){
var innerVar = 4;
alert(rain);
}
inner(); //調(diào)用inner函數(shù)
}
rainman(); //調(diào)用rainman函數(shù)
</script>
觀察alert(rain);這句代碼宇葱。JavaScript首先在inner函數(shù)中查找是否定義了變量rain,如果定義了則使用inner函數(shù)中的rain變量刊头;如果inner函數(shù)中沒有定義rain變量黍瞧,JavaScript則會繼續(xù)在rainman函數(shù)中查找是否定義了rain變量,在這段代碼中rainman函數(shù)體內(nèi)沒有定義rain變量原杂,則JavaScript引擎會繼續(xù)向上(全局對象)查找是否定義了rain印颤;在全局對象中我們定義了rain = 1,因此最終結(jié)果會彈出'1'穿肄。
上面的代碼涉及到了三個作用域鏈對象年局,依次是:inner、rainman咸产、window矢否。
七、代碼題
1脑溢、以下代碼輸出什么僵朗? (難度**)
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('hunger', 28, '男');
getInfo('hunger', 28);
getInfo('男');
2、寫一個函數(shù)屑彻,返回參數(shù)的平方和验庙?如 (難度**)
利用arguments類數(shù)組對象實現(xiàn);
function sumOfSquares(){
var result=0;
for(var i=0; i<arguments.length; i++){
result += arguments[i]*arguments[i];
}
console.log(result);
}
sumOfSquares(2,3,4); // 29
sumOfSquares(1,3); // 10
3社牲、如下代碼的輸出粪薛?為什么 (難度*)
源碼:
console.log(a);
var a = 1;
console.log(b);
可看做:
var a; //--------變量提升
console.log(a);//----log.undefined
a = 1;//---------賦值a
console.log(b);//------log.報錯
4、如下代碼的輸出搏恤?為什么 (難度*)
源碼:
sayName('world');
sayAge(10);
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};
可看做:
var sayAge;//-------變量聲明提升
function sayName(name){---------函數(shù)聲明提升
console.log('hello ', name);//----log. "hello,world"
}
sayName('world');//---調(diào)用函數(shù)
sayAge(10);
sayAge = function(age){
console.log(age);//--------log.報錯汗菜,無此函數(shù);因為sayAge(10)無法調(diào)用挑社;
};
5、 如下代碼的輸出巡揍?為什么 (難度**)
源碼:
function fn(){}
var fn = 3;
console.log(fn);
可看做:
var fn;
function fn(){}
fn = 3;
console.log(fn);//-------log.3
命名沖突:函數(shù)的聲明會覆蓋變量的聲明痛阻,變量的賦值會覆蓋函數(shù)的賦值;
6腮敌、 如下代碼的輸出阱当?為什么 (難度***)
function fn(fn2){
console.log(fn2);
var fn2 = 3;
console.log(fn2);
console.log(fn);
function fn2(){
console.log('fnnn2');
}
}
fn(10)
可看做:
function fn(fn2){
var fn2 = 10;//---傳參數(shù)給fn2賦值10
var fn2;//----變量聲明提升
function fn2(){ //----函數(shù)提升,覆蓋變量fn2變?yōu)楹瘮?shù)
console.log('fnnn2');//--無俏扩,沒有調(diào)用
}
console.log(fn2);//-----log.fn2函數(shù)
fn2 = 3;
console.log(fn2);//-----3
console.log(fn);//------log.fn函數(shù)
}
fn(10)
console.log('fnnn2'):因為沒有調(diào)用fn2函數(shù)所有沒有執(zhí)行;
第一個console.log(fn2);顯示是fn2函數(shù)弊添,由于傳進(jìn)去的參數(shù)fn2=10被聲明為變量后又被函數(shù)fn2給覆蓋了录淡,故log出的就是fn2函數(shù);
第一個console.log(fn2);顯示的是fn函數(shù)油坝,首先會檢查作用域鏈的第一個調(diào)用對象嫉戚,沒有找到fn該屬性則去找第二個對象即全局對象,找到fn的屬性是一個函數(shù)澈圈,故log.出的是fn()函數(shù)彬檀;
7、如下代碼的輸出瞬女?為什么 (難度***)
源碼:
var fn = 1;
function fn(fn){
console.log(fn);
}
console.log(fn(fn));
可看做:
var fn;
function fn(fn){
console.log(fn);
}
fn = 1;
console.log(fn(fn)); //--------報錯
根據(jù)命名沖突窍帝,執(zhí)行console.log(fn(fn))時,fn為變量類型诽偷,值為1坤学,所以報錯,fn不為函數(shù)报慕。
8深浮、如下代碼的輸出?為什么 (難度**)
源碼:
//作用域
console.log(j);
console.log(i);
for(var i=0; i<10; i++){
var j = 100;
}
console.log(i);
console.log(j);
可看做:
var i;
var j;
console.log(j);//---log.undefined
console.log(i);//---log.undefined
for(i=0; i<10; i++){
j = 100;
}
console.log(i);//----log.10
console.log(j);//----log.100
9卖子、如下代碼的輸出略号?為什么 (難度****)
源碼:
fn();
var i = 10;
var fn = 20;
console.log(i);
function fn(){
console.log(i);
var i = 99;
fn2();
console.log(i);
function fn2(){
i = 100;
}
}
可看做:
var i
var fn
function fn(){
var i;
function fn2(){
i = 100;
}
console.log(i);//----log.undefined
i = 99;
fn2();
console.log(i); //----log.100
}
fn();
i = 10;
fn = 20;
console.log(i);//---log.10
第一條console.log(i):由于變量聲明前置,但是還沒初始化洋闽,所以輸出的是undefined玄柠;
第二條console.log(i):先將i賦值為99,然后調(diào)用fn2()函數(shù)诫舅,執(zhí)行函數(shù)i = 100, 因在本層fn2()調(diào)用對象內(nèi)沒有檢索到聲明羽利,故尋下一層,下一層將i聲明為變量刊懈,故作用再了這層作用域內(nèi)这弧,也就是在fn()這個調(diào)用對象里面;所以i輸出100虚汛;
第三條console.log(i):因fn()調(diào)用對象內(nèi)i為局部變量匾浪,是不會影響到外界的,所以i檢索全局變量的值卷哩,i輸出10蛋辈;
10、如下代碼的輸出?為什么 (難度*****)
var say = 0;
(function say(n){
console.log(n);
if(n<3) return;
say(n-1);
}( 10 ));
console.log(say);
輸出: 10 9 8 7 6 5 4 3 2//到2這里就跳出冷溶; 0
在立即執(zhí)行函數(shù)say(n)中渐白,if,return和say(n-1)組成了一個循環(huán)逞频,從n的傳遞參數(shù)10開始纯衍,計算結(jié)果分別為10,9,8,7,6,5,4,3,2,當(dāng)n為2時苗胀,首先console.log襟诸,然后發(fā)現(xiàn)n<3,循環(huán)退出柒巫;
由于(function say(n){console.log(n);if(n<3) return;say(n-1); }( 10 ));這個是立即執(zhí)行函數(shù)励堡,故這可以看作是一個封閉的不受外界影響也不影響外界的局部作用域,所以全局console.log(say);輸出0