jQuery插件 / 进阶

Provide Public Access to Default Plugin Settings

为了使用插件的程序员能更加直观的看到插件的参数(settings),我们常常会把这些数据暴露(expose)出来,比如下面例子中的$.fn.hilight.defaults
==> 将setting从插件中抽离出来

/* plugin definition */
$.fn.hiLight = function( options ) {
    var opts = $.extend({}, $.fn.hiLight.defaults, options);
};


/* plugin default - added as properties into the empty {} */
$.fn.hiLight.defaults = {
    foreground: "red",
    background: "yellow"
}
____________________________________________________________________________________

/* 一个叫Jim的程序员使用了该插件,并对它的设置进行了修改 */
/* This needs only be called once and does not hace to be called from within a "ready-block" */
$.fn.hiLight.defaults.foreground = "blue";


/* 按照自己的意愿修改完setting之后,Jim开始使用该插件了 */
$("#myDiv").hiLight();

=> And now we can call the plugin method like this and it will use a blue foreground color
Jim 也可以这样简化上面两句代码:

$("#myDiv").hiLight({
    foreground: "blue",
});

关于extend()函数:

$.extend( target [, object1 ] [, objectN ] )

- target:   Object类型 目标对象,其他对象的成员属性将被附加到该对象上。
- object1:  可选, Object类型 第一个被合并的对象。
- object2:  可选, Object类型 第N个被合并的对象。
Provide Public Access to Secondary Functions as Applicable

To let others extend your custom plugin.
=> To expose the format function so that it can be redefined. In other words, you can say, we now are capable to write plugins for a plugin.
==> 将function从插件中抽离出来

// Plugin definition.
$.fn.hilight = function( options ) {
    // Iterate and reformat each matched element.
    return this.each(function() {
        var elem = $( this );
        // ...
        var markup = elem.html();       // html(): set content
        // Call our format function.
        markup = $.fn.hilight.format( markup );
        elem.html( markup );            // html(): return content
    });
};
 
// Define our format function.
$.fn.hilight.format = function( txt ) {
    return "<strong>" + txt + "</strong>";
};

关于html()函数:

$(selector).html()

The html() method sets or returns the content (innerHTML) of the selected elements.
- To return content, it returns the content of the FIRST matched element.
- To set content, it overwrites the content of ALL matched elements.
Keep Private Functions Private

虽然以上两种expose提高了plugin的可重写和灵活性。我们插件的大多数函数仍然需要是private的,举例:

(function($) {      // closure
    $.fn.hilight = function( options ) {
        debug(this);
    };

    function debug(obj) {
        if (window.console && window.console.log) {
            window.console.log("hilight selection count: " + obj.length);
        }
    }
})(jQuery);
Bob and Sue

先来看Bob小朋友写的插件:

jQuery.fn.superGallery = function( options ) {
    // Bob's default settings:
    var defaults = {
        textColor: "#000",
        backgroundColor: "#fff",
        fontSize: "1em",
        delay: "quite long",        // 控制动画延迟的参数
        getTextFromTitle: true,
        getTextFromRel: false,
        getTextFromAlt: false,
        animateWidth: true,
        animateOpacity: true,
        animateHeight: true,
        animationDuration: 500,
        clickImgToGoToNext: true,
        clickImgToGoToLast: false,
        nextButtonText: "next",
        previousButtonText: "previous",
        nextButtonTextColor: "red",
        previousButtonTextColor: "red"
    };
    var settings = $.extend( {}, defaults, options );
    return this.each(function() {
        // Plugin code would go here...
    });
};

另一位小朋友Sue决定用Bob的插件编写一些东西,当她想要修改 宽度动画变化速度 animateWidthDuration时,发现Bob写的插件中居然没有这个选项 >> 懵逼小脸(●' - '●)
==> It's not really about how many options your plugin has; but what options it has! 这是Bob犯的第一个错误,参数定义的不够全

Bob小朋友犯的第二个错误 来自参数 delay。他将delay的选项定义成了抽象的描述:

var delayDuration = 0;
switch ( settings.delay ) {
    case "very short":   delayDuration = 100;    break;
    case "quite short":  delayDuration = 200;    break;
    case "quite long":   delayDuration = 300;    break;
    case "very long":    delayDuration = 400;    break;
    default:             delayDuration = 200;
}

=> not only limit the level of control the people have(here I mean Sue or you), but also take quite a bit of place.
==> 应该就直接用数字

Give Full Control of Elements

If your plugin creates elements to be used within the DOM, then it's a good idea to give certain elements IDs or classes.
BUT note that your plugin sholdn't rely on these hooks INTERNALLY!
==> 用人话说,就是不要把选择元素的代码(比如$(".gallery-wrapper").append("...");)放在plugin的闭包里面!
反例:

$.fn.inLight = function( options ) {
    // ...
    $( "<div class='gallery-wrapper' />" ).appendTo( "body" );
    $( ".gallery-wrapper" ).append( "..." );
}

