john's tech blog

hope is coming


  • 首页

  • 标签

  • 归档

jquery_each函数深入分析

发表于 2016-09-01 | 更新于 2019-05-09

jquery1.0源码解读

$().each(function(){})的流程是怎样的

顾名思义,each就是对集合中的每个元素调用传入的function函数.

jQuery.fn.each

1
2
3
4
5
//each实例方法
each: function (fn, args) {
//调用jQuery静态方法each
return jQuery.each( this, fn, args );
}

jQuery.each

那就找到jQuery静态方法each:

1
2
3
4
5
6
7
8
9
10
11
each: function( obj, fn, args ) {
if ( obj.length == undefined )//如果不是数组
for ( var i in obj )
fn.apply( obj[i], args || [i, obj[i]] );
else
for ( var i = 0; i < obj.length; i++ )
//如果存在参数args那么就传索引和当前jquery元素给参数fn函数
//fn内部引用的就是当前obj[i]元素
fn.apply( obj[i], args || [i, obj[i]] );
return obj;
}

因为$(“.li”)返回的都是类数组对象,索引obj.length 就是当前数组元素的个数

$(“.li”)首先调用jQuery.find( a, c )静态函数,c为空

jQuery.find

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
find: function( t, context ) {
// Make sure that the context is a DOM Element
if ( context && context.nodeType == undefined )
context = null;

// Set the correct context (if none is provided)
//设置当前上下文为Document
context = context || jQuery.context || document;

if ( t.constructor != String ) return [t];

if ( !t.indexOf("//") ) {
context = context.documentElement;
t = t.substr(2,t.length);
} else if ( !t.indexOf("/") ) { // ".li".indexOf("/")为-1 那么就为false
context = context.documentElement;
t = t.substr(1,t.length);
// FIX Assume the root element is right :(
if ( t.indexOf("/") >= 1 )
t = t.substr(t.indexOf("/"),t.length);
}

var ret = [context];
var done = [];
var last = null;

while ( t.length > 0 && last != t ) {
var r = [];
last = t;

//去除空格并把//替换为空
t = jQuery.trim(t).replace( /^\/\//i, "" );

var foundToken = false;
//这里调用token 正则匹配 然后在map里传入function的字符串。。
for ( var i = 0; i < jQuery.token.length; i += 2 ) {
var re = new RegExp("^(" + jQuery.token[i] + ")");
var m = re.exec(t);

if ( m ) {
r = ret = jQuery.map( ret, jQuery.token[i+1] );
t = jQuery.trim( t.replace( re, "" ) );
foundToken = true;
}
}

if ( !foundToken ) {
if ( !t.indexOf(",") || !t.indexOf("|") ) {
if ( ret[0] == context ) ret.shift();
done = jQuery.merge( done, ret );//把跟context一样的元素删除
r = ret = [context];
t = " " + t.substr(1,t.length);
} else {
var re2 = /^([#.]?)([a-z0-9\\*_-]*)/i;//# 或 .开头
var m = re2.exec(t);

if ( m[1] == "#" ) {
// Ummm, should make this work in all XML docs
var oid = document.getElementById(m[2]);
r = ret = oid ? [oid] : [];
t = t.replace( re2, "" );
} else {
if ( !m[2] || m[1] == "." ) m[2] = "*";

for ( var i = 0; i < ret.length; i++ )
r = jQuery.merge( r,
m[2] == "*" ?
jQuery.getAll(ret[i]) :
ret[i].getElementsByTagName(m[2])
);
}
}
}

if ( t ) {
var val = jQuery.filter(t,r);
//fileter用来过滤 如 div#example
//先把div过滤掉 然后 根据通过正则取出 example
//再通过getAttribute('id') 比较id名称是否一样

// $(".examle") 如果页面中有两个example元素
//那么 经过jQuery.filter(".example",r)
//r为遍历出来的 所有页面元素
//val.r为 两个元素的数组
//val.t为""
ret = r = val.r;
t = jQuery.trim(val.t);//用于遍历".example.e"这种情况
}
}

if ( ret && ret[0] == context ) ret.shift();//弹出context返回数组原来的第一个元素的值 //该方法会改变数组的长度
done = jQuery.merge( done, ret );//合并元素数组

return done;
}

jQuery.token数组

一共8个,最后一个为function

1
2
3
4
5
6
7
8
9
10
11
12
13
	token: [
"\\.\\.|/\\.\\.", "a.parentNode",
">|/", "jQuery.sibling(a.firstChild)",
"\\+", "jQuery.sibling(a).next",
"~", function(a){
var r = [];
var s = jQuery.sibling(a);
if ( s.n > 0 )
for ( var i = s.n; i < s.length; i++ )
r.push( s[i] );
return r;
}
]

".li"一个都不匹配,进入(!findToken) 匹配/^([#.]?)([a-z0-9\\*_-]*)/i;,且顺序如下:

0: “.example”
1: “.”
2: “example”

jQuery.merge

进入jQuery.merge函数,此时m[2]为”*”,r为[]数组:

1
2
3
4
5
jQuery.merge( r,
m[2] == "*" ?
jQuery.getAll(ret[i]) :
ret[i].getElementsByTagName(m[2])
);

jQuery.getAll静态函数:

1
2
3
4
5
6
7
8
9
10
getAll: function(o,r) {
r = r || [];
var s = o.childNodes;
for ( var i = 0; i < s.length; i++ )
if ( s[i].nodeType == 1 ) {
r.push( s[i] );
jQuery.getAll( s[i], r );
}
return r;
}

getAll递归返回所有o的子节点

0: html
1: head
2: script
3: script
4: body
5: div.example
6: div.example
7: script

jQuery.filter 函数过滤:

1
jQuery.filter(t,r);

jQuery.merge再合并:

1
2
3
4
if ( ret && ret[0] == context ) ret.shift();//弹出context返回数组原来的第一个元素的值 //该方法会改变数组的长度
done = jQuery.merge( done, ret );//合并元素数组

return done;

返回的done就包含合并后的所有元素数组

jquery中$(this)的流程

发表于 2016-08-31 | 更新于 2019-05-07

jquery1.0源码解读

$(this)的流程是怎样的

假定$(this)存在于这样一段代码:

1
2
3
4
5

$("#child").each(function(){

var self = $(this);
})

那么$(this)会首先进入

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
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
	function jQuery(a,c) {

// Shortcut for document ready (because $(document).each() is silly)
//处理 ready函数,$(function(){})
if ( a && a.constructor == Function && jQuery.fn.ready )
return jQuery(document).ready(a);

// Make sure that a selection was provided
a = a || jQuery.context || document;

// Watch for when a jQuery object is passed as the selector
//如果a 是jQuery对象,把a和空数组合并,然后返回,这样做的目的是不破坏原来的jQuery对象。
//(注:jquery属性是每个jQuery对象都有的,值为jQuery的版本。
if (a.jquery)
return $( jQuery.merge( a, [] ) );

// Watch for when a jQuery object is passed at the context
//如果c是jQuery对象,调用find函数,去查找
if ( c && c.jquery )
return $( c ).find(a);

// If the context is global, return a new object
//jquery("#example") 第一次 进入 返回 new Jquery(),
//进入jQuery.find
//直接调用jQuery() 那么this == window,再调用 就返回new jQuery()
if ( window == this )
return new jQuery(a,c);

// Handle HTML strings
//处理html字符串
//以非<开头 当中<> 非>结束
//如果a是html代码,$("<div/>"),把html代码转成Dom元素
//jQuery.clean 就是把html代码 转换成Dom元素数组
var m = /^[^<]*(<.+>)[^>]*$/.exec(a);
if ( m ) a = jQuery.clean( [ m[1] ] );

// Watch for when an array is passed in
//如果a是数组或类数组,并且里面装的都是dom元素,把a和空数组合并一下
//如果是其他情况,就调用find函数,find函数是处理css表达式的
//最后调用get方法,做出jQuery对象返回
this.get( a.constructor == Array || a.length && !a.nodeType && a[0] != undefined && a[0].nodeType
?
// Assume that it is an array of DOM Elements
//假设是个Dom元素数组 ,那么合并
jQuery.merge( a, [] ) :

// Find the matching elements and save them for later
//调用jQuery 静态方法 通过Jquery.extend扩展
//$(this)就直接调用jQuery静态方法find line:588
jQuery.find( a, c ) );

// See if an extra function was provided
//参数里是否有扩展方法提供
var fn = arguments[ arguments.length - 1 ];

// If so, execute it in context
if ( fn && fn.constructor == Function )
this.each(fn);//通过jQuery.prototype 原型链
}

随后根据条件会进入

new jQuery(a,c)

1
2
if ( window == this )
return new jQuery(a,c);

因为直接调用jQuery方法this为window,那么就又会进入jQuery函数,这次就会进入

this.get函数

1
2
3
4
5
6
7
8
9
this.get( a.constructor == Array || a.length && !a.nodeType && a[0] != undefined && a[0].nodeType ?
// Assume that it is an array of DOM Elements
//假设是个Dom元素数组 ,那么合并
jQuery.merge( a, [] ) :

// Find the matching elements and save them for later
//调用jQuery 静态方法 通过Jquery.extend扩展
//$(this)就直接调用jQuery静态方法find line:588
jQuery.find( a, c ) );

get函数中因为a此时为页面元素,所以根据条件会进入

jQuery.find静态函数

1
2
3
4
5
6
7
8
9
10
11
find: function( t, context ) {
// Make sure that the context is a DOM Element
if ( context && context.nodeType == undefined )
context = null;

// Set the correct context (if none is provided)
//设置当前上下文为Document
context = context || jQuery.context || document;

if ( t.constructor != String ) return [t];
//....下面代码省略

因为满足t.constructor不为string,所以返回[t]数组,t就为当前元素。
再进入jQuery对象函数this.get即

jQuery.fn.get函数:

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
get: function( num ) {
// Watch for when an array (of elements) is passed in
if ( num && num.constructor == Array ) {

// Use a tricky hack to make the jQuery object
// look and feel like an array
//var obj = new Object();
//obj.length =0;
//[].push.apply(obj,[1,2,3])
//console.log(obj.length)
//3
//$(this) 又返回一个jQuery对象,又可以调用jQuery的各种方法
this.length = 0;
[].push.apply( this, num );

return this;
} else
return num == undefined ?

// Return a 'clean' array
jQuery.map( this, function(a){ return a } ) :

// Return just the object
this[num];
}

最后就调用

[].push.apply

1
2
3
   this.length = 0;
[].push.apply( this, num );
return this;

返回jQuery对象数组并包含当前元素

csharparalle_problem

发表于 2016-08-30 | 更新于 2019-05-09
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
string[] strs= new string[100];
for (int i = 0; i < 100; i++)
{
strs[i] = DateTime.Now.Add(TimeSpan.FromDays(i)).ToString("yyyy-MM-dd");
}

string[] strs2 = new string[100];
for (int i = 0; i < 100; i++)
{
strs2[i] = DateTime.Now.AddYears(1).Add(TimeSpan.FromDays(i)).ToString("yyyy-MM-dd");
}

string[] strs1 = new string[100];
for (int i = 0; i < 100; i++)
{
strs1[i] = DateTime.Now.Add(TimeSpan.FromDays(i)).ToString("HH:mm:ss");
}

ConcurrentDictionary<int, string> dic = new ConcurrentDictionary<int, string>();

ConcurrentDictionary<DateTime,List<int>> dic1 = new ConcurrentDictionary<DateTime,List<int>>();

ParallelLoopResult result = Parallel.For(0, 10, (i) =>
{
dic1.AddOrUpdate(DateTime.Now.Date,(k) =>
{
// Thread.Sleep(1000);
List<int> newList = new List<int>();
newList.Add(i);
foreach (var item in newList)
{
Console.WriteLine("init add :" + item + " " + DateTime.Now.ToString("HH:mm:ss fff"));
}
return newList;
}, (k, v) =>
{
//Console.WriteLine("Task:update index:" + i + ":" + v + " to " + strs[i]);
// Thread.Sleep(1000);
v.Add(i);
Console.WriteLine("init update : " + DateTime.Now.ToString("HH:mm:ss fff"));
return v;
});

List<int> countList = null;
if (dic1.TryRemove(DateTime.Now.Date, out countList) && countList.Count >= 2)
{
//这边会报集合已修改,无法进行遍历,看来countList线程共享?

//因为上面有可能会有其他线程的update操作
foreach (var item in countList)
{
Console.WriteLine("removed :" + " " + DateTime.Now.ToString("HH:mm:ss fff"));
}
}
else if (countList != null)
{
dic1.AddOrUpdate(DateTime.Now.Date, (k) =>

{
// Thread.Sleep(1000);
foreach (var item in countList)
{
Console.WriteLine("also add : "+ DateTime.Now.ToString("HH:mm:ss fff"));
}
return countList;
}, (k, v) =>
{
//Console.WriteLine("Task:update index:" + i + ":" + v + " to " + strs[i]);
// Thread.Sleep(1000);
v.AddRange(countList);

foreach (var item in v)
{
Console.WriteLine("update : " + DateTime.Now.ToString("HH:mm:ss fff"));
}


return v;
});
}


});

//while (!result.IsCompleted)
//{

//}

//foreach (var item in dic1)
//{
// List<int> intList = item.Value;
// for (int i = 0; i < intList.Count; i++)
// {
// Console.WriteLine(intList[i]);
// }

//}
Console.Read();


Task task0 = Task.Run(() =>

{
for (int i = 0; i < 100; i++)
{
dic.AddOrUpdate(i, (k) =>{
// Thread.Sleep(1000);
Console.WriteLine("Task:add:" +" index:"+i+" "+ strs[i]);
return strs[i];

}, (k, v) =>
{
Console.WriteLine("Task:update index:"+i+":"+ v + " to " + strs[i]);
// Thread.Sleep(1000);
return strs[i];
});
}
});

//task0.Start();

Task task1 = Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
string outValue ;
dic.TryRemove(i, out outValue);
if (!string.IsNullOrEmpty(outValue))
{
Console.WriteLine("Task1:remove:" + " index:" + i + " " + outValue);
dic.AddOrUpdate(i, (k) =>
{
// Thread.Sleep(1000);
Console.WriteLine("Task1:add:" + " index:" + i + " " + strs1[i]);
return strs1[i];

}, (k, v) =>
{
Console.WriteLine("Task1:update index:" + i + ":" + v + " to " + strs1[i]);
//Thread.Sleep(1000);
return strs1[i];
});
}
else
{
Console.WriteLine("Task1:remove:" + " index:" + i + " empty");
}


}
});

Task task2 = Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
dic.AddOrUpdate(i, (k) =>
{
// Thread.Sleep(1000);
Console.WriteLine("Task2:add:" + " index:" + i + " " + strs2[i]);
return strs2[i];

}, (k, v) =>
{
Console.WriteLine("Task2:update index:" + i + ":" + v + " to " + strs2[i]);
// Thread.Sleep(1000);
return strs2[i];
});
}
});

