惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
量子位
小众软件
小众软件
C
Cybersecurity and Infrastructure Security Agency CISA
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
V
Vulnerabilities – Threatpost
Know Your Adversary
Know Your Adversary
T
Threat Research - Cisco Blogs
Latest news
Latest news
Spread Privacy
Spread Privacy
C
Cyber Attacks, Cyber Crime and Cyber Security
NISL@THU
NISL@THU
T
Tor Project blog
Hacker News: Ask HN
Hacker News: Ask HN
V2EX - 技术
V2EX - 技术
T
The Exploit Database - CXSecurity.com
博客园 - 三生石上(FineUI控件)
K
Kaspersky official blog
Cyberwarzone
Cyberwarzone
博客园 - 叶小钗
博客园 - 聂微东
Last Week in AI
Last Week in AI
爱范儿
爱范儿
腾讯CDC
博客园 - Franky
美团技术团队
J
Java Code Geeks
O
OpenAI News
L
Lohrmann on Cybersecurity
Simon Willison's Weblog
Simon Willison's Weblog
有赞技术团队
有赞技术团队
T
Threatpost
G
GRAHAM CLULEY
Hugging Face - Blog
Hugging Face - Blog
博客园 - 【当耐特】
宝玉的分享
宝玉的分享
I
Intezer
N
News and Events Feed by Topic
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
The Blog of Author Tim Ferriss
S
Security @ Cisco Blogs
Forbes - Security
Forbes - Security
N
News | PayPal Newsroom
Stack Overflow Blog
Stack Overflow Blog
Scott Helme
Scott Helme
H
Hacker News: Front Page
Cloudbric
Cloudbric

张鑫旭-鑫空间-鑫生活

