分类 JS 下的文章

关于swfupload插件无法上传文件的问题

因为项目上所使用的flash上传图片插件名叫swfupload,但里面的js写法好像另一个上传插件sapload,所以我也搞不清到底是哪个,但最终了解到这两个插件都是有共同的一个问题,就是对需要进行身份才能上传文件的方式会失效。

我们都知道服务器端要对识别当前用户登录状态需要客户端cookie的支持,请求URL时会带着cookie,服务器端才能找到对应的session。而使用这个flash插件上传文件请求接口时,如果被请求的接口需要登陆才能传文件的话,那很可能就会失败。

一种原因是说:因为该插件会忽略部分浏览器下的cookie,IE和chrome下是正常的,像火狐等浏览器上传文件时,服务器端会发现请求带过来的cookie是空的(建议尝试打印一下cookie)

另一种可能的说法:该插件是通过soket套接字进行通信,相当于建立了一次新对话,即使请求带着cookie过去了,但打印出来发现存储的sessionid并不是当前登录时的,因此用户身份又验证失败。(建议多打印cookie并查看)

归根到底,无法上传文件的原因就是无法验证身份。

解决方法有两种:

1、在初始化flash插件时,在js代码中设置一下传递参数(可参考其插件手册)。swfupload插件中增加一个post_params选项,格式如post_params:{"PHPSESSID": <?php echo session_id(); ?>} ,sapload插件中增加一个args选项,格式如args:"PHPSESSID=<?php echo session_id(); ?>" 。这两个意思都是在post文件时,带上参数PHPSESSID(注:后端是PHP脚本)。

咱用不了cookie中的值,那就用POST过来值,然后在PHP脚本中处理一下:

if(isset($_POST['PHPSESSID'])){
    session_id($_POST['PHPSESSID']);
    session_start()
}

这样就可以使用当前登录正确的session id进行一系列操作了。

2、取消上传URL接口的登陆验证。(有点蛋疼,非必要情况下还是使用第一种方法吧)

 

 

附(这是给我自己看的,swfupload好像格式有所不同,如本文开头提到的,不知道是不是同一个插件):

flash 接口文档:

upUrl :后台文件的地址,必须要绝对路径

etmsg:每次文件上传成功后需不需要回调函数(0|1),默认回调函数为sapLoadMsg(x)函数。

ltmsg:最后一个文件上传成功后需不需要回调函数(0|1),默认回调函数为sapLoadMsg(x)函数。

types:上传文件的类型(如*.apk;*.jpg)用分号分隔各个类型

args:需要传递的参数(如apkName=123;apkName1=1234)用分号分隔各个类型

fileName:此参数可选,允许你自定义文件域名称,默认是Filedata。

maxNum:当最大上传数为1的时候自动切换到单文件上传模式,用户将不能在使用多选选取文件。

优酷真实视频地址解析

