Web 开发后端缓存思路

发布于 1 年前 作者 alsotang 9167 次浏览 来自 分享

Web 应用是个典型的 io 数据流,

QQ20150405-4.png

首先,浏览器发来一个 input,服务器获取之后,做一些查询或者计算,然后把生成的 output 返回给浏览器。

这些查询或计算,还会有衍生的子 io 流。

缓存的目的就是让把 input 变成一个 key,在条件允许的情况下,跳过计算,直接生成 output。在主流程中,或子流程中。

数据查询缓存

resource: http://robbinfan.com/blog/3/orm-cache

n + 1 问题

n + 1 问题是 orm 竟然被诟病的地方。什么是 n + 1 问题呢? 比如一个用户,它写了 20 篇博客。当我们查询这个用户的首页时,需要列出他的所有博客。 “高效”的思路是使用一个 join 语句,把 user 表和 blog 表做 join,然后一条语句取出所有想要的字段。 而 orm,会先取出 user 的记录,再做 20 次遍历,分别用 20 条语句取出他所有的博客。 按照 robbin 的说法,join 语句的结果很难被缓存利用,因为它发生的场景太过特定。 但如果使用 orm,按照 n + 1 的方式取数据。由于数据缓存的粒度比较小,缓存的命中率得到了提高。 首先,orm 内置的缓存一般会在同一个连接中,缓存同一 sql 语句的结果;其次,数据库的缓存会记下特定 sql 语句的对应的结果,当再次收到相同语句时,数据库不必进行扫描,可以直接 O(1) 复杂度地返回缓存结果。

robbin 认为,在这种情况下,n + 1 的查询反而因为有效利用了缓存,而比 join 语句更快。

robbin 得出了这样的结论:即使不使用对象缓存,ORM的n+1条SQL性能仍然很有可能超过SQL的大表关联查询,而且对数据库磁盘IO造成的压力要小很多

缓存层加入

利用 redis 或者 memcached,这个话题 google 一下会有很多。

json to orm 问题

CNode 使用的是 mongoose 这个 odm 来访问 mongodb。在 mongoose 的 model 中,我们定义了不少【虚拟属性】,所谓虚拟属性,就是指:一个 user 实例,它有 first_namelast_name 字段,当我们定义一个名字为 full_name 的虚拟属性时,user.full_name 会根据定义的函数自动拼接 first_name 和 last_name。也就是面向对象编程中的 getter 方法。

当缓存一个 mongoose 取出的文档到 redis 时,我们会将它先装换成 json,再以字符串形式存入。 再次取出并 JSON.parse 的时候,会发现 mongoose model 定义的虚拟属性全都被丢弃了。所以这时,需要重新把这个 json 传入 model 初始化一次,得到一个 model 实例。这样,我们就恢复了原来内存中的那个 model 实例了。

数据写入缓存:

在数据库与服务端之间利用 redis

这是一个很常见的场景。比如文章的浏览数,每次文章被浏览时,浏览数都 +1。如果每次都回写数据库,不免数据量太大。加上数据库看似简单,其实做了不少关于一致性(请看官了解一下所谓【一致性】,【base】,【acid】)的检查。 而同时,浏览数并不要求保证一致性,只要大概准确就行了。 所以这时候,我们可以先将浏览数写入 redis,满足一定条件后,再回写数据库。 比如,在 controller 中,让每次浏览都在 redis 上 +1,+1 完成后,检查浏览数是否除以 10 后余数为 0(count % 10 === 0),是的话,则回写数据库,并将缓存置为 0。

缓存过期策略

可以通过过期时间来控制内容新鲜期

那么就设置设缓存过期时间。比如在一个网站上,总会有一些每日之星用户,或者今日推荐文章。

这些内容的新鲜期都很长,比如每日之星的数据,如果 20 分钟更新一次,用户也不会有异议。那么,我们在查询出这些用户后,可以将结果集存入缓存中,并设置过期时间为 20 分钟。待自动失效后,再重新查询。

无法通过过期时间来控制内容新鲜期

这时,又有两个策略了。一个是【主动过期】策略,一个是【被动过期】策略。比如想要缓存一篇文章的内容 HTML,但文章的页面中包含了评论信息。一些老文章被大量访问而无人添加评论时,缓存的效果杠杠的。但一些近期文章会被用户添加评论, 我们无法判断用户何时会添加评论,所以无法得到一个最佳实践的文章过期时间。

主动过期

顾名思义,主动地去 delete 缓存。还是上面的文章例子。我们可以在评论的 model 中,设置一个回调逻辑。每当评论被更新时,同时去删除评论所对应的文章的缓存内容。

被动过期

被动过期也不是完全不需要回调逻辑,只是相对主动过期来说。它不必理解缓存层的存在。

还是上面的例子,当我们缓存一个文章页面时,不仅以文章的 id 为 cache key,还在 cache key 中拼入文章的 update_at 字段。 当评论更新时,让评论去 touch 一下对应的文章,更新文章的最后修改日期。那么当用户再次访问文章时,由于 cache key 变动,过期的内容就不会被展现,从而实现了被动过期。

同样的例子还有,一篇文章是以 markdown 写成,每次输出的时候,都要进行 markdown 渲染,这是个耗时操作。于是我们可以将 'markdown_result_' + artical.id + artical.updated_at 作为 key,来缓存 markdown 的渲染结果。每当文章更新时,被动地废弃旧有的缓存结果。

当然,这里不能说主动过期好,还是被动过期好。细心的看客也许在上面两个例子中发现了问题,那就是,当文章的内容没有进行改变,而评论添加时,文章却要重新渲染 markdown,可渲染结果其实是一样的。

HTML 片段缓存

resource: https://ruby-china.org/topics/21488

QQ20150405-5.png

CNode 为例,我简单地划分了 1 2 3 4 四个部分。每个部分在逻辑上都是一个相对独立的 setion,它们使用不同的数据进行渲染。在代码组织上,这些部分也是属于不同的 view 文件来负责。

4 的部分就是我们所说的,可以通过过期时间来管理的片段。这个部分 10 分钟更新一次没有问题。

3 的部分类似上面 markdown 的例子,渲染是耗时的,而数据是经常不变的。所以我们可以通过类似 'user_profile' + user.id + user.updated_at 的 cache key 来将其缓存。

而 1 和 2 的部分,就类似上面【被动过期】的例子。1 中,不仅有帖子的标题,还有帖子的作者信息,还有帖子的最后回复者信息,粗略一算,这都是 3 条查询。如果能缓存起来,那是大大滴有用。而 2,包含了所有 1 类似的部分,也可以被缓存。但如果 1 动了,2 怎么办?所以在缓存 2 时,我们可以使用所有 1 中最新的那个帖子的更新时间来作为 key,当有帖子更新后,更新时间对不上,缓存就被动过期了。

如果是个大型站点,1 的内容频繁动,那么会导致 2 的缓存命中率很低。这时,从业务上,我们判断,主页的新鲜期是可以在 5s 内不变的。这时,缓存策略可以改为,最新的帖子的更新时间,如果离现在的时间不超过 5s,则返回之前缓存的内容。我们一下就从【被动过期】的策略,变回【过期时间】的策略了。

所以具体采用什么策略,根据业务场景可以灵活选择。

【被动过期】策略时,切记要让上层片段的缓存 key 可以被下层 touch 更新。【过期时间】策略时,需要我们判断一下内容的新鲜期。

并且有一点比较深入的知识点是,不同的 touch 策略,会对缓存命中率产生影响。这个知识点请参照本小节 resource 部分的链接去看看 Tower 在面对这个情况时的方案。

如果你要问我 CNode 在片段缓存上是怎么选择的,我可以负责任并潇洒地告诉你:目前没有这方面的缓存~~~~

说起来啊,一是访问量比较小,懒得做。二是,从技术上说,渲染是同步的,而在 Node.js 中,数据查询是异步的。我思考了一下,做这个片段缓存不是简单的事情。而 Rails 中做起来就简单多了,虽然玩 Node 的人总是觉得 Node 可以原生异步并发取数据是一件优越的事情。但同步 io 模型在这个地方带来的好处就是【惰性求值】 。Rails 在渲染时,可以判断一下到底是【查询 + 渲染】还是【直接取缓存】。而 Node 由于异步查询和同步渲染之间的冲突,要解决这个问题,必须有个方便地支持异步渲染的模板方案出现。

last_modified 和 etag

resource: http://robbinfan.com/blog/13/http-cache-implement

这节我们讨论的是静态页面在浏览器中的缓存思路。所以不是 max-age 和 cache-control 那套针对静态资源的方案,而是 last_modified 和 etag 这一套。

上面的内容,一直在说数据库,缓存数据库。但有一点不可忽视的是,浏览器中其实也缓存了我们页面的副本,这部分的缓存,也应该有效地利用起来。 最简单利用方式,就是让服务器判断一下最终页面生成的 etag 与浏览器 header 中传来的 etag 是否相同的,相同的话,则返回 304,省去网络传输的带宽开销。

注意,最简单的方式是判断最终内容生成的 etag!其实我们可以自定义 etag。在这里,etag 也可以理解成一定意义上上述的 cache key,只是这回,储存介质变成了用户的浏览器。

还是上面那个文章内容页面的例子,我们文章页面由 文章内容 + 评论 内容决定是否缓存。这时,我们可以把文章内容的更新时间和最新评论的更新时间拼成一个 etag,返回给用户。下次用户再访问时,如果 etag 对得上,服务端根本都不需要再去缓存数据库中取 HTML 片段数据,直接告诉用户一个 304,【内容与上次一样,没变化】。这时浏览器就直接从自己的缓存中取出页面进行展示了。既节省了宽带占用,又节省了查询开销。

etag as cookie

