Function binding is probably the thing you pay the least attention to when you start using JavaScript, but when you realize that you need a solution to how to maintain this context in another function, you What you really need is Function.prototype.bind(), but you may still not realize this.
The first time you encounter this problem, you may be tempted to set this to a variable so that you can continue to refer to it after changing the context. Many people choose to use self, _this or context as variable names (some also use that). These methods are all useful and of course there is nothing wrong with them. But there is a better, more specialized way.
What is the real problem we need to solve?
In the following example code, we can legitimately cache the context into a variable:
var myObj = { specialFunction: function () { }, anotherSpecialFunction: function () { }, getAsyncData: function (cb) { cb(); }, render: function () { var that = this; this.getAsyncData(function () { that.specialFunction(); that.anotherSpecialFunction(); }); } }; myObj.render();
If we simply use this.specialFunction() to call the method, we will receive the following error:
Uncaught TypeError: Object [object global] has no method 'specialFunction'
We need to keep a reference to the myObj object context for the execution of the callback function. Calling that.specialFunction() allows us to maintain scope context and execute our function correctly. However, there is a simpler and cleaner way using Function.prototype.bind():
render: function () { this.getAsyncData(function () { this.specialFunction(); this.anotherSpecialFunction(); }.bind(this)); }
What did we just do?
.bind() creates a function. When this function is called, its this keyword will be set to the value passed in (here refers to the parameter passed in when calling bind()). Therefore, we pass in the desired context, this (actually myObj), to the .bind() function. Then, when the callback function is executed, this will point to the myObj object.
If you are interested in knowing what Function.prototype.bind() looks like internally and how it works, here is a very simple example:
Function.prototype.bind = function (scope) { var fn = this; return function () { return fn.apply(scope); }; }
There is also a very simple use case:
var foo = { x: 3 } var bar = function(){ console.log(this.x); } bar(); // undefined var boundFunc = bar.bind(foo); boundFunc(); // 3
We create a new function, and when it is executed, its this will be set to foo - not the global scope like when we called bar().
Browser support
Browser Version support
Chrome 7
Firefox (Gecko) 4.0 (2)
Internet Explorer 9
Opera 11.60
Safari 5.1.4
As you can see, Function.prototype.bind is unfortunately not supported in IE8 and below, so if you don't have a fallback, you may run into problems.
Fortunately, the Mozilla Developer Network (a great resource library) provides a solid alternative for browsers that do not implement the .bind() method themselves:
if (!Function.prototype.bind) { Function.prototype.bind = function (oThis) { if (typeof this !== "function") { // closest thing possible to the ECMAScript 5 internal IsCallable function throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; }
Applicable modes
When learning technical points, I found that what is useful is not only to thoroughly study and understand the concept, but also to see if there is any application of it in the work at hand, or something close to it. I hope that some of the examples below apply to your code or solve problems you're facing.
CLICK HANDLERS (click handler function)
One use is to record a click event (or perform an action after a click), which may require us to store some information in an object, such as:
var logger = { x: 0, updateCount: function(){ this.x++; console.log(this.x); } }
We may specify the click processing function in the following way, and then call the updateCount() method in the logger object.
document.querySelector('button').addEventListener('click', function(){ logger.updateCount(); });
But we must create an extra anonymous function to ensure that the this keyword in the updateCount() function has the correct value.
We can use the following cleaner way:
document.querySelector('button').addEventListener('click', logger.updateCount.bind(logger));
We cleverly use the convenient .bind() function to create a new function and bind its scope to the logger object.
SETTIMEOUT
If you have used a template engine (such as Handlebars) or especially some of the MV* frameworks (from my experience I can only talk about Backbone.js), then you may know the discussion below about accessing the new DOM immediately after rendering the template Problems encountered when using nodes.
Suppose we want to instantiate a jQuery plugin:
var myView = { template: '/* 一个包含 <select /> 的模板字符串*/', $el: $('#content'), afterRender: function () { this.$el.find('select').myPlugin(); }, render: function () { this.$el.html(this.template()); this.afterRender(); } } myView.render();
You may find that it works - but not every time because there are problems. It's a matter of competition: whoever gets there first wins. Sometimes the rendering comes first, and sometimes the instantiation of the plugin comes first. [Translator's Note: If the rendering process has not been completed (the DOM Node has not been added to the DOM tree), then find('select') will not be able to find the corresponding node to perform instantiation. 】
Now, maybe not many people know, we can use a slight hack based on setTimeout() to solve the problem.
Let’s slightly rewrite our code to safely instantiate our jQuery plug-in after the DOM node is loaded:
afterRender: function () { this.$el.find('select').myPlugin(); }, render: function () { this.$el.html(this.template()); setTimeout(this.afterRender, 0); }
然而,我们获得的是 函数 .afterRender() 不能找到 的错误信息。
我们接下来要做的,就是将.bind()使用到我们的代码中:
// afterRender: function () { this.$el.find('select').myPlugin(); }, render: function () { this.$el.html(this.template()); setTimeout(this.afterRender.bind(this), 0); } //
以上所述就是本文的全部内容了,希望大家能够喜欢。