Task.WaitAll();
//https://msdn.microsoft.com/zh-cn/library/dd997369.aspx
// task1.Start();
Console.Read();

hexo-cli源码分析之控制台注册

发表于 2016-08-29 | 更新于 2019-05-07

hexo-cli 1.0.2版本

loadModule

hexo.js中定义了context.js,用来实例化hexo对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	return findPkg(cwd, args).then(function(path) {
if (!path) return;

hexo.base_dir = path;

return loadModule(path, args).catch(function() {
log.error('Local hexo not found in %s', chalk.magenta(tildify(path)));
log.error('Try running: \'npm install hexo --save\'');
process.exit(2);
});
}).then(function(mod) {
//加载完hexo模块把实例赋给hexo
if (mod) hexo = mod;
log = hexo.log;
//注册help init version
require('./console')(hexo);

return hexo.init();
})

hexo.init()

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
Hexo.prototype.init = function(){
var self = this;

this.log.debug('Hexo version: %s', chalk.magenta(this.version));
this.log.debug('Working directory: %s', chalk.magenta(tildify(this.base_dir)));

// Load internal plugins
//注册内部命令 如deploy new ...
require('../plugins/console')(this);
require('../plugins/filter')(this);
require('../plugins/generator')(this);
require('../plugins/helper')(this);
require('../plugins/processor')(this);
require('../plugins/renderer')(this);
require('../plugins/tag')(this);

// Load config
return Promise.each([
'update_package', // Update package.json
'load_config', // Load config
'load_plugins' // Load external plugins & scripts
], function(name){
return require('./' + name)(self);
}).then(function(){
return self.execFilter('after_init', null, {context: self});
}).then(function(){
// Ready to go!
self.emit('ready');
});
};

