jQuery插件写法总结以及面向对象方式写法总结

前言

最近在折腾jQuery插件,写成插件的目的就是为了实现功能与项目相分离,使得这个代码在下一个项目中能直接引用不出错。这使得我们在写插件的时候,就得考虑清楚,怎么写才能使得插件能够通用、灵活度高、可配置、兼容性好、易用性高、耦合度低等。
接下来就对以下几种写法进行分析,前两个是jQuery插件,后面2个是以对象的形式开发,都类似。而且写法也很多,我们要懂得这样写的利弊。另一篇基础文章:jQuery 插件写法
写法一
插件主体

(function($, window){
    // 初始态定义
    var _oDialogCollections = {};
 
    // 插件定义
    $.fn.MNDialog = function (_aoConfig) {
        // 默认参数,可被重写
        var defaults = {
            // string
            sId : "",
            // num
            nWidth : 400,
            // bollean
            bDisplayHeader : true,
            // object
            oContentHtml : "",
            // function
            fCloseCallback : null
        };
 
        var _oSelf = this,
            $this = $(this);
 
        // 插件配置
        this.oConfig = $.extend(defaults, _aoConfig);
 
        // 初始化函数
        var _init = function () {
            if (_oDialogCollections) {
                // 对于已初始化的处理
                // 如果此时已经存在弹框,则remove掉再添加新的弹框
            }
            // 初始化弹出框数据
            _initData();
            // 事件绑定
            _loadEvent();
            // 加载内容
            _loadContent();            
        }
        // 私有函数
        var _initData = function () {};
        var _loadEvent = function () {};
        var _loadContent = function () {
            // 内容(分字符和函数两种,字符为静态模板,函数为异步请求后组装的模板,会延迟,所以特殊处理)
            if($.isFunction(_oSelf.oConfig.oContentHtml)) {
                _oSelf.oConfig.oContentHtml.call(_oSelf, function(oCallbackHtml) {
                    // 便于传带参函数进来并且执行
                    _oSelf.html(oCallbackHtml);
                    // 有回调函数则执行
                    _oSelf.oConfig.fLoadedCallback && _oSelf.oConfig.fLoadedCallback.call(_oSelf, _oSelf._oContainer$);
                });
            } else if ($.type(_oSelf.oConfig.oContentHtml) === "string") {
                _oSelf.html(_oSelf.oConfig.oContentHtml);
                _oSelf.oConfig.fLoadedCallback && _oSelf.oConfig.fLoadedCallback.call(_oSelf, _oSelf._oContainer$);
            } else {
                console.log("弹出框的内容格式不对,应为function或者string。");
            }
        };
 
        // 内部使用参数
        var _oEventAlias = {
            click         : 'D_ck',
            dblclick     : 'D_dbl'
        };
 
        // 提供外部函数
        this.close = function () {
            _close();
        }        
 
        // 启动插件
        _init();
 
        // 链式调用
        return this;        
    };
    // 插件结束
})(jQuery, window);

调用


var MNDialog = $("#header").MNDialog({
    sId : "#footer",        //覆盖默认值
    fCloseCallback : dialog,//回调函数
    oContentHtml : function(_aoCallback){
            _aoCallback(_oEditGrpDlgView.el);
        }
    }
});
// 调用提供的函数
MNDialog.close;
function dialog(){
 
}

点评

1. 自调用匿名函数

(function($, window) {
  // jquery code
})(jQuery, window);

用处:通过定义一个匿名函数,创建了一个“私有”的命名空间,该命名空间的变量和方法,不会破坏全局的命名空间。这点非常有用也是一个JS框架必须支持的功能,jQuery被应用在成千上万的JavaScript程序中,必须确保jQuery创建的变量不能和导入他的程序所使用的变量发生冲突。

2. 匿名函数为什么要传入window

通过传入window变量,使得window由全局变量变为局部变量,当在jQuery代码块中访问window时,不需要将作用域链回退到顶层作用域,这样可以更快的访问window;这还不是关键所在,更重要的是,将window作为参数传入,可以在压缩代码时进行优化,看看jquery.min.js:

(function(a,b){})(jQuery, window); // jQuery被优化为a, window 被优化为 b

3. 全局变量this定义

var _oSelf = this,
$this = $(this);

使得在插件的函数内可以使用指向插件的this
4. 插件配置

this.oConfig = $.extend(defaults, _aoConfig);

