zoukankan      html  css  js  c++  java
  • 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



  • 相关阅读:
    ini_set /ini_get函数功能-----PHP
    【转】那个什么都懂的家伙
    word 2007为不同页插入不同页眉页脚
    August 26th 2017 Week 34th Saturday
    【2017-11-08】Linux与openCV:opencv版本查看及库文件位置等
    August 25th 2017 Week 34th Friday
    August 24th 2017 Week 34th Thursday
    August 23rd 2017 Week 34th Wednesday
    August 22nd 2017 Week 34th Tuesday
    August 21st 2017 Week 34th Monday
  • 原文地址:https://www.cnblogs.com/xesam/p/2290797.html
Copyright © 2011-2022 走看看