1. 函數(shù)聲明和函數(shù)表達(dá)式有什么區(qū)別 (*)
- 函數(shù)在JS中有三種方式來定義:
- 函數(shù)聲明(function declaration)
- 函數(shù)表達(dá)式(function expression)
- 調(diào)用new function返回
- 區(qū)別:函數(shù)聲明是和變量聲明類似,函數(shù)聲明的解析是在預(yù)執(zhí)行(pre-execution)階段积仗,也就是瀏覽器準(zhǔn)備執(zhí)行代碼的時(shí)候疆拘,因此,通過函數(shù)聲明來定義函數(shù)寂曹,可以在定義前或后被調(diào)用哎迄。然而函數(shù)表達(dá)式不能做到這點(diǎn),因?yàn)楹瘮?shù)表達(dá)式是將函數(shù)賦值給變量隆圆,是將函數(shù)放在語句中而不是代碼主流中漱挚,只有當(dāng)瀏覽器解析到該語句的時(shí)候函數(shù)才能被調(diào)用,并且不能在其他位置調(diào)用渺氧,就是其實(shí)際作用是給變量賦值而非作為一個(gè)函數(shù)被調(diào)用旨涝。
- 例子:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17</title>
</head>
<body>
<script>
var test1 = function say() {
console.log('hello world');
}; // 函數(shù)表達(dá)式,有分號(hào)侣背,是語句
test1(); // hello world
sayName(); // 大阿群
say(); // error
function sayName() {
console.log('大阿群');
} // 函數(shù)聲明白华,無分號(hào)
</script>
</body>
</html>
2. 什么是變量的聲明前置慨默?什么是函數(shù)的聲明前置 (**)
- 變量的聲明前置:JS語法中,在指定的作用域內(nèi)弧腥,聲明的變量會(huì)在提升到代碼的頂部厦取,而賦值操作不會(huì)跟隨提升,如果未聲明變量管搪,則無論變量在任何作用域中虾攻,都視為該變量為全局變量。
- 函數(shù)的聲明前置:JS語法中更鲁,如果函數(shù)定義是通過函數(shù)聲明的台谢,那么在指定作用域內(nèi),函數(shù)會(huì)提升到代碼頂部岁经,并且放在變量聲明的下面。
- 例子:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17</title>
</head>
<body>
<script>
console.log(a); // undefined
var a = 1;
demo();
console.log(a); // 2
function demo() {
a = 2;
}
console.log(b); // error
</script>
</body>
</html>
可以看出蛇券,未定義的b會(huì)出現(xiàn)錯(cuò)誤缀壤,而a變量聲明前置,但是執(zhí)行第一個(gè)
console.log
的時(shí)候a未初始化纠亚,所以值未undefined塘慕,繼續(xù)往下執(zhí)行demo()函數(shù),雖然demo()函數(shù)聲明是在執(zhí)行之后蒂胞,但是由于函數(shù)聲明前置图呢,所以正確執(zhí)行顯示2。
3. arguments 是什么 (*)
- arguments意為參數(shù)骗随。
- 在JavaScript中蛤织,arguments對(duì)象是比較特別的一個(gè)對(duì)象,實(shí)際上是當(dāng)前函數(shù)的一個(gè)內(nèi)置屬性鸿染。arguments非常類似Array指蚜,但實(shí)際上又不是一個(gè)Array實(shí)例≌墙罚可以通過如下代碼得以證實(shí)(當(dāng)然摊鸡,實(shí)際上,在函數(shù)funcArg中蚕冬,調(diào)用arguments是不必要寫成funcArg.arguments免猾,直接寫arguments即可)。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17</title>
</head>
<body>
<script>
Array.prototype.testArg = "test";
function funcArg() {
console.log(funcArg.arguments[0]);
console.log(funcArg.arguments.testArg);
}
console.log(new Array().testArg);
funcArg(1,2);
</script>
</body>
</html>
- arguments對(duì)象的長(zhǎng)度是由實(shí)參個(gè)數(shù)而不是形參個(gè)數(shù)決定的猎提。形參是函數(shù)內(nèi)部重新開辟內(nèi)存空間存儲(chǔ)的變量,但是其與arguments對(duì)象內(nèi)存空間并不重疊赢乓。對(duì)于arguments和值都存在的情況下忧侧,兩者值是同步的石窑,但是針對(duì)其中一個(gè)無值的情況下,對(duì)于此無值的情形值不會(huì)得以同步蚓炬。如下代碼可以得以驗(yàn)證松逊。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17</title>
</head>
<body>
<script>
function f(a, b, c) {
console.log(arguments.length); // 2
console.log(a); // 1
a = 100 ;
console.log(a); // 100
console.log(arguments[0]); // 100
console.log(c); // undefined
c = 200;
console.log(arguments[2]); // undefined
}
f(1,2);
</script>
</body>
</html>
- 由JavaScript中函數(shù)的聲明和調(diào)用特性,可以看出JavaScript中函數(shù)是不能重載的肯夏。
- Javascript函數(shù)的聲明是沒有返回值類型這一說法的经宏;
- JavaScript中形參的個(gè)數(shù)嚴(yán)格意義上來講只是為了方便在函數(shù)中的變量操作,實(shí)際上實(shí)參已經(jīng)存儲(chǔ)在arguments對(duì)象中了驯击。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17</title>
</head>
<body>
<script>
function f(a) {
return a + 10;
}
function f(a) {
return a - 10;
}
console.log(f(1));
</script>
</body>
</html>