这里说点题外话,etag 在一定意义上是可以拿来当 cookie 用的。首先我们要了解,浏览器针对每一个 url(包括 querystring 部分)都可以存储一个 etag 值。

比如我是一个广告服务商,我的广告页面是 https://cnodejs.org/ads。每当不同的用户访问这个页面时,我都根据大数据黑魔法定位到这个匿名用户到底是谁,然后返回他感兴趣的内容。可如果用户禁用了 cookie 的话,我该怎么定位用户呢?这时候可以使用 etag。每当用户不带 etag 访问时,都生成一个不冲突的 etag 给它,那么下次他再访问我 url 时,etag 就回来了。

OK,结束了,结尾语是:Rails 社区代表 Web 开发世界的最先进生产力。

PHP关于VC11,VC9,VC6以及Thread Safe和Non Thread Safe版本选择的问题

标签: php

5748人阅读 评论(0) 收藏 举报
分类:

这里是我在搭建php环境时收集的资料供大家参考:

现在PHP官网上下载PHP安装包都有VC11或VC9的字样,这是什么含义,我们应该下载哪种安装包更好呢?其实PHP官网给出了答案:

VC6版本是使用Visual Studio 6编译器编译的,如果你的PHP是用Apache来架设的,那你就选择VC6版本。(现在PHP已经没有VC6了)。

VC9意思就是该版本PHP是用VisualStudio2008编译的,而VC11则是用VisualStudio2012编译的。这意味着

如果你下载的是VC9版本的,就需要先安VisualC++RedistributableforVisualStudio2008SP1,

如果你下载的是VC11版本的,就需要先安VisualC++RedistributableforVisualStudio2012.

搭建php首先看操作系统的版本,如果是Windows的在这里下:http://windows.php.net/download/

操作系统是32位还是64位?如果是32位的,就选择带“x86”的版本,如果是64位的,就选择带“x64”的版本。然后看WEB服务器是什么?如果是IIS系列,就选择带“Non Thread Safe”的版本,如果是Apache httpd系列,就选择带“Thread Safe”的版本。如果是Apache httpd还要看这个二进制版本是怎么编译的,有三种编译类型,分别是:VC6、VC9、VC11(分别代表Visual C++ 6、Visual C++ 2008、Visual C++ 2012)。这里就有麻烦了:PHP官方的最新版本5.5已经用VC11编译,5.4是用VC9编译的。可是Apache httpd的最新版本呢,至今还是用VC6编译。这意味着一个惊人的事实:Apache httpd的官方版本和PHP的官方版本竟然是无法兼容的!那现有的Windows下的Apache+PHP是怎么搞的呢?可以自己用VC11编译Apache httpd,但这太折腾了,PHP官方建议下载第三方打包好的Apache httpd版本,推荐了这个站点:http://www.apachelounge.com/download/,需要说明一点:这个VC11不支持XP,如果你像我一样有XP怀旧症,最高只能升级到5.4.24,5.5是用VC11编译的,而VC11不支持XP和2003。

如果是Linux系的在这里下载PHP的源码:http://cn2.php.net/downloads.php

与上面Windows版不同的是,这里下载到的不能直接用,只是PHP的源码,要安装需要自己编译,要编译就需要安装gcc、make等一堆编译软件。看到文档里说./configure、make、make install,针对的就是PHP的源码版本而言。

自己编译php就更麻烦了,依赖的库一大堆,等着人掉进去的坑也有一大堆,我将在另一篇文章里说。如果嫌麻烦,可以找已经编译好的版本,具体的说就是各个发行版的官方的软件库。下面以流行的ubuntu系和centos系为例分别说明;

deb包管理式(ubuntu、Debian)

用apt-cache search php命令可以搜出很多结果,我们需要是“php5”,完整的命令是:apt-get install php5

这显然不是永远适用的,因为命令中有版本号5,未来PHP6是什么情况谁也不知道。

如果想在shell中安装一个命令行的php引擎,就安装这个包

apt-getinstall php5-cli

RPM包管理(Fedora、RedHat、SUSE、CentOS)

yuminstall php

如果是php命令行,就安装yuminstall php-cli

如何选择Thread Safe和Non ThreadSafe版本?

    Windows版的PHP从版本5.2.1开始有ThreadSafe(线程安全)和None Thread Safe(NTS,非线程安全)之分,这两者不同在于何处?到底应该用哪种?这里做一个简单的介绍。

  从2000年10月20日发布的第一个Windows版的PHP3.0.17开始的都是线程安全的版本,这是由于与Linux/Unix系统是采用多进程的工作方式不同的是Windows系统是采用多线程的工作方式。如果在IIS下以CGI方式运行PHP会非常慢,这是由于CGI模式是建立在多进程的基础之上的,而非多线程。一般我们会把PHP配置成以ISAPI的方式来运行,ISAPI是多线程的方式,这样就快多了。但存在一个问题,很多常用的PHP扩展是以Linux/Unix的多进程思想来开发的,这些扩展在ISAPI的方式运行时就会出错搞垮IIS。因此在IIS下CGI模式才是 PHP运行的最安全方式,但CGI模式对于每个HTTP请求都需要重新加载和卸载整个PHP环境,其消耗是巨大的。

  为了兼顾IIS下PHP的效率和安全,微软给出了FastCGI的解决方案。FastCGI可以让PHP的进程重复利用而不是每一个新的请求就重开一个进程。同时FastCGI也可以允许几个进程同时执行。这样既解决了CGI进程模式消耗太大的问题,又利用上了CGI进程模式不存在线程安全问题的优势。

     先从字面意思上理解,Thread Safe是线程安全,执行时会进行线程(Thread)安全检查,以防止有新要求就启动新线程的CGI执行方式而耗尽系统资源。Non Thread Safe是非线程安全,在执行时不进行线程(Thread)安全检查。

 因此,如果是使用ISAPI的方式来运行PHP就必须用ThreadSafe(线程安全)的版本;而用FastCGI模式运行PHP的话就没有必要用线程安全检查了,用None Thread Safe(NTS,非线程安全)的版本能够更好的提高效率。

   再来看PHP的两种执行方式:ISAPI和FastCGI。

  ISAPI执行方式是以DLL动态库的形式使用,可以在被用户请求后执行,在处理完一个用户请求后不会马上消失,所以需要进行线程安全检查,这样来提高程序的执行效率,所以如果是以ISAPI来执行PHP,建议选择Thread Safe版本;

    而FastCGI执行方式是以单一线程来执行操作,所以不需要进行线程的安全检查,除去线程安全检查的防护反而可以提高执行效率,所以,如果是以FastCGI来执行PHP,建议选择Non Thread Safe版本。

如何重设 MySQL 的 root 密码

作者:王赛

MySQL下创建新用户、新数据库、设定访问权限控制都需要用到root密码。万一把root密码忘了,该怎么办?

幸运地是,重设密码很容易。

注意:MySQL的root用户和服务器操作系统的root用户是两个不同的用户,不要搞混了。

安全模式重置法

基本的思路是,以安全模式启动mysql,这样不需要密码可以直接以root身份登录,然后重设密码。

首先,我们停掉MySQL服务:

sudo service mysql stop  

以上命令适用于Ubuntu和Debian。CentOS、Fedora和RHEL下使用mysqld替换mysql。

以安全模式启动MySQL:

sudo mysqld_safe --skip-grant-tables --skip-networking &  

注意我们加了--skip-networking,避免远程无密码登录 MySQL。

这样我们就可以直接用root登录,无需密码:

mysql -u root  

接着重设密码:

mysql> use mysql;  
mysql> update user set password=PASSWORD("mynewpassword") where User='root';  
mysql> flush privileges;  

注意,命令后需要加分号。

重设完毕后,我们退出,然后启动 MySQL 服务:

mysql > quit  

quit不需要分号。

重启服务:

sudo service mysql restart  

同样,以上命令适用于Ubuntu和Debian,Centos、Fedora和RHEL需要用mysqld替换mysql

现在可以尝试用新密码登录了:

mysql -u root -pmynewpassword  

注意,-p 和密码间不能有空格。

其他方案

以上是通用方案,在Ubuntu和Debian系统上,有一个debian-sys-maint用户,Debian类系统下一些系统脚本对mysql的操作是通过这个用户完成的。所以我们可以通过这个用户来修改 root 密码。该用户的密码可以在/etc/mysql/debian.cnf下找到:

# Automatically generated for Debian scripts. DO NOT TOUCH!
[client]
host     = localhost  
user     = debian-sys-maint  
password = PASSWORD  
socket   = /var/run/mysqld/mysqld.sock  
[mysql_upgrade]
host     = localhost  
user     = debian-sys-maint  
password = PASSWORD  
socket   = /var/run/mysqld/mysqld.sock  
basedir  = /usr  

用该用户登录 MySQL 后,也可以修改密码(具体修改过程见上):

sudo mysql -u debian-sys-maint -p  

百度网盘爬虫(如何爬取百度网盘)

因为要做去转盘网分类模式点我),所以一定要爬取网盘资源,本来想自己写一个爬虫挺不容易的,不想分享出来,但最后还是决定了拿给大家一起看吧,毕竟有交流才有进步,有兴趣的朋友也可以看看我写的其他日志或者关注我,会发现去转盘网的大部分技术现在可以说是公开状态,如有对你有帮助还是认真读读吧,下面是爬虫代码,我立马公开:

ps:不会python的孩子先去学学python,代码是python写的

我附上点资料:点我下载点我下载2

其实还有个磁力站,不过暂时技术不想公开出来,之后也想公开,喜欢的看看:ok搜搜

#coding: utf8

“””

author:haoning

create time: 2015-8-15

“””

 

import re #正则表达式模块

import urllib2 #获取URLs的组件

import time

from Queue import Queue

import threading, errno, datetime

import json

import requests #Requests is an Apache2 Licensed HTTP library

import MySQLdb as mdb

 

