• Prototype源码浅析——Function.prototype部分(一)


    最近学习都是自己想到什么就些什么,这样进步也不明显,于是偶尔也看看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



  • 相关阅读:
    二手房交易平台需求分析心得——字节移动小组
    结对编程之个人项目代码分析
    五月最新版 重装win10软件
    学习必备的——超级有用的网站!!!
    SQL中sa被禁用
    19 erp沙盘校赛
    temp
    Bootstrap 学习日志(二)
    Bootstrap 学习日志(一)
    关于Android上进行模拟登陆时的验证码问题
  • 原文地址:https://www.cnblogs.com/xesam/p/2290797.html
Copyright © 2020-2023  润新知