前言
最近在折腾jQuery插件,写成插件的目的就是为了实现功能与项目相分离,使得这个代码在下一个项目中能直接引用不出错。这使得我们在写插件的时候,就得考虑清楚,怎么写才能使得插件能够通用、灵活度高、可配置、兼容性好、易用性高、耦合度低等。
接下来就对以下几种写法进行分析,前两个是jQuery插件,后面2个是以对象的形式开发,都类似。而且写法也很多,我们要懂得这样写的利弊。另一篇基础文章:
写法一
插件主体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
(
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
)
;
|
调用
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var
MNDialog
=
$
(
"#header"
)
.
MNDialog
(
{
sId
:
"#footer"
,
//覆盖默认值
fCloseCallback
:
dialog
,
//回调函数
oContentHtml
:
function
(
_aoCallback
)
{
_aoCallback
(
_oEditGrpDlgView
.
el
)
;
}
}
}
)
;
// 调用提供的函数
MNDialog
.
close
;
function
dialog
(
)
{
}
|
点评
1. 自调用匿名函数
1
2
3
|
(
function
(
$
,
window
)
{
// jquery code
}
)
(
jQuery
,
window
)
;
|
用处:通过定义一个匿名函数,创建了一个“私有”的命名空间,该命名空间的变量和方法,不会破坏全局的命名空间。这点非常有用也是一个JS框架必须支持的功能,jQuery被应用在成千上万的JavaScript程序中,必须确保jQuery创建的变量不能和导入他的程序所使用的变量发生冲突。
2. 匿名函数为什么要传入window
通过传入window变量,使得window由全局变量变为局部变量,当在jQuery代码块中访问window时,不需要将作用域链回退到顶层作用域,这样可以更快的访问window;这还不是关键所在,更重要的是,将window作为参数传入,可以在压缩代码时进行优化,看看jquery.min.js:
1
|
(
function
(
a
,
b
)
{
}
)
(
jQuery
,
window
)
;
// jQuery被优化为a, window 被优化为 b
|
3. 全局变量this定义
1
2
|
var
_oSelf
=
this
,
$
this
=
$
(
this
)
;
|
使得在插件的函数内可以使用指向插件的this
4. 插件配置
1
|
this
.
oConfig
=
$
.
extend
(
defaults
,
_aoConfig
)
;
|
设置默认参数,同时也可以再插件定义时传入参数覆盖默认值
5. 初始化函数
一般的插件会有init初始化函数并在插件的尾部初始化
6. 私有函数、公有函数
私有函数:插件内使用,函数名使用”_”作为前缀标识
共有函数:可在插件外使用,函数名使用”this.”作为前缀标识,作为插件的一个方法供外部使用
7. return this
最后返回jQuery对象,便于jQuery的链式操作
写法二
主体结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
(
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,后面的均为参数
语法:
1
|
call
(
[
thisObj
[
,
arg1
[
,
arg2
[
,
[
,
.
argN
]
]
]
]
]
)
|
定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明:call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
4. 关于”this”
在插件的方法中,可能有用到指向插件的this、和指向事件触发的this,所以事件触发的this用event来获取:event.cuerrntTarget
如:
html代码
1
2
3
|
<
div
id
=
"wrapper"
>
<
a
href
=
"#"
id
=
"inner"
>
click
here
!
<
/
a
>
<
/
div
>
|
js代码
1
2
3
4
5
6
7
8
9
10
|
$
(
'#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
)
;
}
)
;
|
结果输出
1
2
3
4
5
6
|
#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
>
|
写法三(原生写法)
主体结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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
)
{
}
|
使用
1
2
|
var
oColleagueCard
=
new
MemberCard
(
{
nHideTime
:
200
}
)
;
oColleagueCard
.
hide
(
)
;
|
点评
1. 关于属性覆盖(对象深拷贝)
原生函数实现方法
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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:
1
2
3
4
5
6
|
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的实现方法
1
|
jQuery
.
extend
(
[
deep
]
,
target
,
object1
,
[
objectN
]
)
;
|
用一个或多个其他对象来扩展一个对象,返回被扩展的对象。
如果不指定target,则给jQuery命名空间本身进行扩展。这有助于插件作者为jQuery增加新方法。 如果第一个参数设置为true,则jQuery返回一个深层次的副本,递归地复制找到的任何对象。否则的话,副本会与原对象共享结构。 未定义的属性将不会被复制,然而从对象的原型继承的属性将会被复制。
demo:
1
2
|
var
options
=
{
id
:
"nav"
,
class
:
"header"
}
var
config
=
$
.
extend
(
{
id
:
"navi"
}
,
options
)
;
//config={id: "nav", class: "header"}
|
2. 关于this
这个对象的所有方法的this都指向这个对象,所以就不需要重新指定
写法四
主体结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
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 '<';
case '>':
return '>';
case '&':
return '&';
case '"
':
return '
&
quot
;
';
}
});
}
return '
';
},
init: function() {
this._memBtn.bind( '
click'
,
function
(
event
)
{
$
(
".error_content"
)
.
hide
(
)
;
return
false
;
}
)
;
}
}
;
// 初始化富文本编辑器
var
editor
=
new
EditorUtil
(
)
;
editor
.
init
(
)
;
|
点评
写法四和写法三其实都差不多,但是你们有没有看出其中的不一样呢?
1. 两种都是利用原型链给对象添加方法
写法三:
1
2
|
MemberCard
.
prototype
.
_timedHide
MemberCard
.
prototype
.
hide
|
写法四:
1
2
3
4
5
|
EditorUtil
.
prototype
=
{
consturctor
:
EditorUtil
,
noteBookmark
:
function
(
)
{
}
,
htmlReplace
:
function
(
)
{
}
}
|
细看写法四利用“对象直接量”的写法给EditorUtil对象添加方法,和写法三的区别在于写法四这样写会造成consturctor属性的改变
constructor属性:始终指向创建当前对象的构造函数
每个函数都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数。如下例所示:
1
2
3
4
5
6
7
8
9
10
11
12
|
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属性的行为就有点奇怪了,如下示例:
1
2
3
4
5
6
7
8
9
10
11
12
|
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时,等价于进行如下代码操作:
1
2
3
4
5
|
Person
.
prototype
=
new
Object
(
{
getName
:
function
(
)
{
return
this
.
name
;
}
}
)
;
|
而constructor属性始终指向创建自身的构造函数,所以此时Person.prototype.constructor === Object,即是:
1
2
3
4
5
6
7
8
9
10
11
12
|
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即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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
|
转自 http://www.xuanfengge.com/jquery-plug-in-written-summary-and-summary-of-writing-object-oriented-manner.html