摘要:但是在調用函數值執行之后并沒有達到我們想要的效果。解析在這里我們為每一個的事件綁定了一個匿名函數,這個匿名函數就形成了一個閉包。這樣我們就為每個的事件的匿名函數,都保存下了自己閉包變量。
理解 Javascript中的this博客原址
基于不同的調用方式this的指向也會有所不同,調用方式大致有如下幾種:
調用方式 | 表達式 |
---|---|
構造函數調用 | new Foo(); |
對象方法調用 | o.method(); |
函數直接調用 | foo(); |
call/apply/bind | func.call(o); |
現在就來看看這些不同的調用模式,this的指向會有怎么樣的區別:
構造函數調用模式function Person(name,age){ this.name = name; this.age = age; this.sayName = function(){ console.info(this.name); }; } var allen = new Person("allen",12); console.info(allen);//{name: "allen", age: 12};...
通過這樣的代碼可以很清楚的的看出,構造函數 Person 內部的this指向被創建的調用對象 allen
對象方法調用通過上面的代碼很明顯我們創建了一個 allen 對象,其中有一個 sayName 方法, 直接打印 this.name ,現在我們就來看一下它會輸出什么。
allen.sayName();//allen
很明顯,這里函數中的this指向allen對象本身。
函數直接調用先來看一段代碼
function add(a, b) { return a + b; } var myNumber = { value: 1, double: function() { function handle() { this.value = add(this.value, this.value); } handle(); } }; console.info(myNumber.value);//1 myNumber.double(); console.info(myNumber.value);//1
解析: 首先我們定義了一個全局函數add用于加法運算,接著我們定義了一個對象,有一屬性value為1,還有一個方法的目的是讓value值乘以二。我們在函數內嵌套定義了一個函數handle,調用add方法并且執行。但是在調用函數值執行之后并沒有達到我們想要的效果。這是為什么呢?
如何你打開chrome調試工具并打下斷點會發現在handle函數內部的this會指向window!
由此可以發現,在函數內部創建的函數,在這個函數調用時,函數內部的this會指向window而不是外部的函數
下面就就可以看一下常見的兩個方案:
// 取消 handle函數的定義,直接在對象的方法中使用this double2: function() { this.value = add(this.value, this.value); }, // 使用變量保存外部函數的this。 double3: function() { var that = this; function handle() { that.value = add(that.value, that.value); } handle(); }使用 call/apply與bind 手動改變 this
先來看下面這樣一段代碼:
function Point(x, y){ this.x = x; this.y = y; } Point.prototype.move = function(stepX, stepY){ this.x += stepX; this.y += stepY; }; var p = new Point(0, 0); console.log(p);//{x: 0, y: 0} p.move(2,2); console.log(p);//{x: 2, y: 2} var circle = {x:1,y:1,r:1}; p.move.apply(circle, [2,1]); console.info(circle);//{x: 3, y: 2, r: 1}
我們使用Point構造函數可以創建出一個點,在其原型上有一個move方法可以使這個點坐標移動。
之后我們又創建circle對象,有x/y/r屬性(把它想象成一個圓),之后我們的需求是:將這個圓的圓心移動,我們就使用了apply來借用move方法,最終將圓移動了位置,最終效果如下圖:
function.prototype.apply/call
在上面我們可以看到能實現圓心移動的關鍵方法就是apply,大致解析如下,p.move是一個函數它的作用就是將一個點移動,然后我們通過apply方法把它借用給circle這個對象。將circle對象上的x/y屬性進行變更,分別加2和1,實現了圓心的移動。很明顯在這里 apply方法描述的就是一個借用的功能.
為什么會把apply/call放在一起說呢,因為他們的功能并沒有實質性的區別。只是在傳入參數的時候,apply需要將參數以數組的形式進行傳遞,而call是將需要傳入的參數一個一個跟在借用的對象后。下面一個小例子足以說明:
function sum(a, b) { return a + b; } function call1(num1, num2) { return sum.call(this, num1, num2); } function apply1(num1, num2) { // return sum.apply(this,[num1,num2]) return sum.apply(this, arguments);//利用函數的arguments對象 } console.info(call1(10, 20));//30 console.info(call1(5, 10));//15
可以看到我們在后兩個函數中,可以直接使用sum方法。
function.prototype.bind
這里來看看ES5引入的bind,又有什么不同,還是和上面類似的代碼
function Point(x, y){ this.x = x; this.y = y; } Point.prototype.move = function(stepX, stepY){ this.x += stepX; this.y += stepY; }; var p = new Point(0, 0); var circle = {x:1,y:1,r:1}; var circleMove = p.move.bind(circle,2,2); circleMove(); console.info(circle);//{x: 3, y: 3, r: 1} circleMove(3,4); console.info(circle);//{x: 5, y: 5, r: 1}
這里我使用了和 call 類似的調用方法,但是顯然 bind 和 call 不一樣,使用 bind 時,它會將我們綁定 this 后的函數引用返回,然后手動執行。可以看到的是,因為在這里我們綁定的對象的后面傳入了x/y兩個值,所以執行后坐標立即變化,并且在后來手動設置偏移量時也不再起到效果。
這樣的相比于apply立即執行的好處時,我們可以使用定時器,例如:setTimeout(circleMove,1000),延遲一秒后移動。
當然,每次只能移動固定的值也不是一件很好的事情,所以我們在使用 bind 的時候常常不會設置其默認參數, var circleMove2 = p.move.bind(circle,);,之后在執行函數時,再將參數傳入circleMove(3,4);,這樣就可以實現每次自定義偏移量了
這又引出了call/apply與bind的作用的另外一種說法: 擴充作用域
var color = "red"; var obj = {color:"blue"}; var obj1 = {color:"black"}; var obj2 = {color:"yellow"}; function showColor(){ console.info(this.color); } showColor();//red showColor.call(obj);//blue showColor.apply(obj1);//black showColor.bind(obj2)();//yellow
可以看到這里都實現了一樣的效果。值得說的是使用call、aplly()來擴充作用域的最大好處就是對象不需要與方法有任何耦合關系。
閉包 簡單定義先來看這樣的一段代碼,在chrome中找到Scope列表,可以看到,在作用域鏈上我們已經創建了一個閉包作用域!
(function() { var a = 0; function b() { a = 1; debugger; } b(); })();
閉包一個最簡單的定義就是:閉包就是說在函數內部定義了一個函數,然后這個函數調用到了父函數內的相關臨時變量,這些相關的臨時變量就會存入閉包作用域里面.這就是閉包最基礎的定義
保存變量下面就來看一下閉包的一個基本特性保存變量
function add(){ var i = 0; return function(){ console.info(i++); }; } var f = add(); f();//1 f();//2
我們定義了一個 add 方法,執行完畢后會返回一個函數,接著我們就把這個函數賦值給了變量f,由于 add 函數也是返回一個函數,在我們每一次執行f()的時候,它引用了add內的變量i,并且保存在自己的閉包作用域內,所以一直輸出執行的話,也會累加輸出。
小tips需要我們記住的是 每次函數調用的時候創建一個新的閉包:
var fun = add(); fun();//1 fun();//2
我們再來通過簡單的例子看看另一個注意的地方:
function test(){ var a = 0; var ff = function(){ console.info(a); }; a = 1214; return ff; } var b = test(); b();//1214
執行的結果是1214,從這里我們可以看到 閉包中局部變量是引用而非拷貝,其實這樣的改變發散開來我們就可以知道,即使在這里變量 a 未在函數 ff 之前定義,而是var a = 1214;我們同樣會得到同樣的結果
點擊li顯示對應編號案例解析其實上面這些我是很暈的,來看一個我們實際在前端編程過程中經常遇到的問題。
我們有一個列表,分別為1/2/3,我們的需求是在點擊不同的數字時,也能把它對應的編號彈出來。然后我們洋洋灑灑寫下了這樣的代碼:
一運行,發現懵了。怎么彈出來的都是3?不對啊,我不是用循環將值都傳進去了嗎?
如果你確實理解了上面的 閉包中局部變量是引用而非拷貝這一節中的兩個案例的話,那么就應該能了解一些。
解析:在這里我們為每一個li的onclick事件 綁定了一個匿名函數,這個匿名函數就形成了一個閉包。這些匿名函數并不立即執行,而是在點擊對應的li的時候才回去執行它。
而在這時就和上面的a = 1214;這個例子一樣,此時的循環早已結束,i 就等于oLi.length,在我們點擊不同的li時,閉包中引用的其實都是引用的同一個變量i自然彈出來的都是3,(這里不理解引用的都是用一個i的話,可以將alert(i);替換成alert(i++);,再到瀏覽器上去進行測試)
解決方案:
(function() { var oLi = document.getElementById("#list").getElementsByTagName("li"); for (var i = 0; i < oLi.length; i++) { oLi[i].onclick = (function(j) { return function(){ alert(j); }; })(i); } })(); /* (function() { var oLi = document.getElementById("#list").getElementsByTagName("li"); for (var i = 0; i < oLi.length; i++) { (function(j){ oLi[i].onclick= function(){ alert(j); }; })(i); } })(); */
可以看到這里給出了兩個簡單的寫法,但實際上除了寫法不同之外、閉包包含范圍、內容也不太一樣(有興趣的可以打開chrome調試工具看看),但是達到的效果是一樣的。這樣我們就為每個li的onclick事件的匿名函數,都保存下了自己閉包變量。就可以實現在點擊每個li的時候彈出對應的標號了。(還可以將alert(j);替換成alert(j++);欣賞一下點擊不同li時的累加效果)
當然如果你只是想要記住一些標號這么簡單的事情,其實還可以將變量保留于元素節點上,也能達到一樣的效果,如下:
(function() { var oLi = document.getElementById("#list").getElementsByTagName("li"); for (var i = 0; i < oLi.length; i++) { oLi[i].flag = i; oLi[i].onclick = function() { alert(this.flag); }; } })();
如果有錯誤之處,請指正。謝謝!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90848.html
摘要:申明與賦值立即執行的函數表達式,通過創建一個函數,并且立即執行,來構造一個新的域,從而控制的范圍。函數接受一個的形參,該參數是一個對象引用,并執行了。在最新的標準中,引入了一個新概念。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以加入winter的專欄...
摘要:申明與賦值立即執行的函數表達式,通過創建一個函數,并且立即執行,來構造一個新的域,從而控制的范圍。函數接受一個的形參,該參數是一個對象引用,并執行了。在最新的標準中,引入了一個新概念。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以加入winter的專欄...
摘要:申明與賦值立即執行的函數表達式,通過創建一個函數,并且立即執行,來構造一個新的域,從而控制的范圍。函數接受一個的形參,該參數是一個對象引用,并執行了。在最新的標準中,引入了一個新概念。 筆記說明 重學前端是程劭非(winter)【前手機淘寶前端負責人】在極客時間開的一個專欄,每天10分鐘,重構你的前端知識體系,筆者主要整理學習過程的一些要點筆記以及感悟,完整的可以加入winter的專欄...
摘要:在中函數是一等對象,它們不被聲明為任何東西的一部分,而所引用的對象稱為函數上下文并不是由聲明函數的方式決定的,而是由調用函數的方式決定的。更為準確的表述應該為當對象充當函數的調用函數上下文時,函數就充當了對象的方法。 引言:當理解了對象和函數的基本概念,你可能會發現,在JavaScript中有很多原以為理所當然(或盲目接受)的事情開始變得更有意義了。 1.JavaScript...
閱讀 2029·2021-11-08 13:14
閱讀 2939·2021-10-18 13:34
閱讀 2027·2021-09-23 11:21
閱讀 3589·2019-08-30 15:54
閱讀 1758·2019-08-30 15:54
閱讀 2929·2019-08-29 15:33
閱讀 2578·2019-08-29 14:01
閱讀 1945·2019-08-29 13:52