1分钟速度了解text-align match-parent声明 « 张鑫旭-鑫空间-鑫生活 光标的形状也能设置了,就是CSS caret-shape属性 « 张鑫旭-鑫空间-鑫生活 pretext与文字四面环绕效果的实现 « 张鑫旭-鑫空间-鑫生活 JSON.rawJSON方法的作用是什么? « 张鑫旭-鑫空间-鑫生活 如何使用CSS判断鼠标从哪个方向进入元素? « 张鑫旭-鑫空间-鑫生活 借助mediabunny纯JS实现视频水印、剪裁、合成等功能 « 张鑫旭-鑫空间-鑫生活 务必谨慎使用JS WeakRef弱引用 « 张鑫旭-鑫空间-鑫生活 CSS六边形头像的实现与蜂巢布局 « 张鑫旭-鑫空间-鑫生活 CSS corner-shape与背景底纹技术 « 张鑫旭-鑫空间-鑫生活 浅学WebTransport API:下一代Web双向通信技术 « 张鑫旭-鑫空间-鑫生活 HTML dialog元素新支持了closedBy属性 « 张鑫旭-鑫空间-鑫生活 HTML interestfor属性与悬停popover交互效果 « 张鑫旭-鑫空间-鑫生活 CSS3模拟window7炫酷界面效果展示 « 张鑫旭-鑫空间-鑫生活 图片旋转+剪裁js插件(兼容各浏览器) « 张鑫旭-鑫空间-鑫生活 纯js无图片无外链CSS图片幻灯片播放插件 « 张鑫旭-鑫空间-鑫生活 CSS border三角、圆角图形生成技术简介 « 张鑫旭-鑫空间-鑫生活 CSS “渐进增强”在web制作中常见应用举例 « 张鑫旭-鑫空间-鑫生活 让IE6/IE7/IE8浏览器支持CSS3属性 « 张鑫旭-鑫空间-鑫生活 jQuery – 高亮动态显示页面HTML代码插件 « 张鑫旭-鑫空间-鑫生活 翻译 – 逐渐消失的Flash网站 « 张鑫旭-鑫空间-鑫生活 CSS reset的重新审视 – 避免样式重置 « 张鑫旭-鑫空间-鑫生活 新浪微博插入话题后部分文字选中的js实现 « 张鑫旭-鑫空间-鑫生活 JavaScript实现http地址自动检测并添加URL链接 « 张鑫旭-鑫空间-鑫生活 CSS实现兼容性的渐变背景(gradient)效果 « 张鑫旭-鑫空间-鑫生活 CSS content内容生成技术以及应用 « 张鑫旭-鑫空间-鑫生活 CSS gradient渐变之webkit核心浏览器下的使用 « 张鑫旭-鑫空间-鑫生活 CSS渐变之CSS3 gradient在Firefox3.6下的使用 « 张鑫旭-鑫空间-鑫生活 区分IE8/IE7/IE6及其他浏览器-CSS “\9″hack « 张鑫旭-鑫空间-鑫生活 CSS实现跨浏览器兼容性的盒阴影效果 « 张鑫旭-鑫空间-鑫生活 CSS3&HTML5各浏览器支持情况一览表 « 张鑫旭-鑫空间-鑫生活 翻译 – CSS Sprites:实用技术还是生厌之物? « 张鑫旭-鑫空间-鑫生活 纯CSS实现易拉罐3D滚动效果 « 张鑫旭-鑫空间-鑫生活 纯CSS实现侧边栏/分栏高度自动相等 « 张鑫旭-鑫空间-鑫生活 CSS+JavaScript实现页面不同布局的切换 « 张鑫旭-鑫空间-鑫生活 JavaScript实现最简单的拖拽效果 « 张鑫旭-鑫空间-鑫生活 自己写的无图片版jQuery zxxbox弹出框插件 « 张鑫旭-鑫空间-鑫生活 中国古代道家思想与网页重构的思考 « 张鑫旭-鑫空间-鑫生活 让所有浏览器支持HTML5 video视频标签 « 张鑫旭-鑫空间-鑫生活 纯CSS实现各类气球泡泡对话框效果 « 张鑫旭-鑫空间-鑫生活 JS HEX十六进制与RGB, HSL颜色的相互转换 « 张鑫旭-鑫空间-鑫生活 CSS实现兼容性的渐变、高光等文字效果 « 张鑫旭-鑫空间-鑫生活 去除冗余 – 精简您的CSS样式代码 « 张鑫旭-鑫空间-鑫生活 对overflow与zoom”清除浮动”的一些认识 « 张鑫旭-鑫空间-鑫生活 jQuery Pagination Ajax分页插件中文详解 « 张鑫旭-鑫空间-鑫生活 absolute绝对定位的非绝对定位用法 « 张鑫旭-鑫空间-鑫生活 网页布局思想浅议-淘宝新版首页为实例 « 张鑫旭-鑫空间-鑫生活 回流与重绘:CSS性能让JavaScript变慢? « 张鑫旭-鑫空间-鑫生活 CSS float浮动的深入研究、详解及拓展(二) « 张鑫旭-鑫空间-鑫生活 CSS float浮动的深入研究、详解及拓展(一) « 张鑫旭-鑫空间-鑫生活 60个极富创造性的震撼的电影海报设计 « 张鑫旭-鑫空间-鑫生活 jQuery Lightbox(balupton版)图片展示插件讲解 « 张鑫旭-鑫空间-鑫生活 博客内嵌flash音乐播放器代码、下载与使用 « 张鑫旭-鑫空间-鑫生活 CSS3 animate实现图片墙3D翻转效果 « 张鑫旭-鑫空间-鑫生活 图片旋转效果的一些研究、jQuery插件及实例 « 张鑫旭-鑫空间-鑫生活 CSS3 border-image详解、应用及jQuery插件 « 张鑫旭-鑫空间-鑫生活 jQuery-两款不同原理的圆角插件讲解 « 张鑫旭-鑫空间-鑫生活 页面可用性之outline轮廓外框的一些研究 « 张鑫旭-鑫空间-鑫生活 CSS3 transition实现超酷图片墙动画效果 « 张鑫旭-鑫空间-鑫生活 HTML CSS列表元素ul,ol,dl的研究与应用 « 张鑫旭-鑫空间-鑫生活 jQuery图片文本滚动切换插件jCarousel中文翻译与详解 « 张鑫旭-鑫空间-鑫生活 酷酷的jQuery鼠标悬停图片放大切换显示效果 « 张鑫旭-鑫空间-鑫生活 IE6下z-index犯癫不起作用bug的初步研究 « 张鑫旭-鑫空间-鑫生活 checkbox复选框的一些深入研究与理解 « 张鑫旭-鑫空间-鑫生活 jQuery之使用slideToggle实现垂直下拉菜单 « 张鑫旭-鑫空间-鑫生活 jQuery-innerfade内部列表自动淡入淡出插件 « 张鑫旭-鑫空间-鑫生活 jQuery-火焰灯效果导航菜单 « 张鑫旭-鑫空间-鑫生活 IE下css bug集合-翻译自haslayout.net « 张鑫旭-鑫空间-鑫生活 翻译-IE7/8@font-face嵌入字体与文字平滑 « 张鑫旭-鑫空间-鑫生活 jQuery-马化腾产品设计与用户体验的一些技术实现 « 张鑫旭-鑫空间-鑫生活 像素的世界及其在web开发制作中的应用 « 张鑫旭-鑫空间-鑫生活 css行高line-height的一些深入理解及应用 « 张鑫旭-鑫空间-鑫生活 CSS实现圆角六色渐变自适应按钮详解 « 张鑫旭-鑫空间-鑫生活 我熟知的三种三栏网页宽度自适应布局方法 « 张鑫旭-鑫空间-鑫生活 jQuery照片图像剪裁插件Jcrop中文翻译详解 « 张鑫旭-鑫空间-鑫生活 Ajax Upload多文件上传插件翻译及中文演示 « 张鑫旭-鑫空间-鑫生活 告别图片—使用字符实现兼容性的圆角尖角效果beta版 « 张鑫旭-鑫空间-鑫生活 大小不固定的图片、多行文字的水平垂直居中 « 张鑫旭-鑫空间-鑫生活 jQuery boxy弹出层对话框插件中文演示及讲解 « 张鑫旭-鑫空间-鑫生活 JavaScript实现新浪微博文字放大显示动画效果 « 张鑫旭-鑫空间-鑫生活 翻译:让网络更快一些——最小化浏览器中的回流(reflow) « 张鑫旭-鑫空间-鑫生活 jQuery之addClasas与removeClass使用实例 « 张鑫旭-鑫空间-鑫生活 关于Google圆角高光高宽自适应按钮及其拓展 « 张鑫旭-鑫空间-鑫生活 JavaScript实现图片幻灯片滚动播放动画效果 « 张鑫旭-鑫空间-鑫生活 jQuery-鼠标经过显示大图并跟随鼠标效果插件 « 张鑫旭-鑫空间-鑫生活 关于gif图片(或png8)杂边锯齿的问题 « 张鑫旭-鑫空间-鑫生活 对html与body的一些研究与理解 « 张鑫旭-鑫空间-鑫生活 鲜为人知的一个解决兼容性问题的利器——小数 « 张鑫旭-鑫空间-鑫生活 搜狐白社会似iphone短信对话框效果的优化 « 张鑫旭-鑫空间-鑫生活 关于文字内容溢出用点点点(…)省略号表示 « 张鑫旭-鑫空间-鑫生活 jquery之append与insertBefore使用实例 « 张鑫旭-鑫空间-鑫生活 jQuery之图片关联伸缩效果 « 张鑫旭-鑫空间-鑫生活 jquery之图片左右切换动画效果 « 张鑫旭-鑫空间-鑫生活 jQuery之replace字符串替换实现不同尺寸图片切换 « 张鑫旭-鑫空间-鑫生活 jQuery-实现图片的放大镜显示效果 « 张鑫旭-鑫空间-鑫生活 jQuery-单击文字或图片内容放大显示效果插件 « 张鑫旭-鑫空间-鑫生活 reflection.js-实现图片投影倒影效果js插件 « 张鑫旭-鑫空间-鑫生活 jQuery-很酷的弹出层效果js插件 « 张鑫旭-鑫空间-鑫生活 复选框单选框与文字对齐问题的研究与解决 « 张鑫旭-鑫空间-鑫生活 css margin的相关属性,问题及应用 « 张鑫旭-鑫空间-鑫生活 IE6下png背景不透明问题的综合拓展 « 张鑫旭-鑫空间-鑫生活
漫谈js自定义事件、DOM/伪DOM自定义事件 « 张鑫旭-鑫空间-鑫生活
张鑫旭,zhangxinxu · 2012-04-01 · via 张鑫旭-鑫空间-鑫生活

