5.6.3 源码分析

分析源码之前来思考一下,通过前面的两种用法可以看出,tooltip组件其实在HTML方面并没有特别的要求,可以定义data-toggle="tooltip",也可以什么都不定义,而使用jQuery的选择器来查找元素,然后应用tooltip函数。

在源码分析之前,一定要熟悉前面的9个属性,因为源码里针对这9个属性做了大量的代码工作。下面来一一分析一下。

步骤1 立即调用的函数。此步骤与Modal插件的步骤1一样,此处不再赘述。

步骤2 插件核心代码。tooltip的原型方法比较多,主要源码如下:

  1. var Tooltip = function (element, options) { // 实例化tooltip时,传入当前元素和options参数
  2. this.type =
  3. this.options =
  4. this.enabled =
  5. this.timeout =
  6. this.hoverState =
  7. this.$element = null
  8.  
  9. // 根据传入的选项进行初始化事件绑定
  10. // 注意:这里传入tooltip,因为popover插件也使用了tooltip的原型方法,所以init提供了一个type参数
  11. this.init('tooltip', element, options)
  12. }
  13.  
  14. Tooltip.DEFAULTS = {
  15. animation: true, // 是否开启动画
  16. placement: 'top', // 显示位置,默认在上方显示
  17. selector: false, // 触发器的选择符
  18. template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-
  19. inner"></div></div>', // tooltip显示的内容模板
  20. trigger: 'hover focus', // 设置触发tooltip的事件
  21. title: '', // 标题
  22. delay: 0, // 延迟
  23. html: false, // tooltip内容是否是html
  24. container: false // tooltip容器设置,如果有直接赋值,没有则默认false
  25. }
  26. // 初始化,事件绑定
  27. // 注意:这里传入的是tooltip,因为popover插件也使用了tooltip的原型方法,所以init提供了一个type参数
  28. // popover插件传入的就是popover
  29. Tooltip.prototype.init = function (type, element, options){};
  30. // 获取默认配置
  31. Tooltip.prototype.getDefaults = function (e){};
  32. // 获取配置参数(将传入的参数和默认参数合并)
  33. Tooltip.prototype.getOptions = function (options) {};
  34. // 手动触发的时候,添加额外的options参数
  35. Tooltip.prototype.getDelegateOptions = function (){};
  36. // 移除元素时的处理,主要是找到元素实例,然后调用show方法
  37. Tooltip.prototype.enter = function (obj) {};
  38. // 离开元素时的处理,主要是找到元素实例,然后调用hide方法
  39. Tooltip.prototype.leave = function (obj) {};
  40. // 显示tooltip提示语
  41. Tooltip.prototype.show = function (){};
  42. // 再次应用更新的placement样式和位置
  43. Tooltip.prototype.applyPlacement = function (offset, placement) {};
  44. // 更新小箭头的位置
  45. Tooltip.prototype.replaceArrow = function (delta, dimension, position) {};
  46. // 在模板里设置title内容,以便正式组装tooltip的内容
  47. Tooltip.prototype.setContent = function (){};
  48. // 关闭tooltip提示框
  49. Tooltip.prototype.hide = function (){};
  50. // 修复title提示,既有title,又有tooltip
  51. Tooltip.prototype.fixTitle = function (){};
  52. // 判断触发元素是拥有内容(及title值),其调用了getTitle
  53. Tooltip.prototype.hasContent = function (){};
  54. // 获取tooltip的当前位置
  55. Tooltip.prototype.getPosition = function (){};
  56. // 计算更新placement样式后的位置
  57. Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth,
  58. actualHeight) {};
  59. // 获取触发元素的title内容
  60. Tooltip.prototype.getTitle = function (){};
  61. // 获取tooltip的模板内容
  62. Tooltip.prototype.tip = function (){};
  63. // 查找小箭头元素
  64. Tooltip.prototype.arrow = function (){};
  65. // 验证元素释放合法
  66. Tooltip.prototype.validate = function (){};
  67. // 设置tooltip可用
  68. Tooltip.prototype.enable = function (){};
  69. // 设置tooltip不可用
  70. Tooltip.prototype.disable = function (){};
  71. // 反转可用状态
  72. Tooltip.prototype.toggleEnabled = function (){};
  73. // 反转tooltip显示/隐藏状态
  74. Tooltip.prototype.toggle = function (e) {};
  75. // 去除tooltip插件的绑定
  76. Tooltip.prototype.destroy = function (){};

