摘要:除語法不同外,兩者的區別在于解析器讀取的順序。解析器會事先讀取函數聲明,即使你把函數聲明放在代碼的末端也沒關系。修改對象將可能導致命名參數失去意義。其實除允許匿名函數遞歸調用自身外,并沒有什么太大用處。
javaScript因為其語法松散,導致函數(尤其是this)看似簡單,其實里面花頭很多。本篇介紹一下JavaScript函數及其調用方法。
? 函數聲明和函數表達式
? arguments
? this
? this補充說明
函數聲明和函數表達式
JavaScript里對象字面量產生的對象將被連接到Object.prototype,函數對象將被連接到Function.prototype(但該對象本身也連接到Object.prototype)。先看一下函數聲明和函數表達式(分匿名和命名):
function count(a,b){ return a*b; } //函數聲明 var d1 = function(n) { return n*2; }; //匿名函數表達式 var d2 = function double(n) { return n*2; }; //命名函數表達式 console.log(count(3,4)); //12 console.log(d1(3)); //6 console.log(d2(3)); //6 console.log(double(3)); //error,double未定義
上面代碼可以看出函數聲明和函數表達式在后續的調用中,效果是沒有差別的。除語法不同外,兩者的區別在于JS解析器讀取的順序。
解析器會事先讀取函數聲明,即使你把函數聲明放在代碼的末端也沒關系。而對于函數表達式,同其它基本類型的變量一樣,只有在執行到該行語句時才解析。因此用函數表達式時,必須確保它在調用語句之前,否則會報錯。
再看匿名和命名函數表達式的區別。上例中命名函數表達式將函數綁定到變量d2上,而非變量double上,因此double(3);會出現未定義error。
那命名函數表達式有什么用呢?比如上面的變量double有什么用呢?函數名double可用于在函數內部做遞歸,但可惜仍舊沒必要,因為變量d2同樣也可以在函數內部遞歸。因此命名函數表達式真正的作用在于調試,JavaScript環境提供對Error對象的棧追蹤功能,可以用double進行棧追蹤。
但命名函數表達式仍舊有很多問題,類似with一樣。因此通常推薦用匿名函數表達式,不推薦用命名函數表達式:
var d1 = function(n) { return n*2; }; //Yes,推薦 var d2 = function double(n) { return n*2; }; //No,不推薦
arguments
每個函數都接受2個附加參數:this和arguments。先看arguments。JS的函數參數其實就是個類似數組的arguments對象,是對形參的一個映射,但是值是通過索引來獲取的。因此JS的函數天然支持可變參數。
arguments對象看似像數組,但請不要使用arguments.shift()等方法來修改arguments。修改arguments對象將可能導致命名參數失去意義。
例如person(name, age),參數name是arguments[0]的別名,age是arguments[1]的別名,如果用shift移除arguments后,name仍舊是arguments[0]的別名,age仍舊是arguments[1]的別名,函數開始失控。
因此,如果你無論如何要修改arguments,需要先將arguments對象轉化為真正的數組:
var args = [].slice.call(arguments);
之后對args對象進行shift()等操作。這也常見于獲取可變參數值,同樣需要上述那樣將arguments對象轉化為真正的數組。
另外每個arguments對象都有兩個額外的屬性:arguments.callee和arguments.caller。前者指向使用該arguments對象被調用的函數。后者指向調用該arguments對象的函數。
其實arguments.callee除允許匿名函數遞歸調用自身外,并沒有什么太大用處。但可惜用函數名也能實現遞歸,所以它真沒什么用處:
//用arguments.callee來遞歸 var factorial = (function(n) { return (n <= 1) ? 1 : (n * arguments.callee(n - 1)); //遞歸 });
//但也可以直接用函數名來遞歸
function factorial(n) {
return (n <= 1) ? 1 : (n * factorial(n - 1));
}
用arguments.caller可以跟蹤棧信息,但它不可靠,如果某函數在棧中出現了不止一次,很容易陷入死循環,大多數環境已經移除了此特性。
JS嚴格模式下禁止使用arguments.callee和arguments.caller,因此這兩個屬性就不多廢話了。
this
arguments介紹完后,再來看看this。在JS中this取決于調用的方式,不同的函數調用方式,this綁定的對象也不同。有4種調用方式:
? 方法調用
? 函數調用
? 構造器調用
? apply / call / bind調用
方法調用:當函數作為對象方法時,函數里的this被綁定到該var myNum = {
value: 0, increment: function(inc) { //函數作為對象方法 this.value += inc; }
};
myNum.increment(2);
console.log(myNum.value); //2,this被綁定到myNum用:函數非對象方法時,this被綁定到全局對象window。這其實是語言設計上的一個錯誤(或曰特性),導致this不能調用內部函數。要調用內部函數,可以將that = this保存起來。
function double(n){ return n*2; } //普通函數,this綁定到全局對象window
//錯誤的例子
myNum.count = function() {
var helper = function() { this.value = double(this.value); }; helper();
}
myNum.count();
console.log(myNum.value); //value不變
//正確的例子:
myNum.count = function() {
var that = this; var helper = function() { that.value = double(that.value); //現在參數是myNum.value }; helper();
}
myNum.count();
console.log(myNum.value); //4
錯誤的例子中,期望this綁定的是對象myNum,但由于double是普通函數,因此this綁定的是window,而window顯然沒有value。即helper里this是window,因此double(this.value);不會被執行。最終myNum的value值并沒有變。
正確的例子在對象myNum方法里,this綁定的是myNum對象,因此先用that將this保存起來。然后在內部傳遞的都是that,回避了helper函數內this發生改變的問題。這里寫代碼片
構造函數調用:用new調用構造函數,會先創建一個連接到構造函數的prototype的新對象,再將this會綁定到該新對象
var Name = function(n) { this.name = n; } Name.prototype.getName = function() { return this.name; } var myName = new Name("Jack"); //this綁定到myName對象 console.log(myName.getName()); //Jack apply / call / bind調用:允許我們自己綁定想要的this var friend = { name: "Betty" }; console.log(Name.prototype.getName.apply(friend)); //Betty console.log(Name.prototype.getName.call(friend)); //Betty console.log(Name.prototype.getName.bind(friend)()); //Betty
this補充說明
這一節并無任何新的內容,只不過對this進一步補充說明一下。我們知道對象都有prototype俗稱原型對象。那prototype里的this綁定誰呢?其實原則沒有變,從上面構造函數調用的例子就能看出this仍舊是綁定調用的對象。
為了更清晰一點,將上面構造函數調用的例子稍微改一下:
var Name = function() {}; Name.prototype = { name: "(not set)", setName: function(n) { this.name = n; } } var myName = new Name(); console.log(myName.name); //(not set) console.log(myName.hasOwnProperty("name")); //false console.log(myName.hasOwnProperty("setName")); //false myName.setName("Jack"); console.log(myName.name); //Jack console.log(myName.hasOwnProperty("name")); //true console.log(myName.hasOwnProperty("setName")); //false
先看第一段結果代碼,Name本身沒有任何屬性,name和setName是在它的原型prototype中定義的。因此用hasOwnProperty來檢查全是false。這與我們的預想完全一致,沒什么可奇怪的。
再看第二段結果代碼,由于執行了myName.setName("Jack");。原型prototype中的this不是綁定原型對象,而是綁定調用的對象。即setName中的this綁定的是對象myName,會給對象增加一個name屬性。所以hasOwnProperty("name")會為true。
self補充說明
這個非常簡單。我們知道,打開任何一個網頁,瀏覽器會首先創建一個窗口,這個窗口就是一個window對象,也是js運行所依附的全局環境對象和全局作用域對象。self 指窗口本身,它返回的對象跟window對象是一模一樣的。也正因為如此,window對象的常用方法和函數都可以用self代替window。舉個例子,常見的寫法如“self.close();”,把它放在標記中:“關閉窗口”,單擊“關閉窗口”鏈接,當前頁面關閉。
明白這些原理后,再回過頭看看以前不明白的代碼里this,that,self等就輕松多了。
更多資源上:去轉盤;或者加我的QQ群參與js,css的討論學習(QQ群:512245829)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80158.html
在上一篇文章(《javascript高級程序設計》筆記:Function類型)中稍微提及了一下函數對象的屬性—this,在這篇文章中有深入的說明: 函數的三種簡單調用模式 1 函數模式 定義的函數,如果單獨調用,不將其與任何對象關聯,那么就是函數調用模式 function fn(num1, num2) { console.log(this); } // 直接在全局調用 fn();// w...
摘要:目錄函數的聲明函數的屬性和方法函數的作用域閉包知識點小結關于函數,可以從以下個方面去理解首先,數據類型上看函數在中是一種數據類型,是對象的一種其次,從功能上看函數本質上是一段反復調用的代碼塊最后,從地位上看函數在中和其他基本數據類型一樣,可 目錄 1.函數的聲明 2.函數的屬性和方法 3.函數的作用域 4.閉包知識點 5.小結 關于函數,可以從以下3個方面去理解:首先,數據類型上看:...
摘要:基本操作理解寫在前面在面向對象的語言中,關鍵字的含義是明確且具體的,即指代當前對象。一般在編譯期確定下來,或稱為編譯期綁定。全局范圍內當在全部范圍內使用,它將會指向全局對象。輸出瀏覽器中運行的腳本,這個全局對象是。 js基本操作-this理解 寫在前面 在面向對象的語言中,this關鍵字的含義是明確且具體的,即指代當前對象。一般在編譯期確定下來,或稱為編譯期綁定。而在 JavaScr...
摘要:理解文章中已經比較全面的分析了在中的指向問題,用一句話來總結就是的指向一定是在執行時決定的,指向被調用函數的對象。與和直接執行原函數不同的是,返回的是一個新函數。這個新函數包裹了原函數,并且綁定了的指向為傳入的。 理解 JavaScript this 文章中已經比較全面的分析了 this 在 JavaScript 中的指向問題,用一句話來總結就是:this 的指向一定是在執行時決定的,...
摘要:延長作用域鏈下面兩種語句可以在作用域鏈的前端臨時增加一個變量對象以延長作用域鏈, 問題 今天看筆記發現自己之前記了一個關于同名標識符優先級的內容,具體是下面這樣的: 形參優先級高于當前函數名,低于內部函數名 形參優先級高于arguments 形參優先級高于只聲明卻未賦值的局部變量,但是低于聲明且賦值的局部變量 函數和變量都會聲明提升,函數名和變量名同名時,函數名的優先級要高。執行代...
閱讀 3280·2023-04-26 02:09
閱讀 2574·2021-11-24 09:39
閱讀 3268·2021-11-16 11:52
閱讀 3614·2021-10-26 09:50
閱讀 2771·2021-10-08 10:05
閱讀 2456·2021-09-22 15:25
閱讀 3299·2019-08-30 13:14
閱讀 908·2019-08-29 17:06