分享

浅谈Bootstrap插件机制

 明灭的烟头 2018-08-27

最近一直在看Bootstrap,不得不说其中有很多学习的地方。今天我就来讲一讲Bootstrap是如何书写jQuery插件的,希望能给大家一些帮助。

要理Bootstrap的解插件机制,首先要知道他的一些特性。我们打开Bootstrap中文网JavaScript插件这一节,首先就可以看到对bootstrap插件的概览。其中有这么几个特性,单个引入还是全部引入,data属性,编程方式的API,避免命名空间冲突,事件。如果你对这些特性比较了解,一定会对你理解他的插件机制大有裨益的。因为一开始我并没有注意这些特性,而是直接从源码入手,很多都是自己思考的,可以说走了很多弯路。下面我们就看一看一个典型的Bootstrap插件是如何书写的。

function ($) {

'use strict';

// SCROLLSPY CLASS DEFINITION

// ==========================

function ScrollSpy(element, options) {

//构造函数

}

ScrollSpy.prototype.getScrollHeight = function () {}

ScrollSpy.prototype.refresh = function () {}

// SCROLLSPY PLUGIN DEFINITION

// ===========================

function Plugin(option) {

return this.each(function () {

var $this = $(this)

var data = $this.data('bs.scrollspy')

var options = typeof option == 'object' && option

if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))

if (typeof option == 'string') data[option]()

})

}

var old = $.fn.scrollspy

$.fn.scrollspy = Plugin

$.fn.scrollspy.Constructor = ScrollSpy

// SCROLLSPY NO CONFLICT

// =====================

$.fn.scrollspy.noConflict = function () {

$.fn.scrollspy = old

return this

}

// SCROLLSPY DATA-API

// ==================

$(window).on('load.bs.scrollspy.data-api', function () {

$('[data-spy='scroll']').each(function () {

var $spy = $(this)

Plugin.call($spy, $spy.data())

})

})

}(jQuery);

这里我们对ScrollSpy进行了简化,只保留了一部分函数。从注释就可以看出,ScrollSpy这个插件包括4个部分,第一部分是ScrollSpy类的定义,第二部分是ScrollSpy插件定义,第三部分是去除命名冲突,第四部分是ScrollSpy的DATA-API。

首先Bootstrap允许对某个插件的单独引用,也可以全部引用。这是因为每个Bootstrap插件都是完整的jQuery插件,如上面的代码所示。而Bootstrap.js是所有插件的组合。这样允许我们只加载必要的插件,节省流量。了解了这一特性,你就会知道我们只要对其中一个插件进行讲解,你就可以理解所有的插件机制。下面我们从代码的Plugin部分开始介绍。

Plugin接受一个option参数,这个参数可以是一个对象,也可以是一个字符串。就像官网介绍的使用方式一样,我们可以通过JavaScript代码启动滚动监听插件,如下所示

$('body').scrollspy({ target: '#navbar-example' })

我们也可以采用下面的方法来直接调用插件的某一方法

var $spy = $(this).scrollspy('refresh')

下面我们就来分析一下Plugin的代码。第一行是return this.each(function(){}),这是什么作用呢?如果去掉each我想你一定明白,就是返回Plugin的调用对象,这样可以实现链式调用。加上each后,其实他还是返回this,因为each本身也是支持链式调用的。那么,这里为甚么要用each呢?这是因为jQuery的选择器引擎。jQuery的选择器引擎是Sizzle,他可以为你的函数提供多个元素,因此我们要为每个元素执行相同的操作。接着,我们把$(this)对象进行了重命名,这不是必须的,但是会让我们的代码更加清晰。此处$(this)就是我们的插件执行主体,是一个jQuery对象。接下来读取了该对象的bs.scrollspy属性,并对传入的option进行了判断,如果是一个对象,就赋值给options属性。接下来的两个if语句,就是插件的关键所在。如果data没有定义,就为该对象的bs.scrollspy属性重新赋值。这里你可以看到,我们是将ScrollSpy的实例直接赋值给了bs.scrollspy属性。这里完成了两个动作,一是实例化ScrollSpy类,二是进行赋值。ScrollSpy类的构造函数中,已经执行了滚动监听的必要函数。因此,我们可以知道ScrollSpy事件已经执行。这就是Bootstrap插件的JavaScript调用机制。传入一个option对象,并对ScrollSpy实例化。注意此处的this

