分類:
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
閒話
我學習jq的時間很短,應該在1月,那時的版本還是1.11.3,通過看妙味課堂的公開課視頻和文檔裡的所有api的註解學習。
源碼則是最近些日子直接生啃,跳過了sizzle和文檔處理的部分(待業狗壓力大,工作以後再看),關注
data
、ready
、event
、queue
、Defferred
(jq的promise編程)、ajax
、animation
的處理,初看甚至有點噁心,耐著性子堅持卻嘆其精妙,在這裡記錄下來加深印象。
(本文採用 1.12.0 版本進行講解,用
#number
來標註行號) jQuery初始化
整體封裝上,考慮了兩點(寫輪子的重要參考)
模塊化支持:通過
衝突避免:保存
noGlobal
變量來決定是否綁定到全局變量,返回值為jQuery
衝突避免:保存
window.$
window.jQuery
,可以調用noConflict
還原,返回jQuery對象。(noGlobal為true時不需要)
主體使用了如下結構,抽離
if
分支,只關注主體邏輯的書寫- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
[源碼]
- 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
- 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
jQuery工廠結構
jq為了能有$.func、 $().func兩種調用方法,選擇了共享
jQuery.prototype
,return new jQuery.fn.init
這並不是唯一的方式(可以如下),之所以選擇如此,個人認為應該是使用頻率太高,這樣每次可以省掉兩次類型判斷。而
jQuery.fn
我想也是起到簡寫、別名的作用jQuery.extend/fn.extend
則決定了jq重要的插件擴展機制- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
[源碼]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
jQuery鏈式調用
精髓:通過
return this
, return this.pushStack()
, return this.prevObject
實現鏈式調用、增棧、回溯
[源碼]
- 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
- 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
看到這,說明你真的是個有耐心的boy了。我們上主菜!
IE內存洩露
講data緩存前,首先要必須介紹一下IE6-8的內存洩露問題。IE低版本dom採用COM組件的形式編寫,垃圾清理機制,使用的引用計數的方式,無法正確的處理包含dom元素的環狀的循環引用。即使刷新頁面內存也不會被釋放。
通常是如何產生循環引用的呢?
此例中,當前作用域裡包含變量a (引用了dom元素),通過綁定事件onclick,使dom引用了事件函數。但是函數在聲明時,會把當前所在活動對象的作用域鏈保存在自身的scope屬性,從而引用了當前環境定義的所有變量,而變量a指向dom,從而產生循環引用。需要手動把變量對dom的引用解除
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
jQuery.data原理
jq內部實現了一個緩存機制,用於分離dom對函數等對象的引用(函數對dom的引用取決於函數本身的特性、定義的位置,這個沒法子)。如何能做到呢?與 策略模式 的思路一致——字符串約定。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
通過
jQuery.data
的封裝可以輕鬆實現數據的存取,但是這樣就結束了麼?太天真了!
這樣做也會產生衍生的問題。雖然不產生循環引用,但對象綁定在jQuery.cache上,即使dom對象被移除,數據仍然不會因此消失。而且如果是函數,仍然會通過引用環境的變量單向引用著dom,導致dom元素也不消失。雖然刷新後內存會清出,不如循環引用嚴重,但也是問題了。
看起來仍然需要手動設置變量為null,彷彿回到原點,但是數據還在,比之前更不如。解決方法其實很簡單,就是移除節點時調用
jQuery.cleanData
,data沒有被任何對象引用,自然的回收。
但是問題仍然沒解決,因為例如綁定事件,即使函數放在jQuery.cache中,也至少有一個觸發函數綁定在dom上,因此
jQuery.event.add( elem, types, handler, data, selector )
中的elem返回前被設為null ,見源碼 #4961。所以如非事事通明,儘量使用jq提供的方式刪除節點、綁定事件等- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
jQuery架構方法
jQuery在此之上考慮了幾點:
核心
- 對普通對象和dom對象做了區分。因為普通對象的垃圾回收機制(GC)使用的不是引用計數,不會內存洩露。其次data機制本身增加了複雜度不說,對象解除引用後綁定的數據還需要手動銷毀,反而造成內存的無端佔用。普通對象綁定在自身的
jQuery.expando
屬性上,初始化為 { toJSON: jQuery.noop },防止序列化時暴露數據。dom對象綁定在 jQuery.cache[ ele[jQuery.expando] ] ,初始化為 {} - 私用和公用隔離,如 jQuery.cache[ guid ]、jQuery.cache[ guid ].data。內部的事件、隊列等等都使用的私有空間調用 _data 方法,而用戶儲存數據調用的 data 方法則是私有,由參數
pvt
決定,true代表私有 - 當移除數據後,jQuery.cache[ guid ]或 jQuery.cache[ guid ].data對象不再有數據,則移除該對象釋放內存。且當移除緩存對象時,綁定在elem的事件函數也將被移除
特性
- 支持通過簡單的自定義配置來增加不支持緩存的特例類型
- 擴展支持讀取 elem.attributes 中
data-
前綴定義的數據 - 以小駝峰書寫為標準讀取方式,內部進行相應轉換
jQuery.data結構
使用常見的外觀模式,定義核心功能,通過再封裝實現個性化的使用規則,以供其他模塊或外部調用
- 核心功能:(特點:參數多而全,邏輯負責全面)
jQuery.internalData( elem, name, data, pvt ) 為dom和對象設置data數據,支持 -> 核心12 jQuery.internalRemoveData( elem, name, pvt ) 移除dom或對象上指定屬性的data數據-> 核心3 jQuery.cleanData( elems, forceAcceptData ) 由於刪除data時還要刪除elem本身上綁定的觸發事件函數,因此不能簡單 delete cache[id]。 單獨封裝,被事件系統和 jQuery.internalRemoveData使用 ->核心3
- 外觀:(工具方法、實例方法)
jQuery.hasData( elem ) 直接在對應的 `cache` 中查找。 jQuery._data( elem, name, data ) jQuery.\_removeData( elem, name )`用於內部私有調用,封裝了核心方法,簡化了傳參數量。 jQuery.data( elem, name, data ) jQuery.removeData( elem, name ) 用於外部公共調用,封裝了核心方法,簡化了傳參數量。 jQuery.fn.data( key, value ) jQuery.fn.removeData( key, value ) 封裝了 jQuery.data jQuery.removeData ,遍歷this來分別調用
- 鉤子:埋在某個環節直接執行,根據需要實現終止流程、改變特性的功能,或不產生任何影響
acceptData( elem ) #3762,特性1,過濾 dataAttr( elem, key, data ) #3780,特性2,data的值不存在則會訪問元素的attribute,返回data,且值會緩存到cache jQuery.camelCase( string ) #356,特性3,小駝峰
[核心 + 外觀 + 鉤子] 感覺應該算一種常見好用的架構方式了。jq中用到了很多
jQuery.some()
-> 核心, jQuery.fn.some()
-> 外觀 的形式,也都差不多。
這裡提一下我所理解的鉤子,Callback的四個字符串特性就類似於四個鉤子,在最常見的幾個需求分歧上埋設,發展了觀察者模式的4種特性支持,event的眾多鉤子
標準方式轉換 + 個例bug修正
完成了兼容的大業與主邏輯的統一。
另外不得不提一個在此維度之外的優化方案 —— 提煉重複代碼。核心邏輯是不可省的。通常支持數種方式定義參數,把其變化為某種固定形式的參數傳入來執行遞歸是分離函數內部個性化與核心邏輯的第一步,還可以進一步,抽出此部分邏輯,保持主邏輯的純粹,變成外觀部分內的邏輯進行 功能增強。jq再進了一步,由於大量方法都支持參數判斷存取、對象或字符串均可、map映射,分離出
access( elems, fn, key, value, chainable, emptyGet, raw )
#4376
,核心 jQuery.xxx(),外觀jQuery.fn.xxx() 裡調用 access`。- 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
- 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
但是,data緩存系統並沒有這樣使用。原因也很簡單,基礎的共性部分支持不同不支持映射,如上面的css是共性相同的情況下可以增加個性,但不同的情況就要重新抽出、或寫在外觀裡、或寫在核心代碼裡使用遞歸。
[源碼]
- 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
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
- 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
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
最後再說幾點:
$(elem).data("key", "name")
與$.data($(elem), "key", "name")
的區別是前者內部元素被迭代,綁定在元素(dom)上,而後者綁定在$(elem)
對象(object)上,區別不言自明。- 對於支持對象等多種參數形式的邏輯本身更多放在外觀裡,這裡在
internalData
,因為公有私有不止一個外觀,避免重複要麼抽出類似access
使用,要麼放到公共方法中。而之所以不放在最終的實例data
方法中,因為工具方法已經在jq模塊內部被多次使用了,這樣可以有效簡化內部操作。 dataAtrr
之所以在實例data
方法中才出現被使用,是因為只有用戶調用的時候dom才加載完呀,才會產生這個需要。