DB_HOST = ‘127.0.0.1’

DB_USER = ‘root’

DB_PASS = ”

 

 

#以下是正则匹配规则

re_start = re.compile(r’start=(\d+)’) #\d 表示0-9 任意一个数字 后面有+号 说明这个0-9单个数位出现一到多次 比如21312314

re_uid = re.compile(r’query_uk=(\d+)’) #查询编号

re_urlid = re.compile(r’&urlid=(\d+)’) #url编号

 

ONEPAGE = 20 #一页数据量

ONESHAREPAGE = 20 #一页分享连接量

 

#缺少专辑列表

URL_SHARE = ‘http://yun.baidu.com/pcloud/feed/getsharelist?auth_type=1&start={start}&limit=20&query_uk={uk}&urlid={id}’ #获得分享列表

“””

{“feed_type”:”share”,”category”:6,”public”:”1″,”shareid”:”1541924625″,”data_id”:”2418757107690953697″,”title”:”\u5723\u8bde\u58c1\u7eb8\u5927\u6d3e\u9001″,”third”:0,”clienttype”:0,”filecount”:1,”uk”:1798788396,”username”:”SONYcity03″,”feed_time”:1418986714000,”desc”:””,”avatar_url”:”http:\/\/himg.bdimg.com\/sys\/portrait\/item\/1b6bf333.jpg”,”dir_cnt”:1,”filelist”:[{“server_filename”:”\u5723\u8bde\u58c1\u7eb8\u5927\u6d3e\u9001″,”category”:6,”isdir”:1,”size”:1024,”fs_id”:870907642649299,”path”:”%2F%E5%9C%A3%E8%AF%9E%E5%A3%81%E7%BA%B8%E5%A4%A7%E6%B4%BE%E9%80%81″,”md5″:”0″,”sign”:”1221d7d56438970225926ad552423ff6a5d3dd33″,”time_stamp”:1439542024}],”source_uid”:”871590683″,”source_id”:”1541924625″,”shorturl”:”1dDndV6T”,”vCnt”:34296,”dCnt”:7527,”tCnt”:5056,”like_status”:0,”like_count”:60,”comment_count”:19},

public:公开分享

title:文件名称

uk:用户编号

“””

URL_FOLLOW = ‘http://yun.baidu.com/pcloud/friend/getfollowlist?query_uk={uk}&limit=20&start={start}&urlid={id}’ #获得订阅列表

“””

{“type”:-1,”follow_uname”:”\u597d\u55e8\u597d\u55e8\u554a”,”avatar_url”:”http:\/\/himg.bdimg.com\/sys\/portrait\/item\/979b832f.jpg”,”intro”:”\u9700\u8981\u597d\u8d44\u6599\u52a0994798392″,”user_type”:0,”is_vip”:0,”follow_count”:2,”fans_count”:2276,”follow_time”:1415614418,”pubshare_count”:36,”follow_uk”:2603342172,”album_count”:0},

follow_uname:订阅名称

fans_count:粉丝数

“””

URL_FANS = ‘http://yun.baidu.com/pcloud/friend/getfanslist?query_uk={uk}&limit=20&start={start}&urlid={id}’ # 获取关注列表

“””

{“type”:-1,”fans_uname”:”\u62e8\u52a8\u795e\u7684\u5fc3\u7eea”,”avatar_url”:”http:\/\/himg.bdimg.com\/sys\/portrait\/item\/d5119a2b.jpg”,”intro”:””,”user_type”:0,”is_vip”:0,”follow_count”:8,”fans_count”:39,”follow_time”:1439541512,”pubshare_count”:15,”fans_uk”:288332613,”album_count”:0}

avatar_url:头像

fans_uname:用户名

“””

 

QNUM = 1000

hc_q = Queue(20) #请求队列

hc_r = Queue(QNUM) #接收队列

success = 0

failed = 0

 

def req_worker(inx): #请求

s = requests.Session() #请求对象

while True:

req_item = hc_q.get() #获得请求项

 

req_type = req_item[0] #请求类型,分享?订阅?粉丝?

url = req_item[1] #url

r = s.get(url) #通过url获得数据

hc_r.put((r.text, url)) #将获得数据文本和url放入接收队列

print “req_worker#”, inx, url #inx 线程编号; url 分析了的 url

 

def response_worker(): #处理工作

dbconn = mdb.connect(DB_HOST, DB_USER, DB_PASS, ‘baiduyun’, charset=’utf8′)

dbcurr = dbconn.cursor()

dbcurr.execute(‘SET NAMES utf8’)

dbcurr.execute(‘set global wait_timeout=60000’) #以上皆是数据库操作

while True:

“””

#正则备注

match() 决定 RE 是否在字符串刚开始的位置匹配

search() 扫描字符串,找到这个 RE 匹配的位置

findall() 找到 RE 匹配的所有子串,并把它们作为一个列表返回

finditer() 找到 RE 匹配的所有子串,并把它们作为一个迭代器返回

百度页面链接:http://pan.baidu.com/share/link?shareid=3685432306&uk=1798788396&from=hotrec

uk 其实用户id值

“””

metadata, effective_url = hc_r.get() #获得metadata(也就是前面的r.text)和有效的url

#print “response_worker:”, effective_url

try:

tnow = int(time.time()) #获得当前时间

id = re_urlid.findall(effective_url)[0] #获得re_urlid用户编号

start = re_start.findall(effective_url)[0] #获得start用户编号

if True:

if ‘getfollowlist’ in effective_url: #type = 1,也就是订阅类

follows = json.loads(metadata) #以将文本数据转化成json数据格式返回

uid = re_uid.findall(effective_url)[0] #获得re_uid,查询编号

if “total_count” in follows.keys() and follows[“total_count”]>0 and str(start) == “0”: #获得订阅数量

for i in range((follows[“total_count”]-1)/ONEPAGE): #开始一页一页获取有用信息

try:

dbcurr.execute(‘INSERT INTO urlids(uk, start, limited, type, status) VALUES(%s, %s, %s, 1, 0)’ % (uid, str(ONEPAGE*(i+1)), str(ONEPAGE)))

#存储url编号,订阅中有用户编号,start表示从多少条数据开始获取,初始status=0为未分析状态

except Exception as ex:

print “E1”, str(ex)

pass

 

if “follow_list” in follows.keys(): #如果订阅者也订阅了,即拥有follow_list

for item in follows[“follow_list”]:

try:

dbcurr.execute(‘INSERT INTO user(userid, username, files, status, downloaded, lastaccess) VALUES(%s, “%s”, 0, 0, 0, %s)’ % (item[‘follow_uk’], item[‘follow_uname’], str(tnow)))

#存储订阅这的用户编号,用户名,入库时间

except Exception as ex:

print “E13”, str(ex)

pass

else:

print “delete 1”, uid, start

dbcurr.execute(‘delete from urlids where uk=%s and type=1 and start>%s’ % (uid, start))

elif ‘getfanslist’ in effective_url: #type = 2,也就是粉丝列表

fans = json.loads(metadata)

uid = re_uid.findall(effective_url)[0]

if “total_count” in fans.keys() and fans[“total_count”]>0 and str(start) == “0”:

for i in range((fans[“total_count”]-1)/ONEPAGE):

try:

dbcurr.execute(‘INSERT INTO urlids(uk, start, limited, type, status) VALUES(%s, %s, %s, 2, 0)’ % (uid, str(ONEPAGE*(i+1)), str(ONEPAGE)))

except Exception as ex:

print “E2”, str(ex)

pass

 

if “fans_list” in fans.keys():

for item in fans[“fans_list”]:

try:

dbcurr.execute(‘INSERT INTO user(userid, username, files, status, downloaded, lastaccess) VALUES(%s, “%s”, 0, 0, 0, %s)’ % (item[‘fans_uk’], item[‘fans_uname’], str(tnow)))

except Exception as ex:

print “E23”, str(ex)

pass

else:

print “delete 2”, uid, start

dbcurr.execute(‘delete from urlids where uk=%s and type=2 and start>%s’ % (uid, start))

else: #type=0,也即是分享列表

shares = json.loads(metadata)

uid = re_uid.findall(effective_url)[0]

if “total_count” in shares.keys() and shares[“total_count”]>0 and str(start) == “0”:

for i in range((shares[“total_count”]-1)/ONESHAREPAGE):

try:

dbcurr.execute(‘INSERT INTO urlids(uk, start, limited, type, status) VALUES(%s, %s, %s, 0, 0)’ % (uid, str(ONESHAREPAGE*(i+1)), str(ONESHAREPAGE)))

except Exception as ex:

print “E3”, str(ex)

pass

if “records” in shares.keys():

for item in shares[“records”]:

try:

dbcurr.execute(‘INSERT INTO share(userid, filename, shareid, status) VALUES(%s, “%s”, %s, 0)’ % (uid, item[‘title’], item[‘shareid’])) #item[‘title’]恰好是文件名称

#返回的json信息:

except Exception as ex:

#print “E33”, str(ex), item

pass

else:

print “delete 0”, uid, start

dbcurr.execute(‘delete from urlids where uk=%s and type=0 and start>%s’ % (uid, str(start)))

dbcurr.execute(‘delete from urlids where id=%s’ % (id, ))

dbconn.commit()

except Exception as ex:

print “E5”, str(ex), id

dbcurr.close()

dbconn.close() #关闭数据库

 

def worker():

global success, failed

dbconn = mdb.connect(DB_HOST, DB_USER, DB_PASS, ‘baiduyun’, charset=’utf8′)

dbcurr = dbconn.cursor()

dbcurr.execute(‘SET NAMES utf8’)

dbcurr.execute(‘set global wait_timeout=60000’)

#以上是数据库相关设置

while True:

 

#dbcurr.execute(‘select * from urlids where status=0 order by type limit 1’)

