最近学习都是自己想到什么就些什么,这样进步也不明显,于是偶尔也看看Prototype的源码,分析分析也算笔记。
记得以前看jquery的源码的时候,网上一搜,源码分析一堆,不过透过表面看实质,大部分都只能算是注释。对于我这样的一个初学者,真算是坑爹啊。
于是到现在,jquery的源码还是只看了前面几百行。选择看Prototype的源码是因为Prototype与jqeury不一样,jquery的所有操作都是在一个(组)jquery对象上来完成的,但是Prototype却扩展了原生的类型,比如这次要说的Function。
所以···Prototype····比较好读··
前面做过一个Prototype中Template的浅析,可以连贯起来。http://www.cnblogs.com/xesam/archive/2011/12/05/2277260.html
在Function.prototype中新增的几个方法:
return {
argumentNames: argumentNames,
bind: bind,
bindAsEventListener: bindAsEventListener,
curry: curry,
delay: delay,
defer: defer,
wrap: wrap,
methodize: methodize
}
(定义过程中,还有两个不公开的函数update,merge,这两个主要作用就是合并数组(参数),所以没什么好说的)
其中,一个最重要的方法就是bind,理解了这一个,其他的就很好理解了。
先不看bind,我们从问题开始。
假定我们有一个函数和一个对象:
function handler(greet){
console.log(greet,this.name);
}
var obj = {
name : 'xesam'
}
现在有个需求,我们要用打印出obj的name,直接handler(obj)显然不行,因为handler的this在定义的时候指向window,handler(obj)相当于console.log(window.name)。于是就得想办法:
最直觉的办法,直接向handler函数传入obj,变成:
function handler(o,greet){
console.log(greet,o.name);
}
var obj = {
name : 'xesam'
}
handler(obj,'hello');
但是这样就破坏了handler原来的定义,乃下下策。
后面的道路有两条:
第一、用call或者apply来改变handler的作用域,handler.call(obj,'hello'),这种情况下需要另一个函数来包装一下。
第二、将handler变成obj的一个方法,让其能这么调用obj.handler()
下面我们先看第一种解决办法,这个应该是很容易想到的:
function someFunction(obj,greet){
handler.call(obj,greet);
//或者xe.apply(obj,[greet]);
}
var obj = {
name : 'xesam'
}
someFunction(obj,'hello');
这里的问题是平白无故多出来一个someFunction,何苦呢。我只想看见handler和obj,其他的一切都是多余的。
重赏之下必有勇夫,匿名函数自然就得过来堵枪口了。
如果handler能够返回功能类似someFunction的匿名函数,那么一切都OK
毫无疑问,handler是一个函数,就在Function的原型上面做文章,当然在Object的原型上面一样也可以,但是殃及池鱼,大家都不想。
期望的调用方式是handler.xxx(obj)之类的,其中xxx指代某一方法名称。
还是尊重Prototype,在Prototype里面的实现方式是handler.bind(obj)
针对此例的情况,我们实现一个最简单的版本:
Function.prototype.bind = function(obj,greet){
return function(){
handler.call(obj,greet);
}
}
function handler(o,greet){
console.log(greet,o.name);
}
var obj = {
name : 'xesam'
}
var handler_1 = handler.bind(obj,'hello');
handler_1();
handler_1是将转换this指向后的handler副本(副本这个词不准确,意会意思就可以了)。
为了更通用,Function.prototype.bind不应该出现obj,greet,handler以及别人想得到的名字,用this替代handler,obj,greet是参数(运用的时候只需要理会实参)列表于是可以改进:
Function.prototype.bind = function(){
var _this = this;
var _obj = arguments[0];
var _params = Array.prototype.slice.call(arguments,1);
return function(){
return _this.apply(_obj,_params);
}
}
我们再回过头看最初的handler:
function handler(greet){
console.log(greet,this.name);
}
当我们执行handler.bind(obj,'hello')的时候,无意之间初始化了他的greet参数,就是提供了一个默认的参数。这并不是我们本来的目的,但是他确实是有这个效果。
如果我们只是单独看着一点,这显然就是函数的“柯里化”,前面提到的curry方法,就是这么做的。
function curry() {
if (!arguments.length) return this;
var __method = this, args = slice.call(arguments, 0);
return function() {
var a = merge(args, arguments);
return __method.apply(this, a);
}
}
这个像极了bind的阉割版本,不多说了。
继续看我们的例子,下面一句:
var _params = Array.prototype.slice.call(arguments,1);
上面的实现中,只能处理bind(obj,param)中的参数param,我们抛弃了handler.bind(obj,param)(param_1);中的param_1,所以这个得补上,于是把两部分的参数合并起来,得到如下的函数:
Function.prototype.bind = function(){
var _this = this;
var _obj = arguments[0];
var _params = Array.prototype.slice.call(arguments,1);
return function(){
var _args = _params.concat(Array.prototype.slice.call(arguments,0)); //合并后的参数数组
return _this.apply(_obj,_args);
}
}
现在,我们得到了Prototype中的bind的大部分实现代码(现实的Prototype中的bind还包含一些参数检测的代码,不过参数检测并非重点)
几个简短的补充实例:
handler.bind(obj,'hello')();//相当于handler.call(obj,'hello')
handler.bind(obj,'hello')('go');//相当于handler.call(obj,'hello','go')
handler.bind(obj)('go');//相当于handler.call(obj,'go')
接着讨论,如果handler是一个事件监听函数,用bind一样可以传值:
下面是一个demo:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
</head>
<body>
<input type="button" value="点击" id="btn"/>
<script type="text/javascript">
//事件绑定函数,这一段函数大家应该很熟悉:
function bindEventListener(el,type,handler){
if(window.addEventListener){
el.addEventListener(type,handler,false);
}else if(window.attachEvent){
el.attachEvent('on' + type,handler);
}else{
el['on' + type] = handler;
}
}
function handler(){
console.log(this.name);
}
var obj = {
name : '小西山子'
}
bindEventListener(document.getElementById('btn'),'click',handler);
</script>
</body>
</html>
点击input#btn的时候,会打印出undefined,因为在IE浏览器里面this指向了window,非IE浏览器里面,this指向了input#btn,不论指向哪里,都是没有name属性的。
现在我要把点击的时候打印obj.name,原理上面说了,用bind实现这个需求只需要改一个地方就行了:
bindEventListener(document.getElementById('btn'),'click',handler.bind(obj));
不过对于事件处理函数,非IE浏览器第一个参数会有一个默认的event(当前事件):
于是Prototype里面给出了一个bind的特定于事件的版本——bindAsEventListener
大致代码如下:
Function.prototype.bindAsEventListener = function(context) {
var _this = this;
var _params = Array.prototype.slice.call(arguments, 1);
return function(event) {
var _args = [].concat(_params); //合并后的参数数组
return _this.apply(context, _args);
}
}
本来是要写完的,但是发现那样会很臭很长,于是这篇就写了bind,curry和bindAsEventListener三个函数,剩下的下一篇补充。
其实看完这一部分,会越发发现JS里面一切果然尼玛都是对象。
转载请注明来自小西山子【http://www.cnblogs.com/xesam/】
本文地址:http://www.cnblogs.com/xesam/archive/2011/12/17/2290797.html