深入理解JavaScript中apply、call与bind方法

作者:函数茶馆

时间:2026-10-29

浏览:500

本文详细解析JavaScript中apply、call和bind三个核心函数的用法与区别。通过对比分析,阐述它们如何改变函数执行时的this指向,并结合Prototype.js源码中的Class.create、bindAsEventListener等示例,探讨在事件处理和对象继承中的实际应用与潜在问题。

文章目录
1、关于javascript的apply和call函数

apply与call函数的核心区别

在Prototype.js库中,大量使用了apply和call函数,若不加注意,容易导致理解上的偏差。


官方文档对这两个方法的解释是:应用某一对象的一个方法,用另一个对象替换当前对象。


apply和call的主要区别在于第二个参数的传递形式不同。apply接收的是数组或arguments对象,而call接收的是以逗号分隔的任意类型参数列表。



apply和call最易混淆之处也是其独特优势所在,但建议不要滥用这些特性。


这两个方法能够改变调用函数时的上下文对象。例如,当函数内部使用了this关键字时,此时的this将指向apply或call方法的第一个参数。



<script src="prototype1.3.1.js"></script>


<input type="text" id="myText" value="input text">


<script>


function Obj(){


this.value="对象!";


   }

var value="global 变量";


function Fun1(){


alert(this.value);


   }

window.Fun1();


Fun1.apply(window);


Fun1.apply($('myText'));


Fun1.apply(new Obj());


</script>



2、关于闭包 

Prototype.js中的闭包与类创建

Prototype.js在Class.create和bind等方法中利用了JavaScript的闭包特性,但整体而言,该库对强大的闭包特性使用并不频繁。大家可以参考我翻译的相关文章来深入了解闭包概念。


3、让我比较反感的两个方法

(1)


var Class = {


create: function() {


return function() {


this.initialize.apply(this, arguments);


    }
  }
}

我不喜欢用其他语言的风格来编写JavaScript。通过这种方法构造自定义类并没有带来明显的便利,反而减少了代码行数,却增加了理解的难度,还需要额外定义一个initialize方法。


其实对这一点的反感有些牵强,但直接修改Object的原型对象确实有些过分。


(2)Object.prototype.extend


首先,取名为extend可能会让熟悉Java的开发者产生歧义。更令人不解的是直接修改Object的prototype属性。我不清楚作者的初衷是什么。当你使用for...in循环遍历对象时,麻烦就会随之而来。可能有人会觉得奇怪,为什么还要用for...in?在我一个项目中,同时使用了DWR和Prototype.js,DWR返回的JavaScript对象中多出了一个extend属性,不得不进行特殊处理。


以前我曾比较过dojo和Prototype.js中继承的实现方式,现在我明白了一个道理:对于JavaScript这种没有静态类型检查且语法宽松的语言来说,如果你选择了某个JS类库,就必须适应作者编写JavaScript的风格。Prototype.js的作者对extend的使用可谓炉火纯青,如果我们不把它仅仅视为一个属性拷贝函数,多阅读Prototype.js的源代码是有好处的。


4、关于函数的绑定

函数绑定方法bind与bindAsEventListener解析

类库提供了Function.prototype.bind和Function.prototype.bindAsEventListener两个方法。首先我们从概念上解释这两个方法。


任何函数都可以调用这两个方法;参数是JavaScript对象或网页上的元素对象;返回类型是一个函数对象。


既然原本就是一个函数,返回的依然是函数,那么这两个函数之间有什么不同呢?查看实现代码,关键在于apply和call函数的使用。这里实际上只是转换了方法调用的对象。



<script src="prototype1.3.1.js"></script>


<input type=checkbox id=myChk name="asf" value=1> Test


<script>


var CheckboxWatcher = Class.create();


CheckboxWatcher.prototype = {


initialize: function(chkBox, message) {


this.chkBox = $(chkBox);


this.message = message;


this.chkBox.onclick = this.showMessage.bindAsEventListener(this);


       },

showMessage: function(evt) {


alert(this.message + ' (' + evt.type + ')');


       }
    };

new CheckboxWatcher('myChk','message!!!!');


//$('myChk').onclick=function(){};


</script>


