国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

JS工作中的小貼士之”閉包“與事件委托的”阻止冒泡“

2019-11-20 09:41:02
字體:
來源:轉載
供稿:網友

說下閉包的由來

function a() {var i = 0;function b() {console.log(i);}return b;}var c = a();c(); 

一般來說,當一個函數內部匿名函數用到了自己的變量,并且這個匿名函數被返回了,這就建立了一個閉包,比如上面的代碼

這個時候,就算a調用結束被銷毀,i也會存在不會消失當a定義時,js解釋器會將函數a的作用域鏈設置為定義a時所在環(huán)境當執(zhí)行a時,a會進入相應的執(zhí)行環(huán)境,執(zhí)行環(huán)境創(chuàng)建后才會有作用域scope屬性,然后創(chuàng)建一個活動對象,然后將其置為作用域鏈的頂端

現(xiàn)在a的作用域鏈就有a的活動對象以及window

然后為活動對象加入arguments屬性

這個時候a的返回函數b的引用給了c,b的作用域鏈包含a的活動對象引用,所以c可以訪問到a的活動對象,這個時候a返回后不會被GC

以上便是對閉包的簡單介紹,說多了就容易繞進去了,我們這里簡單結束,然后進入實際的場景加以說明

實際場景

同事的疑惑

之前一個同事讓我去看一個代碼:

var User = function (opts) {var scope = this;for (var k in opts) {scope['get' + k] = function () {return opts[k];};scope['set' + k] = function (v) {return opts[k] = v;};}};var u = new User({name: '測試',age: 11}); 

代碼本意很簡單,希望對傳入的對象生成get/set方法,但是他這里就遇到一個閉包問題:

導致這個問題的原因就是返回值內部使用的k永遠是“age”,這個k便是由于getXXX函數共享的活動對象,這里修改也比較簡單

var User = function (opts) {var scope = this;for (var k in opts) {(function (k) {scope['get' + k] = function () {return opts[k];};scope['set' + k] = function (v) {return opts[k] = v;};})(k);}};var u = new User({name: '測試',age: 11}); 

在for循環(huán)內部創(chuàng)建一個立即執(zhí)行函數,將k傳入,這個時候getXXX函數共享的就是各個匿名函數的“k”了

生成唯一ID

生成唯一ID也是閉包一個經典的使用方式

function getUUID() {var id = 0;return function () {return ++id;}}var uuid = getUUID(); 

這段代碼其實非常有意義,我們在瀏覽器中不停的執(zhí)行uuid()確實會得到不同的值,但是如果我們只使用getUUID()()的話每次值仍然一樣

導致這個問題的原因是,我們將getUUID執(zhí)行后的結果賦予uuid,這個時候uuid就保存對其中匿名函數的引用,而匿名函數保存著getUUID的活動對象,所以id一直未銷毀

而直接調用的話,每次都會重新生成活動對象,所以id是不能保存的

一段有意思的代碼

Util.tryUrl = function (url) {var iframe = document.createElement('iframe');iframe.height = 1;iframe.width = 1;iframe.frameBorder = 0;iframe.style.position = 'absolute';iframe.style.left = '-9999px';iframe.style.top = '-9999px';document.body.appendChild(iframe);Util.tryUrl = function (url) {iframe.src = url;};U.tryUrl(url);}; 

這段代碼十分有意思,當我們第一次調用時候會創(chuàng)建一個iframe對象,而第二次調用時候iframe對象就存在了,我們這里將代碼做一定簡化后

var getUUID = function () {var i = 0;getUUID = function () {return i++;};return getUUID();}; 

這樣調整后,其實并不存在返回函數,但是我們其實依然形成了閉包

事件委托與閉包

我們都知道jquery的on是采用的事件委托,但是真正了解什么事事件委托仍然要花一定功夫,于是我們這里來試試

閉包是事件委托實現(xiàn)的基石,我們最后就以事件委托深入學習下閉包結束今天閉包的學習吧

加入我們頁面下有如下dom結構

<input id="input" value="input" type="button" /><div id="div">我是div</div><span id="span">我是span</span><div id="wrapper"><input id="inner" value="我是inner" type="button"/></div> 

我們使用zepto的話是使用如下方式綁定事件

$.on('click', 'selector', fn) 

我們這里沒有zepto就自己簡單實現(xiàn)吧

事件委托原理

首先事件委托實現(xiàn)的基石是事件冒泡,我們在頁面的每次點擊最終都會冒泡到其父元素,所以我們在document處可以捕捉到所有的事件

知道了這個問題后,我們可以自己實現(xiàn)一個簡單的delegate事件綁定方式:

function delegate(selector, type, fn) {document.addEventListener(type, fn, false);}delegate('#input', 'click', function () {console.log('ttt');}); 

這段代碼是最簡單的實現(xiàn),首先我們無論點擊頁面什么地方都會執(zhí)行click事件,當然這顯然不是我們想要看到的情況,于是我們做處理,讓每次點擊時候觸發(fā)他應有的事件

這里有幾個問題比較尖銳:

① 既然我們事件是綁定到document上面,那么我怎么知道我現(xiàn)在是點擊的什么元素呢

② 就算我能根據e.target獲取當前點擊元素,但是我怎么知道是哪個元素具有事件呢

③ 就算我能根據selector確定當前點擊的哪個元素需要執(zhí)行事件,但是我怎么找得到是哪個事件呢

如果能解決以上問題的話,我們后面的流程就比較簡單了

確定點擊元素是否觸發(fā)事件

首先,我們點擊時候可以使用e.target獲取當前點擊元素,然后再根據selector依次尋找其父DOM,如果找得到就應該觸發(fā)事件

因為這些都是要在觸發(fā)時候才能決定,所以我們需要重寫其fn回調函數,于是簡單操作后:

var arr = [];var slice = arr.slice;var extend = function (src, obj) {var o = {};for (var k in src) {o[k] = src[k];}for (var k in obj) {o[k] = obj[k];}return o;};function delegate(selector, type, fn) {var callback = fn;var handler = function (e) {//選擇器找到的元素var selectorEl = document.querySelector(selector);//當前點擊元素var el = e.target;//確定選擇器找到的元素是否包含當前點擊元素,如果包含就應該觸發(fā)事件/*************注意,此處只是簡單實現(xiàn),實際應用會有許多判斷*************/if (selectorEl.contains(el)) {var evt = extend(e, { currentTarget: selectorEl });evt = [evt].concat(slice.call(arguments, 1));callback.apply(selectorEl, evt);var s = '';}var s = '';};document.addEventListener(type, handler, false);} 

于是我們可以展開調用了:

delegate('#input', 'click', function () {console.log('input');});delegate('#div', 'click', function () {console.log('div');});delegate('#wrapper', 'click', function () {console.log('wrapper');});delegate('#span', 'click', function () {console.log('span');});delegate('#inner', 'click', function () {console.log('inner');}); 

我們這里來簡單解析下整個程序

① 我們調用delegate為body增加事件

② 在具體綁定時候,我們將其中的回調給重寫了

③ 在具體點擊時候(綁定幾次事件實際就會觸發(fā)幾次click),會獲取當前元素,查看其選擇器搜索的元素是否包含他,如果包含的話便觸發(fā)事件

④ 由于這里每次注冊時候都會形成一個閉包,傳入的callback被維護起來了,所以每次調用便能找到自己的回調函數(這里對閉包理解很有幫助)

⑤ 最后重寫event句柄的currentTarget,于是一次事件委托就結束了

PS:我這里實現(xiàn)還有問題的,比如在event的處理上就有問題,但是作為demo的話我便不去關注了,有興趣的朋友自己去看zepto實現(xiàn)吧

事件委托的問題

事件委托可以提高效率但是有一個比較煩的事情就是阻止冒泡沒用

拿上面代碼來說,有一個inner元素和一個wrapper元素,他們是互相包裹關系

但是其執(zhí)行順序并不是先內再外的事件冒泡順序,因為事件全部綁定到了document上面,所以這里執(zhí)行順序便是以其注冊順序所決定

這里有一個問題便是如何“阻止冒泡”

在inner處完了執(zhí)行

e.stopImmediatePropagation() 

是可以達到目的的,但是仍然要求inner元素必須注冊到之前

除此之外,就只給這種會嵌套的元素綁定一個事件,又e.target決定到底執(zhí)行哪個事件,具體各位自己斟酌

以上問題在使用backbone可能實際會遇到

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 铜山县| 介休市| 囊谦县| 民乐县| 陵川县| 华宁县| 牡丹江市| 平顺县| 广南县| 扎兰屯市| 大竹县| 遵化市| 绥江县| 高要市| 宁南县| 红桥区| 宁陕县| 福清市| 伊金霍洛旗| 乐昌市| 汾阳市| 宜章县| 兴城市| 漠河县| 扎囊县| 威宁| 东乌珠穆沁旗| 桃园县| 东台市| 茂名市| 建湖县| 正镶白旗| 连云港市| SHOW| 林芝县| 九江县| 隆德县| 逊克县| 齐河县| 临桂县| 洞口县|