序:优酷之前更新了次算法(很久之前了,呵呵。。。),故此很多博客的解析算法已经无法使用。很多大牛也已经更新了新的解析方法。我也在此写篇解析过程的文章。(本文使用语言为C#)

由于优酷视频地址时间限制,在你访问本篇文章时,下面所属链接有可能已经失效,望见谅。

例:http://v.youku.com/v_show/id_XNzk2NTI0MzMy.html

1:获取视频vid

在视频url中标红部分。一个正则表达式即可获取。

string getVid(string url)
{
    string strRegex = "(?<=id_)(\\w+)";
    Regex reg = new Regex(strRegex);
    Match match = reg.Match(url);
    return match.ToString();
}

 

2:获取视频元信息

http://v.youku.com/player/getPlayList/VideoIDS/XNzk2NTI0MzMy/Pf/4/ctype/12/ev/1

将前述vid嵌入到上面url中访问即可得到视频信息文件。由于视频信息过长不在此贴出全部内容。下面是部分重要内容的展示。(获取文件为json文件,可直接解析)

{ "data": [ {
            "ip": 996949050,
            "ep": "NQXRTAodIbrd1vnC8+JxB4emuRs41w7DWho=",
            "segs": {
                "hd2": [
                    {
                        "no": "0",
                        "size": "34602810",
                        "seconds": 205,
                        "k": "248fe14b4c1b37302411f67a",
                        "k2": "1c8e113cecad924c5"
                    },
                    {
                        "no": "1",
                    },] }, } ],}

上面显示的内容后面都会使用到。其中segs包含hd3,hd2,flv,mp4,3gp等各种格式,并且每种格式下均分为若干段。本次选用清晰度较高的hd2(视频格式为flv)

3:拼接m3u8地址

http://pl.youku.com/playlist/m3u8?ctype=12&ep={0}&ev=1&keyframe=1&oip={1}&sid={2}&token={3}&type={4}&vid={5}

以上共有6个参数,其中vid和oip已经得到,分别之前的vid和json文件中的ip字段,即(XNzk2NTI0MzMy1991941296),但是ep,sid,token需要重新计算(json文件中的ep值不能直接使用)。type即为之前选择的segs。

3.1计算ep,sid,token

计算方法单纯的为数学计算,下面给出计算的函数。三个参数可一次性计算得到。其中涉及到Base64编码解码知识,点击查看

private static string myEncoder(string a, byte[] c, bool isToBase64)
        {
            string result = "";
            List<Byte> bytesR = new List<byte>();
            int f = 0, h = 0, q = 0;
            int[] b = new int[256];
            for (int i = 0; i < 256; i++)
                    b[i] = i;
            while (h < 256)
            {
                f = (f + b[h] + a[h % a.Length]) % 256;
                int temp = b[h];
                b[h] = b[f];
                b[f] = temp;
                h++;
            }
            f = 0; h = 0; q = 0;
            while (q < c.Length)
            {
                h = (h + 1) % 256;
                f = (f + b[h]) % 256;
                int temp = b[h];
                b[h] = b[f];
                b[f] = temp;
                byte[] bytes = new byte[] { (byte)(c[q] ^ b[(b[h] + b[f]) % 256]) };
                bytesR.Add(bytes[0]);
                result += System.Text.ASCIIEncoding.ASCII.GetString(bytes);
                q++;
            }
            if (isToBase64)
            {
                Byte[] byteR = bytesR.ToArray();
                result = Convert.ToBase64String(byteR);
            }
            return result;
        }
        public static void getEp(string vid, string ep, ref string pNew, ref string token, ref string sid)
        {
            string template1 = "becaf9be";
            string template2 = "bf7e5f01";
            byte[] bytes = Convert.FromBase64String(ep);
            ep = ystem.Text.ASCIIEncoding.ASCII.GetString(bytes);
            string temp = myEncoder(template1, bytes, false);
            string[] part = temp.Split('_');
            sid = part[0];
            token = part[1];
            string whole = string.Format("{0}_{1}_{2}", sid, vid, token);
            byte[] newbytes = System.Text.ASCIIEncoding.ASCII.GetBytes(whole);
            epNew = myEncoder(template2, newbytes, true);
        }

 

计算得到ep,token,sid分别为cCaVGE6OUc8H4ircjj8bMiuwdH8KXJZ0vESH/7YbAMZuNaHQmjbTwg==, 3825, 241273717793612e7b085。注意,此时ep并不能直接拼接到url中,需要对此做一下url编码ToUrlEncode(ep)。最终ep为cCaVGE6OUc8H4ircjj8bMiuwdH8KXJZ0vESH%2f7YbAMZuNaHQmjbTwg%3d%3d

3.2视频格式及清晰度

视频格式和选择的segs有密切关系。如本文选择的hd2,格式即为flv,下面是segs,视频格式和清晰度的对照。之前对此部分理解有些偏差,多谢削着苹果走路提醒。

“segs”,”视频格式”,”清晰度”
"hd3", "flv", "1080P"
"hd2", "flv", "超清"
"mp4", "mp4", "高清"
"flvhd", "flv", "高清"
"flv", "flv", "标清"
"3gphd", "3gp", "高清"

 

3.3拼接地址

  最后的m3u8地址为

http://pl.youku.com/playlist/m3u8?ctype=12&ep=cCaVGE6OUc8H4ircjj8bMiuwdH8KXJZ0vESH%2f7YbAMZuNaHQmjbTwg%3d%3d&ev=1&keyframe=1&oip=996949050&sid=241273717793612e7b085&token=3825&type=hd2&vid=XNzk2NTI0MzMy

4:获取视频地址

将上述m3u8文件下载后,其中内容即为真实地址,不过还需要稍微处理一下。部分内容如下:

#EXTM3U
#EXT-X-TARGETDURATION:12
#EXT-X-VERSION:3
#EXTINF:6.006,
http://59.108.137.14/65666E0ED34581E6B96293A18/0300010F005430BCBA49631468DEFEC61C5678-3A78-37BA-1971-21A0D4EEA0E7.flv?ts_start=0&ts_end=5.906&ts_seg_no=0&ts_keyframe=1
#EXTINF:5.464,
http://59.108.137.14/65666E0ED34581E6B96293A18/0300010F005430BCBA49631468DEFEC61C5678-3A78-37BA-1971-21A0D4EEA0E7.flv?ts_start=5.906&ts_end=11.37&ts_seg_no=1&ts_keyframe=1
#EXTINF:5.505,
http://59.108.137.14/65666E0ED34581E6B96293A18/0300010F005430BCBA49631468DEFEC61C5678-3A78-37BA-1971-21A0D4EEA0E7.flv?ts_start=11.37&ts_end=16.875&ts_seg_no=2&ts_keyframe=1
#EXTINF:9.26,
http://59.108.137.14/65666E0ED34581E6B96293A18/0300010F005430BCBA49631468DEFEC61C5678-3A78-37BA-1971-21A0D4EEA0E7.flv?ts_start=16.875&ts_end=26.135&ts_seg_no=3&ts_keyframe=1
#EXTINF:11.136,
http://59.108.137.14/65666E0ED34581E6B96293A18/0300010F005430BCBA49631468DEFEC61C5678-3A78-37BA-1971-21A0D4EEA0E7.flv?ts_start=26.135&ts_end=37.271&ts_seg_no=4&ts_keyframe=1
#EXTINF:8.258,
http://59.108.137.14/65666E0ED34581E6B96293A18/0300010F005430BCBA49631468DEFEC61C5678-3A78-37BA-1971-21A0D4EEA0E7.flv?ts_start=37.271&ts_end=45.529&ts_seg_no=5&ts_keyframe=1
#EXTINF:9.843,
http://59.108.137.14/65666E0ED34581E6B96293A18/0300010F005430BCBA49631468DEFEC61C5678-3A78-37BA-1971-21A0D4EEA0E7.flv?ts_start=45.529&ts_end=55.372&ts_seg_no=6&ts_keyframe=1
#EXTINF:10.26,
http://59.108.137.14/65666E0ED34581E6B96293A18/0300010F005430BCBA49631468DEFEC61C5678-3A78-37BA-1971-21A0D4EEA0E7.flv?ts_start=55.372&ts_end=65.632&ts_seg_no=7&ts_keyframe=1

其中每条url只包含6s左右视频,但是可将url中参数部分去掉即可得到实际的长度。但是每条去掉后需合并一下相同的url,如上述列表可得到url片段

http://59.108.137.14/65666E0ED34581E6B96293A18/0300010F005430BCBA49631468DEFEC61C5678-3A78-37BA-1971-21A0D4EEA0E7.flv

将m3u8中所有的url片段全部下载即可大功告成。

QQ截图20141008164115

原文地址:http://www.cnblogs.com/zhaojunjie/p/4009192.html

网站如何适配Retina屏幕

前言

随着2012年苹果发布第一款Retina Macbook Pro(以下简称RMBP),Retina屏幕开始进入笔记本行业。两年过去了,RMBP的市场占有率越来越高,且获得了一大批设计师朋友的青睐,网站对于Retina屏幕的适配越来越重要。

如果大家对于Retina适配的重要性不是特别清楚,请看我的两个截图:

QQ20140827-1@2x

上图是Google的首页LOGO,我们对比下图SOSO的LOGO:

QQ20140827-2@2x

如果大家还是看不出来,请自行访问这两个网站或者下载附件的截图对比。

那么说完了重要性,适配Retina的原理又是什么呢?我们知道,当一个图像在标准设备下全屏显示时,一位图像素对应的就是一设备像素,导致一个完全保 真的显示,因为一个位置像素不能进一步分裂。而当在Retina屏幕下时,他要放大四倍来保持相同的物理像素的大小,这样就会丢失很多细节,造成失真的情 形。换句话说,每一位图像素被乘以四填补相同的物理表面在视网膜屏幕下显示。(摘自《走向视网膜(Retina)的Web时代》)

那么,解决方法相信大家也都听过,就是通过手动制图或以编程的方式制作两种不同的图形,一张是普通屏幕的图片,另外一种是Retina屏幕的图形,而且Retina屏幕下的图片是普通屏幕的两倍像素。

原理虽然简单,在现实中要实现就不仅仅如此,需综合考虑加载速度,浏览器适配等多方面因素,本文就是教大家如何对Retina的屏幕进行适配。

正文

1.直接加载2倍大小的图片。

假如要显示的图片大小为200px*300px,你准备的实际图片大小应该为400px*600px,并且使用以下代码控制即可:

<img src="pic.png" height="200px" width="300px" />

这种方法就解决了Retina显示不清楚的问题,但是在普通屏幕下,这种图片要经过浏览器的压缩,在IE6和IE7上有十分差得显示效果,同时,两倍大小的图片势必会导致页面加载时间加长,用户体验下降,此时,我们可以通过Retina.js(http://retinajs.com/)文件解决:

    <img class="pic" src="pic.png" height="200px" width="300px"/>

    <script type="text/javascript">

    $(document).ready(function () {

    if (window.devicePixelRatio > 1) {

    var images = $("img.pic");

    images.each(function(i) {

    var x1 = $(this).attr('src');

    var x2 = x1.replace(/(.*)(.w+)/, "$1@2x$2");

    $(this).attr('src', x2);

    });

    }

    });

    </script>

 

2.Image-set控制

假如要显示的图片大小为200px*300px,你准备的图片应有两张:一张大小为200px*300px,命名为pic.png;另一张大小为 400px*600px,命名为pic@2x.png(@2x是Retina图标的标准命名方式),然后使用以下css代码控制:

    #logo {

    background: url(pic.png) 0 0 no-repeat;

    background-image: -webkit-image-set(url(pic.png) 1x, url(pic@2x.png) 2x);

    background-image: -moz-image-set(url(pic.png) 1x,url(images/pic@2x.png) 2x);

    background-image: -ms-image-set(url(pic.png) 1x,url(images/pic@2x.png) 2x);

    background-image: -o-image-set(url(url(pic.png) 1x,url(images/pic@2x.png) 2x);

    }

或者使用HTML代码控制亦可:

<img src="pic.png" srcset="pic@2x.png 2x" />

 

3.使用@media控制

实际是判断屏幕的像素比来取舍是否显示高分辨率图像,代码如下:

    @media only screen and (-webkit-min-device-pixel-ratio: 1.5),

           only screen and (min--moz-device-pixel-ratio: 1.5), /* 注意这里的写法比较特殊 */

           only screen and (-o-min-device-pixel-ratio: 3/2),

           only screen and (min-device-pixel-ratio: 1.5) {

    #logo {

    background-image: url(pic@2x.png);

    background-size: 100px auto;

    }

    }

使用这个的确定就是IE6、7、8不支持@media,所以无效。但是如果你只是支持苹果的RMBP的话,不存在兼容问题,因为MacOS X上压根没有IE!哈哈哈!

OK,本文到这里就结束了,介绍了上面的三个办法大家可以各有取舍的使用吧~

附件:附件

原文地址:http://www.ui.cn/project.php?id=24556

两个宜于『统一Web』的方案:响应式与自适应

1377683186626你大概早就听别人说过我们正处在『后PC时代』。那么它对于Web开发者来说意味着什么呢?它意味着你那网站的30%到50%的流量如今已经来自移动设备。意味着很快,使用台式机和笔记本访问Web的人将逐渐减少。

(那么)我们该如何在用户行为上处理这种结构性转变?我们已经逾越了用M-dot或T-dot来hack的阶段,开始步入一个由响应式与自适应设计 技术统治的时代——即W3C联盟口中的统一化Web来临。W3C议案的关键部分在于『统一化Web意味着,在合理条件下,无论用户使用什么设备,要将相同 的信息与服务传达给他们。』

对于开发者来说,那就意味着统一化Web方案要确保你的站点不仅工作在今天的智能手机和平板电脑上,也要能应付未来那遥未可知的屏幕画面。

目前有三种流行性方案来开发网站:使用响应式设计、 客户端自适应、服务器端自适应。

这三种方案难分高下,每种都有其自身的优缺点。明智的Web开发者会在实施下一个项目之前权衡各自的利弊。

响应式Web设计

响应式Web设计是最常见的统一化Web方案。它使用CSS Media Queries根据设备显示器的规格来调整网站的呈现方式。从波士顿环球报到迪斯尼站点到Indochino西服网站,响应式站点的数量正在飞速增长。

这种方案的核心优势在于设计者可以为所有设备使用同一模板,仅用CSS来定制不同大小屏幕上内容的呈现方式即可。

然而,一个良好的响应式设计没什么捷径可言。要迈向响应式,团队通常需要进行完全的站点重建。

在设计和测试阶段会非常繁琐,保证为每种可能的设备或内容定制用户体验很困难。我们都见过那样一堆看起来一点儿都不吻合的碎片拼图站点。在移动用户优先考虑的开发过程中,响应式站点设计可以与设备结合得很融洽。对于平板和手提电脑来说,(响应式设计)则更有优势

对响应式站点来说,性能也很可怕。在Mobify公司,我们近期完成了一项对15个流行的响应式电子商务站点的分析。在这些站点中,主页平均加载87个资源、1.9MB的数据量。一些响应式页面可以大到15MB。

之所以数据量这么大是因为响应式方案需考虑到所有设备。你的用户只使用一种设备,但是不得不在使用前等待所有的页面元素和资源加载才行。简单来说,性能影响你的底线。在智能手机端,当用户不得不等上一秒时,转化率会额外降低3.5%。若是达到3秒大关,57%的用户会彻底离开你的站点。

当响应式设计迅速成为普遍的标准时,也为在线电商带来了新的挑战,包括怎样处理图片、怎样优化移动端性能,并且当采用移动优先的方案时,通常意味着网站必须推倒重建。

客户端自适应

客户端自适应秉承着『为定制的设备
内容传达响应式设计用户体验』的原则。它采用JavaScript丰富站点的功能和独特性。比如,自适应站点只为Retina显示器(比如新款iPad)提供Retina质量的图片,而标准精度显示器接收低质量图片。

自适应设计有两种方案:一种是在客户端生效,在用户的浏览器上;另一种是由Web服务器负荷来检测设备类型并加载正确模板。客户端自适应类型的网站 案例包括个性T恤Threadless和奢侈品闪购ideeli。自适应模板方案的优势在于其复用HTML和JavaScript的能力,简化了项目管理 和测试的更迭。

客户端响应方案意味着你不必完全重构网站。相反你可以基于已有内容布局移动响应。对于专家级开发者而言,这种方案也可以让你针对指定设备或屏幕分辨 率。例如,对Mobify的大多在线时尚零售客户而言,95%的移动端流量来自iPhone。客户端自适应意味着他们可以专门针对苹果智能手机进行优化。

与响应式设计不同,自适应模板确保客户端设备只加载所需资源。因为对设备和特性的检测已经转移到了移动设备自身,类似Akamai和Edgecast的内容分发网络可以在不影响用户体验的情况下使用他们大部分的缓存功能。

客户端自适应比起响应式设计而言有着更高的壁垒。开发者用这项技术需要熟练掌握JavaScript,也依托于网站现有的模板为基础。最后,因为客户端自适应作为现有底层代码上的覆盖物,你需要在网站迁移时让它们作为一个整体。

服务器端自适应

依赖于服务器插件和自定义用户代理检测器我们也可以通过多种途径得到服务器端自适应方案。使用服务器端自适应的网站包括Etsy,One Kings Lane和OnlineShoes.com。

为什么要选择服务器端自适应?它通常针对每个设备提供独特的模板,允许更多的定制。并且这种方案把设备检测逻辑块放在服务器上,使得小型移动页面加载得更快。除此之外,对于像Magneto这种常见的CMS和电子商务系统来说,还有多种多样可用的服务器端插件。

这种方案不适用于小心脏——通常需要你大幅度改变后端系统,实施起来将会是冗长(和昂贵)的。管理多种模板需要有持续不断的维护成本。最后,当服务 器过载时这种方案还会带来性能问题。当服务器上加载移动用户代理检测器的时候,需要关掉许多部署在CDN上的缓存机制,像Akamai一样,这将导致移动 端和桌面访客的用户体验速率降低。

当然,许多公司仍然在跟响应的基本要领角力,显然还没有准备好面对重口味的自适应。然而,随着竞争的加剧和移动通信流量的上涨,越来越多的团队将在所有三个方法上浅尝辄止,(最终)择出一个最适合他们用户的方案。

IE678下链接图片点不中问题讨论

QQ截图20140626150002因为今天制作的房产项目中出现了图片链接无法点击问题,重新审视了一下块集元素和内联元素,不是混淆,而是重新想了一下新的东西,有时候在我们进行重构的时候,偶尔会为了实际需要而将内联元素强制显示为块集元素,在一般情况下,这个是不会出问题的,而且可以很轻松的通过标准,但是在某一些情况下,虽然验证工具可以放过我们,但是在ie678下面会出现解析不正确的问题,下面就简单跟大家分享一下这个问题。

有时候由于需要文字和图片同时获取到点击态,或者需要实现一些高难度的效果,就需要多标签来写结构,可能的结构会如下所示:

<div class="block">
<a href="http://www.hualongxiang.com"><span><img src="http://static.hualongxiang.com/logo/logo.jpg" alt="hlx" /></span></a>
</div>

在没有被赋予强大的CSS的时候,是可以正常点击的,但是当赋予需要的CSS的时候,在ie8一下就会出现图片区域点击不到的情况:

.block span{float:left;}

如果将span标签块集化:

.block span{float:left;dispaly:block;width:100px;height:100px;}

最终的结果依然不是不能点击到,但是图片区域以为的内容确可以正常点击。

而对于此,我的理解是这样的:

因为a标签本身就是一个内联元素标签,内联标签内正常情况只允许放置内联元素,放置块集元素本身语法就有问题,虽然表面上放置的span标签,但是我们又将其强制块集化,在某些浏览器下还是会解析错误,比如说ie6/7,所以这里针对链接的这种情况,出现的问题就是图片区域无法点击。

针对这种问题的解决方法:

保证在a标签中不要放置块集元素,或者强制块集元素,如果需要解决一些特殊效果,可以采取将这个强制元素跟a内置的img标签同级放置,这样也可以避免图片点击不到问题。

再谈javascript图片预加载技术

lightbox类效果为了让图片居中显示而使用预加载,需要等待完全加载完毕才能显示,体验不佳(如filick相册的全屏效果)。javascript无法获取img文件头数据,真的是这样吗?本文通过一个巧妙的方法让javascript获取它。

这是大部分人使用预加载获取图片大小的例子:

var imgLoad = function (url, callback) {
	var img = new Image();

	img.src = url;
	if (img.complete) {
		callback(img.width, img.height);
	} else {
		img.onload = function () {
			callback(img.width, img.height);
			img.onload = null;
		};
	};

};

 

可以看到上面必须等待图片加载完毕才能获取尺寸,其速度不敢恭维,我们需要改进。

web应用程序区别于桌面应用程序,响应速度才是最好的用户体验。如果想要速度与优雅兼得,那就必须提前获得图片尺寸,如何在图片没有加载完毕就能获取图片尺寸?

十多年的上网经验告诉我:浏览器在加载图片的时候你会看到图片会先占用一块地然后才慢慢加载完毕,并且不需要预设width与height属性,因为浏览器能够获取图片的头部数据。基于此,只需要使用javascript定时侦测图片的尺寸状态便可得知图片尺寸就绪的状态。

当然实际中会有一些兼容陷阱,如width与height检测各个浏览器的不一致,还有webkit new Image()建立的图片会受以处在加载进程中同url图片影响,经过反复测试后的最佳处理方式:

// 更新:
// 05.27: 1、保证回调执行顺序:error > ready > load;2、回调函数this指向img本身
// 04-02: 1、增加图片完全加载后的回调 2、提高性能

/**
 * 图片头数据加载就绪事件 - 更快获取图片尺寸
 * @version	2011.05.27
 * @author	TangBin
 * @see		http://www.planeart.cn/?p=1121
 * @param	{String}	图片路径
 * @param	{Function}	尺寸就绪
 * @param	{Function}	加载完毕 (可选)
 * @param	{Function}	加载错误 (可选)
 * @example imgReady('http://www.google.com.hk/intl/zh-CN/images/logo_cn.png', function () {
		alert('size ready: width=' + this.width + '; height=' + this.height);
	});
 */
var imgReady = (function () {
	var list = [], intervalId = null,

	// 用来执行队列
	tick = function () {
		var i = 0;
		for (; i < list.length; i++) {
			list[i].end ? list.splice(i--, 1) : list[i]();
		};
		!list.length && stop();
	},

	// 停止所有定时器队列
	stop = function () {
		clearInterval(intervalId);
		intervalId = null;
	};

	return function (url, ready, load, error) {
		var onready, width, height, newWidth, newHeight,
			img = new Image();

		img.src = url;

		// 如果图片被缓存,则直接返回缓存数据
		if (img.complete) {
			ready.call(img);
			load && load.call(img);
			return;
		};

		width = img.width;
		height = img.height;

		// 加载错误后的事件
		img.onerror = function () {
			error && error.call(img);
			onready.end = true;
			img = img.onload = img.onerror = null;
		};

		// 图片尺寸就绪
		onready = function () {
			newWidth = img.width;
			newHeight = img.height;
			if (newWidth !== width || newHeight !== height ||
				// 如果图片已经在其他地方加载可使用面积检测
				newWidth * newHeight > 1024
			) {
				ready.call(img);
				onready.end = true;
			};
		};
		onready();

		// 完全加载完毕的事件
		img.onload = function () {
			// onload在定时器时间差范围内可能比onready快
			// 这里进行检查并保证onready优先执行
			!onready.end && onready();

			load && load.call(img);

			// IE gif动画会循环执行onload,置空onload即可
			img = img.onload = img.onerror = null;
		};

		// 加入队列中定期执行
		if (!onready.end) {
			list.push(onready);
			// 无论何时只允许出现一个定时器,减少浏览器性能损耗
			if (intervalId === null) intervalId = setInterval(tick, 40);
		};
	};
})();

调用例子:

imgReady('http://pic1.hualongxiang.com/attachment/photo/Mon_1405/343656_7b0313991240910abfa97883b96b9.jpg', function () {
	alert('size ready: width=' + this.width + '; height=' + this.height);
});

是不是很简单?这样的方式获取摄影级别照片尺寸的速度往往是onload方式的几十多倍,而对于web普通(800×600内)浏览级别的图片能达到秒杀效果。看了这个再回忆一下你见过的web相册,是否绝大部分都可以重构一下呢?好了,请观赏令人愉悦的 DEMO :

http://www.planeart.cn/demo/imgReady/

(通过测试的浏览器:Chrome、Firefox、Safari、Opera、IE6、IE7、IE8)

 

原文地址:http://www.planeart.cn/?p=1121

JavaScript中的匿名函数及函数的闭包

1、匿名函数

函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途。匿名函数:就是没有函数名的函数。

1.1 函数的定义,首先简单介绍一下函数的定义,大致可分为三种方式

第一种:这也是最常规的一种

function double(x){
    return 2 * x;   
}

第二种:这种方法使用了Function构造函数,把参数列表和函数体都作为字符串,很不方便,不建议使用。

var double = new Function('x', 'return 2 * x;');

第三种:

var double = function(x) { return 2* x; }

注意“=”右边的函数就是一个匿名函数,创造完毕函数后,又将该函数赋给了变量square。

1.2 匿名函数的创建

第一种方式:就是上面所讲的定义square函数,这也是最常用的方式之一。

第二种方式:

(function(x, y){
    alert(x + y);  
})(2, 3);

这里创建了一个匿名函数(在第一个括号内),第二个括号用于调用该匿名函数,并传入参数。

2、闭包

闭包的英文单词是closure,这是JavaScript中非常重要的一部分知识,因为使用闭包可以大大减少我们的代码量,使我们的代码看上去更加清晰等等,总之功能十分强大。

闭包的含义:闭包说白了就是函数的嵌套,内层的函数可以使用外层函数的所有变量,即使外层函数已经执行完毕(这点涉及JavaScript作用域链)。

示例一

function checkClosure(){
    var str = 'rain-man';
    setTimeout(
        function(){ alert(str); } //这是一个匿名函数
    , 2000);
}
checkClosure();

这个例子看上去十分的简单,仔细分析下它的执行过程还是有许多知识点的:checkClosure函数的执行是瞬间的(也许用时只是0.00001毫秒),在checkClosure的函数体内创建了一个变量str,在checkClosure执行完毕之后str并没有被释放,这是因为setTimeout内的匿名函数存在这对str的引用。待到2秒后函数体内的匿名函数被执行完毕,str才被释放。

示例二,优化代码

function forTimeout(x, y){
    alert(x + y);
}
function delay(x , y  , time){
    setTimeout('forTimeout(' +  x + ',' +  y + ')' , time);    
}
/**
 * 上面的delay函数十分难以阅读,也不容易编写,但如果使用闭包就可以让代码更加清晰
 * function delay(x , y , time){
 *     setTimeout(
 *         function(){
 *             forTimeout(x , y) 
 *         }          
 *     , time);   
 * }
 */

3、举例

匿名函数最大的用途是创建闭包(这是JavaScript语言的特性之一),并且还可以构建命名空间,以减少全局变量的使用。

示例三:

var oEvent = {};
(function(){ 
    var addEvent = function(){ /*代码的实现省略了*/ };
    function removeEvent(){}

    oEvent.addEvent = addEvent;
    oEvent.removeEvent = removeEvent;
})();

在这段代码中函数addEvent和removeEvent都是局部变量,但我们可以通过全局变量oEvent使用它,这就大大减少了全局变量的使用,增强了网页的安全性。 我们要想使用此段代码:oEvent.addEvent(document.getElementById('box') , 'click' , function(){});

示例四:

var rainman = (function(x , y){
    return x + y;
})(2 , 3);
/**
 * 也可以写成下面的形式,因为第一个括号只是帮助我们阅读,但是不推荐使用下面这种书写格式。
 * var rainman = function(x , y){
 *    return x + y;
 * }(2 , 3);
 */

在这里我们创建了一个变量rainman,并通过直接调用匿名函数初始化为5,这种小技巧有时十分实用。

示例五:

var outer = null;

(function(){
    var one = 1;
    function inner (){
        one += 1;
        alert(one);
    }
    outer = inner;
})();

outer();    //2
outer();    //3
outer();    //4

这段代码中的变量one是一个局部变量(因为它被定义在一个函数之内),因此外部是不可以访问的。但是这里我们创建了inner函数,inner函数是可以访问变量one的;又将全局变量outer引用了inner,所以三次调用outer会弹出递增的结果。

4、注意

4.1 闭包允许内层函数引用父函数中的变量,但是该变量是最终值

示例六:

/**
 * <body>
 * <ul>
 *     <li>one</li>
 *     <li>two</li>
 *     <li>three</li>
 *     <li>one</li>
 * </ul>
 */

var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
    lists[ i ].onmouseover = function(){
        alert(i);    
    };
}

你会发现当鼠标移过每一个<li&rt;元素时,总是弹出4,而不是我们期待的元素下标。这是为什么呢?注意事项里已经讲了(最终值)。显然这种解释过于简单,当mouseover事件调用监听函数时,首先在匿名函数( function(){ alert(i); })内部查找是否定义了 i,结果是没有定义;因此它会向上查找,查找结果是已经定义了,并且i的值是4(循环后的i值);所以,最终每次弹出的都是4。

解决方法一:

var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
    (function(index){
        lists[ index ].onmouseover = function(){
            alert(index);    
        };                    
    })(i);
}

解决方法二:

var lists = document.getElementsByTagName('li');
for(var i = 0, len = lists.length; i < len; i++){
    lists[ i ].$$index = i;    //通过在Dom元素上绑定$$index属性记录下标
    lists[ i ].onmouseover = function(){
        alert(this.$$index);    
    };
}

解决方法三:

function eventListener(list, index){
    list.onmouseover = function(){
        alert(index);
    };
}
var lists = document.getElementsByTagName('li');
for(var i = 0 , len = lists.length ; i < len ; i++){
    eventListener(lists[ i ] , i);
}