by zhangxinxu from http://www.zhangxinxu.com
本文地址:http://www.zhangxinxu.com/wordpress/?p=2330

一、说明、引言

我JS还是比较薄弱的,本文的内容属于边学边想边折腾的碎碎念,可能没什么条理,可能有表述不准确的地方,可能内容比较拗口生僻。如果您时间紧迫,或者JS造诣已深,至此您就可以点击右侧广告(木有?则RSS或盗版)然后撤了。

事件是个大课题,真要从断奶开始讲起的话,可以写个12期的连载。关于JS事件的文章(类似DOM/BOM事件模型,IE与其他浏览器事件差异,DOM1/DOM2事件定义等)落叶般随处可见。熟豆子反复炒一点意思都没有,因此,这里谈谈自己感兴趣的自定义事件以及周边。

所谓自定义事件,就是有别于有别于带有浏览器特定行为的事件(类似click, mouseover, submit, keydown等事件),事件名称可以随意定义,可以通过特定的方法进行添加,触发以及删除。

二、JS自定义事件

循序渐进便于接收。慢慢来~~

先看个简单的事件添加的例子:

element.addEventListener("click", function() {
    // 我是临时工
});

这是个简单的为DOM元素分配事件处理函数的方法(IE 不支持),有别于:

element.onclick = function() {
   // 我是临时工 
};

