Time after time I find JavaScript code that has bugs caused by lack of proper understanding of how functions work in JavaScript (a lot of that code has been written by me, by the way.) JavaScript has functional programming characteristics, and that can get in our way until we decide to face and learn it.
For starters, let's examine five ways to invoke a function. On the surface we might be tempted to think that functions work exactly like C#, but we will see that there are important differences and ignoring them will undoubtedly result in hard to track bugs.
Let's first create a simple function that we will be using through the rest of this post. This function will just return an array with the current value of this
and the two supplied arguments.
<script type="text/javascript">
function makeArray(arg1, arg2){
return [ this, arg1, arg2 ];
}
</script>
Most common way, unfortunately, global function calls
When we are learning JavaScript we learn how to define functions using the syntax used in the example above. We learn that it's also very easy to call that function — all we need to do is:
makeArray('one', 'two');
// => [ window, 'one', 'two' ]
Wait a minute. What's that window
object doing there? Why is it the value of this
? If you haven't stopped to think about it, please stay with me here.
In JavaScript, and I'm not talking specifically about the browser here, there's a default/global object. It's as if every code that we write which seems to be just "loose" inside your script (i.e. outside of any object declaration) is actually being written in the context of that global object. In our case, that makeArray
function isn't just a loose "global" function, it's a method of the global object. Bringing ourselves back to the browser, the global object is mapped to the window
object in this environment. Let's prove that.
alert( typeof window.methodThatDoesntExist );
// => undefined
alert( typeof window.makeArray);
// => function
What all this means is that calling makeArray
like we did before is the same as calling as follows.
window.makeArray('one', 'two');
// => [ window, 'one', 'two' ]
I say it's unfortunate that this is the most common way because it leads us to declare our functions globally by default. And we all know that global members are not exactly the best practice in software programming. This is especially true in JavaScript. Avoid globals in JavaScript, you won't regret it.
JavaScript function invocation rule #1 In a function called directly without an explicit owner object, like myFunction()
, causes the value of this
to be the default object (window
in the browser).
Method call
Let's now create a small object and use the makeArray
function as one of its methods. We will declare the object using the literal notation. Let's also call this method.
//creating the object
var arrayMaker = {
someProperty: 'some value here',
make: makeArray
};
//invoke the make() method
arrayMaker.make('one', 'two');
// => [ arrayMaker, 'one', 'two' ]
// alternative syntax, using square brackets
arrayMaker['make']('one', 'two');
// => [ arrayMaker, 'one', 'two' ]
See the difference here? The value of this
became the object itself. You may be wondering why isn't it still window
since that's how the original function had been defined. Well, that's just the way functions are passed around in JavaScript. Function
is a standard data type in JavaScript, an object indeed; you can pass them around and copy them. It's as if the entire function with argument list and body was copied and assigned to make in arrayMaker
. It's just like defining arrayMaker
like this:
var arrayMaker = {
someProperty: 'some value here',
make: function (arg1, arg2) {
return [ this, arg1, arg2 ];
}
};
JavaScript function invocation rule #2 In a function called using the method invocation syntax, like obj.myFunction()
or obj['myFunction']()
, causes the value of this
to be obj
.
This is a major source of bugs in event handling code. Look at these examples.
<input type="button" value="Button 1" id="btn1" />
<input type="button" value="Button 2" id="btn2" />
<input type="button" value="Button 3" id="btn3" onclick="buttonClicked();"/>
<script type="text/javascript">
function buttonClicked(){
var text = (this === window) ? 'window' : this.id;
alert( text );
}
var button1 = document.getElementById('btn1');
var button2 = document.getElementById('btn2');
button1.onclick = buttonClicked;
button2.onclick = function(){ buttonClicked(); };
</script>
Clicking the first button will display "btn1" because it's a method invocation and this
will be assigned the owner object (the button input element.) Clicking the second button will display "window" because buttonClicked
is being called directly (i.e. not like obj.buttonClicked()
.) This is the same thing that happens when we assign the event handler directly in the element's tag, as we have done for the third button. Clicking the third button does the same of the second button.
That's another advantage of using a library like jQuery. When defining event handlers in jQuery, the library will take care of overriding the value of this
and make sure it contains a reference to the element that was the source of the event.
//using jQuery
$('#btn1').click( function() {
alert( this.id ); // jQuery ensures 'this' will be the button
});
How does jQuery override the value of this
? Keep reading.
Two more: apply()
and call()
The more you leverage functions in JavaScript, the more you find yourself passing functions around and needing to invoke them in different contexts. Just like jQuery does in the event handler functions, you'll often need to override the value of this
. Remember I told you functions are objects in JavaScript? Functions have predefined methods, two of them are apply()
and call()
. We can use them to do precisely that kind of overriding.
var gasGuzzler = { year: 2008, model: 'Dodge Bailout' };
makeArray.apply( gasGuzzler, [ 'one', 'two' ] );
// => [ gasGuzzler, 'one' , 'two' ]
makeArray.call( gasGuzzler, 'one', 'two' );
// => [ gasGuzzler, 'one' , 'two' ]
The two methods are similar. The first parameter will override this
. They differ on the subsequent arguments. Function.apply()
takes an array of values that will be passed as arguments to the function and Function.call()
takes the same arguments separately. In practice I believe you'll find that apply()
is more convenient in most cases.
JavaScript function invocation rule #3 If we want to override the value of this
without copying the function to another object, we can use myFunction.apply( obj )
or myFunction.call( obj )
.
Constructors
I won't delve into the details of defining types in JavaScript but at minimum we should be aware that there aren't classes in JavaScript and that any custom type needs a constructor function. It's also a good idea to define the methods of your type using the prototype
object, which is a property of the constructor function. Let's create a small type.
//declaring the constructor
function ArrayMaker(arg1, arg2) {
this.someProperty = 'whatever';
this.theArray = [ this, arg1, arg2 ];
}
// declaring instance methods
ArrayMaker.prototype = {
someMethod: function () {
alert( 'someMethod called');
},
getArray: function () {
return this.theArray;
}
};
var am = new ArrayMaker( 'one', 'two' );
var other = new ArrayMaker( 'first', 'second' );
am.getArray();
// => [ am, 'one' , 'two' ]
What's very important to note here is the presence of the new
operator before the function call. Without that your function will just be called like a global function and those properties that we are creating would be created on the global object (window
.) And you don't want to do that. Another issue is that, because you typically don't have an explicit return value in your constructor function, you'll end up assigning undefined
to some variable if you forget to use new
. For these reasons it's a good convention to name your constructor functions starting with an upper case character. This should serve as a reminder to put the new
operator before the call.
With that taken care of, the code inside the constructor is very similar to any constructor you probably have written in other languages. The value of this
will be the new object that you are trying to initialize.
JavaScript function invocation rule #4 When used as a constructor, like new MyFunction()
, the value of this
will be a brand new object provided by the JavaScript runtime. If we don't explictly return anything from that function, this
will be considered its return value.
It's a wrap
I hope understanding the differences between the invocation styles help you keeping bugs out of your JavaScript code. Some of these bugs can be very tricky do identify and making sure you always know what the value of this
will be is a good start to avoiding them in the first place.
相关推荐
Better ways to use prototype-based object-oriented programming Subtleties and solutions for working with arrays and dictionary objects Precise and practical explanations of JavaScript’s functions ...
it is possible to make a control for the JavaScript object inside an HTML page by using a traditional ... do it and a technique to call a JavaScript function from C++ code...
主要介绍了JavaScript中函数(Function)的apply与call理解,本文讲解了JavaScript函数调用分为4中模式以及通过apply和call实现扩展和继承两方面,需要的朋友可以参考下
在JavaScript中,`call`和`apply`是两个非常重要的方法,它们都用于改变函数调用时的上下文(即`this`的值),并且可以灵活地传递参数。本篇文章将深入探讨这两个方法的用法、区别以及实际应用场景。 `call`方法...
在本话题中,我们将深入探讨JavaScript中的类继承,并特别关注`this.callParent`这个方法,它是如何被用来调用超类方法的。 首先,让我们了解JavaScript中的构造函数。构造函数是一种特殊的函数,用于创建和初始化...
### JavaScript中apply、call和bind的用法区分 在JavaScript编程中,`apply`、`call`和`bind`这三个方法被广泛用于改变函数内部`this`的指向,这对于理解和编写复杂的JavaScript代码至关重要。虽然它们的功能相似,...
为了控制函数的this指向,JavaScript提供了apply、call以及bind方法。以下详细解释了apply、call以及bind的用法,并通过实例加深理解。 1. apply()和call()方法 apply()和call()方法都用于指定函数体内this的值。...
淡淡简单描述javascript中方法apply和call
[Addison-Wesley Professional] Effective JavaScript 68 Specific Ways to Harness the Power of JavaScript (E-Book) ☆ 图书概要:☆ In order to truly master JavaScript, you need to learn how to work ...
A Comparative Study of Static and Dynamic Tools”的论文,对现有的JavaScript调用图提取工具进行了深入比较和评估。研究团队来自匈牙利塞格德大学软件工程系,他们的工作受到了欧洲联盟项目、匈牙利创新和技术部...
Sams Teach Yourself HTML, CSS & JavaScript Web Publishing in One Hour a Day, Covers HTML5, CSS3, and jQuery 7th Series: Sams Teach Yourself Paperback: 768 pages Publisher: Sams Publishing; 7 edition ...
这篇文章将深入探讨四个关键概念:caller、callee、call和apply,它们都是JavaScript函数操作的核心部分,对于理解和使用高级JavaScript编程至关重要。 首先,我们来了解`caller`和`callee`。在JavaScript的函数...
在JavaScript中,`apply()`和`call()`方法都是用于改变函数调用时的上下文(即`this`关键字指向的对象)以及传递参数。这两个方法都隶属于`Function.prototype`,因此所有函数实例都拥有这两个方法。它们的主要作用...
本文将详细解释JavaScript中call(), apply(), 和 bind() 方法的作用、语法以及使用场景,并且会探讨回调函数的使用,帮助理解这些概念在实际编程中的应用。 首先,我们来探讨call() 和 apply() 方法。这两个方法都...
Last but not least, the book covers JavaScript phishing, how it works, and ways to counter it. By the end of this book, you will be able to identify various risks of JavaScript and how to prevent ...
在JavaScript中,`Function.prototype.call` 和 `Function.prototype.apply` 是两种非常重要的方法,它们都是用来改变函数调用时的上下文(即`this`值)以及传递参数。在这个特殊的面试题中,这两种方法被结合在一起...