Let's talk about the origin of the closure
function a() {var i = 0;function b() {console.log(i);}return b;}var c = a();c();Generally speaking, when an anonymous function inside a function uses its own variable and the anonymous function is returned, a closure is created, such as the above code.
At this time, even if the end of the a call is destroyed, i will exist and will not disappear. When a is defined, the js interpreter will set the scope chain of function a to the environment in which it is defined. When a is executed, a will enter the corresponding execution environment. Only after the execution environment is created will there be a scope scope attribute, and then create an active object, and then set it to the top of the scope chain.
Now the scope chain of a has the active object of a and window
Then add arguments attribute to the active object
At this time, the reference to the return function b of a is given to c. The scope chain of b contains the active object reference of a, so c can access the active object of a. At this time, a will not be GC after returning
The above is a brief introduction to closures. If you say too much, it will be easy to get around. Let's end it briefly here and then enter the actual scene to explain it.
Actual scene
Colleagues' doubts
A colleague of mine asked me to read a code:
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: 'test',age: 11});The code is very simple, and I hope to generate a get/set method for the incoming object, but he encountered a closure problem here:
The reason for this problem is that the k used internally in the return value is always "age". This k is due to the active object shared by the getXXX function, so it is relatively simple to modify it here.
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: 'test',age: 11});Create an immediate execution function inside the for loop and pass k in. At this time, the getXXX function shares the "k" of each anonymous function.
Generate a unique ID
Generating a unique ID is also a classic way to use closures.
function getUUID() {var id = 0;return function () {return ++id;}}var uuid = getUUID();This code is actually very meaningful. We constantly execute uuid() in the browser and will indeed get different values, but if we only use getUUID()(), the value will still be the same every time.
The reason for this problem is that we assign the result after the getUUID execution to uuid. At this time, uuid saves the reference to the anonymous function, and the anonymous function saves the active object of getUUID, so the id has not been destroyed.
If you call it directly, the active object will be regenerated every time, so the id cannot be saved.
An interesting piece of code
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);};This code is very interesting. When we call it for the first time, we will create an iframe object, and the iframe object exists when the second time. We will simplify the code here.
var getUUID = function () {var i = 0;getUUID = function () {return i++;};return getUUID();};After this adjustment, there is actually no return function, but we still form a closure
Event delegation and closure
We all know that jquery's on uses event delegation, but it still takes a lot of effort to truly understand what event delegation is, so let's try it here
Closures are the cornerstone of the implementation of event delegation. Finally, let’s end today’s closure study with event delegation.
Add to our page to the following dom structure
<input id="input" value="input" type="button" /><div id="div">I am div</div><span id="span">I am span</span><div id="wrapper"><input id="inner" value="I am inner" type="button"/></div>
If we use zepto, we use the following method to bind events
$.on('click', 'selector', fn)We don't have zepto here, just implement it simply by ourselves
The principle of event delegation
First of all, the cornerstone of event delegate implementation is event bubbles. Each click on the page will eventually bubble to its parent element, so we can catch all events at the document.
After knowing this problem, we can implement a simple delegate event binding method ourselves:
function delegate(selector, type, fn) {document.addEventListener(type, fn, false);}delegate('#input', 'click', function () {console.log('ttt');});This code is the simplest implementation. First of all, we will execute the click event no matter where we click the page. Of course, this is obviously not the situation we want to see, so we do the processing to trigger the event it should have whenever clicked.
Here are a few more serious questions:
① Since our event is bound to the document, how do I know what element I am clicking now?
② Even if I can get the current click element based on e.target, how do I know which element has the event?
③ Even if I can determine which element of the current click needs to execute the event based on the selector, how can I find which event it is
If the above problems can be solved, our subsequent process will be relatively simple
Determine whether the click element triggers the event
First, when we click, we can use e.target to get the current click element, and then search for its parent DOM in turn according to the selector. If it is found, the event should be triggered.
Because these can only be decided when triggered, we need to rewrite its fn callback function, so after a simple operation:
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) {//Element found by the selector var selectorEl = document.querySelector(selector);//The current click element var el = e.target;//Determine whether the element found by the selector contains the current click element. If it is included, the event should be triggered/*************************** Note that this is just a simple implementation, and there will be many judgments in actual applications**********************/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);}So we can expand the call:
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');});Let's briefly analyze the entire program
① We call delegate to add events to the body
② When binding, we rewritten the callbacks in it.
③ When clicking (the number of times the binding event will actually trigger a click), the current element will be obtained to check whether the element searched by its selector contains it. If it contains it, the event will be triggered.
④ Since a closure is formed every time you register here, the incoming callback is maintained, so you can find your own callback function every time you call it (it is very helpful for understanding the closure here)
⑤ Finally, rewrite the currentTarget of the event handle, so an event delegation ends
PS: I have problems with the implementation here, such as the processing of event, but as a demo, I won’t pay attention to it. Interested friends can check out the zepto implementation by themselves.
Issues of event delegation
Event delegation can improve efficiency, but one more annoying thing is that it is useless to stop bubbles.
Take the above code as an example. There is an inner element and a wrapper element. They wrap each other's relationship.
However, its execution order is not the order of event bubbles inside and out, because all events are bound to the document, so the execution order here is determined by its registration order
Here is a question: how to "stop bubbles"
Completed execution in inner
e.stopImmediatePropagation()
It can achieve the purpose, but it still requires that the inner element must be registered before
In addition, only one event is bound to such nested elements, and e.target decides which event to execute. For details, please consider it yourself.
The above problems may actually be encountered when using backbone