上述源码,除了定义tooltip插件类函数和默认参数以外,其他的原型方法非常多,有的是用于显示、关闭、反转提示语的,还有对提示语进行位置计算、调整的,甚至还有调整提示语里的小箭头的以及获取和设置相关的内容的。所以读者在阅读这几页源码的时候,一定要静下心来,否则很容易混乱的。细节代码和注释如下:

  1. // 初始化,事件绑定
  2. // 注意:这里传入的是tooltip,因为popover插件也使用了tooltip的原型方法,所以init提供了
  3. // 一个type参数
  4. // popover插件传入的就是popover
  5. Tooltip.prototype.init = function (type, element, options) {
  6. this.enabled = true // 可用状态
  7. this.type = type // 默认是tooltip
  8. this.$element = $(element) // 当前元素
  9. this.options = this.getOptions(options) // 获取配置参数(将传入的参数和默认参数合并)
  10.  
  11. var triggers = this.options.trigger.split(' ')
  12. // 将多个触发事件转换成数字,例如hover click
  13.  
  14. for (var i = triggers.length; i--;) {
  15. var trigger = triggers[i] // 针对每个事件,单独处理如下的内容
  16.  
  17. if (trigger == 'click') { // 如果是click事件
  18. // 则在click.tooltip上绑定toggle回调,即每单击一次,就会反转显示状态
  19. this.$element.on('click.' + this.type, this.options.selector, $.proxy
  20. (this.toggle, this))
  21. } else if (trigger != 'manual') { // 如果不是手动触发
  22. // 如果是hover事件,则进入事件是mouseenter,否则是focusin
  23. var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
  24. // 如果是hover事件,则移出事件是mouseleave,否则是focusout
  25. var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
  26.  
  27. // 给进入事件绑定enter回调,如mouseenter.tooltip
  28. this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy
  29. (this.enter, this))
  30. // 移出事件绑定leave回调,如focusout.tooltip
  31. this.$element.on(eventOut + '.' + this.type, this.options.selector,
  32. $.proxy(this.leave, this))
  33. }
  34. }
  35.  
  36. // 如果指定了内部选择符
  37. // 则合并原来的options到一个新的_options对象上,并添加trigger和selector两个选项
  38. this.options.selector ?
  39. (this._options = $.extend({}, this.options, { trigger: 'manual',
  40. selector: '' })) :
  41. this.fixTitle() // 否则,修复title提示
  42. }
  43. // 获取默认配置
  44. Tooltip.prototype.getDefaults = function () {
  45. return Tooltip.DEFAULTS
  46. }
  47. // 获取配置参数(将传入的参数和默认参数合并)
  48. Tooltip.prototype.getOptions = function (options) {
  49. // 将传入的参数和默认参数合并
  50. options = $.extend({}, this.getDefaults(), this.$element.data(), options)
  51. // 如果传入的delay是数字,则表示show和hide的延迟时间都是该数字
  52. if (options.delay && typeof options.delay == 'number') {
  53. options.delay = {
  54. show: options.delay,
  55. hide: options.delay
  56. }
  57. }
  58. return options
  59. }
  60. // 手动触发的时候,添加额外的options参数
  61. Tooltip.prototype.getDelegateOptions = function () {
  62. var options = {}
  63. var defaults = this.getDefaults() // 默认配置参数
  64.  
  65. this._options && $.each(this._options, function (key, value) {
  66. // 如果_options可用
  67. // 如果默认参数里指定key的值和_options指定key的值不一样,则将_options里的值赋
  68. // 值给options,也就是用新值
  69. if (defaults[key] != value) options[key] = value
  70. })
  71. return options // 返回更新后的选项参数
  72. }
  73. // 移除元素时的处理,主要是找到元素实例,然后调用show方法
  74. Tooltip.prototype.enter = function (obj) {
  75. // 如果obj是当前tooltip的实例就赋值给self
  76. // 否则就取该元素上的实例(data-bs.tooltip属性),即:// $('#id').tooltip(options).data
  77. // ('bs.tooltip')
  78. var self = obj instanceof this.constructor ?
  79. obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.'
  80. + this.type)
  81.  
  82. clearTimeout(self.timeout) // 去除timeout
  83. self.hoverState = 'in' // 设置移入状态是in
  84.  
  85. // 如果delay没提供,后者delay.show没提供,直接返回self.show
  86. if (!self.options.delay || !self.options.delay.show) return self.show()
  87.  
  88. self.timeout = setTimeout(function () { // 重新设置timeout
  89. if (self.hoverState == 'in') self.show() // 如果移入状态是in,调用show方法
  90. }, self.options.delay.show)
  91. }
  92. // 离开元素时的处理,主要是找到元素实例,然后调用hide方法
  93. Tooltip.prototype.leave = function (obj) {
  94. // 如果obj是当前tooltip的实例就赋值给self
  95. // 否则就取该元素上的实例(data-bs.tooltip属性),即:// $('#id').tooltip(options).data
  96. // ('bs.tooltip')
  97. var self = obj instanceof this.constructor ?
  98. obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data
  99. ('bs.' + this.type)
  100.  
  101. clearTimeout(self.timeout) // 去除timeout
  102. self.hoverState = 'out' // 设置移入状态是out
  103.  
  104. // 如果delay没提供,后者delay.hide没提供,直接返回self.hide
  105. if (!self.options.delay || !self.options.delay.hide) return self.hide()
  106.  
  107. self.timeout = setTimeout(function () { // 重新设置timeout
  108. if (self.hoverState == 'out') self.hide() // 如果移入状态是out,调用hide方法
  109. }, self.options.delay.hide)
  110. }
  111. // 显示tooltip提示语
  112. Tooltip.prototype.show = function () {
  113. var e = $.Event('show.bs.' + this.type) // 设置tooltip完全显示之前触发的show事件
  114.  
  115. if (this.hasContent() && this.enabled) { // 如果有要显示的内容,并且tooltip可用
  116. this.$element.trigger(e) // 首先触发show事件
  117.  
  118. if (e.isDefaultPrevented()) return // 如果show回调里阻止了继续操作,则返回
  119. var that = this;
  120. var $tip = this.tip() // 获取tooltip的模板内容
  121.  
  122. this.setContent() // 在模板里设置title内容,以便正式组装tooltip的内容
  123. if (this.options.animation) $tip.addClass('fade')
  124. // 如果设置了动画,则在tooltip上添加fade样式
  125.  
  126. // 如果显示位置参数设置的是function,则直接调用该函数,否则直接利用该位置,比如
  127. // left、right
  128. var placement = typeof this.options.placement == 'function' ?
  129. this.options.placement.call(this, $tip[0], this.$element[0]) :
  130. this.options.placement
  131.  
  132. // 如果位置设置里有auto关键字,先删除auto关键字,比如只剩left;如果什么都没剩余,默认为top
  133. var autoToken = /\s?auto?\s?/i
  134. var autoPlace = autoToken.test(placement)
  135. if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
  136.  
  137. $tip
  138. .detach()
  139. .css({ top: 0, left: 0, display: 'block' }) // 默认左上角块级显示(相对定位)
  140. .addClass(placement) // 设置显示方向是left、right、top、bottom中的一种
  141.  
  142. // 如果指定了container容器,则将tooltip附加到该容器,否则附加到当前元素的最末尾(内部)
  143. this.options.container ? $tip.appendTo(this.options.container) : $tip.
  144. insertAfter(this.$element)
  145.  
  146. var pos = this.getPosition() // 获取tooltip的当前位置
  147. var actualWidth = $tip[0].offsetWidth // 获取实际宽度
  148. var actualHeight = $tip[0].offsetHeight // 获取实际高度
  149.  
  150. if (autoPlace) { // 如果定义了auto方向
  151. var $parent = this.$element.parent() // 获取触发元素的父元素
  152. var orgPlacement = placement // 将原来的方向先临时存到一个临时变量里
  153. // 获取当前页面的距离顶端的top高度
  154. var docScroll = document.documentElement.scrollTop || document.body.scrollTop
  155. // 获取父元素的整个宽度
  156. var parentWidth = this.options.container == 'body' ? window.innerWidth :
  157. $parent.outerWidth()
  158. // 获取父元素的整个高度
  159. var parentHeight = this.options.container == 'body' ? window.innerHeight :
  160. $parent.outerHeight()
  161. // 获取父元素的left值
  162. var parentLeft = this.options.container == 'body' ? 0 : $parent.offset().left
  163.  
  164. // 如果位置是bottom,但是超出了父元素的高度,则使用top
  165. // 如果位置是top,但是超出了浏览器顶部,则使用bottom
  166. // 如果位置是right,但是超出了浏览器右边栏,则使用left
  167. // 如果位置是left,但是超出了浏览器左边栏,则使用right
  168. placement = placement == 'bottom' && pos.top + pos.height +
  169. actualHeight - docScroll > parentHeight ? 'top' :
  170. placement == 'top' && pos.top - docScroll -
  171. actualHeight < 0 ? 'bottom' :
  172. placement == 'right' && pos.right + actualWidth >
  173. parentWidth ? 'left' :
  174. placement == 'left' && pos.left - actualWidth <
  175. parentLeft ? 'right' :
  176. placement // 否则,还使用原来的位置
  177.  
  178. $tip
  179. .removeClass(orgPlacement) // 删除原来设置的位置样式
  180. .addClass(placement) // 使用新计算的位置样式
  181. }
  182.  
  183. // 计算更新placement样式后的位置
  184. var calculatedOffset = this.getCalculatedOffset(placement, pos,
  185. actualWidth, actualHeight)
  186. this.applyPlacement(calculatedOffset, placement)
  187. // 再次应用更新的placement样式和位置
  188. this.hoverState = null
  189.  
  190. var complete = function () {
  191. that.$element.trigger('shown.bs.' + that.type)
  192. }
  193.  
  194. $.support.transition && this.$tip.hasClass('fade') ?
  195. $tip
  196. .one($.support.transition.end, complete)
  197. .emulateTransitionEnd(150) :
  198. complete()
  199. }
  200. }
  201. // 再次应用更新的placement样式和位置
  202. Tooltip.prototype.applyPlacement = function (offset, placement) {
  203. var replace
  204. var $tip = this.tip()
  205. var width = $tip[0].offsetWidth
  206. var height = $tip[0].offsetHeight
  207.  
  208. // 手动获取margin值,因为使用getBoundingClientRect在不同的浏览器不准
  209. var marginTop = parseInt($tip.css('margin-top'), 10)
  210. var marginLeft = parseInt($tip.css('margin-left'), 10)
  211.  
  212. // 在IE8、IE9下必须要判断值为NaN的情况
  213. if (isNaN(marginTop)) marginTop = 0
  214. if (isNaN(marginLeft)) marginLeft = 0
  215.  
  216. // 将margin值更新到offset
  217. offset.top = offset.top + marginTop
  218. offset.left = offset.left + marginLeft
  219.  
  220. // 设置新的offset,但由于$.fn.offset不能彻底设置像素值,所以这里使用setOffset方法直接设置
  221. $.offset.setOffset($tip[0], $.extend({
  222. using: function (props) {
  223. $tip.css({
  224. top: Math.round(props.top),
  225. left: Math.round(props.left)
  226. })
  227. }
  228. }, offset), 0)
  229.  
  230. $tip.addClass('in') // 并添加in样式,用于显示
  231.  
  232. // 检查tooltip在重新应用后,是否又自己重绘并改变大小了
  233. var actualWidth = $tip[0].offsetWidth
  234. var actualHeight = $tip[0].offsetHeight
  235.  
  236. // 如果选择的是top,并且高度改变了,则重新更新offset
  237. if (placement == 'top' && actualHeight != height) {
  238. replace = true
  239. offset.top = offset.top + height - actualHeight
  240. }
  241. if (/bottom|top/.test(placement)) { // 如果使用了bottom或top位置
  242. var delta = 0
  243. if (offset.left < 0) { // 如果超出了浏览器的左边框,则设置为最小值0
  244. delta = offset.left * -2
  245. offset.left = 0
  246.  
  247. $tip.offset(offset) // 重新更新offset
  248.  
  249. actualWidth = $tip[0].offsetWidth // 再次获取重绘以后的offset
  250. actualHeight = $tip[0].offsetHeight
  251. }
  252.  
  253. this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
  254. // 更新小箭头的位置
  255. } else {
  256. this.replaceArrow(actualHeight - height, actualHeight, 'top')
  257. // 更新小箭头的位置
  258. }
  259.  
  260. if (replace) $tip.offset(offset) // 重新应用offset
  261. }
  262. // 更新小箭头的位置
  263. Tooltip.prototype.replaceArrow = function (delta, dimension, position) {
  264. this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + '%') : '')
  265. }
  266. // 在模板里设置title内容,以便正式组装tooltip的内容
  267. Tooltip.prototype.setContent = function () {
  268. var $tip = this.tip()
  269. var title = this.getTitle()
  270.  
  271. // 如果支持HTML,就设置HTML,否则设置text
  272. $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
  273. $tip.removeClass('fade in top bottom left right')
  274. // 如果有多余的样式,全部删除,后面会根据状态再添加
  275. }
  276. // 关闭tooltip提示框
  277. Tooltip.prototype.hide = function () {
  278. var that = this
  279. var $tip = this.tip()
  280. var e = $.Event('hide.bs.' + this.type) // 设置tooltip在开始关闭时触发的hide事件
  281.  
  282. function complete() {
  283. if (that.hoverState != 'in') $tip.detach()
  284. // 如果当前元素没有in样式,直接移除tooltip
  285. that.$element.trigger('hidden.bs.' + that.type)
  286. }
  287.  
  288. this.$element.trigger(e) // 触发hide事件
  289. if (e.isDefaultPrevented()) return // 如果hide回调里阻止了继续操作,则返回
  290.  
  291. $tip.removeClass('in') // 删除in样式
  292.  
  293. $.support.transition && this.$tip.hasClass('fade') ?
  294. // 如果设置了动画,则在tooltip上添加fade样式
  295. $tip
  296. .one($.support.transition.end, complete)
  297. // 设置动画以后,调用complete函数关闭tooltip
  298. .emulateTransitionEnd(150) : // 延迟150毫秒才开始动画
  299. complete() // 否则直接关闭
  300.  
  301. this.hoverState = null
  302. return this
  303. }
  304. // 修复title提示,既有title,又有tooltip
  305. Tooltip.prototype.fixTitle = function () {
  306. var $e = this.$element
  307. // 触发元素有title值,或者data-original-title属性不是字符串时
  308. // 将title(或者空)赋值给data-original-title属性,然后再将title属性设置为空
  309. if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
  310. $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
  311. }
  312. }
  313. // 判断触发元素是拥有内容(及title值),其调用了getTitle
  314. Tooltip.prototype.hasContent = function () {
  315. return this.getTitle()
  316. }
  317. // 获取tooltip的当前位置
  318. Tooltip.prototype.getPosition = function () {
  319. var el = this.$element[0]
  320. return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.
  321. getBoundingClientRect() : {
  322. // 如果系统支持getBoundingClientRect函数,直接用该函数获取位置
  323. width: el.offsetWidth, // 否则使用el.offsetWidth和el.offsetHeight来获取位置
  324. height: el.offsetHeight
  325. }, this.$element.offset()) // 如果都获取不了,则返回默认的offset
  326. }
  327. // 计算更新placement样式后的位置
  328. Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth,
  329. actualHeight) {
  330. return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left +
  331. pos.width / 2 - actualWidth / 2 } :
  332. placement == 'top' ? { top: pos.top - actualHeight, left: pos.left +
  333. pos.width / 2 - actualWidth / 2 } :
  334. placement == 'left' ? { top: pos.top + pos.height / 2 -
  335. actualHeight / 2, left: pos.left - actualWidth } :
  336. /* placement == 'right' */ { top: pos.top + pos.height / 2 -
  337. actualHeight / 2, left: pos.left + pos.width }
  338. }
  339. // 获取触发元素的title内容
  340. Tooltip.prototype.getTitle = function () {
  341. var title
  342. var $e = this.$element
  343. var o = this.options
  344.  
  345. // 第一优先级:data-original-title
  346. // 第二优先级:如果options.title传入的是function,将其调用结果作为title;如果不是直
  347. // 接返回options.title
  348. title = $e.attr('data-original-title')
  349. || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
  350.  
  351. return title
  352. }
  353. // 获取tooltip的模板内容
  354. Tooltip.prototype.tip = function () {
  355. return this.$tip = this.$tip || $(this.options.template)
  356. }
  357. // 查找小箭头元素
  358. Tooltip.prototype.arrow = function () {
  359. return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')
  360. }
  361. // 验证元素释放合法
  362. Tooltip.prototype.validate = function () {
  363. if (!this.$element[0].parentNode) { // 如果元素没有父节点,比如document
  364. this.hide() // 隐藏该元素
  365. this.$element = null
  366. this.options = null
  367. }
  368. }
  369. // 设置tooltip可用
  370. Tooltip.prototype.enable = function () { this.enabled = true}
  371. // 设置tooltip不可用
  372. Tooltip.prototype.disable = function () { this.enabled = false}
  373. // 反转可用状态
  374. Tooltip.prototype.toggleEnabled = function () { this.enabled = !this.enabled}
  375. // 反转tooltip显示/隐藏状态
  376. Tooltip.prototype.toggle = function (e) {
  377. // 如果调用对象时触发元素,就查找该触发元素上的实例,否则就是this
  378. var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).
  379. data('bs.' + this.type) : this
  380.  
  381. // 如果tip上有in样式,就触发移出操作(leave)关闭tooltip;否则触发移入操作(enter)开启tooltip
  382. self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
  383. }
  384. // 去除tooltip组件的绑定
  385. Tooltip.prototype.destroy = function () {
  386. clearTimeout(this.timeout)
  387. // 去除.tooltip命名空间中所有的事件绑定,并移除tooltip实例(data-bs.tooltip)
  388. this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
  389. }