addEventListener()可以为元素分配多个处理函数(而非覆盖),因此,我们可以继续:

element.addEventListener("click", function() {
    // 我是二代临时工
});

然后,当element被click(点击)的时候,就会连续触发“临时工”和“二代临时工”函数。

抽象→具象→本质→数据层
你有没有觉得这种行为表现有点类似于往长枪里面塞子弹(add),(扣动扳手 – click)发射的时候按照塞进去的顺序依次出来。这种行为表现为我们实现自定义事件提供了思路:我们可以定义一个数组,当添加事件的时候,我们push进去这个事件处理函数;当我们执行的时候,从头遍历这个数组中的每个事件处理函数,并执行。

当多个事件以及对应数据处理函数添加后,我们最终会得到一个类似下面数据结构的对象:

_listener = {
    "click": [func1, func2],
    "custom": [func3],
    "defined": [func4, func5, func6]
}

因此,如果我们脱离DOM, 纯碎在数据层面自定义事件的话,我们只要以构建、遍历和删除_listener对象为目的即可。

函数式实现
还是那句话,循序渐进,我们先看看函数式的实现(只展示骨干代码):

var _listener = {};
var addEvent = function(type, fn) {
    // 添加
};
var fireEvent = function(type) {
    // 触发
};
var removeEvent = function(type, fn) {
    // 删除
};

上面的代码虽然显得比较初级,但是目的亦可实现。例如:

addEvent("alert", function() {
    alert("弹出!");
});

// 触发自定义alert事件
fireEvent("alert");

但是,函数式写法缺点显而易见,过多暴露在外的全局变量(全局变量是魔鬼),方法无级联等。这也是上面懒得显示完整代码的原因,略知即可。

字面量实现
众所周知,减少全局变量的方法之一就是使用全局变量(其他如闭包)。于是,我们稍作调整(代码较长,为限制篇幅,使用了滚动条,完整显示点击这里 – JS交互, RSS中无效果):

var Event = {
    _listeners: {},    
    // 添加
    addEvent: function(type, fn) {
        if (typeof this._listeners[type] === "undefined") {
            this._listeners[type] = [];
        }
        if (typeof fn === "function") {
            this._listeners[type].push(fn);
        }    
        return this;
    },
    // 触发
    fireEvent: function(type) {
        var arrayEvent = this._listeners[type];
        if (arrayEvent instanceof Array) {
            for (var i=0, length=arrayEvent.length; i<length; i+=1) {
                if (typeof arrayEvent[i] === "function") {
                    arrayEvent[i]({ type: type });    
                }
            }
        }    
        return this;
    },
    // 删除
    removeEvent: function(type, fn) {
    	var arrayEvent = this._listeners[type];
        if (typeof type === "string" && arrayEvent instanceof Array) {
            if (typeof fn === "function") {
                // 清除当前type类型事件下对应fn方法
                for (var i=0, length=arrayEvent.length; i<length; i+=1){
                    if (arrayEvent[i] === fn){
                        this._listeners[type].splice(i, 1);
                        break;
                    }
                }
            } else {
                // 如果仅仅参数type, 或参数fn邪魔外道,则所有type类型事件清除
                delete this._listeners[type];
            }
        }
        return this;
    }
};

使用类似下面:

Event.addEvent("alert", function() {
    alert("弹出!");
});

// 触发自定义alert事件
Event.fireEvent("alert");

您可以狠狠地点击这里:JS自定义事件字面量书写demo

默认页面document通过Event.addEvent()绑定了两个自定义的alert事件,因此,此时您点击页面的空白区域(非按钮与示例代码区域),就会有如下图所示的连续两个alert框:

demo页面还有两个按钮,用来清除已经绑定的alert事件。第一个按钮清除所有alert事件,而点击第二个按钮清除第一个alert事件。例如我们点击第二个按钮:

清除完毕后再点击页面的空白区域, 您会发现只会弹出“第二个弹出!”字样的弹出框了。这表明,第一个绑定自定义事件被remove掉了。

字面量实现虽然减少了全局变量,但是其属性方法等都是暴露而且都是唯一的,一旦某个关键属性(如_listeners)不小心在某事件处reset了下,则整个全局的自定义事件都会崩溃。因此,我们可以进一步改进,例如,使用原型链继承,让继承的属性(如_listeners)即使出问题也不会影响全局。

原型模式实现
代码如下(相比上面增加了addEvents, fireEvents, removeEvents多事件绑定、执行与删除方法,篇幅较长,增加滚动限高,点击这里完整展示 – JS交互, RSS中无效果)(一堆代码看得头大,建议直接跳过):

var EventTarget = function() {
    this._listener = {};
};

EventTarget.prototype = {
    constructor: this,
    addEvent: function(type, fn) {
        if (typeof type === "string" && typeof fn === "function") {
            if (typeof this._listener[type] === "undefined") {
                this._listener[type] = [fn];
            } else {
                this._listener[type].push(fn);    
            }
        }
        return this;
    },
    addEvents: function(obj) {
        obj = typeof obj === "object"? obj : {};
        var type;
        for (type in obj) {
            if ( type && typeof obj[type] === "function") {
                this.addEvent(type, obj[type]);    
            }
        }
        return this;
    },
    fireEvent: function(type) {
        if (type && this._listener[type]) {
            var events = {
                type: type,
                target: this    
            };
            
            for (var length = this._listener[type].length, start=0; start<length; start+=1) {
                this._listener[type][start].call(this, events);
            }
        }
        return this;
    },
    fireEvents: function(array) {
        if (array instanceof Array) {
            for (var i=0, length = array.length; i<length; i+=1) {
                this.fireEvent(array[i]);
            }
        }
        return this;
    },
    removeEvent: function(type, key) {
        var listeners = this._listener[type];
        if (listeners instanceof Array) {
            if (typeof key === "function") {
                for (var i=0, length=listeners.length; i<length; i+=1){
                    if (listeners[i] === key){
                        listeners.splice(i, 1);
                        break;
                    }
                }
            } else if (key instanceof Array) {
                for (var lis=0, lenkey = key.length; lis<lenkey; lis+=1) {
                    this.removeEvent(type, key[lenkey]);
                }
            } else {
                delete this._listener[type];
            }
        }
        return this;
    },
    removeEvents: function(params) {
        if (params instanceof Array) {
            for (var i=0, length = params.length; i<length; i+=1) {
                this.removeEvent(params[i]);
            }    
        } else if (typeof params === "object") {
            for (var type in params) {
                this.removeEvent(type, params[type]);    
            }
        }
        return this;    
    }
};

啰哩吧嗦的代码直接跳过,其实上面代码跟字面量方法相比,就是增加了下面点东西:

var EventTarget = function() {
    this._listener = {};
};

EventTarget.prototype = {
    constructor: this,
    // .. 完全就是字面量模式实现脚本
};

然后,需要实现自定义事件功能时候,先new构造下:

var myEvents = new EventTarget();
var yourEvents = new EventTarget();

这样,即使myEvents的事件容器_listener跛掉,也不会污染yourEvents中的自定义事件(_listener安然无恙)。

您可以狠狠地点击这里:原型模式下的JS自定义事件demo

从demo右半区域的源代码展示可以看出如何使用addEvents, fireEvents方法同时添加和触发多个自定义事件的。

//zxx: 下面为广告~~注意不要勿点~~嘻嘻~~

三、DOM自定义事件

我们平常所使用的事件基本都是与DOM元素相关的,例如点击按钮,文本输入等,这些为自带浏览器行为事件,而自定义事件与这些行为无关。例如:

element.addEventListener("alert", function() {
    alert("弹出!");
});

这里的alert就属于自定义事件,后面的function就是自定义事件函数。而这个自定义事件是直接绑定在名为element的DOM元素上的,因此,这个称之为自定义DOM事件。