* arguments對(duì)象中有一個(gè)非常有用的屬性:callee烁兰。arguments.callee返回此arguments對(duì)象所在的當(dāng)前函數(shù)引用。在使用函數(shù)遞歸調(diào)用時(shí)推薦使用arguments.callee代替函數(shù)名本身徊都。
``` html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17</title>
</head>
<body>
<script>
function count(a) {
if(a == 1) {
return 1;
}
return a + arguments.callee(--a);
}
var mm = count(10);
console.log(mm);
</script>
</body>
</html>
不過call屬性在嚴(yán)格模式下被禁用了沪斟,這點(diǎn)需要注意。
4. 函數(shù)的重載怎樣實(shí)現(xiàn) (**)
在一些編程語言中暇矫,函數(shù)的返回值類型和參考不同會(huì)使得同一個(gè)函數(shù)有不同的功能一同實(shí)現(xiàn)主之,這是函數(shù)重載;而在Javascript中沒有重載的概念李根,所以正常情況下的函數(shù)是無法重載的:
不過可以用arguments屬性來判斷實(shí)參的個(gè)數(shù)槽奕,模擬重載:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17</title>
</head>
<body>
<script>
function test() {
if(arguments.length == 1) {
console.log(arguments[0] + 3);
} else if(arguments.length == 2) {
console.log(arguments[0] * arguments[1]);
}
}
test(1, 2);
</script>
</body>
</html>

5. 立即執(zhí)行函數(shù)表達(dá)式是什么?有什么作用 (***)
- 用
()
將函數(shù)聲明變?yōu)楸磉_(dá)式并立即執(zhí)行房轿,就是立即執(zhí)行表達(dá)式(IIFE):
(function() {statement} ) ();
(function() {statement} ());
兩種寫法都可以粤攒,推薦使用后者; -
作用:
- 模擬塊作用域:
眾所周知囱持,JavaScript沒有C或Java中的塊作用域(block)夯接,只有函數(shù)作用域,在同時(shí)調(diào)用多個(gè)庫的情況下洪唐,很容易造成對(duì)象或者變量的覆蓋钻蹬,比如:
liba.js
- 模擬塊作用域:
var num = 1;
// code....
libb.js
var num = 2;
// code....
如果在頁面中同時(shí)引用liba.js和liba.js兩個(gè)庫,必然導(dǎo)致num變量被覆蓋凭需,為了解決這個(gè)問題问欠,可以通過IIFE來解決:
liba.js
(function(){
var num = 1;
// code....
}());
libb.js
(function(){
var num = 2;
// code....
}());
經(jīng)過改造之后,兩個(gè)庫的代碼就完全獨(dú)立粒蜈,并不會(huì)互相影響顺献。
- 解決閉包沖突(待補(bǔ)充)
- 與自執(zhí)行函數(shù)表達(dá)式的區(qū)別:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17</title>
</head>
<body>
<script>
// 這是一個(gè)自執(zhí)行的函數(shù),函數(shù)內(nèi)部執(zhí)行自身枯怖,遞歸
function foo() { foo(); }
// 這是一個(gè)自執(zhí)行的匿名函數(shù)注整,因?yàn)闆]有標(biāo)示名稱
// 必須使用arguments.callee屬性來執(zhí)行自己
var foo = function () { arguments.callee(); };
// 這可能也是一個(gè)自執(zhí)行的匿名函數(shù),僅僅是foo標(biāo)示名稱引用它自身
// 如果你將foo改變成其它的,你將得到一個(gè)used-to-self-execute匿名函數(shù)
var foo = function () { foo(); };
// 有些人叫這個(gè)是自執(zhí)行的匿名函數(shù)(即便它不是)肿轨,因?yàn)樗鼪]有調(diào)用自身寿冕,它只是立即執(zhí)行而已。
(function () { /* code */ } ());
// 為函數(shù)表達(dá)式添加一個(gè)標(biāo)示名稱椒袍,可以方便Debug
// 但一定命名了驼唱,這個(gè)函數(shù)就不再是匿名的了
(function foo() { /* code */ } ());
// 立即調(diào)用的函數(shù)表達(dá)式(IIFE)也可以自執(zhí)行,不過可能不常用罷了
(function () { arguments.callee(); } ());
(function foo() { foo(); } ());
// 另外驹暑,下面的代碼在黑莓5里執(zhí)行會(huì)出錯(cuò)玫恳,因?yàn)樵谝粋€(gè)命名的函數(shù)表達(dá)式里,他的名稱是undefined
// 呵呵优俘,奇怪
(function foo() { foo(); } ());
</script>
</body>
</html>
參考:
JavaScript中的立即執(zhí)行函數(shù)表達(dá)式
深入理解JavaScript系列(4):立即調(diào)用的函數(shù)表達(dá)式
6. 什么是函數(shù)的作用域鏈 (****)
-
javascript作用域:
任何程序設(shè)計(jì)語言都有作用域的概念京办,簡(jiǎn)單的說,作用域就是變量與函數(shù)的可訪問范圍帆焕,即作用域控制著變量與函數(shù)的可見性和生命周期惭婿。在JavaScript中,變量的作用域有全局作用域和局部作用域兩種叶雹。-
全局作用域(Global Scope)
在代碼中任何地方都能訪問到的對(duì)象擁有全局作用域审孽,一般來說以下幾種情形擁有全局作用域: - 最外層函數(shù)和在最外層函數(shù)外面定義的變量擁有全局作用域,例如:
-
全局作用域(Global Scope)
var a = 1;
function add() {
var c = 1 + 2;
}
-
所有末定義直接賦值的變量自動(dòng)聲明為擁有全局作用域浑娜,例如:
未聲明局部變量 - 所有window對(duì)象的屬性擁有全局作用域
-
局部作用域(Local Scope)
和全局作用域相反,局部作用域一般只在固定的代碼片段內(nèi)可訪問到式散,最常見的例如函數(shù)內(nèi)部筋遭,所有在一些地方也會(huì)看到有人把這種作用域稱為函數(shù)作用域:
局部作用域 -
作用域鏈:
作用域鏈?zhǔn)莾?nèi)部上下文所有變量對(duì)象(包括父變量對(duì)象)的列表,用來變量查詢暴拄。在代碼執(zhí)行的過程中漓滔,所用到的變量會(huì)在當(dāng)前作用域中進(jìn)行尋找,如果找不到乖篷,就會(huì)往沿著作用域鏈向上一級(jí)進(jìn)行尋找响驴,一直到全局作用域?yàn)橹梗绻业奖銜?huì)停止(而不理會(huì)上一級(jí)是否有同名的變量)撕蔼,如果找不到豁鲤,就會(huì)報(bào)錯(cuò)。
function add(num1,num2) {
var sum = num1 + num2;
return sum;
}
在函數(shù)add創(chuàng)建時(shí)鲸沮,它的作用域鏈中會(huì)填入一個(gè)全局對(duì)象琳骡,該全局對(duì)象包含了所有全局變量,如下圖所示(注意:圖片只例舉了全部變量中的一部分):
執(zhí)行代碼:
var total = add(5, 10);
首先在本身內(nèi)部作用域中尋找所需對(duì)象讼溺,當(dāng)不存在時(shí)楣号,像外層找,執(zhí)行過程中按照從上到下順序執(zhí)行,執(zhí)行一層就會(huì)類似于編鎖鏈上的一節(jié)炫狱,將其保留下來藻懒,在其生命周期內(nèi)反復(fù)被查找。
參考:
JavaScript 開發(fā)進(jìn)階:理解 JavaScript 作用域和作用域鏈
深入理解JavaScript系列(14):作用域鏈(Scope Chain)
代碼:
1. 以下代碼輸出什么视译? (難度**)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17-1</title>
</head>
<body>
<script>
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('男');
</script>
</body>
</html>

2. 寫一個(gè)函數(shù)嬉荆,返回參數(shù)的平方和?如 (難度**)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17-2</title>
</head>
<body>
<script>
function sumOfSquare(){
var sum = 0;
for(var i = 0; i < arguments.length; i++){
sum = arguments[i] * arguments[i] + sum;
}
console.log(sum);
}
sumOfSquare(2,3,4);
sumOfSquare(1,3);
</script>
</body>
</html>
3. 如下代碼的輸出憎亚?為什么 (難度*)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17-3</title>
</head>
<body>
<script>
console.log(a);
var a = 1;
console.log(b);
</script>
</body>
</html>
4. 如下代碼的輸出员寇?為什么 (難度*)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17-4</title>
</head>
<body>
<script>
sayName('world');
sayAge(10);
function sayName(name){
console.log('hello', name);
}
var sayAge = function(age){
console.log(age);
};
</script>
</body>
</html>
5. 如下代碼的輸出?為什么 (難度**)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17-5</title>
</head>
<body>
<script>
function fn(){}
var fn = 3;
console.log(fn);
</script>
</body>
</html>

6. 如下代碼的輸出第美?為什么 (難度***)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17-6</title>
</head>
<body>
<script>
function fn(fn2){
console.log(fn2);
var fn2 = 3;
console.log(fn2);
console.log(fn);
function fn2(){
console.log('fnnn2');
}
}
fn(10);
</script>
</body>
</html>
按照?qǐng)?zhí)行順序重寫函數(shù):
<script>
function fn(fn2){
var fn2;
function fn2(){
console.log('fnnn2');
}
console.log(fn2);
fn2 = 3;
console.log(fn2);
console.log(fn);
}
fn(10);
</script>
7. 如下代碼的輸出蝶锋?為什么 (難度***)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17-7</title>
</head>
<body>
<script>
var fn = 1;
function fn(fn){
console.log(fn);
}
console.log(fn(fn));
</script>
</body>
</html>
執(zhí)行順序:
<script>
var fn;
function fn(fn){
console.log(fn);
}
fn = 1;
console.log(fn(fn));
</script>
fn = 1為最終結(jié)果,無法進(jìn)行()操作什往,所以顯示錯(cuò)誤扳缕。
8. 如下代碼的輸出?為什么 (難度**)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17-8</title>
</head>
<body>
<script>
console.log(j);
console.log(i);
for(var i = 0; i < 10; i++){
var j = 100;
}
console.log(i);
console.log(j);
</script>
</body>
</html>
for是循環(huán)語句别威,不是函數(shù)躯舔,首先并不會(huì)前置,其次其定義的自然就是全局變量省古,所以能夠被解析粥庄,正常順序執(zhí)行并顯示。
9. 如下代碼的輸出豺妓?為什么 (難度****)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17-9</title>
</head>
<body>
<script>
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;
}
}
</script>
</body>
</html>
執(zhí)行順序:
<script>
var i, fn;
function fn(){
var i;
function fn2(){
i = 100;
}
console.log(i);
i = 99;
fn2();
console.log(i);
}
fn();
i = 10;
fn = 20;
console.log(i);
</script>
- 首先將變量和函數(shù)前置惜互,并且將函數(shù)內(nèi)部嵌套函數(shù)作用域內(nèi)也進(jìn)行前置操作;
- 然后按照順序琳拭,首先執(zhí)行fn()训堆,會(huì)遇到第一個(gè)console.log(i),由于此時(shí)i未賦值白嘁,所以為undefined坑鱼,然后執(zhí)行fn2()函數(shù),由于fn2()函數(shù)中的i未聲明絮缅,所以定義的是全局變量鲁沥,所以下一個(gè)console.log(i)為100;
- 繼續(xù)往下執(zhí)行耕魄,i = 10定義全局變量黍析,所以最后一個(gè)console.log(i)為10。
10. 如下代碼的輸出屎开?為什么 (難度*****)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>task17-10</title>
</head>
<body>
<script>
var say = 0;
(function say(n){
console.log(n);
if(n < 3) return;
say(n - 1);
}(10));
console.log(say);
</script>
</body>
</html>

- 首先阐枣,放在一個(gè)()中马靠,并且有分號(hào)結(jié)尾,說明其實(shí)一個(gè)語句蔼两,即“立即執(zhí)行的函數(shù)表達(dá)式”甩鳄,所以函數(shù)不會(huì)前置,按照順序執(zhí)行额划,立即執(zhí)行的表達(dá)式中發(fā)生迭代妙啃,顯示10,9俊戳,8......3揖赴,2,滿足if條件抑胎,return跳出函數(shù)燥滑,繼續(xù)執(zhí)行下面的console.log(say);語句,由于立即執(zhí)行的函數(shù)表達(dá)式中的say生命周期已經(jīng)結(jié)束阿逃,所以console.log尋找全局變量say铭拧,為0。
本文版權(quán)歸本人和饑人谷所有恃锉,轉(zhuǎn)載請(qǐng)注明來源搀菩,謝謝