require('../plugins/console')(this); 会加载plugins/console目录下的index.js文件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = function(ctx){
var console = ctx.extend.console;

console.register('clean', 'Removed generated files and cache.', require('./clean'));

console.register('config', 'Get or set configurations.', {
usage: '[name] [value]',
arguments: [
{name: 'name', desc: 'Setting name. Leave it blank if you want to show all configurations.'},
{name: 'value', desc: 'New value of a setting. Leave it blank if you just want to show a single configuration.'}
]
}, require('./config'));

console.register('deploy', 'Deploy your website.', {
options: [
{name: '--setup', desc: 'Setup without deployment'},
{name: '-g, --generate', desc: 'Generate before deployment'}
]
}, require('./deploy'));
}

首先获取ctx.extend.console,其实就是extend目录下的index.js文件中定义的

1
2
3
4
5
6
7
8
9
exports.Console = require('./console');
exports.Deployer = require('./deployer');
exports.Filter = require('./filter');
exports.Generator = require('./generator');
exports.Helper = require('./helper');
exports.Migrator = require('./migrator');
exports.Processor = require('./processor');
exports.Renderer = require('./renderer');
exports.Tag = require('./tag');

jquery_event[1]

发表于 2016-08-29 | 更新于 2019-05-09

jquery1.0源码解读