由于浏览器的差异,上面的addEventListener在IE浏览器下混不来(attachEvent代替),因此,为了便于规模使用,我们需要新的添加事件方法名(合并addEventListenerattachEvent),例如addEvent, 并附带事件触发方法fireEvent, 删除事件方法removeEvent,(命名均参考自MooTools库)。

如何直接在DOM上扩展新的事件处理方法,以及执行自定义的事件呢?

如果不考虑IE6/7浏览器,我们可以直接在DOM上进行方法扩展。例如添加个addEvent方法:

HTMLElement.prototype.addEvent = function(type, fn, capture) {
    var el = this;
    if (window.addEventListener) {
        el.addEventListener(type, function(e) {
            fn.call(el, e);
        }, capture);
    } else if (window.attachEvent) {
        el.attachEvent("on" + type, function(e) {
            fn.call(el, e);
        });
    } 
};

//zxx: 上面代码中的HTMLElement表示HTML元素。以一个<p>标签元素举例,其向上寻找原型对象用过会是这样:HTMLParagraphElement.prototypeHTMLElement.prototypeElement.prototypeNode.prototypeObject.prototypenull。这下您应该知道HTMLElement所处的位置了吧,上述代码HTMLElement直接换成Element也是可以的,但是会让其他元素(例如文本元素)也扩展addEvent方法,有些浪费了。

这样,我们就可以使用扩展的新方法给元素添加事件了,例如一个图片元素:

elImage.addEvent("click", function() {
    alert("我是点击图片之后的弹出!");
});

由于IE6, IE7浏览器的DOM水平较低,无法直接进行扩展,因此,原型扩展的方法在这两个浏览器下是行不通的。要想让这两个浏览器也支持addEvent方法,只能是页面载入时候遍历所有DOM,然后每个都直接添加addEvent方法了。

var elAll = document.all, lenAll = elAll.length;
for (var iAll=0; iAll<lenAll; iAll+=1) {
    elAll[iAll].addEvent = function(type, fn) {
        var el = this;
        el.attachEvent("on" + type, function(e) {
            fn.call(el, e);
        });
    };
}

您可以狠狠地点击这里:基于DOM扩展自定义方法demo

点击demo页面张含韵小姐年轻时候相片,就会有该图片alt属性值。

测试代码如下(demo页面有代码完整展示):

<img id="image" loading="lazy" src="https://image.zhangxinxu.com/image/study/s/s256/mm1.jpg" alt="年轻的张含韵" />

document.getElementById("image").addEvent("click", function() {
    alert("这是:" + this.alt);    
});

只能点到为止
直接在DOM上进行事件方法扩展其实是个糟糕的做法,因此,这里我并没有对自定义事件做进一步深入探讨(这个下一部分会讲)。

基于DOM扩展缺点有:缺少标准无规律、提高冲突可能性、性能以及浏览器支持。
扩展名字任意命,很有可能就会与未来DOM浏览器本身支持的方法相互冲突;扩展无规律,很有可能出现A和B同名不同功能的扩展而造成冲突;IE6-7浏览器下所有扩展都要通过遍历支持,其性能开销可想而知;另外IE8对DOM扩展的支持并不完整,例如其支持Element.prototype,却没有HTMLElement.prototype.

虽然我从事的站点就是基于MooTools库的,但是,我对MooTools库基于DOM扩展方法的做法是不支持的。相反,我更亲近jQuery库的做法,也就是下面要讲的“伪DOM自定义事件”。

四、伪DOM自定义事件

这里的“伪DOM自定义事件”是自己定义的一个名词,用来区分DOM自定义事件的。例如jQuery库,其是基于包装器(一个包含DOM元素的中间层)扩展事件的,既与DOM相关,又不直接是DOM,因此,称之为“伪DOM自定义事件”。

//zxx: 下面即将展示的代码目的在于学习与认识,要想实际应用可能还需要在细节上做些调整。例如,下面测试的包装器仅仅只是包裹DOM元素,并非选择器之类;$符号未增加冲突处理,且几个重要方法都暴露在全局环境中,没有闭包保护等。

原型以及new函数构造不是本文重点,因此,下面这个仅展示:

var $ = function(el) {
    return new _$(el);    
};
var _$ = function(el) {
    this.el = el;
};
_$.prototype = {
    constructor: this,
    addEvent: function() {
        // ...
    },
    fireEvent: function() {
        // ...
    },
    removeEvent: function() {
        // ...
    }
}

于是我们就可以使用类似$(dom).addEvent()的语法为元素添加事件了(包括不包含浏览器行为的自定义事件)。

