查閱書籍:JavaScript權(quán)威指南
函數(shù)聲明與函數(shù)表達(dá)式
sum(10,10)//20
function sum(n1,n1){
return n1+n2;
}
sum(10,10)//報(bào)錯(cuò)
var sum=function(){
return n1+n2;
}
用函數(shù)聲明定義的函數(shù),函數(shù)可以在函數(shù)聲明之前調(diào)用,而用函數(shù)表達(dá)式定義的函數(shù)只能在聲明之后調(diào)用。
根本原因是
解析器會(huì)率先讀取函數(shù)聲明,將其加入執(zhí)行環(huán)境中,
函數(shù)表達(dá)式,必須等到解析器執(zhí)行到它所在的代碼行,才會(huì)被真正的執(zhí)行
在權(quán)威指南是這么寫到的

ECMAScript規(guī)范中表示,函數(shù)聲明語句可以出現(xiàn)在全局代碼中,或者內(nèi)嵌在其他函數(shù)中,但是不能出現(xiàn)在循環(huán)、條件判、或者try/finally以及with語句中。函數(shù)定義表達(dá)式可以出現(xiàn)在javascript代碼的任何地方。
匿名函數(shù)
JavaScript函數(shù)可以是匿名的。這意味著你可以從函數(shù)聲明中省略函數(shù)名。但是,函數(shù)必須存儲(chǔ)在變量中。
var addNumbers = function (x, y) { return x + y; }
上述語法被也被稱為函數(shù)表達(dá)式。
立即執(zhí)行函數(shù)表達(dá)式
(function() {
// Your code here
}());
這其中定義的任何變量或函數(shù)不能被這個(gè)范圍以外的任何代碼改變。
這是一個(gè)在代碼中創(chuàng)建局部范圍的很好方法。它們可以幫助你保護(hù)變量和函數(shù),以避免被應(yīng)用程序的其他部分更改或覆蓋。
嵌套函數(shù)
function hyotense(a,b){
function square(x){return x*x};
return Math.sqrt(square(a)+square(b));
}
他的特別之處在于他的變量作用域規(guī)則,他們可以訪問嵌套他們的函數(shù)的參數(shù)和變量,這里要說一下作用域鏈
作用域鏈
作用域鏈中的下一個(gè)變量對(duì)象來自包含(外部)環(huán)境,而在下一個(gè)變量對(duì)象則來自下一個(gè)包含環(huán)境,一直延續(xù)到全局執(zhí)行環(huán)境,全局執(zhí)行環(huán)境的變量對(duì)象始終是作用域的最后一個(gè)對(duì)象
函數(shù)調(diào)用
(1)作為函數(shù)
(2)作為方法
(3)作為構(gòu)造函數(shù)
(4)通過它們的call和apply間接調(diào)用
作為函數(shù)
就是我們通??吹降淖詈唵蔚恼{(diào)用
function square(x){return x*x};
square(10);


作為方法
var calcuator={
a:1,
b:2,
add:function(){
this.result=this.a+this.b;
}
}
calcuator.add();
calcuator.result; =>3

方法鏈
當(dāng)方法返回的是一個(gè)對(duì)象的時(shí)候,這個(gè)對(duì)象還可以調(diào)用它的方法,jQuery 就是這樣。
需要注意的是


作為構(gòu)造函數(shù)
函數(shù)可以充當(dāng)構(gòu)造器的角色,并且可以使用構(gòu)造函數(shù)來創(chuàng)建新的對(duì)象。這是JavaScript面向?qū)ο蟮奶攸c(diǎn)之一。使用構(gòu)造函數(shù)的好處是,你將能夠通過預(yù)定義的屬性和方法,創(chuàng)造盡可能多的對(duì)象。
function Programmer(name, company, expertise) {
this.name = name;
this.company = company;
this.expertise = expertise;
this.writeCode = function() {
console.log("Writing some public static thing..");
}
this.makeSkypeCall = function() {
console.log("Making skype call..");
}
this.doSalsa = function() {
console.log("I'm a programmer, I can only do Gangnam style..");
}
this.canWriteJavaScript = function() {
return expertise === "JavaScript";
}
}
始終使用new關(guān)鍵字來從構(gòu)造器創(chuàng)建新的對(duì)象。
var jsProgrammer = Programmer("Douglas Crockford", "Yahoo", "JavaScript")
最終將添加所有屬性和方法到全局的window對(duì)象,原因是,除非明確指定,否則“this”指向全局的window對(duì)象。使用new 設(shè)置“this”上下文到被創(chuàng)建的當(dāng)前對(duì)象。
間接調(diào)用
javascript函數(shù)也是對(duì)象,函數(shù)對(duì)象也可以包括方法,其中call()和apply也可以間接調(diào)用函數(shù)
call和apply都可以改變this的指向
call第二個(gè)參數(shù)是散列分布
apply以數(shù)組的形式傳第二個(gè)參數(shù)
ClassA.call(this,name,age);改變this值
ClassA.apply(this,[name,age]);
ClassA.apply(this,arguments);arguments是一個(gè)數(shù)組,里面放的是所有傳過來的實(shí)參
call可以用做繼承
function Parent(age){
this.name=['mike','jack','smith'];
this.age=age;
}
function Child(age){
Parent.call(this,age);//把this指向Parent,同時(shí)還可以傳遞參數(shù)
}
var test=new Child(21);
console.log(test.age);//21
console.log(test.name);
test.name.push('bill');
console.log(test.name);//mike,jack,smith,bill
ES5還定義了一個(gè)方法:bind(),它會(huì)創(chuàng)建一個(gè)函數(shù)的實(shí)例,其this值會(huì)被綁定到傳給bind()函數(shù)的值。如
window.color='red';
var o={color:'blue'};
function sayColor(){
console.log(this.color);
}
var objectSaycolor=sayColor.bind(o);
//var objectSaycolor=sayColor.bind();
objectSaycolor();//blue
在這里sayColor()調(diào)用bind()并傳入對(duì)象o,創(chuàng)建了objectSayColor()函數(shù)。objectSayColor()函數(shù)的this值等于o,因此即使是在全局作用域中調(diào)用這個(gè)函數(shù),也會(huì)看到blue。
實(shí)參和形參
當(dāng)調(diào)用函數(shù)時(shí),傳入的實(shí)參比函數(shù)聲明時(shí)指定的形參要少的話,剩下的形參都會(huì)設(shè)為undefine
那么當(dāng)調(diào)用函數(shù)時(shí),傳入的實(shí)參超過函數(shù)聲明時(shí)指定的形參的話,沒有辦法直接獲得其他形參,參數(shù)對(duì)象解決了這個(gè)問題,那么什么是參數(shù)對(duì)象呢?
參數(shù)對(duì)象arguments
標(biāo)識(shí)符arguments是指向?qū)崊?duì)象的引用,實(shí)參對(duì)象是一個(gè)類數(shù)組對(duì)象,這樣通過數(shù)組下標(biāo)就可以訪問傳入函數(shù)的實(shí)參值
我們可以通過arguments[0]取到傳入實(shí)參的第一位,和真正的數(shù)組一樣arguments也有l(wèi)ength屬性,用來表示傳入實(shí)參的個(gè)數(shù)。
arguments有一個(gè)重要的用處,讓函數(shù)可以操作任意數(shù)量的實(shí)參。下面的這個(gè)例子就利用arguments實(shí)現(xiàn)任意數(shù)量的數(shù)字去比較大小。