步骤3 jQuery插件定义。在jQuery上定义$.fn.tooltip插件的代码,和前面几个插件几乎一样:遍历元素、实例化或触发动作。源码如下:

  1. var old = $.fn.tooltip
  2. // 保留其他库的$.fn.tooltip代码(如果定义的话),以便在noConflict之后,可以继续使用该老代码
  3. $.fn.tooltip = function (option) {
  4. return this.each(function () { // 根据选择器,遍历所有符合规则的元素
  5. var $this = $(this) // this赋值一个变量,防止作用域改变
  6. var data = $this.data('bs.tooltip') // 查询当前元素上是否已经有了tooltip实例
  7. // 如果option是对象,说明是参数集合,在new tooltip的时候传入
  8. var options = typeof option == 'object' && option
  9.  
  10. if (!data && option == 'destroy') return
  11. // 如果没有实例,就初始化一个,并传入this
  12. if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
  13.  
  14. // 如果option是字符串,说明传入的是一个方法,直接调用该方法
  15. if (typeof option == 'string') data[option]()
  16. })
  17. }
  18. $.fn.tooltip.Constructor = Tooltip // 重设插件构造器,可以通过该属性获取插件的真实类函数

步骤4 防冲突处理。此步骤与Modal插件的步骤4一样,此处不赘述。

步骤5 绑定触发事件。在前面我们已经说了,tooltip插件默认没有提供触发声明式的代码,所以也就没有此步代码了。