dbcurr.execute(‘select * from urlids where status=0 and type>0 limit 1’) #type>0,为非分享列表

d = dbcurr.fetchall()

#每次取出一条数据出来

#print d

if d: #如果数据存在

id = d[0][0] #请求url编号

uk = d[0][1] #用户编号

start = d[0][2]

limit = d[0][3]

type = d[0][4] #哪种类型

dbcurr.execute(‘update urlids set status=1 where id=%s’ % (str(id),)) #状态更新为1,已经访问过了

url = “”

if type == 0: #分享

url = URL_SHARE.format(uk=uk, start=start, id=id).encode(‘utf-8’) #分享列表格式化

#query_uk uk 查询编号

#start

#urlid id url编号

elif  type == 1: #订阅

url = URL_FOLLOW.format(uk=uk, start=start, id=id).encode(‘utf-8’) #订阅列表格式化

elif type == 2: #粉丝

url = URL_FANS.format(uk=uk, start=start, id=id).encode(‘utf-8’) #关注列表格式化

if url:

hc_q.put((type, url)) #如果url存在,则放入请求队列,type表示从哪里获得数据

#通过以上的url就可以获得相应情况下的数据的json数据格式,如分享信息的,订阅信息的,粉丝信息的

 

#print “processed”, url

else: #否则从订阅者或者粉丝的引出人中获得信息来存储,这个过程是爬虫树的下一层扩展

dbcurr.execute(‘select * from user where status=0 limit 1000’)

d = dbcurr.fetchall()

if d:

for item in d:

try:

dbcurr.execute(‘insert into urlids(uk, start, limited, type, status) values(“%s”, 0, %s, 0, 0)’ % (item[1], str(ONESHAREPAGE)))

#uk 查询号,其实是用户编号

#start 从第1条数据出发获取信息

#

dbcurr.execute(‘insert into urlids(uk, start, limited, type, status) values(“%s”, 0, %s, 1, 0)’ % (item[1], str(ONEPAGE)))

dbcurr.execute(‘insert into urlids(uk, start, limited, type, status) values(“%s”, 0, %s, 2, 0)’ % (item[1], str(ONEPAGE)))

dbcurr.execute(‘update user set status=1 where userid=%s’ % (item[1],)) #做个标志,该条数据已经访问过了

#跟新了分享,订阅,粉丝三部分数据

except Exception as ex:

print “E6”, str(ex)

else:

time.sleep(1)

 

dbconn.commit()

dbcurr.close()

dbconn.close()

 

def main():

print ‘starting at:’,now()

for item in range(16):

t = threading.Thread(target = req_worker, args = (item,))

t.setDaemon(True)

t.start() #请求线程开启,共开启16个线程

s = threading.Thread(target = worker, args = ())

s.setDaemon(True)

s.start() #worker线程开启

response_worker()  #response_worker开始工作

print ‘all Done at:’, now()

史上最全的CSS hack方式一览

做前端多年,虽然不是经常需要hack,但是我们经常会遇到各浏览器表现不一致的情况。基于此,某些情况我们会极不情愿的使用这个不太友好的方式来达到大家要求的页面表现。我个人是不太推荐使用hack的,要知道一名好的前端,要尽可能不使用hack的情况下实现需求,做到较好的用户体验。可是啊,现实太残酷,浏览器厂商之间历史遗留的问题让我们在目标需求下不得不向hack妥协,虽然这只是个别情况。今天,结合自己的经验和理解,做了几个demo把IE6~IE10和其他标准浏览器的CSS hack做一个总结,也许本文应该是目前最全面的hack总结了吧。

什么是CSS hack

由于不同厂商的流览器或某浏览器的不同版本(如IE6-IE11,Firefox/Safari/Opera/Chrome等),对CSS的支持、解析不一样,导致在不同浏览器的环境中呈现出不一致的页面展现效果。这时,我们为了获得统一的页面效果,就需要针对不同的浏览器或不同版本写特定的CSS样式,我们把这个针对不同的浏览器/不同版本写相应的CSS code的过程,叫做CSS hack!

CSS hack的原理

由于不同的浏览器和浏览器各版本对CSS的支持及解析结果不一样,以及CSS优先级对浏览器展现效果的影响,我们可以据此针对不同的浏览器情景来应用不同的CSS。

CSS hack分类

CSS Hack大致有3种表现形式,CSS属性前缀法、选择器前缀法以及IE条件注释法(即HTML头部引用if IE)Hack,实际项目中CSS Hack大部分是针对IE浏览器不同版本之间的表现差异而引入的。

  • 属性前缀法(即类内部Hack):例如 IE6能识别下划线”_”和星号” * “,IE7能识别星号” * “,但不能识别下划线”_”,IE6~IE10都认识”\9″,但firefox前述三个都不能认识。
  • 选择器前缀法(即选择器Hack):例如 IE6能识别*html .class{},IE7能识别*+html .class{}或者*:first-child+html .class{}。
  • IE条件注释法(即HTML条件注释Hack):针对所有IE(注:IE10+已经不再支持条件注释): <!–[if IE]>IE浏览器显示的内容 <![endif]–>,针对IE6及以下版本: <!–[if lt IE 6]>只在IE6-显示的内容 <![endif]–>。这类Hack不仅对CSS生效,对写在判断语句里面的所有代码都会生效。

CSS hack书写顺序,一般是将适用范围广、被识别能力强的CSS定义在前面。

CSS hack方式一:条件注释法

这种方式是IE浏览器专有的Hack方式,微软官方推荐使用的hack方式。举例如下

	只在IE下生效
	<!--[if IE]>
	这段文字只在IE浏览器显示
	<![endif]-->
	
	只在IE6下生效
	<!--[if IE 6]>
	这段文字只在IE6浏览器显示
	<![endif]-->
	
	只在IE6以上版本生效
	<!--[if gte IE 6]>
	这段文字只在IE6以上(包括)版本IE浏览器显示
	<![endif]-->
	
	只在IE8上不生效
	<!--[if ! IE 8]>
	这段文字在非IE8浏览器显示
	<![endif]-->
	
	非IE浏览器生效
	<!--[if !IE]>
	这段文字只在非IE浏览器显示
	<![endif]-->

CSS hack方式二:类内属性前缀法

属性前缀法是在CSS样式属性名前加上一些只有特定浏览器才能识别的hack前缀,以达到预期的页面展现效果。

IE浏览器各版本 CSS hack 对照表

hack 写法 实例 IE6(S) IE6(Q) IE7(S) IE7(Q) IE8(S) IE8(Q) IE9(S) IE9(Q) IE10(S) IE10(Q)
* *color 青色 Y Y Y Y N Y N Y N Y
+ +color 绿色 Y Y Y Y N Y N Y N Y
-color 黄色 Y Y N N N N N N N N
_ _color 蓝色 Y Y N Y N Y N Y N N
# #color 紫色 Y Y Y Y N Y N Y N Y
\0 color:red\0 红色 N N N N Y N Y N Y N
\9\0 color:red\9\0 粉色 N N N N N N Y N Y N
!important color:blue !important;color:green; 棕色 N N Y N Y N Y N Y Y

说明:在标准模式中

  • “-″减号是IE6专有的hack
  • “\9″ IE6/IE7/IE8/IE9/IE10都生效
  • “\0″ IE8/IE9/IE10都生效,是IE8/9/10的hack
  • “\9\0″ 只对IE9/IE10生效,是IE9/10的hack