这是https://compdoc2cn.dev.java.net/上举出的例子,个人感觉没什么意思,反而让我对bind和bindAsEventListener产生了一些反感。(JavaScript就是这样,明明大家都知道的语法,但写出来的代码差别却很大)


看下面代码:



<script src="prototype1.3.1.js"></script>


<input type=checkbox id=myChk name="chk" value=1> Test


<script>


function Class(){


this.name="class";


}

Class.prototype.getName=function(){


alert(this.name);


}

var obj=new Class();


//$('myChk').onclick=obj.getName;


$('myChk').onclick=obj.getName.bind(obj);


//$('myChk').onclick=obj.getName.bind($('myChk'));


</script>



从上面的代码可以看出,bind和bindAsEventListener只是包装了一下apply和call方法,改变了方法的调用对象。如例子所示,你可以把obj.getName方法转化为任何对象调用,并且让表单元素触发该方法。(bind和bindAsEventListener之间只是返回函数的参数不同)


这两个方法也可以用于对象之间的方法重用,实现类似继承方法的概念。看以下代码,其实是比较无聊的。



<script src="prototype1.3.1.js"></script>


<script>


function Class1(name){


this.name=name;


}

Class1.prototype.getName=function(){


alert(this.name);


}

function Class2(name){


this.name=name;


this.getName=Class1.prototype.getName.bind(this);


}

var obj1=new Class2("yql");


obj1.getName();


var obj2=new Object();


obj2.name="zkj";


obj2.fun=Class1.prototype.getName.bind(obj2);


obj2.fun();


</script>




我从来没读过Prototype.js的扩展项目代码,也不知道bind的最佳实践,让我们一起挖掘吧。但你绝对不要把bind和bindAsEventListener从绑定的词义上来理解,这可能会让你更加迷惑。应该从apply和call的角度去理解其本质:应用某一对象的一个方法,用另一个对象替换当前对象。



事件注册机制与Event.observe对比

5、关于事件的注册



<script src="prototype1.3.1.js"></script>


<input type=checkbox id=myChk name="chk" value=1> Test


<script>


Event.observe(myChk, 'click', showMessage, false);


//$('myChk').onclick=showMessage;


//$('myChk').onclick=showMessage.bind();


$('myChk').onclick=showMessage.bind($('myChk'));


function showMessage() {


alert(this.value);


}

</script>



执行上面的代码,你就能明白Event.observe与bind和bindAsEventListener之间的区别:


(1)显然Event.observe有限制,只能处理简单的函数,并且函数中不能包含this之类的东西。


(2)Event.observe内部使用了addEventListener/attachEvent。它能把多个函数加到一个触发事件(如window.onload)。而bind则是覆盖。



事件监听的最佳实践探讨

6、关于事件监听最佳实践


很显然Prototype.js提供的事件注册方法不是很完善。那看看dojo的时间注册吧(中文版),更加复杂,估计很多人像我一样,对于dojo暂时持观望态度。


如果你看过的前篇关于闭包的介绍,可能见过以下代码。


看以下代码前我想表述一个观点,任何网页中元素,浏览器都会为你创建一个对象(见)。(我觉得)这些对象与你建立javascript对象区别是它们有事件监听,会响应鼠标键盘的事件。如果你用了以下代码,那么把事件监听代码很好的转化到你的javascript代码中。



function associateObjWithEvent(obj, methodName){


return (function(e){


e = e||window.event;


return obj[methodName](e, this);


    });
}

function DhtmlObject(elementId){


var el = getElementWithId(elementId);


if(el){


el.onclick = associateObjWithEvent(this, "doOnClick");


el.onmouseover = associateObjWithEvent(this, "doMouseOver");


el.onmouseout = associateObjWithEvent(this, "doMouseOut");


    }
}

DhtmlObject.prototype.doOnClick = function(event, element){


... // doOnClick method body.


}

DhtmlObject.prototype.doMouseOver = function(event, element){


... // doMouseOver method body.


}

DhtmlObject.prototype.doMouseOut = function(event, element){


... // doMouseOut method body.


}

有时间我想用以上思想实现一个网页浮动框拖拉的代码(其实已经有很多了),待续........



实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章