换一种新的方式不用唱吧自定义音效参数这么多参数,精简js的方法

iScroll5 API速查随记
时间: 18:29:31
&&&& 阅读:102016
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&版本
针对iScroll的优化。为了达到更高的性能,iScroll分为了多个版本。你可以选择最适合你的版本。目前我们有以下版本:
iscroll.js,这个版本是常规应用的脚本。它包含大多数常用的功能,有很高的性能和很小的体积。
iscroll-lite.js,精简版本。它不支持快速跳跃,滚动条,鼠标滚轮,快捷键绑定。但如果你所需要的是滚动(特别是在移动平台) iScroll 精简版 是又小又快的解决方案。
iscroll-probe.js,探查当前滚动位置是一个要求很高的任务,这就是为什么我决定建立一个专门的版本。如果你需要知道滚动位置在任何给定的时间,这是iScroll给你的。(我正在做更多的测试,这可能最终在常规iscroll.js脚本,请留意)。
iscroll-zoom.js,在标准滚动功能上增加缩放功能。
iscroll-infinite.js,可以做无限缓存的滚动。处理很长的列表的元素为移动设备并非易事。 iScroll infinite版本使用缓存机制,允许你滚动一个潜在的无限数量的元素。
IScroll是一个类,每个需要使用滚动功能的区域均要进行初始化。每个页面上的iScroll实例数目在设备的CPU和内存能承受的范围内是没有限制的。
尽可能保持DOM结构的简洁。iScroll使用硬件合成层但是有一个限制硬件可以处理的元素。最佳的HTML结构如下:
iScroll作用于滚动区域的外层。在上面的例子中,UL元素能进行滚动。只有容器元素的第一个子元素能进行滚动,其他子元素完全被忽略。
最基本的脚本初始化的方式如下:
第一个参数可以是滚动容器元素的DOM选择器字符串,也可以是滚动容器元素的引用对象。下面是一个有效的语法:
var wrapper = document.getElementById(‘wrapper‘);
var myScroll = new IScroll(wrapper);
所以基本上你要么直接传递元素,要么传递一个querySelector字符串。因此可以使用css名称代替ID去选择一个滚动器容器,如下:
var myScroll = new IScroll(‘.wrapper‘);
注意,iScroll使用的是querySelector 而不是 querySelectorAll,所以iScroll只会作用到选择器选中元素的第一个。如果你需要对多个对象使用iScroll,你需要构建自己的循环机制。
当DOM准备完成后iScroll需要被初始化。最保险的方式是在window的onload事件中启动它。在DOMContentLoaded事件中或者inline initialization中做也可以,需要记住的是脚本需要知道滚动区域的高度和宽度。如果你有一些图片在滚动区域导致不能立马获取区域的高度和宽度,iScroll的滚动尺寸有可能会错误。
为滚动起容器增加position:relative或者absolute样式。这将解决大多数滚动器容器大小计算不正确的问题。综上所述,最小的iScroll配置如下:
如果你有一个复杂的DOM结构,最好在onload事件之后适当的延迟,再去初始化iScroll。最好给浏览器100或者200毫秒的间隙再去初始化iScroll。
在iScroll初始化阶段可以通过构造函数的第二个参数配置它。
var myScroll = new IScroll(‘#wrapper‘, {
mouseWheel: true,
scrollbars: true
上面的例子示例了在iScroll初始化时开启鼠标滚轮支持和滚动条支持。
在初始化后你可以通过options对象访问标准化值。例如:&js console.dir(myScroll.options);上面的语句将返回myScroll实例的配置信息。所谓的标准化意味着如果你设置useTransform:true,但是浏览器并不支持CSStransforms,那么useTransform属性值将为false。
iScroll使用基于设备和浏览器性能的各种技术来进行滚动。通常不需要你来配置引擎,iScroll会为你选择最佳的方式。
尽管如此,理解iScroll工作机制和了解如何去配置他们也是很重要的。
options.useTransform
默认情况下引擎会使用CSStransform属性。如果现在还是2007年,那么可以设置这个属性为false,这就是说:引擎将使用top/left属性来进行滚动。这个属性在滚动器感知到Flash,iframe或者视频插件内容时会有用,但是需要注意:性能会有极大的损耗。默认值:true
options.useTransition
iScroll使用CSS transition来实现动画效果(动量和弹力)。如果设置为false,那么将使用requestAnimationFrame代替。在现在浏览器中这两者之间的差异并不明显。在老的设备上transitions执行得更好。默认值:true
options.HWCompositing
这个选项尝试使用translateZ(0)来把滚动器附加到硬件层,以此来改变CSS属性。在移动设备上这将提高性能,但在有些情况下,你可能想要禁用它(特别是如果你有太多的元素和硬件性能跟不上)。默认值:true
如果不确定iScroll的最优配置。从性能角度出发,上面的所有选项应该设置为true。(或者更好的方式,让他们自动设置属性为true)。你可以尝试这配置他们,但是要小心内存泄漏。
options.bounce
当滚动器到达容器边界时他将执行一个小反弹动画。在老的或者性能低的设备上禁用反弹对实现平滑的滚动有帮助。默认值:true
options.click
为了重写原生滚动条,iScroll禁止了一些默认的浏览器行为,比如鼠标的点击。如果你想你的应用程序响应click事件,那么该设置次属性为true。请注意,建议使用自定义的tap 事件来代替它(见下面)。默认属性:false
options.disableMouse
options.disablePointer
options.disableTouch
默认情况下,iScroll监听所有的指针事件,并且对这些事件中第一个被触发的做出反应。这看上去是浪费资源,但是在大量的浏览器/设备上兼容,特定的事件侦测证明是无效的,所以listen-to-all是一个安全的做法。
如果你有一种设备侦测的内部机制,或者你知道你的脚本将在什么地方运行,你可以把你不需要的事件禁用(鼠标,指针或者触摸事件)。下面的例子是禁用鼠标和指针事件:
var myScroll = new IScroll(‘#wrapper‘, {
disableMouse: true,
disablePointer: true
默认值:false
options.eventPassthrough
有些时候你想保留原生纵向的滚动条但想为横向滚动条增加iScroll功能(比如走马灯)。设置这个属性为true并且iScroll区域只将影响横向滚动,纵向滚动将滚动整个页面。
在移动设备上访问。注意,这个值也可以设置为horizontal,其作用和上面介绍的相反(横向滚动条保持原生,纵向滚动条使用iScroll)。
options.freeScroll
此属性针对于两个两个纬度的滚动条(当你需要横向和纵向滚动条)。通常情况下你开始滚动一个方向上的滚动条,另外一个方向上会被锁定不动。有些时候,你需要无约束的移动(横向和纵向可以同时响应),在这样的情况下此属性需要设置为true。请参考&。默认值:false
options.keyBindings
此属性为true时激活键盘(和远程控制)绑定。请参考下面的Key bindings内容。默认值:false
options.invertWheelDirection
当鼠标滚轮支持激活后,在有些情况下需要反转滚动的方向。(比如,鼠标滚轮向下滚动条向上,反之亦然)。默认值:false
options.momentum
在用户快速触摸屏幕时,你可以开/关势能动画。关闭此功能将大幅度提升性能。默认值:true
options.mouseWheel
侦听鼠标滚轮事件。默认值:false
options.preventDefault
当事件触发时师傅执行preventDefault()。此属性应该设置为true,除非你真的知道你需要怎么做。请参考下面的Advanced features中的preventDefaultException,可以获取更多控制preventDefault行为的信息。默认值:true
options.scrollbars
是否显示为默认的滚动条。更多信息请查看Scrollbar默认值:false
options.scrollX
options.scrollY
默认情况下只有纵向滚动条可以使用。如果你需要使用横向滚动条,需要将scrollX 属性值设置为 true。请参考示例。
也可以参考freeScroll选项。
默认值:scrollX: false,scrollY: true
注意属性&scrollX/Y: true&与overflow: auto有相同的效果。设置一个方向上的值为&false&可以节省一些检测的时间和CPU的计算周期。
options.startX
options.startY
默认情况下iScroll从0, 0 (top left)位置开始,通过此属性可以让滚动条从不同的位置开始滚动。默认值:0
options.tap
设置此属性为true,当滚动区域被点击或者触摸但并没有滚动时,可以让iScroll抛出一个自定义的tap事件。这是处理与可以点击元素之间的用户交互的建议方式。侦听tap事件和侦听标准事件的方式一致。示例如下:
element.addEventListener(‘tap‘, doSomething, false); \\ Native
$(‘#element‘).on(‘tap‘, doSomething); \\ jQuery
你可以通过传递一个字符串来自定义事件名称。比如:
tap: ‘myCustomTapEvent‘
在这个示例里你应该侦听名为myCustomTapEvent的事件。默认值:false
滚动条不只是像名字所表达的意义一样,在内部它们是作为indicators的引用。
一个指示器侦听滚动条的位置并且现实它在全局中的位置,但是它可以做更多的事情。先从最基本的开始。
options.scrollbars
正如我们在基本功能介绍中提到的,激活滚动条只需要做一件事情,这件事情就是:
var myScroll = new IScroll(‘#wrapper‘, {
scrollbars: true
当然这个默认的行为是可以定制的。
options.fadeScrollbars
不想使用滚动条淡入淡出方式时,需要设置此属性为false以便节省资源。默认值:false
options.interactiveScrollbars
此属性可以让滚动条能拖动,用户可以与之交互。默认值:false
options.resizeScrollbars
滚动条尺寸改变基于容器和滚动区域的宽/高之间的比例。此属性设置为false让滚动条固定大小。这可能有助于自定义滚动条样式(参考下面的滚动条样式)。默认值:true
options.shrinkScrollbars
当在滚动区域外面滚动时滚动条是否可以收缩到较小的尺寸。有效的值为:clip&和&scale。clip是移动指示器到它容器的外面,效果就是滚动条收缩起来,简单的移动到屏幕以外的区域。属性设置为此值后将大大的提升整个iScroll的性能。scale是关闭属性useTransition,之后所有的动画效果将使用requestAnimationFrame实现。指示器实际上有各种尺寸,并且最终的效果最好。默认值:false
注意改变大小不是在GPU上执行的,所以‘scale‘ 是在CPU上执行。
滚动条样式
如果你不喜欢默认的滚动条样式,而且你认为你可以做的更好,你可以自定义滚动条样式。第一步就是设置选项scrollbars的值为custom:
var myScroll = new IScroll(‘#wrapper‘, {
scrollbars: ‘custom‘
使用下面的CSS类可以简单的改变滚动条样式。
.iScrollHorizontalScrollbar,这个样式应用到横向滚动条的容器。这个元素实际上承载了滚动条指示器。
.iScrollVerticalScrollbar,和上面的样式类似,只不过适用于纵向滚动条容器。
.iScrollIndicator,真正的滚动条指示器。
.iScrollBothScrollbars,这个样式将在双向滚动条显示的情况下被加载到容器元素上。通常情况下其中一个(横向或者纵向)是可见的
如果你设置resizeScrollbars: false,滚动条将是固定大小,否则它将基于滚动区域的尺寸变化。
上面所有关于滚动条的选项实际上是包装了一个底层的选项indicators。它看起来或多或少像这样:
var myScroll = new IScroll(‘#wrapper‘, {
indicators: {
el: [element|element selector]
fade: false,
ignoreBoundaries: false,
interactive: false,
listenX: true,
listenY: true,
resize: true,
shrink: false,
speedRatioX: 0,
speedRatioY: 0,
options.indicators.el
这是一个强制性的参数,它保留了指向滚动条容器元素的引用。容器里的第一个子元素就是指示器。有效的语法如下:
indicators: {
el: document.getElementById(‘indicator‘)
更简单的方式:
indicators: {
el: ‘#indicator‘
注意,滚动条可以在你的文档的任意地方,它不需要在滚动条包装器内。
options.indicators.ignoreBoundaries
这个属性是告诉指示器忽略它容器所带来的边界。当我们能改变滚动条速度的比率,在让滚动条滚动时这个属性很有用。比如你想让指示器是滚动条速度的两倍,指示器将很快到达它的结尾。这个属性被用在视差滚动。默认值:false
options.indicators.listenX
options.indicators.listenY
指示器的那一个轴(横向和纵向)被侦听。可以设置一个或者都设置默认值:true
options.indicators.speedRatioX
options.indicators.speedRatioY
指示器移动的速度和主要滚动条大小的关系。默认情况下是设置为自动。你很少需要去改变这个值。默认值:0
options.indicators.fade
options.indicators.interactive
options.indicators.resize
options.indicators.shrink
这几个选项和我们已经介绍过的滚动条中的一样,在这里不重复介绍。请参考示例,你将体验indicators选项的神奇力量。
你应该已经注意到选项indicators是复数,是的,实际上,传递一个对象数组你可以得到一个虚拟的无限大小的指示器。我不知道你是否需要,但是,这里我是想你介绍参数设置,所以要提及此。
视差滚动是指示器功能的一个&附属功能指示器是一个遵循主滚动条移动和动画的层。如果你了解它你就会理解这个功能背后的力量。增加这个功能你就可以创建任意数量的指示器和视差滚动。请参考&.
滚动的编程接口
当然还可以通过编程来进行滚动。
scrollTo(x, y, time, easing)
对应存在的一个叫做myScroll的iScroll实例,可以通过下面的方式滚动到任意的位置:
myScroll.scrollTo(0, -100);
通过上面的方式将向下滚动100个像素。记住:0永远是左上角。需要滚动你必须传递负数。time&和&easing是可选项。他们控制滚动周期(毫秒级别)和动画的擦除效果。擦除功能是一个有效的IScroll.utils.ease对象。例如应用一个一秒的经典擦除动画你应该这么做:
myScroll.scrollTo(0, -100, 1000, IScroll.utils.ease.elastic);
擦除动画的类型选项有:quadratic,&circular,&back,&bounce,&elastic。
和上面一个方法类似,但是可以传递X和Y的值从当前位置进行滚动。
myScroll.scrollBy(0, -10);
上面这个语句将在当前位置向下滚动10个像素。如果你当前所在位置为-100,那么滚动结束后位置为-110.
这是一个很有用的方法,你会喜欢它的。在这个方法中只有一个强制的参数就是el。传递一个元素或者一个选择器,iScroll将尝试滚动到这个元素的左上角位置。time是可选项,用于设置动画周期。offsetX&和&offsetY定义像素级的偏移量,所以你可以滚动到元素并且加上特别的偏移量。但并不仅限于此。如果把这两个参数设置为true,元素将会位于屏幕的中间。请参考例子 滚动到元素 example。easing参数和scrollTo方法里的一样。
iScroll能对齐到固定的位置和元素。
options.snap
最简单的对齐配置如下:
var myScroll = new IScroll(‘#wrapper‘, {
snap: true
这将按照页面容器的大小自动分割滚动条。snap属性也可以传递字符类型类型的值。这个值是滚动条将要对齐到的元素的选择器。比如下面:
var myScroll = new IScroll(‘#wrapper‘, {
snap: ‘li‘
这个示例中滚动条将会对齐到每一个LI标记的元素。下面将帮助你快速浏览iScroll提供的关于对齐的一系列有趣的方法。
x&和&y呈现你想滚动到横向轴或者纵向轴的页面数。如果你需要在单个唯独上使用滚动条,只需要为你不需要的轴向传递0值。time属性是动画周期,easing属性是滚动到指定点使用的擦除功能类型。请参考高级功能中的option.bounceEasing。这两个属性都是可选项。
myScroll.goToPage(10, 0, 1000);
上面这个例子将在一秒内沿着横向滚动到第10页。
滚动到当前位置的下一页或者前一页。
为了使用缩放功能,你最好使用iscroll-zoom.js脚本。
options.zoom
此属性设置为true启用缩放功能。默认值:false
options.zoomMax
最大缩放级数。默认值:4
options.zoomMin
最小缩放级数。默认值:1
options.zoomStart
初始的缩放级数。默认值:1
options.wheelAction
鼠标滚轮的动作可以设置为zoom,这样在滚动滚轮时缩放操作会代替原来的滚动操作。默认值:undefined(即:鼠标滚轮滚动)
和前面的示例一样,一个好的缩放功能的配置如下:
myScroll = new IScroll(‘#wrapper‘, {
zoom: true,
mouseWheel: true,
wheelAction: ‘zoom‘
缩放功能使用的CSS的转换功能。iScroll只能在支持此CSS功能的浏览器上执行。
一些浏览器(特别是基于webkit的)采取的快照缩放区域就放在硬件合成层(比如当你申请转换)。该快照作为纹理的缩放区域,它几乎不能被更新。这意味着您的纹理将基于scale 1 进行缩放,将导致文本和图像模糊,清晰度低。
一个简单的解决方案是使用实际分辨率双倍(或者三倍)装载内容,然后放到一个按照scale(0.5)比例缩小的div中。这种方法大多数情况下能适用。
一个有意思的的方法,能让你进行缩放编程。scale是缩放因子。x&和&y是缩放关注点,即缩放的中心。如果没有指定,这个中心就是屏幕中心。time是毫秒级别的动画周期(可选项)。
iScroll集成智能缓存系统,允许处理的使用(重用)一群元素几乎无限数量的数据。无限滚动开发的早期阶段,尽管它可以被认为是稳定的,但它还没有准备好被广泛使用。请参考&&并请提交你的建议和报告bug。
下面这些选项主要针对核心开发人员。
options.bindToWrapper
move事件通常绑定到文档而不是滚动器容器(wrapper)。当你在滚动器容器(wrapper)外移动光标/手指,滚动条将不断滚动。这通常是你想要的,但是你也可以绑定事件转移到滚动器容器(wrapper)本身。这样做一旦指针离开了容器,滚动就会停止。默认值:false
options.bounceEasing
擦除功能在弹跳动画过程中执行。有效的值为:‘quadratic‘, ‘circular‘, ‘back‘, ‘bounce‘, ‘elastic‘. 参见bounce easing demo,往下拽滚动条然后释放。bounceEasing比上面的示例更强大。你可以自定义一个消除的方式,比如:
bounceEasing: {
style: ‘cubic-bezier(0,0,1,1)‘,
fn: function (k) { return k; }
上面这个示例将执行一个线性的擦出。style选项将在在每次动画执行时使用CSS转场执行。fn和requestAnimationFrame一起使用。如果一个擦出功能太复杂,不能由一个三次贝塞尔曲线展现,那么为style属性传递 ‘‘ (空字符串)。默认值:circular
注意:bounce 和 elastic这两种方式不能被CSS转场执行。
弹跳动画的持续时间,使用毫秒级。默认值:600
options.deceleration
这个值可以改变改变动画的势头持续时间/速度。更高的数字使动画更短。你可以从0.01开始去体验,这个值和基本的值比较,基本上没有动能。默认值:0.0006
options.mouseWheelSpeed
设置鼠标滚轮滚动的速度值。默认值:20
options.preventDefaultException
调用preventDefault()方法时所有的异常将被触发,尽管preventDefault设置了值。这是一个强大的选项,如果你想为所有包含formfield样式名称的元素上应用preventDefault()方法,你可以设置为下面的值:
preventDefaultException: { className: /(^|\s)formfield(\s|$)/ }
默认值:{ tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/ }.
options.resizePolling
当你改变窗口的大小iScroll重新计算元素的位置和尺寸。这可能是一个相当艰巨的任务。轮询设置为60毫秒。通过降低这个值你获得更好的视觉效果,但会占用更多的CPU资源。默认值是一个很好的折中。默认值:60
iScroll需要知道包装器和滚动器确切的尺寸,在iScroll初始化的时候进行计算,如果元素大小发生了变化,需要告诉iScroll DOM发生了变化。下面将提供调用refresh方法的正确时机。
每次触摸DOM,浏览器渲染器重绘页面。一旦发生了重画我们可以安全地读新的DOM属性。重新绘制阶段不是瞬时发生的只是范围结束时触发。这就是为什么我们需要给渲染器刷新iScroll之前一点时间。
为了确保javascript得到更新后的属性,应该像下面的例子这样使用刷新方法:
ajax(‘page.php‘, onCompletion);
function onCompletion () {
这里调用refresh()使用了零秒等待,如果你需要立即刷新iScroll边界就是如此使用。当然还有其他方法可以等待页面重绘,但零超时方式相当稳定。
如果你有一个相当复杂的HTML结构,你应该给浏览器更多的执行事件,可以设置100到200毫秒的超时时间。
这通常适用于所有任务必须在DOM上进行。通常给渲染器一些执行的时间。
自定义事件
Custom events
iScroll还提供额一些你可以挂靠的有用的自定义事件。使用on(type, fn)方法注册事件。
myScroll = new IScroll(‘#wrapper‘);
myScroll.on(‘scrollEnd‘, doSomething);
上面的代码会在每次滚动停止是执行doSomething方法。可以挂靠的事件如下:
beforeScrollStart,在用户触摸屏幕但还没有开始滚动时触发。
scrollCancel,滚动初始化完成,但没有执行。
scrollStart,开始滚动
scroll,内容滚动时触发,只有在scroll-probe.js版本中有效,请参考onScroll event。
scrollEnd,停止滚动时触发。
flick,用户打开左/右。
zoomStart,开始缩放。
zoomEnd,缩放结束。
onScroll事件
scroll事件在iScroll probe edition版本中有效(仅包含在iscroll-probe.js脚本文件中)。可以通过改变probeType选项值来改变scroll事件的触发时机。
options.probeType
这个属性是调节在scroll事件触发中探针的活跃度或者频率。有效值有:1, 2, 3。数值越高表示更活跃的探测。探针活跃度越高对CPU的影响就越大。
probeType: 1 对性能没有影响。scroll事件只有在滚动条不繁忙的时候触发。
probeType: 2 除了在势能和反弹范围内,将在scroll事件周期内一直执行。这类似于原生的onScroll事件。
probeType: 3 像素级的触发scroll事件。注意,此时滚动只关注requestAnimationFrame (即:useTransition:false).
你可以激活keyBindings选项来支持键盘控制。默认情况下iScroll监听方向键,上下翻页建,home/end键,但这些按键绑定完全可以自定义。你可以通过传递一个包含按键代码列表的对象来进行按键绑定。默认的按键值如下:
keyBindings: {
pageUp: 33,
pageDown: 34,
right: 39,
当然你也可以传递字符串进行按键绑定(例如:pageUp: ‘a‘)。只要你设置了对于的按键值,那么iScroll就会响应你的设置。
滚动条信息
iScroll存储了很多有用的信息,您可以使用它们来增强您的应用。你可能会发现有用的:
myScroll.x/y,当前位置
myScroll.directionX/Y,最后的方向 (-1 down/right, 0 still, 1 up/left)
myScroll.currentPage,当前对齐捕获点
下面是关于处理时间的代码示例:
myScroll = new IScroll(‘#wrapper‘);
myScroll.on(‘scrollEnd‘, function () {
if ( this.x & -1000 ) {
如果 x 位置是低于-1000 像素滚轮停止时,上述执行某些代码。请注意我用这个产品而不是 myScroll,您可以使用两个当然,但 iScroll 传递本身作为这种情况下,当触发自定义事件的功能。
在不需要使用iScoll的时候调用iScroll实例的公共方法destroy()可以释放一些内存。
myScroll.destroy();
myScroll = null;
原文链接:标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&原文地址:http://www.cnblogs.com/sunshq/p/4119055.html
&&国之画&&&& &&&&chrome插件&&
版权所有 京ICP备号-2
迷上了代码!2017年11月 Web 开发大版内专家分月排行榜第二2017年10月 Web 开发大版内专家分月排行榜第二2017年9月 Web 开发大版内专家分月排行榜第二2017年8月 Web 开发大版内专家分月排行榜第二2017年7月 Web 开发大版内专家分月排行榜第二2017年5月 Web 开发大版内专家分月排行榜第二2017年4月 Web 开发大版内专家分月排行榜第二2017年3月 Web 开发大版内专家分月排行榜第二2017年1月 Web 开发大版内专家分月排行榜第二2016年11月 Web 开发大版内专家分月排行榜第二2016年9月 Web 开发大版内专家分月排行榜第二2016年8月 Web 开发大版内专家分月排行榜第二2016年7月 Web 开发大版内专家分月排行榜第二2016年6月 Web 开发大版内专家分月排行榜第二2016年5月 Web 开发大版内专家分月排行榜第二2016年4月 Web 开发大版内专家分月排行榜第二2016年2月 Web 开发大版内专家分月排行榜第二2015年9月 Web 开发大版内专家分月排行榜第二2015年7月 Web 开发大版内专家分月排行榜第二2015年6月 Web 开发大版内专家分月排行榜第二2015年4月 Web 开发大版内专家分月排行榜第二2015年3月 Web 开发大版内专家分月排行榜第二2015年2月 Web 开发大版内专家分月排行榜第二
2016年12月 Web 开发大版内专家分月排行榜第三2016年10月 Web 开发大版内专家分月排行榜第三2016年1月 Web 开发大版内专家分月排行榜第三2015年12月 Web 开发大版内专家分月排行榜第三2015年11月 Web 开发大版内专家分月排行榜第三2015年10月 Web 开发大版内专家分月排行榜第三2015年5月 Web 开发大版内专家分月排行榜第三2015年1月 Web 开发大版内专家分月排行榜第三2014年12月 Web 开发大版内专家分月排行榜第三
本帖子已过去太久远了,不再提供回复功能。2017年8月 总版技术专家分月排行榜第一
2017年11月 总版技术专家分月排行榜第二2016年2月 总版技术专家分月排行榜第二2014年2月 总版技术专家分月排行榜第二2013年4月 总版技术专家分月排行榜第二
2017年12月 Web 开发大版内专家分月排行榜第三2013年3月 Web 开发大版内专家分月排行榜第三
本帖子已过去太久远了,不再提供回复功能。如何定义一个高逼格的原生JS插件 - 简书
如何定义一个高逼格的原生JS插件
作为一个前端er,如果不会写一个小插件,都不好意思说自己是混前端界的。写还不能依赖jquery之类的工具库,否则装得不够高端。那么,如何才能装起来让自己看起来逼格更高呢?当然是利用js纯原生的写法啦。以前一直说,掌握了js原生,就基本上可以解决前端的所有脚本交互工作了,这话大体上是有些浮夸了。不过,也从侧面说明了原生js在前端中占着多么重要的一面。好了。废话不多说。咱们就来看一下怎么去做一个自己的js插件吧。
插件的需求
我们写代码,并不是所有的业务或者逻辑代码都要抽出来复用。首先,我们得看一下是否需要将一部分经常重复的代码抽象出来,写到一个单独的文件中为以后再次使用。再看一下我们的业务逻辑是否可以为团队服务。
插件不是随手就写成的,而是根据自己业务逻辑进行抽象。没有放之四海而皆准的插件,只有对插件,之所以叫做插件,那么就是开箱即用,或者我们只要添加一些配置参数就可以达到我们需要的结果。如果都符合了这些情况,我们才去考虑做一个插件。
插件封装的条件
一个可复用的插件需要满足以下条件:
插件自身的作用域与用户当前的作用域相互独立,也就是插件内部的私有变量不能影响使用者的环境变量;
插件需具备默认设置参数;
插件除了具备已实现的基本功能外,需提供部分API,使用者可以通过该API修改插件功能的默认参数,从而实现用户自定义插件效果;
插件支持链式调用;
插件需提供监听入口,及针对指定元素进行监听,使得该元素与插件响应达到插件效果。
关于插件封装的条件,可以查看一篇文章:
而我想要说明的是,如何一步一步地实现我的插件封装。所以,我会先从简单的方法函数来做起。
插件的外包装
用函数包装
所谓插件,其实就是封装在一个闭包中的一种函数集。我记得刚开始写js的时候,我是这样干的,将我想要的逻辑,写成一个函数,然后再根据不同需要传入不同的参数就可以了。
比如,我想实现两个数字相加的方法:
function add(n1,n2) {
return n1 + n2;
// 输出:3
这就是我们要的功能的简单实现。如果仅仅只不过实现这么简单的逻辑,那已经可以了,没必要弄一些花里胡哨的东西。js函数本身就可以解决绝大多数的问题。不过我们在实际工作与应用中,一般情况的需求都是比较复杂得多。
如果这时,产品来跟你说,我不仅需要两个数相加的,我还要相减,相乘,相除,求余等等功能。这时候,我们怎么办呢?
当然,你会想,这有什么难的。直接将这堆函数都写出来不就完了。然后都放在一个js文件里面。需要的时候,就调用它就好了。
function add(n1,n2) {
return n1 + n2;
function sub(n1,n2) {
return n1 - n2;
function mul(n1,n2) {
return n1 * n2;
function div(n1,n2) {
return n1 / n2;
function sur(n1,n2) {
return n1 % n2;
OK,现在已经实现我们所需要的所有功能。并且我们也把这些函数都写到一个js里面了。如果是一个人在用,那么可以很清楚知道自己是否已经定义了什么,并且知道自己写了什么内容,我在哪个页面需要,那么就直接引入这个js文件就可以搞定了。
不过,如果是两个人以上的团队,或者你与别人一起协作写代码,这时候,另一个人并不知道你是否写了add方法,这时他也定义了同样的add方法。那么你们之间就会产生命名冲突,一般称之为变量的 全局污染
用全局对象包装
为了解决这种全局变量污染的问题。这时,我们可以定义一个js对象来接收我们这些工具函数。
var plugin = {
add: function(n1,n2){...},//加
sub: function(n1,n2){...},//减
mul: function(n1,n2){...},//乘
div: function(n1,n2){...},//除
sur: function(n1,n2){...} //余
plugin.add(1,2)
上面的方式,约定好此插件名为plugin,让团队成员都要遵守命名规则,在一定程度上已经解决了全局污染的问题。在团队协作中只要约定好命名规则了,告知其它同学即可以。当然不排除有个别人,接手你的项目,并不知道此全局变量已经定义,则他又定义了一次并赋值,这时,就会把你的对象覆盖掉。当然,可能你会这么干来解决掉命名冲突问题:
if(!plugin){ //这里的if条件也可以用: (typeof plugin == 'undefined')
var plugin = {
// 以此写你的函数逻辑
或者也可以这样写:
if(!plugin){
plugin = {
这样子,就不会存在命名上的冲突了。
也许有同学会疑问,为什么可以在此声明plugin变量?实际上js的解释执行,会把所有声明都提前。如果一个变量已经声明过,后面如果不是在函数内声明的,则是没有影响的。所以,就算在别的地方声明过var plugin,我同样也以可以在这里再次声明一次。关于声明的相关资料可以看阮一锋的。
基本上,这就可以算是一个插件了。解决了全局污染问题,方法函数可以抽出来放到一单独的文件里面去。
利用闭包包装
上面的例子,虽然可以实现了插件的基本上的功能。不过我们的plugin对象,是定义在全局域里面的。我们知道,js变量的调用,从全局作用域上找查的速度会比在私有作用域里面慢得多得多。所以,我们最好将插件逻辑写在一个私有作用域中。
实现私有作用域,最好的办法就是使用闭包。可以把插件当做一个函数,插件内部的变量及函数的私有变量,为了在调用插件后依旧能使用其功能,闭包的作用就是延长函数(插件)内部变量的生命周期,使得插件函数可以重复调用,而不影响用户自身作用域。
故需将插件的所有功能写在一个立即执行函数中:
;(function(global,undefined) {
var plugin = {
add: function(n1,n2){...}
// 最后将插件对象暴露给全局对象
'plugin' in global && global.plugin =
})(window);
对上面的代码段传参问题进行解释一下:
在定义插件之前添加一个分号,可以解决js合并时可能会产生的错误问题;
undefined在老一辈的浏览器是不被支持的,直接使用会报错,js框架要考虑到兼容性,因此增加一个形参undefined,就算有人把外面的 undefined 定义了,里面的 undefined 依然不受影响;
把window对象作为参数传入,是避免了函数执行的时候到外部去查找。
其实,我们觉得直接传window对象进去,我觉得还是不太妥当。我们并不确定我们的插件就一定用于浏览器上,也有可能使用在一些非浏览端上。所以我们还可以这么干,我们不传参数,直接取当前的全局this对象为作顶级对象用。
;(function(global,undefined) {
"use strict" //使用js严格模式检查,使语法更规范
var plugin = {
add: function(n1,n2){...}
// 最后将插件对象暴露给全局对象
_global = (function(){ return this || (0, eval)('this'); }());
!('plugin' in _global) && (_global.plugin = plugin);
如此,我们不需要传入任何参数,并且解决了插件对环境的依事性。如此我们的插件可以在任何宿主环境上运行了。
上面的代码段中有段奇怪的表达式:(0, eval)('this'),实际上(0,eval)是一个表达式,这个表达式执行之后的结果就是eval这一句相当于执行eval('this')的意思,详细解释看此篇:或者看一下这篇
关于立即自执行函数,有两种写法:
(function(){})()
(function(){}())
上面的两种写法是没有区别的。都是正确的写法。个人建议使用第二种写法。这样子更像一个整体。
附加一点知识:
js里面()括号就是将代码结构变成表达式,被包在()里面的变成了表达式之后,则就会立即执行,js中将一段代码变成表达式有很多种方式,比如:
void function(){...}();
!function foo(){...}();
+function foot(){...}();
当然,我们不推荐你这么用。而且乱用可能会产生一些歧义。
到这一步,我们的插件的基础结构就已经算是完整的了。
使用模块化的规范包装
虽然上面的包装基本上已经算是ok了的。但是如果是多个人一起开发一个大型的插件,这时我们要该怎么办呢?多人合作,肯定会产生多个文件,每个人负责一个小功能,那么如何才能将所有人开发的代码集合起来呢?这是一个讨厌的问题。要实现协作开发插件,必须具备如下条件:
每功能互相之间的依赖必须要明确,则必须严格按照依赖的顺序进行合并或者加载
每个子功能分别都要是一个闭包,并且将公共的接口暴露到共享域也即是一个被主函数暴露的公共对象
关键如何实现,有很多种办法。最笨的办法就是按顺序加载js
&script type="text/javascript" src="part1.js"&&/script&
&script type="text/javascript" src="part2.js"&&/script&
&script type="text/javascript" src="part3.js"&&/script&
&script type="text/javascript" src="main.js"&&/script&
但是不推荐这么做,这样做与我们所追求的插件的封装性相背。
不过现在前端界有一堆流行的模块加载器,比如、,或者也可以像类似于Node的方式进行加载,不过在浏览器端,我们还得利用打包器来实现模块加载,比如。不过在此不谈如何进行模块化打包或者加载的问题,如有问题的同学可以去上面的链接上看文档学习。
为了实现插件的模块化并且让我们的插件也是一个模块,我们就得让我们的插件也实现模块化的机制。
我们实际上,只要判断是否存在加载器,如果存在加载器,我们就使用加载器,如果不存在加载器。我们就使用顶级域对象。
if (typeof module !== "undefined" && module.exports) {
module.exports =
} else if (typeof define === "function" && define.amd) {
define(function(){});
_globals.plugin =
这样子我们的完整的插件的样子应该是这样子的:
// plugin.js
;(function(undefined) {
"use strict"
var plugin = {
add: function(n1,n2){ return n1 + n2; },//加
sub: function(n1,n2){ return n1 - n2; },//减
mul: function(n1,n2){ return n1 * n2; },//乘
div: function(n1,n2){ return n1 / n2; },//除
sur: function(n1,n2){ return n1 % n2; } //余
// 最后将插件对象暴露给全局对象
_global = (function(){ return this || (0, eval)('this'); }());
if (typeof module !== "undefined" && module.exports) {
module.exports =
} else if (typeof define === "function" && define.amd) {
define(function(){});
!('plugin' in _global) && (_global.plugin = plugin);
我们引入了插件之后,则可以直接使用plugin对象。
with(plugin){
console.log(add(2,1)) // 3
console.log(sub(2,1)) // 1
console.log(mul(2,1)) // 2
console.log(div(2,1)) // 2
console.log(sur(2,1)) // 0
插件的默认参数
我们知道,函数是可以设置默认参数这种说法,而不管我们是否传有参数,我们都应该返回一个值以告诉用户我做了怎样的处理,比如:
function add(param){
var args = !!param ? Array.prototype.slice.call(arguments) : [];
return args.reduce(function(pre,cur){
return pre +
console.log(add()) //不传参,结果输出0,则这里已经设置了默认了参数为空数组
console.log(add(1,2,3,4,5)) //传参,结果输出15
则作为一个健壮的js插件,我们应该把一些基本的状态参数添加到我们需要的插件上去。
假设还是上面的加减乘除余的需求,我们如何实现插件的默认参数呢?道理其实是一样的。
// plugin.js
;(function(undefined) {
"use strict"
function result(args,fn){
var argsArr = Array.prototype.slice.call(args);
if(argsArr.length & 0){
return argsArr.reduce(fn);
var plugin = {
add: function(){
return result(arguments,function(pre,cur){
return pre +
sub: function(){
return result(arguments,function(pre,cur){
return pre -
mul: function(){
return result(arguments,function(pre,cur){
return pre *
div: function(){
return result(arguments,function(pre,cur){
return pre /
sur: function(){
return result(arguments,function(pre,cur){
return pre %
// 最后将插件对象暴露给全局对象
_global = (function(){ return this || (0, eval)('this'); }());
if (typeof module !== "undefined" && module.exports) {
module.exports =
} else if (typeof define === "function" && define.amd) {
define(function(){});
!('plugin' in _global) && (_global.plugin = plugin);
// 输出结果为:
with(plugin){
console.log(add()); // 0
console.log(sub()); // 0
console.log(mul()); // 0
console.log(div()); // 0
console.log(sur()); // 0
console.log(add(2,1)); // 3
console.log(sub(2,1)); // 1
console.log(mul(2,1)); // 2
console.log(div(2,1)); // 2
console.log(sur(2,1)); // 0
实际上,插件都有自己的默认参数,就以我们最为常见的表单验证插件为例:
(function(window, document, undefined) {
// 插件的默认参数
var defaults = {
messages: {
required: 'The %s field is required.',
matches: 'The %s field does not match the %s field.',
"default": 'The %s field is still set to default, please change.',
valid_email: 'The %s field must contain a valid email address.',
valid_emails: 'The %s field must contain all valid email addresses.',
min_length: 'The %s field must be at least %s characters in length.',
max_length: 'The %s field must not exceed %s characters in length.',
exact_length: 'The %s field must be exactly %s characters in length.',
greater_than: 'The %s field must contain a number greater than %s.',
less_than: 'The %s field must contain a number less than %s.',
alpha: 'The %s field must only contain alphabetical characters.',
alpha_numeric: 'The %s field must only contain alpha-numeric characters.',
alpha_dash: 'The %s field must only contain alpha-numeric characters, underscores, and dashes.',
numeric: 'The %s field must contain only numbers.',
integer: 'The %s field must contain an integer.',
decimal: 'The %s field must contain a decimal number.',
is_natural: 'The %s field must contain only positive numbers.',
is_natural_no_zero: 'The %s field must contain a number greater than zero.',
valid_ip: 'The %s field must contain a valid IP.',
valid_base64: 'The %s field must contain a base64 string.',
valid_credit_card: 'The %s field must contain a valid credit card number.',
is_file_type: 'The %s field must contain only %s files.',
valid_url: 'The %s field must contain a valid URL.',
greater_than_date: 'The %s field must contain a more recent date than %s.',
less_than_date: 'The %s field must contain an older date than %s.',
greater_than_or_equal_date: 'The %s field must contain a date that\'s at least as recent as %s.',
less_than_or_equal_date: 'The %s field must contain a date that\'s %s or older.'
callback: function(errors) {
var ruleRegex = /^(.+?)\[(.+)\]$/,
numericRegex = /^[0-9]+$/,
integerRegex = /^\-?[0-9]+$/,
decimalRegex = /^\-?[0-9]*\.?[0-9]+$/,
emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,
alphaRegex = /^[a-z]+$/i,
alphaNumericRegex = /^[a-z0-9]+$/i,
alphaDashRegex = /^[a-z0-9_\-]+$/i,
naturalRegex = /^[0-9]+$/i,
naturalNoZeroRegex = /^[1-9][0-9]*$/i,
ipRegex = /^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$/i,
base64Regex = /[^a-zA-Z0-9\/\+=]/i,
numericDashRegex = /^[\d\-\s]+$/,
urlRegex = /^((http|https):\/\/(\w+:{0,1}\w*@)?(\S+)|)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,
dateRegex = /\d{4}-\d{1,2}-\d{1,2}/;
... //省略后面的代码
})(window,document);
* Export as a CommonJS module
if (typeof module !== 'undefined' && module.exports) {
module.exports = FormV
当然,参数既然是默认的,那就意味着我们可以随意修改参数以达到我们的需求。插件本身的意义就在于具有复用性。
如表单验证插件,则就可以new一个对象的时候,修改我们的默认参数:
var validator = new FormValidator('example_form', [{
name: 'req',
display: 'required',
rules: 'required'
name: 'alphanumeric',
rules: 'alpha_numeric'
name: 'password',
rules: 'required'
name: 'password_confirm',
display: 'password confirmation',
rules: 'required|matches[password]'
name: 'email',
rules: 'valid_email'
name: 'minlength',
display: 'min length',
rules: 'min_length[8]'
names: ['fname', 'lname'],
rules: 'required|alpha'
}], function(errors) {
if (errors.length & 0) {
// Show the errors
插件的钩子
我们知道,设计一下插件,参数或者其逻辑肯定不是写死的,我们得像函数一样,得让用户提供自己的参数去实现用户的需求。则我们的插件需要提供一个修改默认参数的入口。
如上面我们说的修改默认参数,实际上也是插件给我们提供的一个API。让我们的插件更加的灵活。如果大家对API不了解,可以百度一下
通常我们用的js插件,实现的方式会有多种多样的。最简单的实现逻辑就是一个方法,或者一个js对象,又或者是一个构造函数等等。
** 然我们插件所谓的API,实际就是我们插件暴露出来的所有方法及属性。 **
我们需求中,加减乘除余插件中,我们的API就是如下几个方法:
var plugin = {
add: function(n1,n2){ return n1 + n2; },
sub: function(n1,n2){ return n1 - n2; },
mul: function(n1,n2){ return n1 * n2; },
div: function(n1,n2){ return n1 / n2; },
sur: function(n1,n2){ return n1 % n2; }
可以看到plubin暴露出来的方法则是如下几个API:
在插件的API中,我们常常将暴露的方法或者属性统称为钩子(Hook),方法则直接叫钩子函数。这是一种形象生动的说法,就好像我们在一条绳子上放很多挂钩,我们可以按需要在上面挂东西。
实际上,我们即知道插件可以像一条绳子上挂东西,也可以拿掉挂的东西。那么一个插件,实际上就是个形象上的链。不过我们上面的所有钩子都是挂在对象上的,用于实现链并不是很理想。
插件的链式调用(利用当前对象)
插件并非都是能链式调用的,有些时候,我们只是用钩子来实现一个计算并返回结果,取得运算结果就可以了。但是有些时候,我们用钩子并不需要其返回结果。我们只利用其实现我们的业务逻辑,为了代码简洁与方便,我们常常将插件的调用按链式的方式进行调用。
最常见的jquery的链式调用如下:
$(&id&).show().css('color','red').width(100).height(100)....
那,如何才能将链式调用运用到我们的插件中去呢?假设我们上面的例子,如果是要按照plugin这个对象的链式进行调用,则可以将其业务结构改为:
var plugin = {
add: function(n1,n2){ },
sub: function(n1,n2){ },
mul: function(n1,n2){ },
div: function(n1,n2){ },
sur: function(n1,n2){ }
显示,我们只要将插件的当前对象this直接返回,则在下一下方法中,同样可以引用插件对象plugin的其它勾子方法。然后调用的时候就可以使用链式了。
plugin.add().sub().mul().div().sur()
//如此调用显然没有任何实际意义
显然这样做并没有什么意义。我们这里的每一个钩子函数都只是用来计算并且获取返回值而已。而链式调用本身的意义是用来处理业务逻辑的。
插件的链式调用(利用原型链)
JavaScript中,万物皆对象,所有对象都是继承自原型。JS在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__的内置属性,用于指向创建它的函数对象的原型对象prototype。关于原型问题,感兴趣的同学可以看这篇:
在上面的需求中,我们可以将plugin对象改为原型的方式,则需要将plugin写成一个构造方法,我们将插件名换为Calculate避免因为Plugin大写的时候与Window对象中的API冲突。
function Calculate(){}
Calculate.prototype.add = function(){}
Calculate.prototype.sub = function(){}
Calculate.prototype.mul = function(){}
Calculate.prototype.div = function(){}
Calculate.prototype.sur = function(){}
当然,假设我们的插件是对初始化参数进行运算并只输出结果,我们可以稍微改一下:
// plugin.js
// plugin.js
;(function(undefined) {
"use strict"
function result(args,type){
var argsArr = Array.prototype.slice.call(args);
if(argsArr.length == 0) return 0;
switch(type) {
case 1: return argsArr.reduce(function(p,c){return p +});
case 2: return argsArr.reduce(function(p,c){return p -});
case 3: return argsArr.reduce(function(p,c){return p *});
case 4: return argsArr.reduce(function(p,c){return p /});
case 5: return argsArr.reduce(function(p,c){return p %});
default: return 0;
function Calculate(){}
Calculate.prototype.add = function(){console.log(result(arguments,1));}
Calculate.prototype.sub = function(){console.log(result(arguments,2));}
Calculate.prototype.mul = function(){console.log(result(arguments,3));}
Calculate.prototype.div = function(){console.log(result(arguments,4));}
Calculate.prototype.sur = function(){console.log(result(arguments,5));}
// 最后将插件对象暴露给全局对象
_global = (function(){ return this || (0, eval)('this'); }());
if (typeof module !== "undefined" && module.exports) {
module.exports = C
} else if (typeof define === "function" && define.amd) {
define(function(){return C});
!('Calculate' in _global) && (_global.Calculate = Calculate);
这时调用我们写好的插件,则输出为如下:
var plugin = new Calculate();
.sur(2,1);
上面的例子,可以并没有太多的现实意义。不过在网页设计中,我们的插件基本上都是服务于UI层面,利用js脚本实现一些可交互的效果。这时我们编写一个UI插件,实现过程也是可以使用链式进行调用。
编写UI组件
一般情况,如果一个js仅仅是处理一个逻辑,我们称之为插件,但如果与dom和css有关系并且具备一定的交互性,一般叫做组件。当然这没有什么明显的区分,只是一种习惯性叫法。
利用原型链,可以将一些UI层面的业务代码封装在一个小组件中,并利用js实现组件的交互性。
现有一个这样的需求:
实现一个弹层,此弹层可以显示一些文字提示性的信息;
弹层右上角必须有一个关闭按扭,点击之后弹层消失;
弹层底部必有一个“确定”按扭,然后根据需求,可以配置多一个“取消”按扭;
点击“确定”按扭之后,可以触发一个事件;
点击关闭/“取消”按扭后,可以触发一个事件。
根据需求,我们先写出dom结构:
&!DOCTYPE html&
&html lang="en"&
&meta charset="UTF-8"&
&title&index&/title&
&link rel="stylesheet" type="text/css" href="index.css"&
&div class="mydialog"&
&span class="close"&×&/span&
&div class="mydialog-cont"&
&div class="cont"&hello world!&/div&
&div class="footer"&
&span class="btn"&确定&/span&
&span class="btn"&取消&/span&
&script src="index.js"&&/script&
写出css结构:
* { padding: 0; margin: 0; }
.mydialog { background: # box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.3); overflow: width: 300 height: 180 border: 1px solid # position: top: 0; right: 0; bottom: 0; left: 0; margin: }
.close { position: right: 5 top: 5 width: 16 height: 16 line-height: 16 text-align: font-size: 18 cursor: }
.mydialog-cont { padding: 0 0 50 display: width: 100%; height: 100%; }
.mydialog-cont .cont { display: table- text-align: vertical-align: width: 100%; height: 100%; }
.footer { display: table-layout: width: 100%; position: bottom: 0; left: 0; border-top: 1px solid # }
.footer .btn { display: table- width: 50%; height: 50 line-height: 50 text-align: cursor: }
.footer .btn:last-child { display: table- width: 50%; height: 50 line-height: 50 text-align: cursor: border-left: 1px solid # }
接下来,我们开始编写我们的交互插件。
我们假设组件的弹出层就是一个对象。则这个对象是包含了我们的交互、样式、结构及渲染的过程。于是我们定义了一个构造方法:
function MyDialog(){} // MyDialog就是我们的组件对象了
对象MyDialog就相当于一个绳子,我们只要往这个绳子上不断地挂上钩子就是一个组件了。于是我们的组件就可以表示为:
function MyDialog(){}
MyDialog.prototype = {
constructor: this,
_initial: function(){},
_parseTpl: function(){},
_parseToDom: function(){},
show: function(){},
hide: function(){},
css: function(){},
然后就可以将插件的功能都写上。不过中间的业务逻辑,需要自己去一步一步研究。无论如何写,我们最终要做到通过实例化一个MyDialog对象就可以使用我们的插件了。
在编写的过程中,我们得先做一些工具函数:
1.对象合并函数
// 对象合并
function extend(o,n,override) {
for(var key in n){
if(n.hasOwnProperty(key) && (!o.hasOwnProperty(key) || override)){
o[key]=n[key];
2.自定义模板引擎解释函数
// 自定义模板引擎
function templateEngine(html, data) {
var re = /&%([^%&]+)?%&/g,
reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
code = 'var r=[];\n',
cursor = 0;
var add = function(line, js) {
js ? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
(code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
while (match = re.exec(html)) {
add(html.slice(cursor, match.index))(match[1], true);
cursor = match.index + match[0].
add(html.substr(cursor, html.length - cursor));
code += 'return r.join("");';
return new Function(code.replace(/[\r\t\n]/g, '')).apply(data);
3.查找class获取dom函数
// 通过class查找dom
if(!('getElementsByClass' in HTMLElement)){
HTMLElement.prototype.getElementsByClass = function(n, tar){
var el = [],
_el = (!!tar ? tar : this).getElementsByTagName('*');
for (var i=0; i&_el. i++ ) {
if (!!_el[i].className && (typeof _el[i].className == 'string') && _el[i].className.indexOf(n) & -1 ) {
el[el.length] = _el[i];
((typeof HTMLDocument !== 'undefined') ? HTMLDocument : Document).prototype.getElementsByClass = HTMLElement.prototype.getElementsByC
结合工具函数,再去实现每一个钩子函数具体逻辑结构:
// plugin.js
;(function(undefined) {
"use strict"
// 插件构造函数 - 返回数组结构
function MyDialog(opt){
this._initial(opt);
MyDialog.prototype = {
constructor: this,
_initial: function(opt) {
// 默认参数
var def = {
ok_txt: '确定',
cancel: false,
cancel_txt: '取消',
confirm: function(){},
close: function(){},
content: '',
tmpId: null
this.def = extend(def,opt,true);
this.tpl = this._parseTpl(this.def.tmpId);
this.dom = this._parseToDom(this.tpl)[0];
this.hasDom =
_parseTpl: function(tmpId) { // 将模板转为字符串
var data = this.
var tplStr = document.getElementById(tmpId).innerHTML.trim();
return templateEngine(tplStr,data);
_parseToDom: function(str) { // 将字符串转为dom
var div = document.createElement('div');
if(typeof str == 'string') {
div.innerHTML =
return div.childN
show: function(callback){
var _this =
if(this.hasDom)
document.body.appendChild(this.dom);
this.hasDom =
document.getElementsByClass('close',this.dom)[0].onclick = function(){
_this.hide();
document.getElementsByClass('btn-ok',this.dom)[0].onclick = function(){
_this.hide();
if(this.def.cancel){
document.getElementsByClass('btn-cancel',this.dom)[0].onclick = function(){
_this.hide();
callback && callback();
hide: function(callback){
document.body.removeChild(this.dom);
this.hasDom =
callback && callback();
modifyTpl: function(template){
if(!!template) {
if(typeof template == 'string'){
this.tpl =
} else if(typeof template == 'function'){
this.tpl = template();
// this.tpl = this._parseTpl(this.def.tmpId);
this.dom = this._parseToDom(this.tpl)[0];
css: function(styleObj){
for(var prop in styleObj){
var attr = prop.replace(/[A-Z]/g,function(word){
return '-' + word.toLowerCase();
this.dom.style[attr] = styleObj[prop];
width: function(val){
this.dom.style.width = val + 'px';
height: function(val){
this.dom.style.height = val + 'px';
_global = (function(){ return this || (0, eval)('this'); }());
if (typeof module !== "undefined" && module.exports) {
module.exports = MyD
} else if (typeof define === "function" && define.amd) {
define(function(){return MyD});
!('MyDialog' in _global) && (_global.MyDialog = MyDialog);
到这一步,我们的插件已经达到了基础需求了。我们可以在页面这样调用:
&script type="text/template" id="dialogTpl"&
&div class="mydialog"&
&span class="close"&×&/span&
&div class="mydialog-cont"&
&div class="cont"&&% this.content %&&/div&
&div class="footer"&
&% if(this.cancel){ %&
&span class="btn btn-ok"&&% this.ok_txt %&&/span&
&span class="btn btn-cancel"&&% this.cancel_txt %&&/span&
&% } else{ %&
&span class="btn btn-ok" style="width: 100%"&&% this.ok_txt %&&/span&
&script src="index.js"&&/script&
var mydialog = new MyDialog({
tmpId: 'dialogTpl',
cancel: true,
content: 'hello world!'
mydialog.show();
插件的监听
弹出框插件我们已经实现了基本的显示与隐藏的功能。不过我们在怎么时候弹出,弹出之后可能进行一些操作,实际上还是需要进行一些可控的操作。就好像我们进行事件绑定一样,只有用户点击了按扭,才响应具体的事件。那么,我们的插件,应该也要像事件绑定一样,只有执行了某些操作的时候,调用相应的事件响应。
这种js的设计模式,被称为 订阅/发布模式,也被叫做 观察者模式。我们插件中的也需要用到观察者模式,比如,在打开弹窗之前,我们需要先进行弹窗的内容更新,执行一些判断逻辑等,然后执行完成之后才显示出弹窗。在关闭弹窗之后,我们需要执行关闭之后的一些逻辑,处理业务等。这时候我们需要像平时绑定事件一样,给插件做一些“事件”绑定回调方法。
我们jquery对dom的事件响应是这样的:
$(&dom&).on("click",function(){})
我们照着上面的方式设计了对应的插件响应是这样的:
mydialog.on('show',function(){})
则,我们需要实现一个事件机制,以达到监听插件的事件效果。关于自定义事件监听,可以参考一篇博文:。在此不进行大篇幅讲自定义事件的问题。
最终我们实现的插件代码为:
// plugin.js
;(function(undefined) {
"use strict"
// 工具函数
// 对象合并
function extend(o,n,override) {
for(var key in n){
if(n.hasOwnProperty(key) && (!o.hasOwnProperty(key) || override)){
o[key]=n[key];
// 自定义模板引擎
function templateEngine(html, data) {
var re = /&%([^%&]+)?%&/g,
reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
code = 'var r=[];\n',
cursor = 0;
var add = function(line, js) {
js ? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
(code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
while (match = re.exec(html)) {
add(html.slice(cursor, match.index))(match[1], true);
cursor = match.index + match[0].
add(html.substr(cursor, html.length - cursor));
code += 'return r.join("");';
return new Function(code.replace(/[\r\t\n]/g, '')).apply(data);
// 通过class查找dom
if(!('getElementsByClass' in HTMLElement)){
HTMLElement.prototype.getElementsByClass = function(n){
var el = [],
_el = this.getElementsByTagName('*');
for (var i=0; i&_el. i++ ) {
if (!!_el[i].className && (typeof _el[i].className == 'string') && _el[i].className.indexOf(n) & -1 ) {
el[el.length] = _el[i];
((typeof HTMLDocument !== 'undefined') ? HTMLDocument : Document).prototype.getElementsByClass = HTMLElement.prototype.getElementsByC
// 插件构造函数 - 返回数组结构
function MyDialog(opt){
this._initial(opt);
MyDialog.prototype = {
constructor: this,
_initial: function(opt) {
// 默认参数
var def = {
ok_txt: '确定',
cancel: false,
cancel_txt: '取消',
confirm: function(){},
close: function(){},
content: '',
tmpId: null
this.def = extend(def,opt,true); //配置参数
this.tpl = this._parseTpl(this.def.tmpId); //模板字符串
this.dom = this._parseToDom(this.tpl)[0]; //存放在实例中的节点
this.hasDom = //检查dom树中dialog的节点是否存在
this.listeners = []; //自定义事件,用于监听插件的用户交互
this.handlers = {};
_parseTpl: function(tmpId) { // 将模板转为字符串
var data = this.
var tplStr = document.getElementById(tmpId).innerHTML.trim();
return templateEngine(tplStr,data);
_parseToDom: function(str) { // 将字符串转为dom
var div = document.createElement('div');
if(typeof str == 'string') {
div.innerHTML =
return div.childN
show: function(callback){
var _this =
if(this.hasDom)
if(this.listeners.indexOf('show') & -1) {
if(!this.emit({type:'show',target: this.dom}))
document.body.appendChild(this.dom);
this.hasDom =
this.dom.getElementsByClass('close')[0].onclick = function(){
_this.hide();
if(_this.listeners.indexOf('close') & -1) {
_this.emit({type:'close',target: _this.dom})
!!_this.def.close && _this.def.close.call(this,_this.dom);
this.dom.getElementsByClass('btn-ok')[0].onclick = function(){
_this.hide();
if(_this.listeners.indexOf('confirm') & -1) {
_this.emit({type:'confirm',target: _this.dom})
!!_this.def.confirm && _this.def.confirm.call(this,_this.dom);
if(this.def.cancel){
this.dom.getElementsByClass('btn-cancel')[0].onclick = function(){
_this.hide();
if(_this.listeners.indexOf('cancel') & -1) {
_this.emit({type:'cancel',target: _this.dom})
callback && callback();
if(this.listeners.indexOf('shown') & -1) {
this.emit({type:'shown',target: this.dom})
hide: function(callback){
if(this.listeners.indexOf('hide') & -1) {
if(!this.emit({type:'hide',target: this.dom}))
document.body.removeChild(this.dom);
this.hasDom =
callback && callback();
if(this.listeners.indexOf('hidden') & -1) {
this.emit({type:'hidden',target: this.dom})
modifyTpl: function(template){
if(!!template) {
if(typeof template == 'string'){
this.tpl =
} else if(typeof template == 'function'){
this.tpl = template();
this.dom = this._parseToDom(this.tpl)[0];
css: function(styleObj){
for(var prop in styleObj){
var attr = prop.replace(/[A-Z]/g,function(word){
return '-' + word.toLowerCase();
this.dom.style[attr] = styleObj[prop];
width: function(val){
this.dom.style.width = val + 'px';
height: function(val){
this.dom.style.height = val + 'px';
on: function(type, handler){
// type: show, shown, hide, hidden, close, confirm
if(typeof this.handlers[type] === 'undefined') {
this.handlers[type] = [];
this.listeners.push(type);
this.handlers[type].push(handler);
off: function(type, handler){
if(this.handlers[type] instanceof Array) {
var handlers = this.handlers[type];
for(var i = 0, len = handlers. i & i++) {
if(handlers[i] === handler) {
this.listeners.splice(i, 1);
handlers.splice(i, 1);
emit: function(event){
if(!event.target) {
event.target =
if(this.handlers[event.type] instanceof Array) {
var handlers = this.handlers[event.type];
for(var i = 0, len = handlers. i & i++) {
handlers[i](event);
// 最后将插件对象暴露给全局对象
_global = (function(){ return this || (0, eval)('this'); }());
if (typeof module !== "undefined" && module.exports) {
module.exports = MyD
} else if (typeof define === "function" && define.amd) {
define(function(){return MyD});
!('MyDialog' in _global) && (_global.MyDialog = MyDialog);
然后调用的时候就可以直接使用插件的事件绑定了。
var mydialog = new MyDialog({
tmpId: 'dialogTpl',
cancel: true,
content: 'hello world!'
mydialog.on('confirm',function(ev){
console.log('you click confirm!');
// 写你的确定之后的逻辑代码...
document.getElementById('test').onclick = function(){
mydialog.show();
给出此例子的,有需要具体实现的同学可以去查阅。
我们写好了插件,实际上还可以将我们的插件发布到开源组织去分享给更多人去使用(代码必须是私人拥有所有支配权限)。我们将插件打包之后,就可以发布到开源组织上去供别人下载使用了。
我们熟知的npm社区就是一个非常良好的发布插件的平台。具体可以如下操作:
写初始化包的描述文件:
$ npm init
注册包仓库帐号
$ npm adduser
Username: &帐号&
Password: &密码&
Email:(this IS public) &邮箱&
Logged in as &帐号& on https://registry.npmjs.org/.
$ npm publish
$ npm install mydialog
到此,我们的插件就可以直接被更多人去使用了。
写了这么多,比较啰嗦,我在此做一下总结:
关于如何编写出一个好的js原生插件,需要平时在使用别人的插件的同时,多查看一下api文档,了解插件的调用方式,然后再看一下插件的源码的设计方式。基本上我们可以确定大部分插件都是按照原型的方式进行设计的。而我从上面的例子中,就使用了好多js原生的知识点,函数的命名冲突、闭包、作用域,自定义工具函数扩展对象的钩子函数,以及对象的初始化、原型链继承,构造函数的定义及设计模式,还有事件的自定义,js设计模式的观察者模式等知识。这些内容还是需要初学者多多了解才能进行一些高层次一些的插件开发。
如果一个人不知道他要驶向哪头,那么任何风都不是顺风。
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金相信有很多朋友...
用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你能获得这些料: 知道setContentView()之后发生了什么? ... Android 获取 View 宽高的常用正确方式,避免为零 - 掘金 相信有很多...
用到的组件1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SDWebImage多个缩略图缓存组件 UICKeyChainStore存放用户账号密码组件 Reachability监测网络状态 DateTools友好...
用到的组件1、通过CocoaPods安装项目名称项目信息AFNetworking网络请求组件FMDB本地数据库组件SDWebImage多个缩略图缓存组件UICKeyChainStore存放用户账号密码组件Reachability监测网络状态DateTools友好化时间MBP...
js文档加载完毕有哪几种写法 1:js加载完毕有哪几种写法 一、当不引入jQuery框架,只写原生JS代码时,需要用window对象的onload事件 window.onload= function(){ //要执行的js代码段 } (注:在使用时,window.onloa...
还记得以前看过的一个笑话。有人问,你觉得这个世界上最棒的发明是什么?一个男人答道,当然是面膜啦!提问者非常惊奇,女人用的那个面膜?那有啥神奇的?男人答道,那当然。一个面膜几块钱一张,贴在脸上至少可以使我老婆30分钟不说话。还有比这更好的解决唠叨的办法吗? 这个笑话浅显易懂,...
董沛沛 洛阳 焦点讲师班三期 坚持原创分享第160天 我从未见过懒惰的人: 我见过有个人有时在下午睡觉, 在雨天不出门, 但他不是个懒惰的人。 请在说我胡言乱语之前, 想一想,他是个懒惰的人,还是他的行为被我们称为懒惰? 我从未见过愚蠢的孩子: 我见过有个孩子有时做的事,我...
39W4D。3D。 其实,按照近期丁大姐给我算的预产期,今天是39周2天,距离预产期5天。 周一凌晨挂的今天的号。 早晨如往常一样,婆婆打电话叫我们起床吃饭。凌晨四点起夜醒来,能猫胎动了近一个小时。我也饿了,起来吃个苹果,喝包麦片。早上起来,把吃的苹果全吐了。 饭后继续窝床...
在新年里,说这句话挺沮丧的。我认为自己患上了“失去”斗志的某种疾病。我花了3小时时间用来思考现在的生活,以及生活的意义。我断定自己80%丧失了对生活的热情,甚至对旅行。我花了近一个月来伪装我对生活充满了信心,结果我发现这不是一种好的治疗手段,甚至会让生活更糟糕,这种方式只会...
文章很长,没有经过严谨的数据和实证分析,如有错误,还请谅解。笔者是一位江西老表,对自己的家乡十分热爱,也希望这篇文章让你对江西多一份了解。
在此之前,“江西”一直被吐槽是中国最没有存在感的一个省。可以说,江西的无论是地理位置还是经济实力都是中规中矩。从位置上来说,不东不...}

我要回帖

更多关于 可变参数宏定义 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信