demo如下

  1. <script type=“text/javascript”>
  2.     //alert(document.compatMode);
  3. </script>
  4. <style type=“text/css”>
  5. body:nth-of-type(1) .iehack{
  6.     color#F00;/* 对Windows IE9/Firefox 7+/Opera 10+/所有Chrome/Safari的CSS hack ,选择器也适用几乎全部Mobile/Linux/Mac browser*/
  7. }
  8. .demo1,.demo2,.demo3,.demo4{
  9.     width:100px;
  10.     height:100px;
  11. }
  12. .hack{
  13. /*demo1 */
  14. /*demo1 注意顺序,否则IE6/7下可能无法正确显示,导致结果显示为白色背景*/
  15.     background-color:red/* All browsers */
  16.     background-color:blue !important;/* All browsers but IE6 */
  17.     *background-color:black/* IE6, IE7 */
  18.     +background-color:yellow;/* IE6, IE7*/
  19.     background-color:gray\9/* IE6, IE7, IE8, IE9, IE10 */
  20.     background-color:purple\0/* IE8, IE9, IE10 */
  21.     background-color:orange\9\0;/*IE9, IE10*/
  22.     _background-color:green/* Only works in IE6 */
  23.     *+background-color:pink; /*  WARNING: Only works in IE7 ? Is it right? */
  24. }
  25. /*可以通过javascript检测IE10,然后给IE10的<html>标签加上class=”ie10″ 这个类 */
  26. .ie10 #hack{
  27.     color:red/* Only works in IE10 */
  28. }
  29. /*demo2*/
  30. .iehack{
  31. /*该demo实例是用于区分标准模式下ie6~ie9和Firefox/Chrome的hack,注意顺序
  32. IE6显示为:绿色,
  33. IE7显示为:黑色,
  34. IE8显示为:红色,
  35. IE9显示为:蓝色,
  36. Firefox/Chrome显示为:橘色,
  37. (本例IE10效果同IE9,Opera最新版效果同IE8)
  38. */
  39.     background-color:orange;  /* all – for Firefox/Chrome */
  40.     background-color:red\0;  /* ie 8/9/10/Opera – for ie8/ie10/Opera */
  41.     background-color:blue\9\0;  /* ie 9/10 – for ie9/10 */
  42.     *background-color:black;  /* ie 6/7 – for ie7 */
  43.     _background-color:green;  /* ie 6 – for ie6 */
  44. }
  45. /*demo3
  46. 实例是用于区分标准模式下ie6~ie9和Firefox/Chrome的hack,注意顺序
  47. IE6显示为:红色,
  48. IE7显示为:蓝色,
  49. IE8显示为:绿色,
  50. IE9显示为:粉色,
  51. Firefox/Chrome显示为:橘色,
  52. (本例IE10效果同IE9,Opera最新版效果也同IE9为粉色)
  53. */
  54. .element {
  55.     background-color:orange;    /* all IE/FF/CH/OP*/
  56. }
  57. .element {
  58.     *background-colorblue;    /* IE6+7, doesn’t work in IE8/9 as IE7 */
  59. }
  60. .element {
  61.     _background-colorred;     /* IE6 */
  62. }
  63. .element {
  64.     background-colorgreen\0/* IE8+9+10  */
  65. }
  66. :root .element { background-color:pink\0; }  /* IE9+10 */
  67. /*demo4*/
  68. /*
  69. 该实例是用于区分标准模式下ie6~ie10和Opera/Firefox/Chrome的hack,本例特别要注意顺序
  70. IE6显示为:橘色,
  71. IE7显示为:粉色,
  72. IE8显示为:黄色,
  73. IE9显示为:紫色,
  74. IE10显示为:绿色,
  75. Firefox显示为:蓝色,
  76. Opera显示为:黑色,
  77. Safari/Chrome显示为:灰色,
  78. */
  79. .hacktest{
  80.     background-color:blue;      /* 都识别,此处针对firefox */
  81.     background-color:red\9;      /*all ie*/
  82.     background-color:yellow\0;    /*for IE8/IE9/10 最新版opera也认识*/
  83.     +background-color:pink;        /*for ie6/7*/
  84.     _background-color:orange;       /*for ie6*/
  85. }
  86. @media screen and (min-width:0){
  87.     .hacktest {background-color:black\0;}  /*opera*/
  88. }
  89. @media screen and (min-width:0) {
  90.     .hacktest { background-color:purple\9; }/*  for IE9/IE10  PS:国外有些习惯常写作\0,根本没考虑Opera也认识\0的实际 */
  91. }
  92. @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
  93.    .hacktest { background-color:green; } /* for IE10+ 此写法可以适配到高对比度和默认模式,故可覆盖所有ie10的模式 */
  94. }
  95. @media screen and (-webkit-min-device-pixel-ratio:0){ .hacktest {background-color:gray;} }  /*for Chrome/Safari*/
  96. /* #963棕色 :root is for IE9/IE10, 优先级高于@media, 慎用!如果二者合用,必要时在@media样式加入 !important 才能区分IE9和IE10 */
  97. /*
  98. :root .hacktest { background-color:#963\9; } 
  99. */
  100. </style>

 

demo1是测试不同IE浏览器下hack 的显示效果
IE6显示为:粉色,
IE7显示为:粉色,
IE8显示为:蓝色,
IE9显示为:蓝色,
Firefox/Chrome/Opera显示为:蓝色,
若去掉其中的!important属性定义,则IE6/7仍然是粉色,IE8是紫色,IE9/10为橙色,Firefox/Chrome变为红色,Opera是紫色。是不是有些奇怪:除了IE6以外,其他所有的表现都符合我们的期待。那为何IE6表现的颜色不是_background-color:green;的绿色而是*+background-color:pink的粉色呢?其实是最后一句所谓的IE7私有hack惹的祸?不是说*+是IE7的专有hack吗???错,你可能太粗心了!我们常说的IE7专有*+hack的格式是*+html selector,而不是上面的直接在属性上加*+前缀。如果是为IE7定制特殊样式,应该这样使用:

*+html #ie7test { /* IE7 only*/
	color:green;
}

经过测试,我发现属性前缀*+background-color:pink;只有IE6和IE7认识。而*+html selector只有IE7认识。所以我们在使用时候一定要特别注意。

demo2实例是用于区分标准模式下ie6~ie9和Firefox/Chrome的hack,注意顺序
IE6显示为:绿色,
IE7显示为:黑色,
IE8显示为:红色,
IE9显示为:蓝色,
Firefox/Chrome显示为:橘色,
(本例IE10效果同IE9,Opera最新版效果同IE8)

demo3实例也是用于区分标准模式下ie6~ie9和Firefox/Chrome的hack,注意顺序
IE6显示为:红色,
IE7显示为:蓝色,
IE8显示为:绿色,
IE9显示为:粉色,
Firefox/Chrome显示为:橘色,
(本例IE10效果同IE9,Opera最新版效果也同IE9为粉色)

demo4实例是用于区分标准模式下ie6~ie10和Opera/Firefox/Chrome的hack,本例特别要注意顺序
IE6显示为:橘色,
IE7显示为:粉色,
IE8显示为:黄色,
IE9显示为:紫色,
IE10显示为:绿色,
Firefox显示为:蓝色,
Opera显示为:黑色,
Safari/Chrome显示为:灰色,

CSS hack方式三:选择器前缀法

选择器前缀法是针对一些页面表现不一致或者需要特殊对待的浏览器,在CSS选择器前加上一些只有某些特定浏览器才能识别的前缀进行hack。

目前最常见的是

*html *前缀只对IE6生效
*+html *+前缀只对IE7生效
@media screen\9{...}只对IE6/7生效
@media \0screen {body { background: red; }}只对IE8有效
@media \0screen\,screen\9{body { background: blue; }}只对IE6/7/8有效
@media screen\0 {body { background: green; }} 只对IE8/9/10有效
@media screen and (min-width:0\0) {body { background: gray; }} 只对IE9/10有效
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {body { background: orange; }} 只对IE10有效
等等

结合CSS3的一些选择器,如html:first-child,body:nth-of-type(1),衍生出更多的hack方式,具体的可以参考下表:

CSS3选择器结合JavaScript的Hack

我们用IE10进行举例:

由于IE10用户代理字符串(UserAgent)为:Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0),所以我们可以使用javascript将此属性添加到文档标签中,再运用CSS3基本选择器匹配。

JavaScript代码:

	var htmlObj = document.documentElement;
	htmlObj.setAttribute('data-useragent',navigator.userAgent);
	htmlObj.setAttribute('data-platform', navigator.platform );

CSS3匹配代码:

html[data-useragent*='MSIE 10.0'] #id {
	color: #F00;
}

CSS hack利弊

一般情况下,我们尽量避免使用CSS hack,但是有些情况为了顾及用户体验实现向下兼容,不得已才使用hack。比如由于IE8及以下版本不支持CSS3,而我们的项目页面使用了大量CSS3新属性在IE9/Firefox/Chrome下正常渲染,这种情况下如果不使用css3pie或htc或条件注释等方法时,可能就得让IE8-的专属hack出马了。使用hack虽然对页面表现的一致性有好处,但过多的滥用会造成html文档混乱不堪,增加管理和维护的负担。相信只要大家一起努力,少用、慎用hack,未来一定会促使浏览器厂商的标准越来越趋于统一,顺利过渡到标准浏览器的主流时代。抛弃那些陈旧的IE hack,必将减轻我们编码的复杂度,少做无用功。

最后补上一张引自国外某大牛总结的CSS hack表,这时一张6年前的旧知识汇总表了,放在这里仅供需要时候方便参考。

说明:本文测试环境为IE6~IE10,Chrome 29.0.1547.66 m,Firefox 20.0.1 ,Opera 12.02等。一边工作,一边总结,总结了几天写下整理好,今天把它分享出来,文中难免有纰漏,如大侠发现请及时告知!

CSS 的优先级机制[总结]

样式的优先级

多重样式(Multiple Styles):如果外部样式、内部样式和内联样式同时应用于同一个元素,就是使多重样式的情况。

一般情况下,优先级如下:

(外部样式)External style sheet <(内部样式)Internal style sheet <(内联样式)Inline style

 

有个例外的情况,就是如果外部样式放在内部样式后面,则外部样式将覆盖内部样式。

示例如下:

<head>
    <style type=”text/css”>
      /* 内部样式 */
      h3{color:green;}
    </style>
    <!– 外部样式 style.css –>
    <link rel=”stylesheet” type=”text/css” href=”style.css”/>
    <!– 设置:h3{color:blue;} –>
</head>
<body>
    <h3>测试!</h3>
</body>

 

选择器的优先权

 

jc6_002

 

解释:

1.  内联样式表的权值最高 1000;

2.  ID 选择器的权值为 100

3.  Class 类选择器的权值为 10

4.  HTML 标签选择器的权值为 1

 

利用选择器的权值进行计算比较,示例如下:

<html>
  <head>
    <style type=”text/css”>
        #redP p {
             /* 权值 = 100+1=101 */
             color:#F00;  /* 红色 */
        }
        #redP .red em {
             /* 权值 = 100+10+1=111 */
             color:#00F; /* 蓝色 */
        }
        #redP p span em {
             /* 权值 = 100+1+1+1=103 */
             color:#FF0;/*黄色*/
        }
    </style>
  </head>
  <body>
     <div id=”redP”>
        <p class=”red”>red
           <span><em>em red</em></span>
        </p>
        <p>red</p>
     </div>
  </body>
</html>

结果:<em> 标签内的数据显示为蓝色。

 

CSS 优先级法则:

A  选择器都有一个权值,权值越大越优先;

B  当权值相等时,后出现的样式表设置要优于先出现的样式表设置;

C  创作者的规则高于浏览者:即网页编写者设置的CSS 样式的优先权高于浏览器所设置的样式;

D  继承的CSS 样式不如后来指定的CSS 样式;

E  在同一组属性设置中标有!important”规则的优先级最大;示例如下:

<html>
  <head>
    <style type=”text/css”>
     #redP p{
        /*两个color属性在同一组*/
        color:#00f !important; /* 优先级最大 */
        color:#f00;
     }
    </style>
  </head>
  <body>
     <div id=”redP”>
       <p>color</p>
       <p>color</p>
     </div>
  </body>
