查閱書籍:JavaScript權(quán)威指南
函數(shù)聲明與函數(shù)表達式
sum(10,10)//20
function sum(n1,n1){
return n1+n2;
}
sum(10,10)//報錯
var sum=function(){
return n1+n2;
}
用函數(shù)聲明定義的函數(shù),函數(shù)可以在函數(shù)聲明之前調(diào)用,而用函數(shù)表達式定義的函數(shù)只能在聲明之后調(diào)用。
根本原因是
解析器會率先讀取函數(shù)聲明癣丧,將其加入執(zhí)行環(huán)境中,
函數(shù)表達式瓜浸,必須等到解析器執(zhí)行到它所在的代碼行泪酱,才會被真正的執(zhí)行
在權(quán)威指南是這么寫到的
ECMAScript規(guī)范中表示,函數(shù)聲明語句可以出現(xiàn)在全局代碼中录语,或者內(nèi)嵌在其他函數(shù)中庄拇,但是不能出現(xiàn)在循環(huán)劫流、條件判、或者try/finally以及with語句中丛忆。函數(shù)定義表達式可以出現(xiàn)在javascript代碼的任何地方祠汇。
匿名函數(shù)
JavaScript函數(shù)可以是匿名的。這意味著你可以從函數(shù)聲明中省略函數(shù)名熄诡。但是可很,函數(shù)必須存儲在變量中。
var addNumbers = function (x, y) { return x + y; }
上述語法被也被稱為函數(shù)表達式凰浮。
立即執(zhí)行函數(shù)表達式
(function() {
// Your code here
}());
這其中定義的任何變量或函數(shù)不能被這個范圍以外的任何代碼改變我抠。
這是一個在代碼中創(chuàng)建局部范圍的很好方法苇本。它們可以幫助你保護變量和函數(shù),以避免被應(yīng)用程序的其他部分更改或覆蓋菜拓。
嵌套函數(shù)
function hyotense(a,b){
function square(x){return x*x};
return Math.sqrt(square(a)+square(b));
}
他的特別之處在于他的變量作用域規(guī)則瓣窄,他們可以訪問嵌套他們的函數(shù)的參數(shù)和變量,這里要說一下作用域鏈
作用域鏈
作用域鏈中的下一個變量對象來自包含(外部)環(huán)境纳鼎,而在下一個變量對象則來自下一個包含環(huán)境俺夕,一直延續(xù)到全局執(zhí)行環(huán)境,全局執(zhí)行環(huán)境的變量對象始終是作用域的最后一個對象
函數(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)方法返回的是一個對象的時候劝贸,這個對象還可以調(diào)用它的方法,jQuery 就是這樣逗宁。
需要注意的是
作為構(gòu)造函數(shù)
函數(shù)可以充當(dāng)構(gòu)造器的角色映九,并且可以使用構(gòu)造函數(shù)來創(chuàng)建新的對象。這是JavaScript面向?qū)ο蟮奶攸c之一瞎颗。使用構(gòu)造函數(shù)的好處是件甥,你將能夠通過預(yù)定義的屬性和方法,創(chuàng)造盡可能多的對象哼拔。
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)建新的對象引有。
var jsProgrammer = Programmer("Douglas Crockford", "Yahoo", "JavaScript")
最終將添加所有屬性和方法到全局的window對象,原因是管挟,除非明確指定,否則“this”指向全局的window對象弄捕。使用new 設(shè)置“this”上下文到被創(chuàng)建的當(dāng)前對象僻孝。
間接調(diào)用
javascript函數(shù)也是對象,函數(shù)對象也可以包括方法守谓,其中call()和apply也可以間接調(diào)用函數(shù)
call和apply都可以改變this的指向
call第二個參數(shù)是散列分布
apply以數(shù)組的形式傳第二個參數(shù)
ClassA.call(this,name,age);改變this值
ClassA.apply(this,[name,age]);
ClassA.apply(this,arguments);arguments是一個數(shù)組穿铆,里面放的是所有傳過來的實參
call可以用做繼承
function Parent(age){
this.name=['mike','jack','smith'];
this.age=age;
}
function Child(age){
Parent.call(this,age);//把this指向Parent,同時還可以傳遞參數(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還定義了一個方法:bind(),它會創(chuàng)建一個函數(shù)的實例斋荞,其this值會被綁定到傳給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()并傳入對象o,創(chuàng)建了objectSayColor()函數(shù)。objectSayColor()函數(shù)的this值等于o,因此即使是在全局作用域中調(diào)用這個函數(shù)平酿,也會看到blue凤优。
實參和形參
當(dāng)調(diào)用函數(shù)時,傳入的實參比函數(shù)聲明時指定的形參要少的話蜈彼,剩下的形參都會設(shè)為undefine
那么當(dāng)調(diào)用函數(shù)時筑辨,傳入的實參超過函數(shù)聲明時指定的形參的話,沒有辦法直接獲得其他形參幸逆,參數(shù)對象解決了這個問題棍辕,那么什么是參數(shù)對象呢暮现?
參數(shù)對象arguments
標(biāo)識符arguments是指向?qū)崊ο蟮囊茫瑢崊ο笫且粋€類數(shù)組對象楚昭,這樣通過數(shù)組下標(biāo)就可以訪問傳入函數(shù)的實參值
我們可以通過arguments[0]取到傳入實參的第一位栖袋,和真正的數(shù)組一樣arguments也有l(wèi)ength屬性,用來表示傳入實參的個數(shù)抚太。
arguments有一個重要的用處塘幅,讓函數(shù)可以操作任意數(shù)量的實參。下面的這個例子就利用arguments實現(xiàn)任意數(shù)量的數(shù)字去比較大小凭舶。
要注意的是晌块,實參對象并不是真正的數(shù)組,他不能夠使用數(shù)組的方法
實參對象的callee和caller屬性
callee是標(biāo)準(zhǔn)的帅霜,指向當(dāng)前正在執(zhí)行的函數(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)會定義一個內(nèi)部函數(shù) 2.內(nèi)部函數(shù)調(diào)用外部函數(shù)的變量 3.外部函數(shù)的變量不會回收
函數(shù)對象可以可以通過作用域鏈相互關(guān)聯(lián)起來钝尸,函數(shù)內(nèi)部的變量可以保存在函數(shù)內(nèi)作用域。
從技術(shù)角度講搂根,javascript的函數(shù)都是閉包珍促,定義大多數(shù)函數(shù)時的作用域鏈在調(diào)用函數(shù)時依然有效。
var scope="global scope";
function checkscope(){
var scope="local scope"
function f(){return scope};
retunr f();
}
checkscope();
這個例子很明顯剩愧,會輸出local scope猪叙,將f函數(shù)執(zhí)行后的結(jié)果返回。
做一點改變
var scope="global scope";
function checkscope(){
var scope="local scope"
function f(){return scope};
retunr f;
}
checkscope()();
答案是“l(fā)ocal scope”
書上的答案是這樣的
注意:函數(shù)定義時的作用域在函數(shù)執(zhí)行時依然有效
很多人會認為外部函數(shù)中定義的局部變量在函數(shù)返回后就不存在了仁卷,
其實原理是這樣的穴翩,
當(dāng)執(zhí)行流進入一個函數(shù)時,函數(shù)的環(huán)境(變量锦积,函數(shù))會被推進一個環(huán)境棧(我習(xí)慣叫運行棧)芒帕,在棧里面存的是對函數(shù)的一個引用,真正的函數(shù)是放在堆上面的丰介,當(dāng)函數(shù)執(zhí)行之后背蟆,正常的情況會將環(huán)境中的變量彈出來,斷開函數(shù)的引用哮幢,但是由于在外面checkscope變量指向了function(){return scope}這個函數(shù),而在函數(shù)里面還需要scope這個變量带膀,因此在這種情況下,編譯器會將scope捕捉(我理解就是復(fù)制了一份引用)橙垢,這樣我們就可以取到scope本砰,這就是在內(nèi)部函數(shù)里面調(diào)用外部函數(shù)的變量,外部函數(shù)的變量不會被釋放的原因钢悲。
閉包有什么用處呢点额?
看下面這個例子
for(var i=0;i<10;i++){
ali[i].onclick=function(){
alert(i);
}
}
想想這個例子舔株,當(dāng)我們點擊不同的li時,會如我們所愿的輸出當(dāng)前l(fā)i的下標(biāo)么还棱?答案當(dāng)然是不载慈,點擊不同的li時都會輸出10,因為for循環(huán)瞬間執(zhí)行完畢的時候珍手,每次i出了函數(shù)办铡,都將i從棧里面彈出去,在重新給i賦值琳要,i最后變成了10寡具,這個時候無論點擊什么,都會alert 10稚补,閉包就可以解決這個問題童叠,將變量保存下來
for(var i=0;i<10;i++){
(function(idx){
ali[i].onclick=function(){
alert(i);
}
})(i);
}
看一下這個閉包,通過這種方式就可以保存下來每一個下標(biāo)课幕,每個點擊事件都將當(dāng)前的i保留下來了厦坛,原理和上面那個例子是一樣的。