要注意的是,實(shí)參對(duì)象并不是真正的數(shù)組,他不能夠使用數(shù)組的方法

實(shí)參對(duì)象的callee和caller屬性
callee是標(biāo)準(zhǔn)的,指向當(dāng)前正在執(zhí)行的函數(shù), 一般用于遞歸函數(shù) 可以實(shí)現(xiàn)低耦合 防止將函數(shù)賦值為其他函數(shù),
caller是非標(biāo)準(zhǔn)的,調(diào)用當(dāng)前正在執(zhí)行的函數(shù)的函數(shù)
function factorial(num){
if(num <= 1){
return 1;
}else{
return num*arguments.callee(num-1);
}
}
閉包
用一句可以概括一下閉包:外部函數(shù)內(nèi)會(huì)定義一個(gè)內(nèi)部函數(shù) 2.內(nèi)部函數(shù)調(diào)用外部函數(shù)的變量 3.外部函數(shù)的變量不會(huì)回收
函數(shù)對(duì)象可以可以通過作用域鏈相互關(guān)聯(lián)起來,函數(shù)內(nèi)部的變量可以保存在函數(shù)內(nèi)作用域。
從技術(shù)角度講,javascript的函數(shù)都是閉包,定義大多數(shù)函數(shù)時(shí)的作用域鏈在調(diào)用函數(shù)時(shí)依然有效。
var scope="global scope";
function checkscope(){
var scope="local scope"
function f(){return scope};
retunr f();
}
checkscope();
這個(gè)例子很明顯,會(huì)輸出local scope,將f函數(shù)執(zhí)行后的結(jié)果返回。
做一點(diǎn)改變
var scope="global scope";
function checkscope(){
var scope="local scope"
function f(){return scope};
retunr f;
}
checkscope()();
答案是“l(fā)ocal scope”
書上的答案是這樣的