设置默认参数,同时也可以再插件定义时传入参数覆盖默认值
5. 初始化函数
一般的插件会有init初始化函数并在插件的尾部初始化
6. 私有函数、公有函数
私有函数:插件内使用,函数名使用”_”作为前缀标识
共有函数:可在插件外使用,函数名使用”this.”作为前缀标识,作为插件的一个方法供外部使用
7. return this
最后返回jQuery对象,便于jQuery的链式操作
写法二
主体结构

(function($){
    $.fn.addrInput = function(_aoOptions){
        var _oSelf = this;
        _oSelf.sVersion = 'version-1.0.0';
        _oSelf.oConfig = {
            nInputLimitNum : 9
        };
        // 插件配置
        $.extend(_oSelf.oConfig, _aoOptions);
 
        // 调用这个对象的方法,传递this
        $.fn.addrInput._initUI.call(_oSelf, event);
        $.fn.addrInput._initEvents.call(_oSelf);
 
        // 提供外部函数
        this.close = function () {
            _close();
        }
 
        //返回jQuery对象,便于Jquery的链式操作    
        return _oSelf;                   
    }
    $.fn.addrInput._initUI = function(event){
        var _oSelf = this,
            _oTarget = $(event.currentTarget);
    }
    $.fn.addrInput._initEvents = function(){}
})(window.jQuery);

点评
1. 美观
插件的方法写在外部,并通过在插件主体传递this的方式调用
2. 定义插件版本号
不过在这里还是没有用到
3. 关于call
这里的第一个参数为传递this,后面的均为参数
语法:

call([thisObj[,arg1[, arg2[, [,.argN]]]]])

定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明:call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
4. 关于”this”
在插件的方法中,可能有用到指向插件的this、和指向事件触发的this,所以事件触发的this用event来获取:event.cuerrntTarget
event.currentTarget:指向事件所绑定的元素,按照事件冒泡的方式,向上找到元素
event.target:始终指向事件发生时的元素

如:
html代码

<div id="wrapper"> 
    <a href="#" id="inner">click here!</a> 
</div>

** js代码**

$('#wrapper').click(function(e) { 
    console.log('#wrapper'); 
    console.log(e.currentTarget); 
    console.log(e.target); 
}); 
$('#inner').click(function(e) { 
    console.log('#inner'); 
    console.log(e.currentTarget); 
    console.log(e.target); 
});

结果输出

#inner
<a href=​"#" id=​"inner">​click here!​</a>​
<a href=​"#" id=​"inner">​click here!​</a>​
#wrapper
<div id=​"wrapper">​<a href=​"#" id=​"inner">​click here!​</a>​</div>​
<a href=​"#" id=​"inner">​click here!​</a>​

写法三(原生写法)
主体结构

var MemberCard = function(_aoOption){
    // 配置(默认是从外面传进来)
    _aoOption || (_aoOption = {}) ;
    // 初始化函数
    _init(this);
}
 
var _init = function(_aoSelf) {
    // 函数执行
    _initData(_aoSelf);
    // 调用对象的私有方法
    _aoSelf._timedHide();
}
 
var _initData = function ( _aoSelf ) {}
 
// 私有方法
MemberCard.prototype._timedHide = function(_aoOptions) {
    var _oSelf = this;
    clearTimeout(this.iHideTimer);    
    // 使用underscore.js的extend方法来实现属性覆盖
    var oDefault = extend( { nHideTime: 300 }, _aoOptions );
    _oSelf.iHideTimer = setTimeout( function(){
        // 调用对象的共有方法
        _oSelf.hide();
    }, oDefault.nHideTime);        
}
 
// 公有方法
MemberCard.prototype.hide = function(_aoOptions) {}

使用

var oColleagueCard = new MemberCard({ nHideTime: 200 });
oColleagueCard.hide();

点评
1. 关于属性覆盖(对象深拷贝)
原生函数实现方法

function getType(o){
    return ((_t = typeof(o)) == "object" ? o==null && "null" || Object.prototype.toString.call(o).slice(8,-1):_t).toLowerCase();
}
function extend(destination,source){
    for(var p in source){
        if(getType(source[p])=="array"||getType(source[p])=="object"){
            destination[p]=getType(source[p])=="array"?[]:{};
            arguments.callee(destination[p],source[p]);
        }else{
            destination[p]=source[p];
        }
    }
}

demo:

var test={a:"ss",b:[1,2,3],c:{d:"css",e:"cdd"}};
var test1={};
extend(test1,test);
test1.b[0]="change"; //改变test1的b属性对象的第0个数组元素
alert(test.b[0]);  //不影响test,返回1
alert(test1.b[0]); //返回change

基于jQuery的实现方法

jQuery.extend([deep], target, object1, [objectN]);

用一个或多个其他对象来扩展一个对象,返回被扩展的对象。
如果不指定target,则给jQuery命名空间本身进行扩展。这有助于插件作者为jQuery增加新方法。 如果第一个参数设置为true,则jQuery返回一个深层次的副本,递归地复制找到的任何对象。否则的话,副本会与原对象共享结构。 未定义的属性将不会被复制,然而从对象的原型继承的属性将会被复制。
demo:

var options = {id: "nav", class: "header"}
var config = $.extend({id: "navi"}, options); //config={id: "nav", class: "header"}

** 2. 关于this**
这个对象的所有方法的this都指向这个对象,所以就不需要重新指定
写法四
主体结构

function EditorUtil() {
    this._editorContent = $( '#editor_content' );
    this._picBtn = $( '#project_pic' );
    this.ieBookmark = null;
}
EditorUtil.prototype = {
    consturctor: EditorUtil,
 
    noteBookmark: function() {
    },
    htmlReplace: function( text ) {
        if( typeof text === 'string' ) {
            return text.replace( /[<>"&]/g, function( match, pos, originalText ) {
                switch( match ) {
                    case '<':
                        return '&lt;';
                    case '>':
                        return '&gt;';
                    case '&':
                        return '&amp;';
                    case '"':
                        return '&quot;';
                }
            });
        }
        return '';
    },
    init: function() {
        this._memBtn.bind( 'click', function( event ) {
            $(".error_content").hide();
            return false;
        });
    }
};
 
// 初始化富文本编辑器
var editor = new EditorUtil();
editor.init();

点评
写法四和写法三其实都差不多,但是你们有没有看出其中的不一样呢?
1. 两种都是利用原型链给对象添加方法
写法三:

MemberCard.prototype._timedHide
MemberCard.prototype.hide

写法四:

EditorUtil.prototype = {
    consturctor: EditorUtil,
    noteBookmark: function(){},    
    htmlReplace: function(){}
}

细看写法四利用“对象直接量”的写法给EditorUtil对象添加方法,和写法三的区别在于写法四这样写会造成consturctor属性的改变
constructor属性:始终指向创建当前对象的构造函数
每个函数都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数。如下例所示:

function Person(name) {  
    this.name = name;  
};  
Person.prototype.getName = function() {  
    return this.name;  
};  
var p = new Person("ZhangSan");  
 
console.log(p.constructor === Person);  // true  
console.log(Person.prototype.constructor === Person); // true  
// 将上两行代码合并就得到如下结果  
console.log(p.constructor.prototype.constructor === Person); // true

当时当我们重新定义函数的prototype时(注意:和上例的区别,这里不是修改而是覆盖),constructor属性的行为就有点奇怪了,如下示例:

function Person(name) {  
    this.name = name;  
};  
Person.prototype = {  
    getName: function() {  
        return this.name;  
    }  
};  
var p = new Person("ZhangSan");  
console.log(p.constructor === Person);  // false  
console.log(Person.prototype.constructor === Person); // false  
console.log(p.constructor.prototype.constructor === Person); // false

为什么呢?
原来是因为覆盖Person.prototype时,等价于进行如下代码操作:

Person.prototype = new Object({  
    getName: function() {  
        return this.name;  
    }  
});

而constructor属性始终指向创建自身的构造函数,所以此时Person.prototype.constructor === Object,即是:


function Person(name) {  
    this.name = name;  
};  
Person.prototype = {  
    getName: function() {  
        return this.name;  
    }  
};  
var p = new Person("ZhangSan");  
console.log(p.constructor === Object);  // true  
console.log(Person.prototype.constructor === Object); // true  
console.log(p.constructor.prototype.constructor === Object); // true

怎么修正这种问题呢?方法也很简单,重新覆盖Person.prototype.constructor即可:

function Person(name) {  
    this.name = name;  
};  
Person.prototype = new Object({  
    getName: function() {  
        return this.name;  
    }  
});  
Person.prototype.constructor = Person;  
var p = new Person("ZhangSan");  
console.log(p.constructor === Person);  // true  
console.log(Person.prototype.constructor === Person); // true  
console.log(p.constructor.prototype.constructor === Person); // true
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容