data = new ScrollSpy(this, options)

,他是一个DOM元素。看到这里,你就可以理解官网上的这句话

如果你想获取某个插件的实例,可以直接通过页面元素获取:$('[rel='popover']').data('popover')。

data属性存储的插件实例,自然可以通过data()函数获取了。如果仅仅是为了实例化一个ScrollSpy类,我们大可以直接new,但是作者为我们提供了一个简便的获取实例的方法,不得不说这个设计十分巧妙。接下来介绍

if (typeof option == 'string') data[option]()

这里对输入参数option进行检查,如果是一个字符串,就直接调用ScrollSpy的option方法。就像官网上的介绍的用法

使用滚动监听插件的同时在 DOM 中添加或删除元素后,你需要像下面这样调用此刷新( refresh) 方法

var $spy = $(this).scrollspy('refresh')

插件部分到这里还没有结束,还有最后三行代码

var old = $.fn.scrollspy

$.fn.scrollspy = Plugin

$.fn.scrollspy.Constructor = ScrollSpy

前两行明显是为了解决命名冲突的,将原始的$.fn.scrollspy存储在old变量中,并将我们的插件赋值给scrollspy。最后一行代码让我困惑了好久,一开始我以为是继承的写法,但是缺少prototype,而且constructor应该小写才对。后来看了官方文档,每个插件还通过 Constructor 属性暴露了其原始的构造函数:$.fn.ScrollSpy.Constructor。这才豁然大悟,Constructor就是一个普通属性,而不是构造函数,他的作用就是暴露原始构造函数。而且他的用法也十分简单,文档中的用法是这样的

每个插件都可以通过修改其自身的Constructor.DEFAULTS 对象从而改变插件的默认设置:

$.fn.modal.Constructor.DEFAULTS.keyboard = false // 将模态框插件的 `keyboard` 默认选参数置为 false

原来Constructor就是为了让我们访问到Scrollspy的一些属性,豁然大悟,和继承没有一点关系。看到这里你就已经完全理解Bootstrap插件中编程方式的API这一特性了。

第三部分比较简单,为了解决命名冲突,noConflict 方法将原始的old变量赋值给$.fn.srollspy变量。比较值得关注的是他的返回值,this。他返回了调用者本身,也就是ScrollSpy插件。这下我们就可以对他进行重新赋值,更改变量的名称。

最后一部分,是插件的DATA-API。官网中如是说

你可以仅仅通过 data 属性 API 就能使用所有的 Bootstrap 插件,无需写一行 JavaScript 代码。这是 Bootstrap 中的一等 API,也应该是你的首选方式。

我们就来看看这传说中的一等API是如何工作的,是不是有他说的这么神呢?

代码中函数为window对象注册了load.bs.scrollspy.data-api事件。这一点比较关键,为什么是window对象,而且是load事件呢?这确保了文档加载完毕后就立即执行,而处理函数中的选择器$('[data-spy='scroll']')就是我们的DATA-API。仅仅匹配data-spy属性为scroll的元素,并对元素逐一执行Plugin函数。注意Plugin函数的参数, $spy.data()。此处data并没有任何参数,就是读取出$spy对象中的data,并作为option传入Plugin中。最后你可能还会有疑惑,为什么这个load事件的名字起得这么长呢?我们来看看官方的用法你就会明白。

话又说回来,在某些情况下可能需要将此功能关闭。因此,我们还提供了关闭 data 属性 API 的方法,即解除以 data-api 为命名空间并绑定在文档上的事件。就像下面这样:

$(document).off('.data-api')

明白了吧?我们可以通过.data-api命名空间解除文档中所有的具有这一命名空间的事件,而不用逐一解除绑定。

文/丁向明

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多