注意:函數(shù)定義時(shí)的作用域在函數(shù)執(zhí)行時(shí)依然有效
很多人會(huì)認(rèn)為外部函數(shù)中定義的局部變量在函數(shù)返回后就不存在了,
其實(shí)原理是這樣的,
當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)時(shí),函數(shù)的環(huán)境(變量,函數(shù))會(huì)被推進(jìn)一個(gè)環(huán)境棧(我習(xí)慣叫運(yùn)行棧),在棧里面存的是對(duì)函數(shù)的一個(gè)引用,真正的函數(shù)是放在堆上面的,當(dāng)函數(shù)執(zhí)行之后,正常的情況會(huì)將環(huán)境中的變量彈出來,斷開函數(shù)的引用,但是由于在外面checkscope變量指向了function(){return scope}這個(gè)函數(shù),而在函數(shù)里面還需要scope這個(gè)變量,因此在這種情況下,編譯器會(huì)將scope捕捉(我理解就是復(fù)制了一份引用),這樣我們就可以取到scope,這就是在內(nèi)部函數(shù)里面調(diào)用外部函數(shù)的變量,外部函數(shù)的變量不會(huì)被釋放的原因。
閉包有什么用處呢?
看下面這個(gè)例子
for(var i=0;i<10;i++){
ali[i].onclick=function(){
alert(i);
}
}
想想這個(gè)例子,當(dāng)我們點(diǎn)擊不同的li時(shí),會(huì)如我們所愿的輸出當(dāng)前l(fā)i的下標(biāo)么?答案當(dāng)然是不,點(diǎn)擊不同的li時(shí)都會(huì)輸出10,因?yàn)閒or循環(huán)瞬間執(zhí)行完畢的時(shí)候,每次i出了函數(shù),都將i從棧里面彈出去,在重新給i賦值,i最后變成了10,這個(gè)時(shí)候無論點(diǎn)擊什么,都會(huì)alert 10,閉包就可以解決這個(gè)問題,將變量保存下來
for(var i=0;i<10;i++){
(function(idx){
ali[i].onclick=function(){
alert(i);
}
})(i);
}
看一下這個(gè)閉包,通過這種方式就可以保存下來每一個(gè)下標(biāo),每個(gè)點(diǎn)擊事件都將當(dāng)前的i保留下來了,原理和上面那個(gè)例子是一樣的。