</html>

结果:在Firefox 下显示为蓝色;在IE  6 下显示为红色

 

使用脚本添加样式

当在连接外部样式后,再在其后面使用JavaScript 脚本插入内部样式时(即内部样式使用脚本创建),IE 浏览器就表现出它的另类了。代码如下:

<html>
<head>
  <title> demo </title>
  <meta name=”Author” content=”xugang” />
  <!– 添加外部CSS 样式 –>
  <link rel=”stylesheet” href=”styles.css” type=”text/css” />
  <!– 在外部的styles.css文件中,代码如下:
       h3 {color:blue;}
  –>
  <!– 使用javascript 创建内部CSS 样式 –>
  <script type=”text/javascript”>
  <!–
    (function(){
        var agent = window.navigator.userAgent.toLowerCase();
        var is_op = (agent.indexOf(opera) != -1);
        var is_ie = (agent.indexOf(msie) != -1) && document.all && !is_op;
        var is_ch = (agent.indexOf(chrome) != -1);
        var cssStr=h3 {color:green;};
        var s=document.createElement(style);
        var head=document.getElementsByTagName(head).item(0);
        var link=document.getElementsByTagName(link);
        link=link.item(0);
        if(is_ie)
        {
            if(link)
                head.insertBefore(s,link);
            else
                head.appendChild(s);
            document.styleSheets.item(document.styleSheets.length-1).cssText=cssStr;
        }
        else if(is_ch)
        {
            var t=document.createTextNode();
            t.nodeValue=cssStr;
            s.appendChild(t);
            head.insertBefore(s,link);
        }
        else
        {
            s.innerHTML=cssStr;
            head.insertBefore(s,link);
        }
    })();
  //–>
  </script>
</head>
<body>
  <h3>在IE中我是绿色,非IE浏览器下我是蓝色!</h3>
</body>
</html>

结果在Firefox / Chrome / Safari / Opera 中,文字都是蓝色的。而在IE 浏览器中,文字却是绿色的。

 

附加

在IE 中添加样式内容的JavaScript 代码:

var s=document.createElement(“style”);
var head=document.getElementsByTagName(“head”).item(0);
var link=document.getElementsByTagName(“link”).item(0);
head.insertBefore(s,link);
/* 注意:在IE 中,
   虽然代码是将<style>插入在<link>之前,
   但实际内存中,<style>却在<link>之后。
   这也是“IE中奇怪的应用CSS的BUG”之所在!
*/
var oStyleSheet = document.styleSheets[0];
//这实际是在<link>的外部样式中追加
oStyleSheet.addRule(“h3″,”color:green;”);
alert(oStyleSheet.rules[0].style.cssText);
alert(document.styleSheets[0].rules[0].style.cssText);
//方式2
var cssStr=”h3 {color:green;}”;
document.styleSheets.item(document.styleSheets.length-1).cssText=cssStr;

 

IE 浏览器下载或者渲染的顺序可能如下:

●   IE 下载的顺序是从上到下;

●   JavaScript 函数的执行会阻塞IE 的下载;

●   IE 渲染的顺序也是从上到下;

●   IE 的下载和渲染是同时进行的;

●   在渲染到页面的某一部分时,其上面的所有部分都已经下载完成(但并不是说所有相关联的元素都已经下载完。)

●   在下载过程中,如果遇到某一标签是嵌入文件,并且文件是具有语义解释性的(例如:JS脚本,CSS样式),那么此时IE的下载过程会启用单独连接进行下载。并且在下载后进行解析,如果JS、CSS中如有重定义,后面定义的函数将覆盖前面定义的函数。

●   解析过程中,停止页面所有往下元素的下载。样式表文件比较特殊,在其下载完成后,将和以前下载的所有样式表一起进行解析,解析完成后,将对此前所有元素(含以前已经渲染的)重新进行样式渲染。并以此方式一直渲染下去,直到整个页面渲染完成。

●   Firefox 处理下载和渲染的顺序大体相同,只是在细微之处有些差别,例如:iframe 的渲染。

深入理解Javascript之执行上下文(Execution Context)

在这篇文章中,将比较深入地阐述下执行上下文 – Javascript中最基础也是最重要的一个概念。相信读完这篇文章后,你就会明白javascript引擎内部在执行代码以前到底做了些什么,为什么某些函数以及变量在没有被声明以前就可以被使用,以及它们的最终的值是怎样被定义的。

什么是执行上下文

Javascript中代码的运行环境分为以下三种:

  • 全局级别的代码 – 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境。
  • 函数级别的代码 – 当执行一个函数时,运行函数体中的代码。
  • Eval的代码 – 在Eval函数内运行的代码。

在网上可以找到很多阐述作用域的资源,为了使该文便于大家理解,我们可以将“执行上下文”看做当前代码的运行环境或者作用域。下面我们来看一个示例,其中包括了全局以及函数级别的执行上下文:

上图中,一共用4个执行上下文。紫色的代表全局的上下文;绿色代表person函数内的上下文;蓝色以及橙色代表person函数内的另外两个函数的上下文。注意,不管什么情况下,只存在一个全局的上下文,该上下文能被任何其它的上下文所访问到。也就是说,我们可以在person的上下文中访问到全局上下文中的sayHello变量,当然在函数firstName或者lastName中同样可以访问到该变量。

至于函数上下文的个数是没有任何限制的,每到调用执行一个函数时,引擎就会自动新建出一个函数上下文,换句话说,就是新建一个局部作用域,可以在该局部作用域中声明私有变量等,在外部的上下文中是无法直接访问到该局部作用域内的元素的。在上述例子的,内部的函数可以访问到外部上下文中的声明的变量,反之则行不通。那么,这到底是什么原因呢?引擎内部是如何处理的呢?

执行上下文堆栈

在浏览器中,javascript引擎的工作方式是单线程的。也就是说,某一时刻只有唯一的一个事件是被激活处理的,其它的事件被放入队列中,等待被处理。下面的示例图描述了这样的一个堆栈:

我们已经知道,当javascript代码文件被浏览器载入后,默认最先进入的是一个全局的执行上下文。当在全局上下文中调用执行一个函数时,程序流就进入该被调用函数内,此时引擎就会为该函数创建一个新的执行上下文,并且将其压入到执行上下文堆栈的顶部。浏览器总是执行当前在堆栈顶部的上下文,一旦执行完毕,该上下文就会从堆栈顶部被弹出,然后,进入其下的上下文执行代码。这样,堆栈中的上下文就会被依次执行并且弹出堆栈,直到回到全局的上下文。请看下面一个例子:

1
2
3
4
5
6
7
8
(function foo(i) {
if (i === 3) {
return;
}
else {
foo(++i);
}
}(0));

上述foo被声明后,通过()运算符强制直接运行了。函数代码就是调用了其自身3次,每次是局部变量i增加1。每次foo函数被自身调用时,就会有一个新的执行上下文被创建。每当一个上下文执行完毕,该上上下文就被弹出堆栈,回到上一个上下文,直到再次回到全局上下文。真个过程抽象如下图:

由此可见 ,对于执行上下文这个抽象的概念,可以归纳为以下几点:

  • 单线程
  • 同步执行
  • 唯一的一个全局上下文
  • 函数的执行上下文的个数没有限制
  • 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。

执行上下文的建立过程

我们现在已经知道,每当调用一个函数时,一个新的执行上下文就会被创建出来。然而,在javascript引擎内部,这个上下文的创建过程具体分为两个阶段:

  1. 建立阶段(发生在当调用一个函数时,但是在执行函数体内的具体代码以前)
    • 建立变量,函数,arguments对象,参数
    • 建立作用域链
    • 确定this的值
  2. 代码执行阶段:
    • 变量赋值,函数引用,执行其它代码

实际上,可以把执行上下文看做一个对象,其下包含了以上3个属性:

1
2
3
4
5
 executionContextObj = {
variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ },
scopeChain: { /* variableObject 以及所有父执行上下文中的variableObject */ },
this: {}
}

建立阶段以及代码执行阶段的详细分析

确切地说,执行上下文对象(上述的executionContextObj)是在函数被调用时,但是在函数体被真正执行以前所创建的。函数被调用时,就是我上述所描述的两个阶段中的第一个阶段 – 建立阶段。这个时刻,引擎会检查函数中的参数,声明的变量以及内部函数,然后基于这些信息建立执行上下文对象(executionContextObj)。在这个阶段,variableObject对象,作用域链,以及this所指向的对象都会被确定。

上述第一个阶段的具体过程如下:

  1. 找到当前上下文中的调用函数的代码
  2. 在执行被调用的函数体中的代码以前,开始创建执行上下文
  3. 进入第一个阶段-建立阶段:
    • 建立variableObject对象:
      1. 建立arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值
      2. 检查当前上下文中的函数声明:
        • 每找到一个函数声明,就在variableObject下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用
        • 如果上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。
      3. 检查当前上下文中的变量声明:
        • 每找到一个变量的声明,就在variableObject下,用变量名建立一个属性,属性值为undefined。
        • 如果该变量名已经存在于variableObject属性中,直接跳过(防止指向函数的属性的值被变量属性覆盖为undefined),原属性值不会被修改。
    • 初始化作用域链
    • 确定上下文中this的指向对象
  4. 代码执行阶段:
    • 执行函数体中的代码,一行一行地运行代码,给variableObject中的变量属性赋值。

下面来看个具体的代码示例:

1
2
3
4
5
6
7
8
9
10
11
  function foo(i) {
var a = ‘hello’;
var b = function privateB() {

};
function c() {

}
}

foo(22);

在调用foo(22)的时候,建立阶段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fooExecutionContext = {
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
scopeChain: {},
this: {}
}

