# 擴展
當我們使用jQuery對象的方法時,由于jQuery對象可以操作一組DOM,而且支持鏈式操作,所以用起來非常方便。
但是jQuery內置的方法永遠不可能滿足所有的需求。比如,我們想要高亮顯示某些DOM元素,用jQuery可以這么實現:
```
$('span.hl').css('backgroundColor', '#fffceb').css('color', '#d85030');
$('p a.hl').css('backgroundColor', '#fffceb').css('color', '#d85030');
```
總是寫重復代碼可不好,萬一以后還要修改字體就更麻煩了,能不能統一起來,寫個`highlight()`方法?
```
$('span.hl').highlight();
$('p a.hl').highlight();
```
答案是肯定的。我們可以擴展jQuery來實現自定義方法。將來如果要修改高亮的邏輯,只需修改一處擴展代碼。這種方式也稱為編寫jQuery插件。
## 編寫jQuery插件
給jQuery對象綁定一個新方法是通過擴展`$.fn`對象實現的。讓我們來編寫第一個擴展——`highlight1()`:
```
$.fn.highlight1 = function () {
// this已綁定為當前jQuery對象:
this.css('backgroundColor', '#fffceb').css('color', '#d85030');
return this;
}
```
注意到函數內部的`this`在調用時被綁定為jQuery對象,所以函數內部代碼可以正常調用所有jQuery對象的方法。
對于如下的HTML結構:
```
<!-- HTML結構 -->
<div id="test-highlight1">
<p>什么是<span>jQuery</span></p>
<p><span>jQuery</span>是目前最流行的<span>JavaScript</span>庫。</p>
</div>
```
來測試一下`highlight1()`的效果:
```
'use strict';
$('#test-highlight1 span').highlight1();
```
細心的童鞋可能發現了,為什么最后要`return this;`?因為jQuery對象支持鏈式操作,我們自己寫的擴展方法也要能繼續鏈式下去:
```
$('span.hl').highlight1().slideDown();
```
不然,用戶調用的時候,就不得不把上面的代碼拆成兩行。
但是這個版本并不完美。有的用戶希望高亮的顏色能自己來指定,怎么辦?
我們可以給方法加個參數,讓用戶自己把參數用對象傳進去。于是我們有了第二個版本的`highlight2()`:
<script>$.fn.highlight2 = function (options) { var bgcolor = options && options.backgroundColor || '#fffceb'; var color = options && options.color || '#d85030'; this.css('backgroundColor', bgcolor).css('color', color); return this; }</script>
```
$.fn.highlight2 = function (options) {
// 要考慮到各種情況:
// options為undefined
// options只有部分key
var bgcolor = options && options.backgroundColor || '#fffceb';
var color = options && options.color || '#d85030';
this.css('backgroundColor', bgcolor).css('color', color);
return this;
}
```
對于如下HTML結構:
```
<!-- HTML結構 -->
<div id="test-highlight2">
<p>什么是<span>jQuery</span> <span>Plugin</span></p>
<p>編寫<span>jQuery</span> <span>Plugin</span>可以用來擴展<span>jQuery</span>的功能。</p>
</div>
```
來實測一下帶參數的`highlight2()`:
```
'use strict';
$('#test-highlight2 span').highlight2({
backgroundColor: '#00a8e6',
color: '#ffffff'
});
```
對于默認值的處理,我們用了一個簡單的`&&`和`||`短路操作符,總能得到一個有效的值。
另一種方法是使用jQuery提供的輔助方法`$.extend(target, obj1, obj2, ...)`,它把多個object對象的屬性合并到第一個target對象中,遇到同名屬性,總是使用靠后的對象的值,也就是越往后優先級越高:
```
// 把默認值和用戶傳入的options合并到對象{}中并返回:
var opts = $.extend({}, {
backgroundColor: '#00a8e6',
color: '#ffffff'
}, options);
```
緊接著用戶對`highlight2()`提出了意見:每次調用都需要傳入自定義的設置,能不能讓我自己設定一個缺省值,以后的調用統一使用無參數的`highlight2()`?
也就是說,我們設定的默認值應該能允許用戶修改。
那默認值放哪比較合適?放全局變量肯定不合適,最佳地點是`$.fn.highlight2`這個函數對象本身。
于是最終版的`highlight()`終于誕生了:
<script>$.fn.highlight = function (options) { var opts = $.extend({}, $.fn.highlight.defaults, options); this.css('backgroundColor', opts.backgroundColor).css('color', opts.color); return this; } $.fn.highlight.defaults = { color: '#d85030', backgroundColor: '#fff8de' }</script>
```
$.fn.highlight = function (options) {
// 合并默認值和用戶設定值:
var opts = $.extend({}, $.fn.highlight.defaults, options);
this.css('backgroundColor', opts.backgroundColor).css('color', opts.color);
return this;
}
// 設定默認值:
$.fn.highlight.defaults = {
color: '#d85030',
backgroundColor: '#fff8de'
}
```
這次用戶終于滿意了。用戶使用時,只需一次性設定默認值:
```
$.fn.highlight.defaults.color = '#fff';
$.fn.highlight.defaults.backgroundColor = '#000';
```
然后就可以非常簡單地調用`highlight()`了。
對如下的HTML結構:
```
<!-- HTML結構 -->
<div id="test-highlight">
<p>如何編寫<span>jQuery</span> <span>Plugin</span></p>
<p>編寫<span>jQuery</span> <span>Plugin</span>,要設置<span>默認值</span>,并允許用戶修改<span>默認值</span>,或者運行時傳入<span>其他值</span>。</p>
</div>
```
實測一下修改默認值的效果:
```
'use strict';
$.fn.highlight.defaults.color = '#659f13';
$.fn.highlight.defaults.backgroundColor = '#f2fae3';
$('#test-highlight p:first-child span').highlight();
$('#test-highlight p:last-child span').highlight({
color: '#dd1144'
});
```
最終,我們得出編寫一個jQuery插件的原則:
1. 給`$.fn`綁定函數,實現插件的代碼邏輯;
2. 插件函數最后要`return this;`以支持鏈式調用;
3. 插件函數要有默認值,綁定在`$.fn.<pluginName>.defaults`上;
4. 用戶在調用時可傳入設定值以便覆蓋默認值。
## 針對特定元素的擴展
我們知道jQuery對象的有些方法只能作用在特定DOM元素上,比如`submit()`方法只能針對`form`。如果我們編寫的擴展只能針對某些類型的DOM元素,應該怎么寫?
還記得jQuery的選擇器支持`filter()`方法來過濾嗎?我們可以借助這個方法來實現針對特定元素的擴展。
舉個例子,現在我們要給所有指向外鏈的超鏈接加上跳轉提示,怎么做?
先寫出用戶調用的代碼:
```
$('#main a').external();
```
然后按照上面的方法編寫一個`external`擴展:
<script>$.fn.external = function () { return this.filter('a').each(function () { var a = $(this); var url = a.attr('href'); if (url && (url.indexOf('http://')===0 || url.indexOf('https://')===0)) { a.attr('href', '#0') .removeAttr('target') .append(' <i class="uk-icon-external-link"></i>') .click(function () { if(confirm('你確定要前往' + url + '?')) { window.open(url); } }); } }); }</script>
```
$.fn.external = function () {
// return返回的each()返回結果,支持鏈式調用:
return this.filter('a').each(function () {
// 注意: each()內部的回調函數的this綁定為DOM本身!
var a = $(this);
var url = a.attr('href');
if (url && (url.indexOf('http://')===0 || url.indexOf('https://')===0)) {
a.attr('href', '#0')
.removeAttr('target')
.append(' <i class="uk-icon-external-link"></i>')
.click(function () {
if(confirm('你確定要前往' + url + '?')) {
window.open(url);
}
});
}
});
}
```
對如下的HTML結構:
```
<!-- HTML結構 -->
<div id="test-external">
<p>如何學習<a href="http://jquery.com">jQuery</a>?</p>
<p>首先,你要學習<a href="/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000">JavaScript</a>,并了解基本的<a href="https://developer.mozilla.org/en-US/docs/Web/HTML">HTML</a>。</p>
</div>
```
實測外鏈效果:
```
'use strict';
$('#test-external a').external();
```
## 小結
擴展jQuery對象的功能十分簡單,但是我們要遵循jQuery的原則,編寫的擴展方法能支持鏈式調用、具備默認值和過濾特定元素,使得擴展方法看上去和jQuery本身的方法沒有什么區別。
- JavaScript教程
- JavaScript簡介
- 快速入門
- 基本語法
- 數據類型和變量
- 字符串
- 數組
- 對象
- 條件判斷
- 循環
- Map和Set
- iterable
- 函數
- 函數定義和調用
- 變量作用域
- 方法
- 高階函數
- map/reduce
- filter
- sort
- 閉包
- 箭頭函數
- generator
- 標準對象
- Date
- RegExp
- JSON
- 面向對象編程
- 創建對象
- 原型繼承
- 瀏覽器
- 瀏覽器對象
- 操作DOM
- 更新DOM
- 插入DOM
- 刪除DOM
- 操作表單
- 操作文件
- AJAX
- Promise
- Canvas
- jQuery
- 選擇器
- 層級選擇器
- 查找和過濾
- 操作DOM
- 修改DOM結構
- 事件
- 動畫
- 擴展
- underscore
- Collections
- Arrays
- Functions
- Objects
- Chaining
- Node.js
- 安裝Node.js和npm
- 第一個Node程序
- 模塊
- 基本模塊
- fs
- stream
- http
- buffer
- Web開發
- koa
- mysql
- swig
- 自動化工具
- 期末總結
- Python 2.7教程
- Python簡介
- 安裝Python
- Python解釋器
- 第一個Python程序
- 使用文本編輯器
- 輸入和輸出
- Python基礎
- 數據類型和變量
- 字符串和編碼
- 使用list和tuple
- 條件判斷和循環
- 使用dict和set
- 函數
- 調用函數
- 定義函數
- 函數的參數
- 遞歸函數
- 高級特性
- 切片
- 迭代
- 列表生成式
- 生成器
- 函數式編程
- 高階函數
- map/reduce
- filter
- sorted
- 返回函數
- 匿名函數
- 裝飾器
- 偏函數
- 模塊
- 使用模塊
- 安裝第三方模塊
- 使用__future__
- 面向對象編程
- 類和實例
- 訪問限制
- 繼承和多態
- 獲取對象信息
- 面向對象高級編程
- 使用__slots__
- 使用@property
- 多重繼承
- 定制類
- 使用元類
- 錯誤、調試和測試
- 錯誤處理
- 調試
- 單元測試
- 文檔測試
- IO編程
- 文件讀寫
- 操作文件和目錄
- 序列化
- 進程和線程
- 多進程
- 多線程
- ThreadLocal
- 進程 vs. 線程
- 分布式進程
- 正則表達式
- 常用內建模塊
- collections
- base64
- struct
- hashlib
- itertools
- XML
- HTMLParser
- 常用第三方模塊
- PIL
- 圖形界面
- 網絡編程
- TCP/IP簡介
- TCP編程
- UDP編程
- 電子郵件
- SMTP發送郵件
- POP3收取郵件
- 訪問數據庫
- 使用SQLite
- 使用MySQL
- 使用SQLAlchemy
- Web開發
- HTTP協議簡介
- HTML簡介
- WSGI接口
- 使用Web框架
- 使用模板
- 協程
- gevent
- 實戰
- Day 1 - 搭建開發環境
- Day 2 - 編寫數據庫模塊
- Day 3 - 編寫ORM
- Day 4 - 編寫Model
- Day 5 - 編寫Web框架
- Day 6 - 添加配置文件
- Day 7 - 編寫MVC
- Day 8 - 構建前端
- Day 9 - 編寫API
- Day 10 - 用戶注冊和登錄
- Day 11 - 編寫日志創建頁
- Day 12 - 編寫日志列表頁
- Day 13 - 提升開發效率
- Day 14 - 完成Web App
- Day 15 - 部署Web App
- Day 16 - 編寫移動App
- 期末總結
- Python3教程
- Python簡介
- 安裝Python
- Python解釋器
- 第一個Python程序
- 使用文本編輯器
- Python代碼運行助手
- 輸入和輸出
- Python基礎
- 數據類型和變量
- 字符串和編碼
- 使用list和tuple
- 條件判斷
- 循環
- 使用dict和set
- 函數
- 調用函數
- 定義函數
- 函數的參數
- 遞歸函數
- 高級特性
- 切片
- 迭代
- 列表生成式
- 生成器
- 迭代器
- 函數式編程
- 高階函數
- map/reduce
- filter
- sorted
- 返回函數
- 匿名函數
- 裝飾器
- 偏函數
- 模塊
- 使用模塊
- 安裝第三方模塊
- 面向對象編程
- 類和實例
- 訪問限制
- 繼承和多態
- 獲取對象信息
- 實例屬性和類屬性
- 面向對象高級編程
- 使用__slots__
- 使用@property
- 多重繼承
- 定制類
- 使用枚舉類
- 使用元類
- 錯誤、調試和測試
- 錯誤處理
- 調試
- 單元測試
- 文檔測試
- IO編程
- 文件讀寫
- StringIO和BytesIO
- 操作文件和目錄
- 序列化
- 進程和線程
- 多進程
- 多線程
- ThreadLocal
- 進程 vs. 線程
- 分布式進程
- 正則表達式
- 常用內建模塊
- datetime
- collections
- base64
- struct
- hashlib
- itertools
- XML
- HTMLParser
- urllib
- 常用第三方模塊
- PIL
- virtualenv
- 圖形界面
- 網絡編程
- TCP/IP簡介
- TCP編程
- UDP編程
- 電子郵件
- SMTP發送郵件
- POP3收取郵件
- 訪問數據庫
- 使用SQLite
- 使用MySQL
- 使用SQLAlchemy
- Web開發
- HTTP協議簡介
- HTML簡介
- WSGI接口
- 使用Web框架
- 使用模板
- 異步IO
- 協程
- asyncio
- async/await
- aiohttp
- 實戰
- Day 1 - 搭建開發環境
- Day 2 - 編寫Web App骨架
- Day 3 - 編寫ORM
- Day 4 - 編寫Model
- Day 5 - 編寫Web框架
- Day 6 - 編寫配置文件
- Day 7 - 編寫MVC
- Day 8 - 構建前端
- Day 9 - 編寫API
- Day 10 - 用戶注冊和登錄
- Day 11 - 編寫日志創建頁
- Day 12 - 編寫日志列表頁
- Day 13 - 提升開發效率
- Day 14 - 完成Web App
- Day 15 - 部署Web App
- Day 16 - 編寫移動App
- FAQ
- 期末總結
- Git教程
- Git簡介
- Git的誕生
- 集中式vs分布式
- 安裝Git
- 創建版本庫
- 時光機穿梭
- 版本回退
- 工作區和暫存區
- 管理修改
- 撤銷修改
- 刪除文件
- 遠程倉庫
- 添加遠程庫
- 從遠程庫克隆
- 分支管理
- 創建與合并分支
- 解決沖突
- 分支管理策略
- Bug分支
- Feature分支
- 多人協作
- 標簽管理
- 創建標簽
- 操作標簽
- 使用GitHub
- 自定義Git
- 忽略特殊文件
- 配置別名
- 搭建Git服務器
- 期末總結