1.函數聲明和函數表達式有什么區別 (*)
函數聲明和函數表達式都可以創建函數。
函數聲明寫法:
function 函數名稱 (參數:可選) {函數體 }
舉例:
function foo() {}
函數表達式寫法:
var 變量 = function 函數名稱(可選) (參數:可選) {函數體 }
舉例:
var foo=function(){};
這種寫法將一個匿名函數賦值給變量。這時,這個匿名函數又稱函數表達式(Function Expression),因為賦值語句的等號右側只能放表達式。
采用函數表達式聲明函數時,function命令后面不帶有函數名。如果加上函數名,該函數名只在函數體內部有效,在函數體外部無效。
二者區別主要在于,函數聲明有聲明提前的特點,函數聲明會被提到被提升到作用域的最前面,即使寫代碼的時候是寫在最后面,也還是會被提升至最前面。
舉例:
這段代碼的實際執行順序是:
var btn;
function fo(){}
console.log(fo);
console.log(btn);
btn=function(){};
所以輸出結果,是function fo(){}
undefined
。
2.什么是變量的聲明前置?什么是函數的聲明前置 ?(**)
變量的聲明前置,把所有變量聲明放到代碼的頭部。
函數的聲明前置是把函數聲明放到代碼的頭部。如果我們使用函數聲明的方式,那么即使函數寫在最后也可以在前面語句調用,前提是函數聲明部分已經被下載到本地。
舉例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>聲明提前</title>
</head>
<body>
<script>
console.log(a);
function fn(){
console.log(b);
console.log(foo);
var b=4;
function foo(){}
}
var a = 9;
fn();
</script>
</body>
</html>
結果:
這就是變量的聲明前置和函數的聲明前置的結果。
3.arguments 是什么 (*)
arguments 是一個類數組對象。代表傳給一個function的參數列表。arguments 對象是函數內部的本地變量;arguments 已經不再是函數的屬性了。
在函數內部可以用arguments對象來獲取函數的所有參數。而且arguments對象僅在函數內部有效,在函數外部調用 arguments 對象會出現一個錯誤。
它的作用有:
1.無須明確指出參數名,可以訪問函數。
2.無需明確命名參數,就可以重寫函數。
可以看到name的值被改變了。
3.可以用 arguments 對象檢測函數的參數個數,引用屬性 arguments.length 即可。
可以看到實際函數調用了幾個參數,arguments.length就等于幾。
4.用arguments 對象判斷傳遞給函數的參數個數,可以模擬函數重載。
當只有一個參數時,doAdd() 函數給參數加 5。如果有兩個參數,則會把兩個參數相加,返回它們的和。所以,doAdd(10) 輸出的是 "15",而 doAdd(40, 20) 輸出的是 "60"。雖然不如重載那么好,不過已足以避開 ECMAScript 的這種限制。
4.函數的重載怎樣實現 (**)
從語言角度來說,javascript不支持函數重載,先定義的函數會被后定義的函數覆蓋。不能夠定義同樣的函數然后通過編譯器去根據不同的參數執行不同的函數。但是javascript卻可以通過自身屬性去模擬函數重載。如上題中介紹的arguments的第四個特性,就可以模擬函數重載。
參考 js函數重載
5.立即執行函數表達式是什么?有什么作用 (***)
在Javascript中,一對圓括號()
是一種運算符,跟在函數名之后,表示調用該函數。比如,foo()就表示調用foo函數。
所以我們也可以在函數聲明后加上()
,表示立即執行函數。但是不能直接加括號,function(){}();
會引起語法錯誤。這是因為,JavaScript引擎規定,如果function關鍵字出現在行首,一律解釋成語句。因此,JavaScript引擎看到行首是function關鍵字之后,認為這一段都是函數的定義,不應該以圓括號結尾,所以就報錯了。
立即執行函數可以寫成下面的形式:
(function(){ /*code */ }());
或者
(function(){/*code*/})();
上面兩種寫法都是以圓括號開頭,引擎就會認為后面跟的是一個表示式,而不是函數定義語句,所以就避免了錯誤。這就叫做“立即調用的函數表達式”(Immediately-Invoked Function Expression),簡稱IIFE。
它的目的有兩個:
一是不必為函數命名,避免了污染全局變量;
二是IIFE內部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變量。
// 寫法一
var tmp =newData;
processData(tmp);
storeData(tmp);
// 寫法二
(function (){
var tmp = newData;
processData(tmp);
storeData(tmp);}());
上面代碼中,寫法二比寫法一更好,因為完全避免了污染全局變量。
6.什么是函數的作用域鏈 (****)
- 函數作用域就是函數的可訪問范圍,簡單的說,一個function從
{
開始,到}
結束,是這個函數的作用域。不同的函數有不同的作用域。作用域中聲明的變量無法被作用域外部所訪問。但是作用域中可以訪問上級作用域中的變量。 - 作用域鏈正是內部上下文所有變量對象(包括父變量對象)的列表,用來變量查詢。在代碼執行的過程中,所用到的變量會在當前作用域中進行尋找,如果找不到,就會往沿著作用域鏈向上一級進行尋找,一直到全局作用域為止,如果找到便會停止(而不理會上一級是否有同名的變量),如果找不到,就會報錯。
1.參考JavaScript 開發進階:理解 JavaScript 作用域和作用域鏈
2.參考深入理解JavaScript系列(14):作用域鏈(Scope Chain)
代碼題
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, '男');
/*name:hunger
age:28
sex:男
["hunger",28,"男"]
name valley*/
getInfo('hunger', 28);
/*name:hunger
age:28
sex:undefined
["hunger",28]
name valley*/
getInfo('男');
/*name:男
age:undefined
sex:undefined
["男"]
name valley*/
結果:
2.寫一個函數,返回參數的平方和?如 (難度**)
function sumOfSquares(){
}
sumOfSquares(2,3,4); // 29
sumOfSquares(1,3); // 10
代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>函數平方和</title>
</head>
<body>
<script>
function sumOfSquares(){
var s=0;
for(var i=0;i<arguments.length;i++){
s= arguments[i]*arguments[i]+s;
}
alert(s);
}
sumOfSquares(2,3,4);
sumOfSquares(1,3);
</script>
</body>
</html>
3.如下代碼的輸出?為什么 (難度*)
console.log(a);//undefined
var a = 1;
console.log(b);//not defined
結果:
因為變量提升,把對a的聲明提到前面,但是并沒有定義a,所以輸出undefined。而b則未被聲明,輸出錯誤。
4.如下代碼的輸出?為什么 (難度*)
sayName('world');//hello world
sayAge(10); //not a function
function sayName(name){
console.log('hello ', name);
}
var sayAge = function(age){
console.log(age);
};
結果:
函數聲明提前到代碼最前面,調用函數sayName。而
var sayAge = function(age){console.log(age);};
定義了函數表達式,sayAge是一個變量,調用sayAge(10)
會出錯。
5.如下代碼的輸出?為什么 (難度**)
function fn(){}
var fn = 3;
console.log(fn);//3
結果:
當在同一個作用域內定義了名字相同的變量和方法的話,無論其順序如何,變量的賦值會覆蓋方法的賦值。
6.如下代碼的輸出?為什么 (難度***)
function fn(fn2){
var fn2
function fn2(){
console.log('fnnn2');
}
console.log(fn2);
var fn2 = 3;
console.log(fn2);
console.log(fn);
}
fn(10);
結果:
執行順序:
function fn(fn2){
var fn2;
function fn2(){
console.log('fnnn2');
}
console.log(fn2);/*function fn2(){
console.log('fnnn2');
}*/
fn2 = 3;
console.log(fn2);//3
console.log(fn);
}
fn(10);
調用函數,才會執行函數中的內容。console.log(fn);
調用上級函數。
7.如下代碼的輸出?為什么 (難度***)
var fn = 1;
function fn(fn){
console.log(fn);
}
console.log(fn(fn)); //not a function
結果:
執行順序:
var fn;
function fn(fn){
console.log(fn);
}
fn = 1;
console.log(fn(fn));
fn被賦值為1,是一個數字不是函數,出錯。
8.如下代碼的輸出?為什么 (難度**)
//作用域
console.log(j);//undefined
console.log(i);//undefined
for(var i=0; i<10; i++){
var j = 100;
}
console.log(i);//10
console.log(j);//100
結果:
for循環把i增加到10,j的值被設置為100。這兩個都是全局變量。
類似于:
console.log(j);
console.log(i);
var i=10;
var j=100;
console.log(i);
console.log(j);
輸出結果如上圖示。
9.如下代碼的輸出?為什么 (難度****)
fn();//undefined 100
var i = 10;
var fn = 20;
console.log(i);//10
function fn(){
console.log(i);//undefined
var i = 99;
fn2();
console.log(i);//100
function fn2(){
i = 100;
}
}
結果:
根據變量的聲明提升和函數的聲明提升,可以把上面的代碼寫成:
var i;
var fn;
function fn(){
var i;
function fn2(){
i = 100;
}//并沒有被調用,沒有運行。
console.log(i);//undefined
i = 99;
fn2();//i=100,i是全局變量
console.log(i);//100
}
fn();
i=10;//定義i是10
fn=20;
console.log(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 0
結果:
(function say(n){
console.log(n);
if(n<3) return;
say(n-1);
}( 10 ));
是立即執行函數,這個函數是對n從10依次遞減,直到n<3,即n的值是2時,停止執行這個函數。得到數字10 9 8 7 6 5 4 3 2
。而say又被賦值為0,最后輸出0。
本文版權歸本人和饑人谷所有,轉載請注明出處