標籤:for 導致 argument 屬性 fun out tool 條件 copy
知識內容:
1.預備知識 - 函數運算式
2.匿名函數
3.閉包
一、函數運算式
1.定義函數的兩種方式
函式宣告:
1 function func(arg0, arg1, arg2){2 // 函數體 3 }
函數運算式:
1 var func = function (arg0, arg1, arg2){2 // 函數體 3 }
2.注意事項
函數運算式使用前必須賦值!像下面的代碼是錯誤的:
1 say()2 var say = function(){3 console.log("123")4 }
函數運算式可以作為一個普通變數在分支中根據條件來賦值:
1 // 下面的代碼將根據不同的condition 將不同的函數運算式賦給sayHi 2 var sayHi 3 var condition = true // Hi! 4 // var condition = false // Hello! 5 6 if(condition){ 7 sayHi = function(){ 8 console.log("Hi!") 9 }10 }11 else{12 sayHi = function(){13 console.log("Hello!")14 }15 }16 17 sayHi()
上述代碼不同的運行效果:
能建立函數賦值給變數,當然也可以把函數作為其他函數的傳回值返回
二、匿名函數
1.什麼是匿名函數
匿名函數:一般用到匿名函數的時候都是立即執行的。通常叫做自執行匿名函數或者自調用匿名函數。常用來構建沙箱模式,作用是開闢封閉的變數範圍環境,在多人聯合工作中,合并js代碼後,不會出現相同變數互相衝突的問題
2.匿名函數的詳細寫法
匿名函數的詳細寫法有以下兩種,推薦使用第一種:
1 (function(){ 2 console.log("我是匿名方式1");3 })(); // 我是匿名方式14 5 (function(){ 6 console.log("我是匿名方式2");7 }()); // 我是匿名方式28 9 console.log((function(){}).name); // name為空白
註:實際上,立即執行的匿名函數並不是函數,因為已經執行過了,所以它是一個結果,這個結果是對當前這個匿名函數執行結果的一個引用(函數執行預設return undefined)。這個結果可以是一個字串、數字或者null/false/true,也可以是對象、數組或者一個函數(對象和數組都可以包含函數),當返回的結果包含函數時,這個立即執行的匿名函數所返回的結果就是典型的閉包了
三、閉包
1.什麼是閉包
什麼是閉包:閉包指有權訪問另一個函數範圍中的變數的函數,官方對閉包的解釋是:一個擁有許多變數和綁定了這些變數的環境的運算式(通常是一個函數),因而這些變數也是該運算式的一部分
- 作為一個函數變數的一個引用,當函數返回時,其處於啟用狀態
- 一個閉包就是當一個函數返回時,一個沒有釋放資源的棧區
簡單說Javascript允許使用內建函式---即函數定義和函數運算式位於另一個函數的函數體內。而且這些內建函式可以訪問它們所在的外部函數中聲明的所有局部變數、參數和聲明的其他內建函式。當其中一個這樣的內建函式在包含它們的外部函數之外被調用時,就會形成閉包
2.閉包的寫法
| 12345678910 |
function a(){ var n = 0; function inc() { n++; console.log(n); } inc(); inc(); }a(); //控制台輸出1,再輸出2 |
簡單吧。再來看一段代碼:
| 12345678910 |
function a(){ var n = 0; this.inc = function () { n++; console.log(n); };}var c = new a();c.inc(); //控制台輸出1c.inc(); //控制台輸出2 |
簡單吧。
什麼是閉包?這就是閉包!
有權訪問另一個函數範圍內變數的函數都是閉包。這裡 inc 函數訪問了建構函式 a 裡面的變數 n,所以形成了一個閉包。
再來看一段代碼:
| 1234567891011 |
function a(){ var n = 0; function inc(){ n++; console.log(n); } return inc;}var c = a();c(); //控制台輸出1c(); //控制台輸出2 |
看看是怎麼執行的:
var c = couter(),這一句 couter()返回的是函數 inc,那這句等同於 var c = inc;
c(),這一句等同於 inc(); 注意,函數名只是一個標識(指向函數的指標),而()才是執行函數。
後面三句翻譯過來就是: var c = inc; inc(); inc();,跟第一段代碼有區別嗎? 沒有
這樣寫的原因:
我們知道,js的每個函數都是一個個小黑屋,它可以擷取外界資訊,但是外界卻無法直接看到裡面的內容。將變數 n 放進小黑屋裡,除了 inc 函數之外,沒有其他辦法能接觸到變數 n,而且在函數 a 外定義同名的變數 n 也是互不影響的,這就是所謂的增強“封裝性”。
而之所以要用 return 返回函數標識 inc,是因為在 a 函數外部無法直接調用 inc 函數,所以 return inc 與外部聯絡起來,代碼 2 中的 this 也是將 inc 與外部聯絡起來而已
3.閉包的作用
(1)閉包實現匿名自執行函數見下面的匿名函數中的詳細介紹
(2)閉包實現封裝
JavaScript中沒有私人成員的概念,所有對象屬性都是公有的,不過卻有一個私人變數的概念。簡單說任何定義在函數中的變數都可以認為是私人變數,因為不能在函數的外部存取這些變數
私人變數包括:
- 函數的參數
- 函數的局部變數
- 在函數內部定義的其他函數
另外利用閉包原理,我們可以建立用於訪問私人變數的公有方法:在函數內部建立一個閉包,閉包通過範圍鏈訪問這些變數,從而實現外部無法直接存取內部變數只能通過某些方法來訪問,這樣就實現了封裝
1 var person = function(){ 2 // 變數範圍為函數內部,外部無法訪問 3 var name = "default"; 4 5 return { 6 getName : function(){ 7 return name; 8 }, 9 setName : function(newName){ 10 name = newName; 11 } 12 } 13 }(); 14 15 print(person.name); // 直接存取,結果為undefined 16 print(person.getName()); 17 person.setName("wyb"); 18 print(person.getName()); 19
(3)閉包實作類別和繼承
1 function Person(){ 2 var name = "default"; 3 4 return { 5 getName : function(){ 6 return name; 7 }, 8 setName : function(newName){ 9 name = newName; 10 } 11 } 12 }; 13 14 var p = new Person();15 p.setName("Tom");16 alert(p.getName());17 18 var s= function(){};19 // 繼承自Person20 s.prototype = new Person();21 // 添加私人方法22 s.prototype.Say = function(){23 alert("Hello,my name is s");24 };25 var j = new s();26 j.setName("s");27 j.Say();28 alert(j.getName());
4.閉包注意事項
(1)閉包與變數
閉包只能取得包含函數中任何變數的最後一個值,閉包所儲存的是整個變數對象,而不是某個特殊的變數
1 function createFunctions(){ 2 var result = new Array() 3 4 for (var i=0; i < 10; i++){ 5 result[i] = function(){ 6 return i; 7 } 8 } 9 10 return result11 } 12 13 var foo = createFunction()
結果foo中並不是預料中的那樣,而是10,這是為什麼呢?僅僅聲明某一個函數,引擎並不會對函數內部的任何變數進行尋找或賦值操作。只會對函數內部的語法錯誤進行檢查(如果往內建函式加上非法語句,那麼不用調用也會報錯),也就是說在返回result之前
result並未執行,返回之後再執行時匿名函數中的所有i引用的都是同一個變數(最後一個值10),故每個函數內部i的值為10
詳細說明看這一篇文章:https://www.cnblogs.com/kindofblue/p/4907847.html
通過建立另一個匿名函數強制讓閉包的行為符合預期:
1 function createFunctions(){ 2 var result = new Array() 3 4 for (var i=0; i < 10; i++){ 5 result[i] = function(num){ 6 return function(){ 7 return num 8 } 9 }(i)10 }11 12 return result13 }
(2)閉包中的this對象
關於this對象:
- this對象是在運行時基於函數的執行環境綁定的
- 在全域函數中this等價於window,當函數被作為某個對象的方法調用時this等價於那個對象
- 在閉包中使用this對象可能會導致一些問題
1 var name = "The Window"; 2 var object = { 3 name: "My object", 4 getNameFunc: function() { 5 return function() { 6 return this.name; 7 }; 8 } 9 }10 alert(object.getNameFunc()()); // "The Window"
為什麼最後的結果是"The Window"而不是object裡面的name"My object"呢?
首先,要理解函數作為函數調用和函數作為方法調用,我們把最後的一句拆成兩個步驟執行:
var first = object.getNameFunc();var second = first();
其中第一步,獲得的first為返回的匿名函數,此時的getNameFunc()作為object的方法調用,如果在getNameFunc()中使用this,此時的this指向的是object對象。
第二步,調用first函數,可以很清楚的發現,此時調用first函數,first函數沒有在對象中調用,因此是作為函數調用的,是在全域範圍下,因此first函數中的this指向的是window。
再看下面這句話:
為什麼匿名函數沒有取得其包含範圍(外部範圍)的this對象呢?
每個函數被調用時,其使用中的物件都會自動取得兩個特殊變數:this和arguments。內建函式在搜尋這兩個變數時,只會搜尋到其使用中的物件為止,因此永遠不可能直接存取外部函數中的這兩個變數。 《Javascript進階程式設計》
那麼,如何獲得外部範圍中的this呢?可以把外部範圍中的this儲存在閉包可以訪問到的變數裡。如下:
1 var name = "The Window"; 2 var object = { 3 name: "My object", 4 getNameFunc: function() { 5 var that = this; // 將getNameFunc()的this儲存在that變數中 6 var age = 15; 7 return function() { 8 return that.name; 9 };10 }11 }12 alert(object.getNameFunc()()); // "My object"
(3)模仿塊級範圍
在JavaScript中沒有塊級範圍,JavaScript從來不會告訴你是否多次同時聲明同一個變數,遇到這種情況它只會對後續的聲明視而不見,不過我們可以使用匿名函數和閉包來實現塊級範圍
用作塊級範圍的匿名函數如下:
1 (function(){2 // 這裡是塊級範圍 3 }) ();
上述匿名函數也等價於這種形式:
1 var someFunction = function(){2 // 這裡是塊級範圍 3 }4 someFunction()
關於匿名函數及模仿塊級範圍的注意事項:
- 在匿名函數中定義的任何變數都會在執行結束時被銷毀
- 在私人範圍(匿名函數)中可以訪問外層函數的變數是因為這個匿名函數是一個閉包,它能夠訪問包含範圍中的所有變數
關於以上兩點,範例程式碼如下:
1 function func(count){2 (function (){3 for(var i=0; i < count; i++){4 console.log(i) 5 }6 })(); 7 8 console.log(i) // 導致一個錯誤! 9 }
JavaScript中的閉包與匿名函數