前天去面试,有个gg问了一些js知识,其中有一道call与apply用法的题目,尽管在365天前用过call方法,但当时还是没能答上来,今天深入总结一下

call和apply,它们的作用都是将函数绑定到另外一个对象上去运行


两者的格式和参数定义:


call( thisArg [,arg1,arg2,… ] );       // 参数列表,arg1,arg2,...


apply(thisArg [,argArray] );                 // 参数数组,argArray

上面两个函数内部的this指针,都会被赋值为thisArg,这可实现将函数作为另外一个对象的方法运行的目的




一、call 的简单用法




首先,我们先看个简单的例子(call):


<!doctype html>

<html>
	<head>
		<title> call-apply </title>
	</head>

	<body>
		<input type="text" id="idTxt" value="input text">
		
		<script type="text/javascript">
			var value = "global var";
			
			function mFunc()
			{
				this.value = "member var";
			}
			
			function gFunc()
			{
				alert(this.value);
			}		
													
			window.gFunc();									// show gFunc, global var
			gFunc.call(window);								// show gFunc, global var
			gFunc.call(new mFunc());						// show mFunc, member var
			gFunc.call(document.getElementById('idTxt'));	// show element, input text
		</script>
		
		<script language="javascript">
			var func = new function()
			{
				this.a = "func";
			}
			
			var func2 = function(x)
			{
				var a = "func2";
				alert(this.a);				
				alert(x);
			}
			
			func2.call(func, "func2");						// show func and func2
		</script>
	</body>
</html>


然后,运行结果如下:


global var

global var

member var

input text

func

func2

测试环境:Google Chrome 10.0.648.45


最后,分析结果

1、全局对象window调用函数gFunc,

this指向window对象

,因此this.value为

global var

2、函数gFunc调用call方法,

this默认指向第一个参数window对象

,因此this.value也为

global var

3、函数gFunc调用call方法,

this默认指向第一个参数new mFunc(),即mFunc的对象

,因此this.value为mFunc的成员变量

member var

4、函数gFunc调用call方法,

this默认指向第一个参数input text控件,即id=‘idTxt’的控件

,因此this.value为input控件的value值

input text

5、函数func2调用call方法,

this默认指向第一个参数func函数对象

,因此this.value为this.a,即

func

6、函数func2调用call方法,

第二个参数属于函数对象func2的参数

,因此alert(x)为第二个参数

func2




二、call 继承用法与改进



js使用call模拟继承


测试代码:

<!doctype html>

<html>
	<head>
		<title> call - apply for inherit </title>
	</head>
	
	<body>
		<script type="text/javascript">
			function baseA()		// base Class A
			{
				this.member = "baseA member";
				this.showSelfA = function()
				{
					window.alert(this.member);
				}
			}
			
			function baseB()		// base Class B
			{
				this.member = "baseB member";
				this.showSelfB = function()
				{
					window.alert(this.member);
				}
			}
			
			function extendAB()		// Inherit Class from A and B
			{
				baseA.call(this);	// call for A
				baseB.call(this);	// call for B
			}
			
			window.onload = function()
			{
				var extend = new extendAB();	
				extend.showSelfA();		// show A
				extend.showSelfB();		// show B
			}
		</script>
	</body>
</html>


运行结果如下:


baseB member


baseB member

测试环境:Google Chrome 10.0.648.45


结果分析:

预期的结果,应该是输出

baseA member 和 baseB member

,但实际输出却是

baseB member 和 baseB member

(已在IE9、8、6,Maxthon、Chrome、FF、Opera、Safari、360等浏览器测试过,结果都是后者:baseB member)

至此,机器是不会错的,这就需要我们深入分析

我们可能会很容易想到是this引起的,this两次都指向了baseB对象,但是推测真是这样吗?

为了探究实质,我们借助chrome浏览器的调试工具,下断点,进行调试,结果发现:


当调用


extend.showSelfA();

时,此时的this指向extendAB(

并不是我们推测的两次都指向baseB对象



真实原因是extendAB对象的成员变量member在被

baseB.call(this);

实例化时,被baseB的成员member覆盖了,即extendAB的成员member由baseA member赋值成了baseB member

当然,我们也可以对上面baseA代码稍作修改,来验证我们调试分析的正确性:



function baseA()


// base Class A



{



this.

memberA

= "baseA member";   // member改成memberA,以区分baseB中的member



this.showSelfA = function()



{



window.alert(this.

memberA

);    // 显示memberA



}



}

再次运行chrome等浏览器,结果如下:


baseA  member


baseB member


结果和我们的预期相同,同时chrome调试信息也验证了我们的正确性:














继承改进(prototype)

以上模拟继承方法,仔细分析不是最好的。

因为每次在函数(类)中定义了成员方法,都会导致实例有副本,因此可以借助prototype原型,进行改进

改进举例如下:

<!doctype html>

<html>
	<head>
		<title> call - apply for prototype </title>
	</head>
	
	<body>
		<script type="text/javascript">
			var Class = {
				create: function()				// create Function
				{
					return function()
					{
						this.initialize.apply(this, arguments);
					}
				}
			};
			
			var Person = Class.create();		// Create Class Person
			Person.prototype = {				// prototype initialize
				initialize: function(obj1, obj2)
				{
					this.obj1 = obj1;
					this.obj2 = obj2;
				},
				showSelf: function()
				{
					alert("obj: " + this.obj1 + " and " + this.obj2);
				}
			}
			
			// instance Class
			var person = new Person("man", "women");	// two params
			person.showSelf();							// show person
		</script>
	</body>
</html>

运行结果如下:


obj: man and women