添加元素事件

1.0源码:

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
new function(){

var e = ("blur,focus,load,resize,scroll,unload,click,dblclick," +
"mousedown,mouseup,mousemove,mouseover,mouseout,change,reset,select," +
"submit,keydown,keypress,keyup,error").split(",");
//比如$("#element").click(function(){});其实执行的就是bind
// Go through all the event names, but make sure that
// it is enclosed properly
//适当地封闭
//包装在一个function中执行
for ( var i = 0; i < e.length; i++ ) new function(){

var o = e[i];

// Handle event binding
//处理事件绑定 调用bind方法
jQuery.fn[o] = function(f){
//如果没参数f绑定 那么就执行事件。。
return f ? this.bind(o, f) : this.trigger(o);
};

// Handle event unbinding
jQuery.fn["un"+o] = function(f){ return this.unbind(o, f); };

// Finally, handle events that only fire once
//绑定只能触发一次事件
jQuery.fn["one"+o] = function(f){
// Attach the event listener
//遍历元素绑定
return this.each(function(){

//巧妙运用闭包
var count = 0;

// Add the event
jQuery.event.add( this, o, function(e){
// If this function has already been executed, stop
//只能执行一次
if ( count++ ) return;

// And execute the bound function
return f.apply(this, [e]);
});
});
};

};

// If Mozilla is used
if ( jQuery.browser.mozilla || jQuery.browser.opera ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", jQuery.ready, false );

// If IE is used, use the excellent hack by Matthias Miller
// http://www.outofhanwell.com/blog/index.php?title=the_window_onload_problem_revisited
} else if ( jQuery.browser.msie ) {

// Only works if you document.write() it
//script标签的defer属性,这个defer属性是IE独有的。当它被设为true的时候,表示这段script要等文档加载好了才执行。
document.write("<scr" + "ipt id=__ie_init defer=true " +
"src=//:><\/script>");

// Use the defer script hack
var script = document.getElementById("__ie_init");
script.onreadystatechange = function() {
if ( this.readyState == "complete" )
jQuery.ready();
};

// Clear from memory
script = null;

// If Safari is used
} else if ( jQuery.browser.safari ) {
// Continually check to see if the document.readyState is valid
//用个定时器轮询 1.2.6改为setTimeout( arguments.callee, 0 );
//1.4.3改为document.addEventListener
jQuery.safariTimer = setInterval(function(){
// loaded and complete are both valid states
if ( document.readyState == "loaded" ||
document.readyState == "complete" ) {

// If either one are found, remove the timer
clearInterval( jQuery.safariTimer );
jQuery.safariTimer = null;

// and execute any waiting functions
jQuery.ready();
}
}, 10);
}

// A fallback to window.onload, that will always work
jQuery.event.add( window, "load", jQuery.ready );

};