由此可见,在建立阶段,除了arguments,函数的声明,以及参数被赋予了具体的属性值,其它的变量属性默认的都是undefined。一旦上述建立阶段结束,引擎就会进入代码执行阶段,这个阶段完成后,上述执行上下文对象如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fooExecutionContext = {
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: ‘hello’,
b: pointer to function privateB()
},
scopeChain: {},
this: {}
}

我们看到,只有在代码执行阶段,变量属性才会被赋予具体的值。

局部变量作用域提升的缘由

在网上一直看到这样的总结: 在函数中声明的变量以及函数,其作用域提升到函数顶部,换句话说,就是一进入函数体,就可以访问到其中声明的变量以及函数。这是对的,但是知道其中的缘由吗?相信你通过上述的解释应该也有所明白了。不过在这边再分析一下。看下面一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function() {

console.log(typeof foo); // function pointer
console.log(typeof bar); // undefined

var foo = ‘hello’,
bar = function() {
return ‘world’;
};

function foo() {
return ‘hello’;
}

}());

上述代码定义了一个匿名函数,并且通过()运算符强制理解执行。那么我们知道这个时候就会有个执行上下文被创建,我们看到例子中马上可以访问foo以及bar变量,并且通过typeof输出foo为一个函数引用,bar为undefined。

  • 为什么我们可以在声明foo变量以前就可以访问到foo呢?

    因为在上下文的建立阶段,先是处理arguments, 参数,接着是函数的声明,最后是变量的声明。那么,发现foo函数的声明后,就会在variableObject下面建立一个foo属性,其值是一个指向函数的引用。当处理变量声明的时候,发现有var foo的声明,但是variableObject已经具有了foo属性,所以直接跳过。当进入代码执行阶段的时候,就可以通过访问到foo属性了,因为它已经就存在,并且是一个函数引用。

  • 为什么bar是undefined呢?

    因为bar是变量的声明,在建立阶段的时候,被赋予的默认的值为undefined。由于它只要在代码执行阶段才会被赋予具体的值,所以,当调用typeof(bar)的时候输出的值为undefined。

好了,到此为止,相信你应该对执行上下文有所理解了,这个执行上下文的概念非常重要,务必好好搞懂之!

javascript 闭包详解

来源:极客标签   时间:2014-11-13 10:25:04   阅读数:26771

分享到:3

[导读] 今天我们从内存结构上来讲解下javascript中的闭包概念。闭包:是指有权访问另外一个函数作用域中的变量的函数。创建闭包的常见方式就是在一个函数内部创建另外一个函数。在javascript中没有块级作用域,一般为了

\
(单击图片查看大图)
今天我们从内存结构上来讲解下 javascript中的闭包概念。

闭包:是指有权访问另外一个函数作用域中的变量的函数。创建闭包的常见方式就是在一个函数内部创建另外一个函数。

在javascript中没有块级作用域,一般为了给某个函数申明一些只有该函数才能使用的局部变量时,我们就会用到闭包,这样我们可以很大程度上减少全局作用域中的变量,净化全局作用域。

使用闭包有如上的好处,当然这样的好处是需要付出代价的,代价就是内存的占用。

如何理解上面的那句话呢?

每个函数的执行,都会创建一个与该函数相关的函数执行环境,或者说是函数执行上下文。这个执行上下文中有一个属性 scope chain(作用域链指针),这个指针指向一个作用域链结构,作用域链中的指针又都指向各个作用域对应的活动对象。正常情况,一个函数在调用开始执行时创建这个函数执行上下文及相应的作用域链,在函数执行结束后释放函数执行上下文及相应作用域链所占的空间。

比如:

//声明函数
function test(){
var str = “hello world”;
console.log(str);
}

//调用函数
test();

在调用函数的时候会在内存中生成如下图的结构:
\
(单击图片查看大图)
但是闭包的情况就有点特殊了,由于闭包函数可以访问外层函数中的变量,所以外层函数在执行结束后,其作用域活动对象并不会被释放(注意,外层函数执行结束后执行环境和对应的作用域链就会被销毁),而是被闭包函数的作用域链所引用,直到闭包函数被销毁后,外层函数的作用域活动对象才会被销毁。这也正是闭包要占用内存的原因。

所以使用闭包有好处,也有坏处,滥用闭包会造成内存的大量消耗。

使用闭包还有其他的副作用,可以说是bug,也可以说不是,相对不同的业务可能就会有不同的看法。

这个副作用是闭包函数只能取到外层函数变量的最终值。

测试代码如下:(这里使用了jquery对象)

/*闭包缺陷*/
(function($){
var result = new Array(),
i = 0;
for(;i<10;i++){
result[i] = function(){
return i;
};
}
$.RES1 = result;
})(jQuery);
// 执行数组中的函数
$.RES1[0]();

上面的代码先通过匿名函数表达式开辟了一块私有作用域,这个匿名函数就是我们上面所说的外层函数,该外层函数有一个参数$,同时还定义了变量result和 I , 通过for循环给数组result赋值一个匿名函数,这个匿名函数就是闭包,他访问了外层函数的变量I , 理论上数组result[i]() 会返回相应的数组下标值,实际情况却不如所愿。

如上代码 $.RES1[0]() 的执行结果是10.

为什么会这样呢,因为i的最终值就是10.

下面我们通过下图来详细说明下,上面的那段代码执行时在内存中到底发生了什么:
\
(单击图片查看大图)
那么这个副作用有没有办法可以修复呢?当然可以!

我们可以通过下面的代码来达到我们的预期。

/*修复闭包缺陷*/
(function($){
var result = new Array(),
i = 0;
for(;i<10;i++){
result[i] = function(num){
return function(){
return num;
}
}(i);
}
$.RES2 = result;
})(jQuery);
//调用闭包函数
console.log($.RES2[0]());

上面的代码又在内存中发生了什么?我们同样用下面的一幅图来详细解释。看懂了上面的图,我们也就不难理解下面的图。
\
(单击图片查看大图)
只要看懂上面的三张图,我们也就可以深入的理解清楚javascript中闭包的原理,以及闭包的好处和弊端,在我们的代码中合理的使用闭包,达到代码的整洁和高效。

JavaScript 事件属性绑定带参数的函数

作者: 字体:[增加 减小] 类型:转载 时间:2009-03-13 我要评论

在JavaScript中,为了实现表现和控制相分离,可以通过0级的DOM事件属性或者2级的事件模型来实现,不过这两者在针对某个事件类型调用相应的事件句柄的时候,不能给事件句柄提供参数,也就是说,事件属性的值只能是一个函数引用。
例如不能采用这种调用方式:element.onclick = test();element.onclick = test(arg1,arg2);只能通过element.onclick = function(){ … };或者element.onclick = test这种方式来实现,所以无法给函数传递参数。参考了大量的网上资料,解决这个问题的方式,以代码为例,如下所示:

复制代码代码如下:
function Handler() { };
Handler.prototype = {
/*
* 把eventType类型的事件绑定到element元素,并使用handler事件句柄进行处理
* 兼容 IE 及 Firefox 等浏览器
*
* @param element 在其上注册事件的对象(Object)
* @param eventType 注册的事件类型(String),不加“on”
* @param handler 事件句柄(Function)
*/
registerEvent : function(element, eventType, handler) {
if(element.attachEvent) { //2级DOM的事件处理
element.attachEvent(‘on’+ eventType, handler);
}else if (element.addEventListener) {
element.addEventListener(eventType, handler, false);
} else { //0级DOM的事件处理
element[‘on’+ eventType] = handler;
}
},
/*
* 获得带参数的事件句柄的引用
*
* @param obj 需要绑定事件处理函数的所有者,null 表示 window 对象
* @param func 需要绑定的事件处理函数名
* @param … 第三个参数开始为绑定事件处理函数的参数,由 0 到多个构成
*/
bind: function(obj, handler) {
obj = obj || window;
var args = [];
for(var i =2; i < arguments.length; i++)
{
args.push(arguments[i]);
}
return function() { handler.apply(obj, args) };
}
}
可能是使用方式为:
function show(txtObj) {
alert(txtObj.value);
txtObj.focus();
txtObj.select();
}
window.onload = function(){
var handler = new Handler();
handler.registerEvent($(“txt”), “change”, handler.bind(null,show,$(“txt”)));//采用2级事件模型的方式
//$(“txt”).onchange = handler.bind(null,show,$(“txt”));//JavaScript事件属性的方式
}

Javascript事件绑定的几种方式

 

来源:http://www.cnblogs.com/rainman/archive/2009/02/11/1387955.html

上篇文章讲到了事件绑定的3中常用方法:传统绑定、W3C绑定方法、IE绑定方法。但是,在实际开发中对于我们来讲重要的是需要一个通用的、跨浏览器的绑定方法。如果我们在互联网上搜索一下会发现许多方法,一下是比较知名的几种方法:

在开始学期下面几种方法之前,应当讨论一下,一个好的addEvent()方法应当达到哪些要求:

a、支持同一元素的同一事件句柄可以绑定多个监听函数;

b、如果在同一元素的同一事件句柄上多次注册同一函数,那么第一次注册后的所有注册都被忽略;

c、函数体内的this指向的应当是正在处理事件的节点(如当前正在运行事件句柄的节点);

d、监听函数的执行顺序应当是按照绑定的顺序执行;

e、在函数体内不用使用 event = event || window.event; 来标准化Event对象;

一、John Resig 所写的 addEvent() 函数:http://ejohn.org/projects/flexible-javascript-events/

[javascript] view plain copy

  1. function addEvent( obj, type, fn ) {
  2.         if ( obj.attachEvent ) {
  3.             obj[‘e’+type+fn] = fn;
  4.             obj[type+fn] = function(){obj[‘e’+type+fn]( window.event );}
  5.             obj.attachEvent( ‘on’+type, obj[type+fn] );
  6.         } else
  7.             obj.addEventListener( type, fn, false );
  8.     }
  9.     function removeEvent( obj, type, fn ) {
  10.         if ( obj.detachEvent ) {
  11.             obj.detachEvent( ‘on’+type, obj[type+fn] );
  12.             obj[type+fn] = null;
  13.         } else
  14.             obj.removeEventListener( type, fn, false );
  15.     }

 