自定义事件的添加
如果只考虑事件添加,我们的工作其实很简单,根据支持情况,addEventListenerattachEvent方法分别添加事件(attachEvent方法后添加事件先触发)即可:

addEvent: function(type, fn, capture) {
    var el = this.el;
    if (window.addEventListener) {
        el.addEventListener(type, fn, capture);        
    } else if (window.attachEvent) {
        el.attachEvent("on" + type, fn);
    }
    return this;
}

显然,事情不会这么简单,有句古话叫做“上山容易下山难”,自定义事件添加容易,但是如何触发它们呢?——考虑到自定义事件与浏览器行为无关,同时浏览器没有直接的触发事件的方法。

自定义事件的触发
又是不可避免的,由于浏览器兼容性问题,我们要分开说了,针对标准浏览器和IE6/7等考古浏览器。

1. 对于标准浏览器,其提供了可供元素触发的方法:element.dispatchEvent(). 不过,在使用该方法之前,我们还需要做其他两件事,及创建和初始化。因此,总结说来就是:

document.createEvent()
event.initEvent()
element.dispatchEvent()

举个板栗:

$(dom).addEvent("alert", function() {
    alert("弹弹弹,弹走鱼尾纹~~");
});

// 创建
var evt = document.createEvent("HTMLEvents");
// 初始化
evt.initEvent("alert", false, false);

// 触发, 即弹出文字
dom.dispatchEvent(evt);

createEvent()方法返回新创建的Event对象,支持一个参数,表示事件类型,具体见下表:

参数 事件接口 初始化方法
HTMLEvents HTMLEvent initEvent()
MouseEvents MouseEvent initMouseEvent()
UIEvents UIEvent initUIEvent()

关于createEvent()方法我自己了解也不是很深入,不想滥竽充数,误人子弟,所以您有疑问我可能作答不了,希望对熟知该方法的人可以做进一步的解释说明(例如事件接口与document关系,UIEvent是什么东西等)。

initEvent()方法用于初始化通过DocumentEvent接口创建的Event的值。支持三个参数:initEvent(eventName, canBubble, preventDefault). 分别表示事件名称,是否可以冒泡,是否阻止事件的默认操作。

dispatchEvent()就是触发执行了,dom.dispatchEvent(eventObject), 参数eventObject表示事件对象,是createEvent()方法返回的创建的Event对象。

2. 对于IE浏览器,由于向下很多版本的浏览器都不支持document.createEvent()方法,因此我们需要另辟蹊径(据说IE有document.createEventObject()event.fireEvent()方法,但是不支持自定义事件~~)。

IE浏览器有不少自给自足的东西,例如下面要说的这个"propertychange"事件,顾名思意,就是属性改变即触发的事件。例如文本框value值改变,或是元素id改变,或是绑定的事件改变等等。

我们可以利用这个IE私有的东西实现自定义事件的触发,大家可以先花几分钟想想……

// zxx: 假设几分钟已经过去了……

大家现在有思路了没?其实说穿了很简单,当我们添加自定义事件的时候,顺便给元素添加一个自定义属性即可。例如,我们添加自定义名为"alert"的自定义事件,顺便我们可以对元素做点小手脚:

dom.evtAlert = "2012-04-01";

再顺便把自定义事件fn塞到"propertychange"事件中:

dom.attachEvent("onpropertychange", function(e) {
    if (e.propertyName == "evtAlert") {
        fn.call(this);
    }
});

这个,当我们需要触发自定义事件的时候,只要修改DOM上自定义的evtAlert属性的值即可:

dom.evtAlert = Math.random();	// 值变成随机数

此时就会触发dom上绑定的onpropertychange事件,又因为修改的属性名正好是"evtAlert", 于是自定义的fn就会被执行。这就是IE浏览器下事件触发实现的完整机制,应该说讲得还是蛮细的。

自定义事件的删除
与触发事件不同,事件删除,各个浏览器都提供了对于的时间删除方法,如removeEventListenerdetachEvent。不过呢,对于IE浏览器,还要多删除一个事件,就是为了实现触发功能额外增加的onpropertychange事件:

dom.detachEvent("onpropertychange", evt);

大综合
结合上面所有论述与展示,我们可以得到类似下面的完整代码(为限制篇幅,滚动定高,想查看完整代码推荐去原demo,或是点击这里完整显示— js交互,RSS中无效果。):