以上这种情况,有个隐患,万一用户使用该plugin的时候,想要修改元素的某些属性(比如颜色或者字体)将无法实现。
To allow users to access and even manipulate that information, you can store it in a variable containing the settings of your plugin.

$.fn.hilight = function( options ) {

    // ...

    var defaults = {
        wrapperAttrs : {
            class: "gallery-wrapper"
        },
    };
     
    // We can use the extend method to merge options/settings as usual:
    // But with the added first parameter of TRUE to signify a DEEP COPY:
    var settings = $.extend( true, {}, defaults, options );

    // Retain an internal reference
    var wrapper = $("<div />")
                    .attr( settings.wrapperAttrs )
                    .appendTo( settings.container );
};

// Easy to reference later
wrapper.append("...");

==> 用户可以通过定义的变量wrapper来修改类的名字, 同理,我们也可以修改元素的CSS样式,比如这里,我们将变量多设置一个css()方法

var defaults = {
    wrapperAttrs: {...};
    wrapperCSS: {...},
    // ... rest of settings
};

// Retain an internal reference:
var wrapper = $('<div />')
       .attr( settings.wrapperAttrs )
       .css( settings.wrapperCSS )        // 允许我们修改CSS样式
       .appendTo( settings.container );
Provide Callback Capabilities

What is a callback? – A callback is essentially a function to be called later, normally triggered by an event.

=> If your plugin is driven by events then it might be a good idea to provide a callback capability for each event.
==> Plus, you can create your own custom events and then provide callbacks for those.

var defaults = {
    /**
     *  We define an empty anonymous function
     *  so that we don't need to check its existence before calling it.
     *  because it exists
     */
    onImageShow: function() {},

    //... rest of settings...
}

nextButton.on("click", showNextImage);  // Event "Click" triggered the function showNextImage()

function showNextImage() {
    var image = getNextImage();     // Returns reference to the next image node

    //... code to show the image here

    settings.onImageShow.call(image);    // CALLBACK: image call the function onImageShow()
}

关于call()函数:

call( [ thisObj [,arg1 [, arg2 [, [,.argN ]]]]] ) 
call 方法可以用来代替另一个对象调用一个方法

---------------- 相似的方法 > apply() ----------------
apply([thisObj[,argArray]]) 
应用某一对象的一个方法,用另一个对象替换当前对象。 

---------------- 举例 ----------------
/**定义一个animal类*/  
    function Animal(){   
        this.name = "Animal";   
        this.showName = function(){   
            alert(this.name);   
        }   
    }   
/**定义一个Cat类*/  
    function Cat(){   
        this.name = "Cat";   
    }   
      
/**创建两个类对象*/  
    var animal = new Animal();   
    var cat = new Cat();   
      
/*通过call或apply方法,将原本属于Animal对象的showName()方法交给当前对象cat来使用了。*/   

    animal.showName.call(cat,",");           //输入结果为"Cat"   
    // 或 animal.showName.apply(cat,[]);   

---------------- 结论 ----------------
以上代码无论是采用animal.showName.call或是animal.showName.apply方法,运行的结果都是输出一个"Cat"的字符串。
说明showName方法的调用者被换成了cat对象,而不是最初定义它的animal了。这就是call和apply方法的妙用!

这里的callback初始化的方法比较特别
=> 我们调用了imagecontext
==> 也就是说,我们可以通过this访问当前的image对象, 比如:

$(" ul.img li ").superGallery({
    onImageShow: function() {
        $(this).after( "<span>" + $(this).attr("longdesc") + "</span>" )
    }
});

关于after()函数:

.after( content )
- Insert content, specified by the parameter, after each element in the set of matched elements. 
content can be one or more HTML string, DOM element, text node, array of elements and text nodes, or jQuery object to insert after each element in the set of matched elements.

.after(function)
- a function that returns aforementioned content
Remember, It's a Compromise

And equally, it's not going to be very useful if you offer no or very few methods of control. So, remember, it's always going to be a compromise.

Three things you must always take into account are:

  • Flexibility: How many situations will your plugin be able to deal with?
  • Size: Does the size of your plugin correspond to its level of functionality? I.e. Would you use a very basic tooltip plugin if it was 20k in size? – Probably not!
  • Performance: Does your plugin heavily process the options in any way? Does this affect speed? Is the overhead caused worth it for the end user?

推荐阅读更多精彩内容

  • 还有一种婚姻状态叫两地 年少时匆匆结婚,以为结婚是两个人甜甜蜜蜜的永远生活在一起。今时,一晃眼婚龄...
    哆啦1980阅读 68评论 0 0
  • 「小伙子,来跑马拉松的啊。」刚跑完厦马的我,在售卖中老年睡衣店铺前坐下休息。一位 50 岁左右满口黄牙,头发乱蓬蓬...
    萤风白阅读 151评论 0 0
  • 曾经总听说坐火车多么的痛苦,多么的累。可在我实际体会过后,其实觉得没有想象中难受。每次给别人说我买的硬坐,...
    梦所到达的地方阅读 68评论 0 0