这个函数如此简单易懂,的确非常令人惊讶。那么我们还是要看看上面的五点要求:

对于第一点满足了;

对于第三点和第五点,肯定也满足了;

对于第二点,并没有满足,因为addEventListener()会忽略重复注册,而attachEvent()则不会忽略;

但是第四点,并没有满足,因为Dom标准没有确定调用一个对象的时间处理函数的顺序,所以不应该想当然的认为它们以注册的顺序调用。

但是这个函数仍然是一个非常优秀的函数。

二、Dean Edward 所写的 addEvent() 函数 :http://dean.edwards.name/weblog/2005/10/add-event2/

[javascript] view plain copy

  1. function addEvent(element, type, handler) {
  2.     if (!handler.
    guid)handler.

    guid = addEvent.guid++;

  3.     if (!element.events) element.events = {};
  4.         var handlers = element.events[type];
  5.     if (!handlers) {
  6.         handlers = element.events[type] = {};
  7.         if (element[“on” + type]) {
  8.             handlers[0] = element[“on” + type];
  9.         }
  10.     }
  11.     handlers[handler.$$guid] = handler;
  12.     element[“on” + type] = handleEvent;
  13. }
  14. addEvent.guid = 1;
  15. function removeEvent(element, type, handler) {
  16.     if (element.events && element.events[type]) {
  17.         delete element.events[type][handler.$$guid];
  18.     }
  19. }
  20. function handleEvent(event) {
  21.     var returnValue = true;
  22.     event = event || fixEvent(window.event);
  23.     var handlers = this.events[event.type];
  24.     for (var i in handlers) {
  25.         this.$$handleEvent = handlers[i];
  26.         if (this.$$handleEvent(event) === false) {
  27.             returnValue = false;
  28.         }
  29.     }
  30.     return returnValue;
  31. };
  32. function fixEvent(event) {
  33.     event.preventDefault = fixEvent.preventDefault;
  34.     event.stopPropagation = fixEvent.stopPropagation;
  35.     return event;
  36. };
  37. fixEvent.preventDefault = function() {
  38.     this.returnValue = false;
  39. };
  40. fixEvent.stopPropagation = function() {
  41.     this.cancelBubble = true;
  42. };

该函数使用了传统的绑定方法,所以它可以在所有的浏览器中工作,也不会造成内存泄露。

但是对于最初提出的5点,该函数只是满足了前四点。只有最后一点没有满足,因为在JavaScript中对for/in语句的执行顺序没有规定是按照赋值的顺序执行,尽管大部分时刻是按照预期的顺序执行,因此在不同的JavaScript版本或实现中这一语句的顺序有可能不同。

三、Dean Edward 的 addEvent() 函数的改进

[javascript] view plain copy

  1. Array.prototype.indexOf = function( obj ){
  2.     var result = -1 , length = this.length , i=length – 1;
  3.     for ( ; i>=0 ; i– ) {
  4.         if ( this[i] == obj ) {
  5.             result = i;
  6.             break;
  7.         }
  8.     }
  9.     return result;
  10. }
  11. Array.prototype.contains = function( obj ) {
  12.     return ( this.indexOf( obj ) >=0 )
  13. }
  14. Array.prototype.append = function( obj , nodup ) {
  15.     if ( !(nodup && this.contains( obj )) ) {
  16.         this[this.length] = obj;
  17.     }
  18. }
  19. Array.prototype.remove = function( obj ) {
  20.     var index = this.indexOf( obj );
  21.     if ( !index ) return ;
  22.     return this.splice( index , 1);
  23. };
  24. function addEvent(element , type , fun){
  25.     if (!element.events) element.events = {};
  26.     var handlers = element.events[type];
  27.     if (!handlers) {
  28.         handlers = element.events[type] = [];
  29.         if(element[‘on’ + type]) {
  30.             handlers[0] = element[‘on’ + type];
  31.         }
  32.     }
  33.     handlers.append( fun , true)
  34.     element[‘on’ + type] = handleEvent;
  35. }
  36. function removeEvent(element , type , fun) {
  37.     if (element.events && element.events[type]) {
  38.         element.events[type].remove(fun);
  39.     }
  40. }
  41. function handleEvent(event) {
  42.     var returnValue = true , i=0;
  43.     event = event || fixEvent(window.event);
  44.     var handlers = this.events[event.type] , length = handlers.length;
  45.     for ( ; i < length ; i++) {
  46.         if ( handlers[i].call( this , event) === false ){
  47.             returnValue = false;
  48.         }
  49.     }
  50.     return returnValue;
  51. }
  52. function fixEvent(event) {
  53.     event.preventDefault = fixEvent.preventDefault;
  54.     event.stopPropagation = fixEvent.stopPropagation;
  55.     return event;
  56. }
  57. fixEvent.preventDefault = function() {
  58.     this.returnValue = false;
  59. };
  60. fixEvent.stopPropagation = function() {
  61.     this.cancelBubble = true;
  62. };

该函数是本人对Dean Edward 的 addEvent() 函数的改进,完全满足了最初提出的5点要求。如果大家有更好的方法,期待分享!

来源:http://blog.csdn.net/haijiaoxiaowu/article/details/5150389

Javascript的事件绑定主要有四种方法(一下在IE中运行正常,但不保证其他浏览器):

[注:onXXX为某一事件,fun为某一function,domId为某一DOM对象id,event类型见后边附录。]

1、在DOM中,直接用onXXX=”fun();”进行绑定

2、在Javascript代码中用 DOM对象.onXXX=fun 进行绑定

3、用 DOM对象.attachEvent(“onXXX”,fun) 进行绑定

4、用<script for=”domId” event=”onXXX”>fun();</script> 进行绑定

  1. <html>
  2. <head>
  3. <title>event test</title>
  4. </head>
  5. <body onload=“init()”>
  6. <!– 绑定方式一:在元素中,通过onXXX(事件)设置绑定方法 –>
  7. <button id=“btn1” onclick=“display()” >绑定方式一</button>
  8. <!– 绑定方式二:在Javascript代码中,通过获得元素,为元素的onXXX(事件)设置绑定方法  –>
  9. <button id=“btn2”>绑定方式二</button>
  10. <!– 绑定方式三:通过for、event为元素绑定事件(IE4+)。for后面是元素id,event是具体事件  –>
  11. <button id=“btn3”>绑定方式三</button>
  12. <!– 绑定方式四:通过attachEvent为元素绑定事件(IE5+)。第一个参数是事件名,第二个参数是绑定的方法 –>
  13. <button id=“btn4”>绑定方式四</button>
  14. </body>
  15. </html>
  16. <script type=“text/javascript”>
  17.     function init() {
  18.         document.getElementById(“btn2”).onclick = display;//为button2绑定事件
  19.         document.getElementById(“btn4”).attachEvent(“onclick”, display);//为button4绑定事件
  20.     }
  21.     function display(event) {
  22.         var targ;//触发事件的对象引用
  23.         if (!event) {
  24.             var event = window.event;//获得当前事件(IE)
  25.         }
  26.         if (event.target) {//IE没有target
  27.             targ = evente.target;
  28.         } else if (event.srcElement) {//适用于IE
  29.             targ = event.srcElement;
  30.         }
  31.         //对触发事件的对象进行操作
  32.         alert(targ.tagName+”-“+targ.id+”-“+event.x+”-“+event.offsetX);
  33.         targ.disabled=“disabled” ;
  34.     }
  35. <script>
  36. <script for=“btn3” event=“onclick”>
  37.     display();//为button3绑定事件
  38. <script>

 

附一:event事件:

onabort: 图像的加载被中断
onblur: 元素失去焦点
onchange: 域的内容被改变
onclick: 当用户点击某个对象时调用的事件句柄
ondblclick: 当用户双击某个对象时调用的事件句柄
onerror: 在加载文档或图像时发生错误
onfocus: 元素获得焦点
onkeydown: 某个键盘按键被按下
onkeypress: 某个键盘按键被按下并松开
onkeyup: 某个键盘按键被松开
onload: 一张页面或一幅图像完成加载
onmousedown: 鼠标按钮被按下
onmousemove: 鼠标被移动
onmouseout: 鼠标从某元素移开
onmouseover: 鼠标移到某元素之上
onmouseup: 鼠标按键被松开
onreset: 重置按钮被点击
onresize: 窗口或框架被重新调整大小
onselect: 文本被选中
onsubmit: 确认按钮被点击
onunload: 用户退出页面

 

附二:IE event属性:

cancelBubble: 如果事件句柄想阻止事件传播到包容对象,必须把该属性设为 true。
fromElement :对于 mouseover 和 mouseout 事件,fromElement 引用移出鼠标的元素。
keyCode :对于 keypress 事件,该属性声明了被敲击的键生成的 Unicode 字符码。对于 keydown 和 keyup 事件,它指定了被敲击的键的虚拟键盘码。虚拟键盘码可能和使用的键盘的布局相关。
offsetX,offsetY :发生事件的地点在事件源元素的坐标系统中的 x 坐标和 y 坐标。
returnValue: 如果设置了该属性,它的值比事件句柄的返回值优先级高。把这个属性设置为 fasle,可以取消发生事件的源元素的默认动作。
srcElement :对于生成事件的 Window 对象、Document 对象或 Element 对象的引用。
toElement: 对于 mouseover 和 mouseout 事件,该属性引用移入鼠标的元素。
x,y: 事件发生的位置的 x 坐标和 y 坐标,它们相对于用CSS动态定位的最内层包容元素。