jQuery.event.add函数:

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
// Bind an event to an element
// Original by Dean Edwards
add: function(element, type, handler) {
// For whatever reason, IE has trouble passing the window object
// around, causing it to be cloned in the process
if ( jQuery.browser.msie && element.setInterval != undefined )
element = window;

// Make sure that the function being executed has a unique ID
if ( !handler.guid )
handler.guid = this.guid++;

// Init the element's event structure
//初始化元素事件结构
if (!element.events)
element.events = {};

// Get the current list of functions bound to this event
var handlers = element.events[type];

// If it hasn't been initialized yet
if (!handlers) {
// Init the event handler queue
handlers = element.events[type] = {};

// Remember an existing handler, if it's already there
if (element["on" + type])
handlers[0] = element["on" + type];
}

// Add the function to the element's handler list
//核心一句 添加此handler函数到handlers 对象
handlers[handler.guid] = handler;

// And bind the global event handler to the element
//最终执行的是jQuery.event.handle方法
//handle方法内部遍历调用events[type]
//还有阻止冒泡
element["on" + type] = this.handle;

// Remember the function in a global list (for triggering)
if (!this.global[type])
this.global[type] = [];
this.global[type].push( element );
//放到global对象的type数组中
//用于trigger函数调用
}

jQuery.fn.bind函数

1
2
3
4
5
6
7
bind: function( type, fn ) {
//如果fn是字符串
//比如".css()",那么就执行$(this).css()方法
if ( fn.constructor == String )
fn = new Function("e", ( !fn.indexOf(".") ? "$(this)" : "return " ) + fn);
jQuery.event.add( this, type, fn );
}
1…414243…47

John

232 日志
43 标签
GitHub Twitter
欢迎关注我的公众号:沉迷Spring
© 2023 johnwonder
由 Hexo 强力驱动 v3.2.0
|
主题 – NexT.Pisces v7.1.1
|
0%