var $ = function(el) {
    return new _$(el);    
};
var _$ = function(el) {
    this.el = (el && el.nodeType == 1)? el: document;
};
_$.prototype = {
    constructor: this,
    addEvent: function(type, fn, capture) {
        var el = this.el;
        if (window.addEventListener) {
            el.addEventListener(type, fn, capture);
            var ev = document.createEvent("HTMLEvents");
            ev.initEvent(type, capture || false, false);
            
            if (!el["ev" + type]) {
                el["ev" + type] = ev;
            }  
        } else if (window.attachEvent) {
            el.attachEvent("on" + type, fn);    
            if (isNaN(el["cu" + type])) {
                // 自定义属性
                el["cu" + type] = 0; 
            }   
            var fnEv = function(event) {
                if (event.propertyName == "cu" + type) { fn.call(el); }
            };
            el.attachEvent("onpropertychange", fnEv);     
            if (!el["ev" + type]) {
                el["ev" + type] = [fnEv];
            } else {
                el["ev" + type].push(fnEv);    
            }
        }
        return this;
    },
    fireEvent: function(type) {
        var el = this.el;
        if (typeof type === "string") {
            if (document.dispatchEvent) {
                if (el["ev" + type]) {
                    el.dispatchEvent(el["ev" + type]);
                }
            } else if (document.attachEvent) {
                el["cu" + type]++;
            }    
        }    
        return this;
    },
    removeEvent: function(type, fn, capture) {
        var el = this.el;
        if (window.removeEventListener) {
            el.removeEventListener(type, fn, capture || false);
        } else if (document.attachEvent) {
            el.detachEvent("on" + type, fn);
            var arrEv = el["ev" + type];
            if (arrEv instanceof Array) {
                for (var i=0; i<arrEv.length; i+=1) {
                    el.detachEvent("onpropertychange", arrEv[i]);
                }
            }
        }
        return this;    
    }
};

您可以狠狠地点击这里:JS DOM自定义事件demo

demo页面中的的张含韵小姐图片上通过级联形式联系添加了三个事件(一个是包含浏览器行为的click事件,还有两个是自定义不含行为的alert事件):

$(elImage)
    .addEvent("click", funClick);
    .addEvent("alert", funAlert1)
    .addEvent("alert", funAlert2);

funClick方法中有等同下面脚本:

$(e.target).fireEvent("alert");

因此,点击图片,才会出现三个弹出框:用户点击图片 → 执行funClick → 第一个弹框 → 执行fireEvent → 触发自定义"alert"事件 → 连续两个"alert"事件弹框

当点击图片下面的按钮清除掉自定义"alert"事件后,再点击图片就只有一个弹出咯(funAlert1funAlert2提前回家扫墓去了)!

五、清明节前的结语

鑫表情

明天回家,很显然,我要钓鱼钓死在河边上。

时间等客观原因,本文展示的些脚本并未做非常详尽严谨的测试,因此,不建议直接Copy到实际项目中应用,更多旨在相互交流与学习。例如在IE浏览器下,最后的“伪DOM自定义事件”,click事件通过点击触发时的事件类型(event.type)是click, 但是通过fireEvent触发的时候事件类型是propertychange, 这些细节在测试学习的时候都是可以忽略的,但是要是实际应用,这都是需要完善的。

本想以很通俗易懂的语言阐述我想表达的内容,但是,现在回过头看看,做得并不好,术语,啰嗦的话语还是显得多了点,这方面的功力还需要加强,或许是本身理解不透彻的缘故,无法驾驭自然无法语言通俗化。

虽说自己JS方面的学习比两年前要好多了(那个时候连addEventListenerattachEvent放在一起干嘛的都不清楚),但是心里清楚的很,JS还是很薄弱的,跟真正优秀的JS开发人员相比,要积累的还有很多。什么时候能够像看有色小说一样把《JavaScript语言精粹》一书读下来,恩,估计可以有脸得瑟得瑟了~~

本文涉及的一些知识点欢迎补充提点,有表述不准确的地方欢迎指正。

最后,祝大家清明节快乐!额?怎么这句话怪怪的——上坟一般快乐不起来吧~~那大家祝我清明回家钓鱼大丰收,大爆箱!!哈哈!!

本文为原创文章,转载请注明来自张鑫旭-鑫空间-鑫生活[http://www.zhangxinxu.com]
本文地址:http://www.zhangxinxu.com/wordpress/?p=2330

(本篇完)