分类目录归档:Javascript/jQuery

Javascript计算图像灰度

对应一张图片,如果把文字写上去,随着图片拉伸,文字会变得不清晰。为了保证图像清晰,可以通过控制样式把文字叠加上去,但紧接着就遇到一个新问题,文字可能被图片吃掉,比如完全黑色的图片,这个字体设置为黑色,那么文字就不会显示了。所以文字需要根据图片的灰度来自动调整。

一个像素点是RGBA四个值(A这里忽略),计算公式:
Gray = R*0.299 + G*0.587 + B*0.114
由于JavaScript对于浮点数计算性能较差,一般会换算成整数计算(后面那个除法是整数除法,所以需要加上500来实现四舍五入):
Gray = (R*299 + G*587 + B*114 + 500) / 1000

知道如何换算灰度,那接下来就是如何获取到图像的像素的RGB。H5中提供了canvas对象,可以用来绘制图像。

var averageGray = function (src, x, y, width, height) {
        var image = new Image();
        image.src = src;
        if (!image.complete) {
            return 0;
        }
        if (width < 1) {
            width = image.width;
        }
        if (height < 1) {
            height = image.height;
        }
        //var canvas = document.createElement('__canvas__');
        var canvas = document.getElementById('sliderCanvas');
        var context = canvas.getContext('2d');
        context.drawImage(image, 0, 0, image.width, image.height);
        x = x > image.width ? image.width : x;
        y = y > image.height ? image.height : y;
        width = width > image.width - x ? image.width - x : width;
        height = height > image.height - y ? image.height - x : height;
        var dots = context.getImageData(x, y, width, height).data;
        var length = parseInt(dots.length / 4);
        var r = 0, g = 0, b = 0, gray = 0;
        for (var i = 0; i < length; i++) {
            r = dots[i];
            g = dots[i + 1];
            b = dots[i + 2];
            gray += (r * 299 + g * 587 + b * 114 + 500) / 1000;
        }
        return gray / length;
    };

计算成平均灰度,然后根据这个灰度设置字体颜色。对于色彩比较复杂的图片,这个方法还是不合适,只有人工针对这个图片指定一个颜色了。

jQuery 实例

一 根据窗口大小,调整位置

$(function () {
    var fitIt = function (hold, items, limit) {
        limit = parseInt(limit, 10);
        if (!limit || limit > 10) {
            limit = 2;
        }
        var width = hold.width();
        hold.css({'position': 'relative'});
        items.css({'position': 'absolute'});
        if (width > 767) {
            items.css({'width': 100 / limit + '%'});
            var ht = 0;
            $.each(items, function (i, v) {
                var row = parseInt(i / limit, 10);
                var column = i % limit;
                var left = 0;
                var top = 0;
                if (column - 1 >= 0) {
                    left = items.eq(row * limit + column - 1).outerWidth();
                }
                if (row - 1 >= 0) {
                    top = items.eq((row - 1) * limit + column).outerHeight();
                }
                if (column === 0) {
                    ht += $(this).outerHeight();
                }
                $(this).stop().animate({'top': top, 'left': left}, 400);
            });
            hold.css({'height': ht + 10});
        } else {
            var total = 0;
            items.css({'width': '100%'});
            var prev = 0;
            $.each(items, function (i, v) {
                if (i > 0) {
                    prev += items.eq(i - 1).outerHeight();
                }
                total += $(this).outerHeight();
                items.eq(i).stop().animate({'top': prev, 'left': 0}, 400);
            });
            hold.css({'height': total + 10});
        }
    };
    $(window).resize(function () {
        var hold = $(".newIn .newInWrap:first");
        var items = $(".newIn .newInWrap .newInItem");
        fitIt(hold, items, 2);
    });
    $(document).trigger('resize');
});

iPad的像素是768,所以767作为移动端和PC端的分界点。大于767就是PC和平板设备。多个图片进行排列,比如一行两个或三个,通过样式,可以控制随着屏幕拉伸就自动拉伸,不过需要应用一个动画,就需要用JavaScript来控制了。还有一个问题是:如果图片的高度不一致(需要形成一个高低搭配),为了排版美观性,需要动态计算高度和偏移。

以上代码就是循环计算每个元素的左偏移和上偏移,在重新适应到新位置时,应用一个动画。在屏幕不断拉伸缩小过程中,动画可能会进行多次,所以在元素应用动画前首先停止之前的所有动画(stop()方法)。计算出总高度后,设置容器高度。

二 在以上算法过程中改动一下,计算每行最高元素,下一行元素的上偏移以这个最高元素为偏移,并应用一个动画

$(function () {
    var fitTo = function (hold, items, limit) {
        limit = parseInt(limit, 10);
        if (!limit || limit > 10) {
            limit = 3;
        }
        var width = hold.width();
        hold.css({'position': 'relative'});
        items.css({'position': 'absolute'});
        if (width > 767) {
            var p = limit > 3 ? (100 / limit - 0.1) : (100 / limit - 0.1);
            items.css({'width': p + '%'});
            var itemWidth = items.eq(0).width();
            var mapTo = {'-1': 0};
            // Max
            $.each(items, function (i, v) {
                var row = parseInt(i / limit, 10);
                var itemHeight = $(this).outerHeight();
                if (!mapTo.hasOwnProperty(row)) {
                    mapTo[row] = itemHeight;
                } else if (itemHeight > mapTo[row]) {
                    mapTo[row] = itemHeight;
                }
            });
            // Fit
            $.each(items, function (i, v) {
                var row = parseInt(i / limit, 10);
                var top = mapTo[row - 1];
                var column = i % limit;
                $(this).stop().animate({'top': top * row, 'left': column * itemWidth}, 400);
            });
            var heightTotal = 0;
            $.each(mapTo, function (i, v) {
                heightTotal += v;
            });
            hold.css({'height': heightTotal});
        } else {
            var total = 0;
            items.css({'width': '100%'});
            var prev = 0;
            $.each(items, function (i, v) {
                if (i > 0) {
                    prev += items.eq(i - 1).height() + 5;
                }
                total += $(this).height() + 5;
                items.eq(i).stop().animate({'top': prev, 'left': 0}, 400);
            });
            hold.css({'height': total});
        }
    };
    $(window).resize(function () {
        var hold = $(".pressBodyList:first");
        var items = $(".pressBodyList .pressListItem");
        fitTo(hold, items, 3);
    });
    $(document).trigger('resize');
});

HTML编辑器 – UEditor

UEditor 是一个功能比较齐全的HTML编辑器,百度出品,官方网站为:http://ueditor.baidu.com/website/。这个编辑器对于文件上传方面提供了强大支持,比如可以直接粘贴(CTRL + V)图片,上传涂鸦文件(客户端画图),多文件上传等。

其中有一个叫UMeditor的产品,简称UM,它是UEditor(简称UE)的功能缩减版本。也可以使用其提供的Ubuilder来构建一个自定义的版本(主要指功能模块)。

一般直接使用UEditor这个全功能版本即可:
ueditor

目录结构:
ueditor-struct
目录dialogs对应各种弹框,目录lang对应语言包(前端展示),目录themes对应皮肤,third-party是第三方插件。文件ueditor.all.js是编辑器实现代码,可见未压缩有1M以上,min压缩后也有接近400K,问津ueditor.config.js是前端的主要配置。

前台主要配置都在ueditor.config.js中,这个文件几乎不用改变,一般需要修改的地方就是serverUrl参数,当有图片或文件需要上传时,就提交到这个指定的地址。其它配置项在这个文件中都有详细的注释(比如皮肤,语言等):

(function () {
    var URL = window.UEDITOR_HOME_URL || getUEBasePath();
    var origin = getOrigin();

    /**
     * 配置项主体。注意,此处所有涉及到路径的配置别遗漏URL变量。
     */
    window.UEDITOR_CONFIG = {

        //为编辑器实例添加一个路径,这个不能被注释
        UEDITOR_HOME_URL: URL

        // 服务器统一请求接口路径
        , serverUrl: origin + "/ueditor/server"
    };
    // ...
    // ...
    function getOrigin() {
        if (typeof location.origin === 'undefined') {
            location.origin = location.protocol + '//' + location.host;
        }
        return location.origin;
    }

    window.UE = {
        getUEBasePath: getUEBasePath,
        getOrigin: getOrigin
    };
})();

其中getOrigin()原始文件是没有的,这个用来解决原来的函数获取基本路径不准确的问题。

以上配置指定了/ueditor/serve,那么文件上传时都会POST到这个地址。所以后端需要处理文件上传的逻辑,原本下载包已经提供了相关的实现,不过为了真正可用,需要做一些改造,比如需要验证:

// 抽象类,Upload, 处理文件上传逻辑,有上个子类:UploadCatch.php 、 UploadFile.php 、 UploadScrawl.php
// 分别实现fire()方法,这个方法实际是读取配置,处理上传逻辑
<?php
namespace UEditor;

abstract class Upload
{
    //文件域名
    protected $fileField;
    //文件上传对象
    protected $file;
    //文件上传对象
    protected $base64;
    //配置信息
    protected $config;
    //原始文件名
    protected $oriName;
    //新文件名
    protected $fileName;
    //完整文件名,即从当前配置目录开始的URL
    protected $fullName;
    //完整文件名,即从当前配置目录开始的URL
    protected $filePath;
    //文件大小
    protected $fileSize;
    //文件类型
    protected $fileType;
    //上传状态信息,
    protected $stateInfo;
    //上传状态映射表,国际化用户需考虑此处数据的国际化
    protected $stateMap = array(
        //上传成功标记,在UEditor中内不可改变,否则flash判断会出错
        "SUCCESS",
        "文件大小超出 upload_max_filesize 限制",
        "文件大小超出 MAX_FILE_SIZE 限制",
        "文件未被完整上传",
        "没有文件被上传",
        "上传文件为空",
        "ERROR_TMP_FILE" => "临时文件错误",
        "ERROR_TMP_FILE_NOT_FOUND" => "找不到临时文件",
        "ERROR_SIZE_EXCEED" => "文件大小超出网站限制",
        "ERROR_TYPE_NOT_ALLOWED" => "文件类型不允许",
        "ERROR_CREATE_DIR" => "目录创建失败",
        "ERROR_DIR_NOT_WRITEABLE" => "目录没有写权限",
        "ERROR_FILE_MOVE" => "文件保存时出错",
        "ERROR_FILE_NOT_FOUND" => "找不到上传文件",
        "ERROR_WRITE_CONTENT" => "写入文件内容错误",
        "ERROR_UNKNOWN" => "未知错误",
        "ERROR_DEAD_LINK" => "链接不可用",
        "ERROR_HTTP_LINK" => "链接不是http链接",
        "ERROR_HTTP_CONTENTTYPE" => "链接contentType不正确",
        "INVALID_URL" => "非法 URL",
        "INVALID_IP" => "非法 IP"
    );

    public function __construct(array $config, $request)
    {
        $this->config = $config;
        $this->request = $request;
        $this->fileField = $this->config['fieldName'];
        if (isset($config['allowFiles'])) {
            $this->allowFiles = $config['allowFiles'];
        } else {
            $this->allowFiles = [];
        }
    }

    abstract function fire();

    public function handle()
    {
        $this->fire();
        return $this->getFileInfo();
    }

    /**
     * 上传错误检查
     * @param $errCode
     * @return string
     */
    protected function getStateInfo($errCode)
    {
        return !$this->stateMap[$errCode] ? $this->stateMap["ERROR_UNKNOWN"] : $this->stateMap[$errCode];
    }

    /**
     * 文件大小检测
     * @return bool
     */
    protected function checkSize()
    {
        return $this->fileSize <= ($this->config["maxSize"]);
    }

    /**
     * 获取文件扩展名
     * @return string
     */
    protected function getFileExt()
    {
        return '.' . $this->file->guessExtension();
    }

    /**
     * 重命名文件
     * @return string
     */
    protected function getFullName()
    {
        //替换日期事件
        $t = time();
        $d = explode('-', date("Y-y-m-d-H-i-s"));
        $format = $this->config["pathFormat"];
        $format = str_replace("{yyyy}", $d[0], $format);
        $format = str_replace("{yy}", $d[1], $format);
        $format = str_replace("{mm}", $d[2], $format);
        $format = str_replace("{dd}", $d[3], $format);
        $format = str_replace("{hh}", $d[4], $format);
        $format = str_replace("{ii}", $d[5], $format);
        $format = str_replace("{ss}", $d[6], $format);
        $format = str_replace("{time}", $t, $format);

        //过滤文件名的非法自负,并替换文件名
        $oriName = substr($this->oriName, 0, strrpos($this->oriName, '.'));
        $oriName = preg_replace("/[\|\?\"\<\>\/\*\\\\]+/", '', $oriName);
        $format = str_replace("{filename}", $oriName, $format);

        //替换随机字符串
        $randNum = rand(1, 10000000000) . rand(1, 10000000000);
        if (preg_match("/\{rand\:([\d]*)\}/i", $format, $matches)) {
            $format = preg_replace("/\{rand\:[\d]*\}/i", substr($randNum, 0, $matches[1]), $format);
        }

        $ext = $this->getFileExt();
        return $format . $ext;
    }

    /**
     * 获取文件完整路径
     * @return string
     */
    protected function getFilePath()
    {
        $fullName = $this->fullName;
        $rootPath = public_path();
        $fullName = ltrim($fullName, '/');

        return $rootPath . '/' . $fullName;
    }

    /**
     * 文件类型检测
     * @return bool
     */
    protected function checkType()
    {
        return in_array($this->getFileExt(), $this->config["allowFiles"]);
    }

    /**
     * 获取当前上传成功文件的各项信息
     * @return array
     */
    public function getFileInfo()
    {
        return array(
            "state" => $this->stateInfo,
            "url" => $this->fullName,
            "title" => $this->fileName,
            "original" => $this->oriName,
            "type" => $this->fileType,
            "size" => $this->fileSize
        );
    }
}


// 文件上传
class UploadFile extends Upload
{
    public function fire()
    {
        $file = $this->request->file($this->fileField);
        if (empty($file)) {
            $this->stateInfo = $this->getStateInfo("ERROR_FILE_NOT_FOUND");
            return false;
        }
        if (!$file->isValid()) {
            $this->stateInfo = $this->getStateInfo($file->getError());
            return false;
        }

        $this->file = $file;
        $this->oriName = $this->file->getClientOriginalName();
        $this->fileSize = $this->file->getSize();
        $this->fileType = $this->getFileExt();
        $this->fullName = $this->getFullName();
        $this->filePath = $this->getFilePath();
        $this->fileName = basename($this->filePath);

        //检查文件大小是否超出限制
        if (!$this->checkSize()) {
            $this->stateInfo = $this->getStateInfo("ERROR_SIZE_EXCEED");
            return false;
        }
        //检查是否不允许的文件格式
        if (!$this->checkType()) {
            $this->stateInfo = $this->getStateInfo("ERROR_TYPE_NOT_ALLOWED");
            return false;
        }

        if (config('ueditor.drivers.default') == 'local') {
            try {
                $this->file->move(dirname($this->filePath), $this->fileName);
                $this->stateInfo = $this->stateMap[0];
            } catch (\Symfony\Component\HttpFoundation\File\Exception\FileException $exception) {
                $this->stateInfo = $this->getStateInfo("ERROR_WRITE_CONTENT");
                return false;
            }
        } else {
            $this->stateInfo = $this->getStateInfo("ERROR_UNKNOWN");
            return false;
        }
        return true;
    }
}

要真正可用,还需要结合具体使用的框架做配置,比如在Laravel框架中,经过一些改造,就可以继承到项目中,如下是实际使用部署:
ueditor.zip

至于使用,则非常简单,需要编辑器的地方引入:

<script type="text/javascript" charset="utf-8" src="/ueditor/ueditor.config.js"></script>
<script type="text/javascript" charset="utf-8" src="/ueditor/ueditor.all.min.js"> </script>
<script type="text/javascript" charset="utf-8" src="/ueditor/lang/zh-cn/zh-cn.js"></script>

然后插入类似如下模板:

<div>
    <script id="editor" type="text/plain" style="width:1024px;height:500px;"></script>
</div>

<script type="text/javascript">

    //实例化编辑器
    //建议使用工厂方法getEditor创建和引用编辑器实例,如果在某个闭包下引用该编辑器,直接调用UE.getEditor('editor')就能拿到相关的实例
    var ue = UE.getEditor('editor');
</script>

具体的配置和使用,可以参考:http://fex.baidu.com/ueditor/

PhantomJS 模拟登录二

以下的模拟登录程序,流程跟直接操作浏览器一致,不相同的地方是一个是程序开做,一个是人来做。

"use strict";
var page = require('webpage').create();

page.onResourceRequested = function (req) {
    //console.log('requested: ' + JSON.stringify(req, undefined, 4));
};
page.onResourceReceived = function (res) {
    //console.log('received: ' + JSON.stringify(res, undefined, 4));
};
page.onConsoleMessage = function(message) {
    console.log(message);
};


// 访问需要登录的页面
var url = 'http://my.sandbox.ebay.com/ws/eBayISAPI.dll?MyEbay&gbh=1&CurrentPage=MyeBayAllSelling&ssPageName=STRK:ME:LNLK:MESX';

page.open(url, function (status) {  
    if (status === 'success') {
        page.includeJs("http://xxx:8888/js/jquery.min.js", function() {
           var res = page.evaluate(function() {
                var str = '';
                $("table.my_itl-iT tr.my_itl-itR td a.g-asm").each(function(){
                    str += $(this).text()+"\n";
                });
                return str;
           });
           
           console.log(res);
            
           phantom.exit();
        });
    } else {
        phantom.exit();
    }
});

/*
page.open('https://signin.sandbox.ebay.com/ws/eBayISAPI.dll', function (status) {
    if (status === 'success') {
        page.includeJs("http:/xxx:8888/js/jquery.min.js", function() {
            // evaluate里面是一个沙箱,主要的DOM操作地
            page.evaluate(function() {
                var form = $("#SignInForm");
                form.find("input[name=userid]").val("testuser_xxx");
                form.find("input[name=pass]").val("xxxx");
                
                form.trigger("submit");
            });
            // 等待5秒后收集结果
            window.setTimeout(function() {
                //console.log(page.content);
                
                var cookies = page.cookies;
                var saveData = '';
                for(var i in cookies) {
                    console.log(cookies[i].name + '=+' + cookies[i].value);
                    saveData += cookies[i].name+"="+cookies[i].value+"&";
                }
    
                // 1 
                // 如果供外部程序使用,这里把cookie送出
                var save = webpage.create();
                save.open('http://xxx:7070/i.php', 'POST', saveData, function (status) {
                    if (status === "success") {
                        console.log(save.content);
                    }
                    phantom.exit();
                });
                // 2 
                // 如果不需要供外部使用,成功登录后直接退出记录
                // Cookie记录到文件,这样仅需要在Cookie失效时重新登录即可
                // phantom.exit();
            }, 5000);
        });
    } else {
        phantom.exit();
    }
});
*/

登录成功后,需要访问登录保护的页,可以把Cookie发出出去,然后外部程序来完成,也可以继续让PhantomJs去访问,如果抓取内容不连续,那么就类似于用完后,关闭浏览器,下次用再次打开浏览器,载入浏览器是低效的,而且它会载入URL相关的所有页,所以应该使用外部程序完成抓取,程序一旦检测到Cookie失效(比如检测到了跳到登录页),那么马上启动浏览器,重新自动登录,然后取到最新Cookie,这个类似于数据库链接里面的断线重连,如果断线重连无法链接,那么就需要终止了。 需要注意的是PhantomJs并不会把会话Cookie写入到你指定的cookie文件,会话cookie仅在phantom存在时有效,这个跟真实浏览器一致,浏览器关闭,所有会话Cookie会被删除,因为会话cookie的过期时间为0,意味关闭则失效,所以如果这些会话Cookie是维持登录状态的,就需要主动发送出去或记录,而不能依赖cookie文件。

用一个真实的浏览器去登录,再牛逼的防机器登录机制都无效(不使用验证码的情况,防程序登录的机制无非是用JS产生Cookie, 在HTML渲染后根据环境动态添加表单域,这些在一般模拟登录中,几乎可以通杀,因为你不可能得到HTML渲染后的值,也取不到JS在客户端产生的Cookie)。

jQuery UI – Datepicker

单日期选择:

$(function() {
	var dayNamesMin =['日','一', '二', '三', '四', '五', '六'];
	var monthNamesShort = ['一','二','三','四','五','六','七','八','九','十','十一','十二'];
	$('#datepicker').datepicker({
		changeMonth: true,
		changeYear: true,
		dayNamesMin:dayNamesMin,
		monthNamesShort:monthNamesShort,
		dateFormat: 'yy-mm-dd'
	});
});

jquery-ui-datepicker-only

$(function(){
    var dayNamesMin =['日','一', '二', '三', '四', '五', '六'];
    var monthNamesShort = ['一','二','三','四','五','六','七','八','九','十','十一','十二'];
    $("#datefrom").datepicker({
	defaultDate: "+1w",
        changeMonth: true,
        dayNamesMin:dayNamesMin,
	monthNamesShort:monthNamesShort,
	dateFormat: 'yy-mm-dd',
        numberOfMonths: 3,
        onClose: function( selectedDate ) {
        	$("#dateto").datepicker( "option", "minDate", selectedDate );
        }
    });
    
    $("#dateto").datepicker({
        defaultDate: "+1w",
        changeMonth: true,
        dayNamesMin:dayNamesMin,
	monthNamesShort:monthNamesShort,
	dateFormat: 'yy-mm-dd',
        numberOfMonths: 3,
        onClose: function( selectedDate ) {
          	$("#datefrom").datepicker( "option", "maxDate", selectedDate );
        }
    });
});

jquery-ui-datepicker

jQuery UI中提供的是日历选择,无法选择到具体的时间,如果需要选择时间,可以引入一个扩展:

<link href="/css/jquery-ui-timepicker-addon.min.css" rel="stylesheet">
<script src="/js/jquery-ui-timepicker-addon.min.js"></script>
<script type="text/javascript">
$(function(){
	var dayNamesMin =['日','一', '二', '三', '四', '五', '六'];
	var monthNamesShort = ['一','二','三','四','五','六','七','八','九','十','十一','十二'];
	$("#datepicker").datetimepicker({
		changeMonth: true,
		changeYear: true,
		dayNamesMin:dayNamesMin,
		monthNamesShort:monthNamesShort,
		dateFormat: 'yy-mm-dd',
		showSecond: true,
		timeFormat: 'HH:mm:ss',
		stepHour: 1,
		stepMinute: 1,
		stepSecond: 1
	});
});
</script>

(http://trentrichardson.com/examples/timepicker)
jquery-ui-datetime

这个时间选择器的JS文件体积有点大。

JavaScript权威指南 – 语言核心 笔记

以下为JavaScript权威指南 – 语言核心读书笔记,版本v1.0,部分内容没有很细致,后续补充,同时修改版本号。

1 JavaScript概述
JavaScript是Sun Microsystem公司(Oracle收购了此公司)的注册商标。而JavaScript这个语言是由网景(现在的Mozilla)实现的(注:由于竞争关系,同时代的相同产物是VBScript,微软已经放弃)。网景将这门语言作为标准提交给了ECMA(欧洲计算机制造协会),由于商标上的冲突(JavaScript是Sun的注册商标,Java的发明者),所以ECMA为这门语言的标准改了一个名字“ECMAScript”,同样由于商标的原因,微软对这门语言的实现叫JScript。一般,用JavaScript来指语言本身,用ECMAScrip来指这个语言的标准。

目前ECMAScript有3、5、6版本。目前绝大多数浏览器都已经实现了第5版本的标准。对于JavaScript 1.5 和 JavaScript 1.8这样的版本号,是Mozilla对JavaScript语言实现的版本编号,1.5对应ECMAScript 3,后续的包含了非标准扩展。另外,JavaScript解释器或引擎也有版本号,比如Google将它的JavaScript解释器命名为V8,它的版本号有3.0等。
……………………..

2 词法结构
2.1 字符集
JavaScript程序是用Unicode字符集编写的(UTF-16)。 注:JS实际只支持2字节Unicode。
2.1.1 区分大小写
JavaScript区分大小写。但HTML不区分大小写(XHTML区分大小写,不过浏览器可以包容)。
2.1.2 空格、换行符和格式控制符
除了普通空格符(\u0020),JavaScript还可以识别如下这些表示空格的字符:
水平制表符(\u0009)、垂直制表符(\u000B)、换页符(\uoooC)、不中断空白(\uooAo)、字节序标记(\uFEFF)。

JavaScript将如下字符识别为行结束符:换行符(\u000A),回车符(\u000D),行分隔符(\u2028),段分隔符(\u2029)。回车符加换行符在一起被解析为一个单行结束符。

2.1.3 Unicode转义序列
JavaScript定义了一种特殊序列,使用6个ASCII字符来代表任意16位Unicode内码。这些Unicode转义序列均以\u为前缀,其后跟随4个十六进制数。这种Unicode转义写法可以用在JavaScript字符直接量、正则表达式直接量和标识符中(关键字除外)。

2.1.4 标准化

2.2 注释
单行注释(//)和块注释(/**/)
2.3 直接量 literal
2.4 标识符和保留字
JavaScript标识符必须以字母、下划线或美元符开始(数字不允许作为首字符出现)。JavaScript允许标识符中出现Unicode字符全集中的字母和数字。
2.5 可选的分号
在return,break和continue和随后的表达式之间不能有换行。(一般应该总是使用分号)

3 类型、值和变量
JavaScript的数据类型分为两类:原始类型和对象类型。原始类型包括数字、字符串和布尔值。JavaScript中有两个特殊的原始值:null(空)和undefined(未定义),它们不是数字,字符串和布尔值。它们通常分别代表了各自特殊类型的唯一的成员。

JavaScript中除了数字、字符串、布尔值、null和undefined之外就是对象。对象是属性的集合,每个属性都由名值对构成。普通的JavaScript对象是命名值的无序集合。

有一个称为全局的对象,一般是指Window对象。它定义了全局的属性。

JavaScript同样定义了一种特殊对象–数组(array),表示带编号的值的有序集合。

JavaScript还定义了另一种特殊对象–函数。函数是具有与它相关联的可执行代码的对象,通过调用函数来运行可执行代码,并返回运算结果。

注:这里把对象跟数字、字符串等放在一起,而它们是类型,所以JavaScript中的所谓对象,也是指类型,务必要区别对象实例。

3.1 数字
JavaScript不区分整数值和浮点数值,所有数字均用浮点数值表示。注:JavaScript中实际的操作则是基于32为整数(如数组的索引)。

3.1.1 整型直接量
除了十进制的整数直接量,JavaScript同样能识别十六进制值(0x或0X为前缀)。ECMAScript 6的严格模式下,八进制直接量是明令禁止的。

3.1.2 浮点型直接量
3.1.3 JavaScript中的算术运算
除了加减乘除和求余这些基本运算符外,JavaScript还支持更加复杂的算术运算:

Math.pow(2,53)
Math.round(.6)
Math.ceil(.6)
Math.floor(.6)
Math.abs(-5)
Math.max(x,y,z)
Math.min(x,y,z)
Math.random()
Math.PI
Math.E
Math.sqrt(3)
Math.pow(3,1/3)
Math.sin(0)
Math.log(10)
Math.log(100)/Math.LN10	//以10为底100的对数
Math.exp(3)		//e的三次幂

JavaScript中的算术运算在溢出(overflow)、下溢(underflow)或被零整除时不会报错。正负无穷大(infinity),在JavaScript中以Infinity表示。

被零整除在JavaScript并不报错:它只是简单的返回无穷大(Infinity)或负无穷大(-Infinity)。有一个例外,零除以零是没有意义的,这个运算结果也是一个非数字值(NaN)。JavaScript预定义了全局变量Infinity和NaN来表示正无穷大和非数字值。

JavaScript中的非数字值有一点特殊:它和任何值都不相等,包括自身。可用函数isNaN()来判断值是否是NaN。

JavaScript中有一个类似的函数isFinite(),在参数不是NaN、Infinity或-Infinity的时候返回true(就是合法正确的数值)。

负零值也特殊,它和正零是相等的。不过作为除数时例外(因为正负零被除得到Infinity和-Infinity, 正无穷与负无穷不相等)

3.1.4 二进制浮点数和四色五入错误
JavaScript通过浮点数形式表示数字,在表示整数时,只是它的一个近似值(意味着判断两个数字是否相等时,总是不等的)

3.1.5 日期和时间

3.2 文本
JavaScript内部使用UTF-16表示,这个编码特点是在2字节时,和Unicode对应的2字节编码一致,超过这个范围就要用4个字节甚至8个字节,但是JavaScript对字符串长度的判断,永远都是以2个字节作为单位的,意思就是,如果一个特殊字符用了4个字节,它返回的字符长度是2,但是实际是一个字符。(JS实际是UCS2方案,跟Unicode 基本平面,即2字节码段一致,UTF-16编码方案在对应基本平面的Unicode字符时是直接对应的,但是对于非基本平面的字符,UTF-16会转码成4个字节来表示,而JS实际无法识别这种转换,所以当遇到这个情况时,它还是老实地按照2个字节2个字节来读,所以说JS根本不支持UTF-16,而非要说它采用了UTF-16也是混淆视听装逼装上瘾的感觉)

3.2.1 字符串直接量
在EC3中,字符串直接量必须写在一行中,在EC5中,字符串直接量可以写在多行中,每行必须以反斜线结束,反斜线和行结束符都不算是字符串直接量的内容。(HTML中习惯用双引号界定字符串,JS嵌入HTML时,JS中最好使用单引号界定字符串)

3.2.2 转义字符
如果把反斜线放在非预定义的转义字符前,那么反斜线将被忽略(比如\# 和 #一样,因为\#是非预定义转义字符,斜线被忽略)。

3.2.3 字符串的使用
加号用于数字,表示两数字相加;用于字符串,表示字符串连接。JS中字符串是固定不变的,类似replace()和toUpperCase()的方法都返回新字符串,原字符串本身并没有发生改变。在EC5中,字符串数组可以当做只读数组,可以用括号语法访问。

3.2.4 模式匹配
RegExp不是语言中的基本数据类型,但具有直接量写法(/^[0-9]/)。

3.3 布尔值
任意JS的值都可以转换为布尔值,如下值会被装换为false:
undefined null 0 -0 NaN “”。 所有其他值,包括所有对象(数组)都会转换成true。

3.4 null和undefinded
空值用null表示,表示已经定义,但值为空,undefined表示没有定义,或定义未初始化,或初始化为undefined值。
3.5 全局对象
在客户端JavaScript中,在其表示的浏览器窗口中的所有JavaScript代码中,Window对象充当了全局对象。这个全局Window对象有一个属性window引用其自身,它可以代替this来引用全局对象。Window对象定义了核心全局属性,单它也针对Web浏览器和客户端JavaScript定义了少部分其它全局属性。
3.6 包装对象
主要有String、Number、Boolean,比如定义了一个字符串变量,当调用其方法时,会调用new String(s)来转换成字符串对象,然后调用其方法,这个就是包装对象。
3.7 不可变的原始值和可变的对象引用
不可变原始值指数字、字符串、布尔、null和undefined类型的值,它们都是不可改变的(字符串修改会返回一个新字符串),这些值的比较是值比较。而对象类型(数组 函数)是引用类型,即使两个完全相同的对象,比较时也是不等的,因为它们的引用不一样。同理,对象类型值赋值也是引用的赋值(引用同一个地址)。****

3.8 类型转换
期望是数字 字符串 布尔值的,会对应转换为数字字符串布尔值:

10 + " object"   => "10 object"
"7" * "4"        => 28
var n = 1 - "x"; => NaN, 字符串"x"装换为数字得到NaN
n + " object"    => "NaN object", n是NaN, 转换为字符串为“NaN”

// 得到NaN, 1x转换为字符串是NaN, 1-NaN还是NaN
// 这里的1x隐形转换并不是转换为1,而是转换成了NaN, 而parseInt("1x")会得到1 ****
var m = 2 - "1x"; 

数字表示的字符串可以直接转换为数字,也允许头尾包含空格,但是只要包含非数字,就都是NaN, 而parseInt()函数显式转换则不然,(123x装换为数字得到123,12×3得到12, x123得到NaN)。原始值到对象的转换调用String()Number()Boolean()构造函数,转换为各自包装对象。undefined转为数字时是NaN,而Null装换为数字时是0。 undefined和null转换为对象时都会抛出异常,因为没有对应的包装对象。 对象一般会转换为字符串,调用toString()方法。

3.8.1 转换和相等性(4.9.1)
3.8.2 显示类型装换
加号(+)在加字符串时,会把数字转换为字符串做链接操作,都是数字时会做相加操作。一元操作符:x+会把x转换为数字,!x会把x装换为布尔值。
3.8.3 对象装换为原始值
toString方法。

3.9 变量声明
如果未在var声明语句中给变量指定初始值,那么虽然声明了这个变量,但在给它存入一个值之前,它的初始值就是undefined。

如果试图读取一个没有声明的变量,JS会报错。在ES5严格模式中,给一个没有声明的变量赋值也会报错。非严格模式下,则会在全局对象创建一个同名属性(在Node.js中会产生内存泄露,一般来说,变量务必先定义后使用,为了能避免变量未声明就可使用,应该让代码运行在严格模式下)

3.10 变量作用域
函数内声明的变量只在函数体内有定义,它们是局部变量。函数参数也是局部变量。在函数体内,局部变量的优先级高于同名的全局变量。

函数定义是可以嵌套的。由于每个函数都有它自己的作用域,因此会出现几个局部作用域嵌套的情况。
3.10.1 函数作用域和声明提前
JavaScript的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。这意味变量在声明之前甚至已经可用。JavaScript的这个特性被非正式地称为声明提前(hoisting),即JavaScript函数里声明的所有变量(但不涉及赋值)都被提前至函数体的顶部:

var scope = "global";
function f(){
	console.log(scope); 	// undefined 而不是 global
	var scope = "local";
	console.log(scope);	//local
}

第一次输出scope可能会认为应该是global,但是实际输出了undefined,这个就是声明提前起了作用。可以看到,由于声明提前,函数内部相当于是先定义scope,但没有赋值,于是undefined就是scope的值,运行到第二行时才真正赋值。(声明提前是JS引擎的预编译时进行的)
3.10.2 作为属性的变量
当声明一个JS全局变量时,实际是定义了全局对象的一个属性。当使用var声明一个变量时,创建的这个属性是不可配置的(不能delete):

var tv =1;
fv = 2; 
this.fvv = 3;

delete tv; // false;
delete fv; // true
delete this.fvv; // true

当delete一个var声明的全局变量时,实际是无法删除的(不可配置)。全局变量可以通过this来引用。
3.10.3 作用域链
函数可以嵌套,外部定义的变量内部可见,内部定义的和外部同名的变量,优先级最大,这里就形成了一条作用域链。

4 表达式和运算符
4.1 原始表达式
JavaScript中的原始表达式包含常量或直接量、关键字和变量。在EC5的严格模式中个,对不存在的变量进行求值会抛出一个引用错误常量。
4.2 对象和数组的初始化表达式

[]
[1+2,3+4]
[[1,2,3],[4,5,6],[7,8,9]]

var sparseArray = [1,,,,5];
var sparseArray = [1,,,,5,];

数组直接量中的列表逗号之间的元素可以省略,这时省略的空位会填充值undefined。数组直接量的元素列表结尾处可以留下单个逗号,这时并不会创建一个新的值为undefined的元素。

4.3 函数定义表达式
4.4 属性访问表达式
点语法 和 中括号语法。在”.”和”[“之前的表达式总是会首先计算。如果结算结果是null或者undefined,表达式会抛出一个类型错误,因为这两个值都不能包含任意属性。如果命名的属性不存在,那么整个属性访问表达式的值就是undefined。
4.5 调用表达式
函数表达式后跟随一对圆括号,括号内是一个以逗号隔开的参数列表,参数可以有0个也可以多个。如果函数使用return语句给出一个返回值,那么这个返回值就是整个调用表达式的值。否则,调用表达式的值就是undefined。
4.6 对象创建表达式
4.7 运算符概述
4.7.1 操作数的个数
大多是一元和二元运算符,支持一个三元运算符。
4.7.2 操作数类型和结果类型
JavaScript中的所有值不是真值就是假值,因此对于那些希望操作数是布尔值的操作符来说,它们的操作数可以是任意类型。
4.7.3 左值
4.7.4 运算符的副作用
对变量产生影响,比如增加减少等…
4.7.5 运算符优先级
使用圆括号。
4.7.6 运算符的结合性
4.7.7 运算顺序

4.8 算术表达式
4.8.1 “+”运算符

1 + 2  			//3 加法
"1" + "2" 		//"12" 字符串连接
"1" + 2			//"12" 数字转换成字符串后进行字符串连接
1+{}			//"1[object Object]" 对象装换为字符串后进行字符串连接
true + true		//2 布尔值转换为数字后做加法
2 + null		//2 null转换为0后做加法
2 + undefined		//NaN undefined转换为NaN后做加法

1 + 2 + " hello"	//"3 hello" 最边先加法,结果转换成字符串后连接

对于对象,首先是把对象转换为原始值,看这个原始值是字符串还是数字来决定是字符串链接操作还是数字相加操作。

4.8.2 一元算术运算符
4.8.3 位运算符

4.9 关系表达式
4.9.1 相等和不等运算符
严格等===在比较时不会发生类型装换,如下是比较特殊的情况:

null !== null;
undefined !== undefined;
NaN !== NaN; // isNaN()
0 === -1; 

相等运算符“==”比较时,当类型不一致时会进行类型转换:
– null == undefined
– 一个是数字,一个是字符串,先把字符串换成数字,然后比较
– 布尔值会先转换为1 和 0
– 对象则首先转换为原始值

// true首先换成1, 然后符合一个数字一个字符串,则把字符串换成数字1
// 最后是1等于1
"1" == true;

4.9.2 比较运算符
比较操作只能针对数字和字符串,一般认为比较的是数字比较,所以一个字符串和一个数字比较时,先把字符串换成数字再比较(+运算符优先偏向字符串,如果有字符串,则首先转换为字符串; 而比较运算更加偏向数字,只有都是字符串时才是对字符串进行比较)。字符串转换为数字可能是NaN,只要出现NaN,比较结果都是false(NaN !== NaN)

4.9.3 in运算符

var point = {x:1, y:1};
"x" in point 		//true
"z" in point		//false
"toString" in point 	//true 对象有toString方法

var data = [7,8,9];
"0" in data		//true
1 in data		//true
3 in data		//false

4.9.4 instanceof运算符

var d = new Date();
d instanceof Date;	//true
d instanceof Object;	//true
d instanceof Number;	//false

var a = [1,2,3];
a instanceof Array;	//true
a instanceof Object;	//true
a instanceof RegExp;	//false

所有对象都是Object的实例。

4.10 逻辑表达式
4.10.1 逻辑与(&&)
比如a&&b,返回第一个可以转换为false的元素值,否则返回true。
4.10.2 逻辑或(||)
比如a||b,返回第一个可以装换为true的元素值,否则返回false。
4.10.3 逻辑非(!)

4.11 赋值表达式
4.12 表达式计算
4.12.1 eval()
4.12.2 全局eval()
4.12.3 严格eval()

4.13 其他运算符
4.13.1 条件运算符(?:)
4.13.2 typeof运算符

typeof undefined === "undefined";
typeof null === "object"; // null的类型是object是一个很意外的结果,大概原始设置时只有对象可以设置为null
typeof true === "boolean";
typeof 任意数字或NaN === "number";
typeof 任意字符串 === "string";
typeof 函数 === "function";
typeof 任意内置对象(非函数) === “object”;

4.13.3 delete运算符
delete一个对象或数组元素时,可以看做是对应赋值为undefined,实际就表示变量未定义的意思。注:如果是数组,索引不会重排。内置核心和客户端属性,用户通过var语句声明的变量,通过function语句定义的函数和函数参数都不能删除。
4.13.4 void运算符
4.13.5 逗号运算符(,)
(这个章节重点是+号运算和字符串拼接的理解,相等判断,比较运算类型的隐式转化)

5 语句
5.1 表达式语句
5.2 复合语句和空语句

{
	x = Math.PI;
	cx = Math.cos(x);
	console.log(cx);
}

//空语句,一个逗号一行
;

语句块的结尾不需要分号。块中的原始语句必须以分号结束。

5.3 声明语句
5.3.1 var
5.3.2 function

5.4 条件语句
5.4.1 if
5.4.2 else if
5.4.3 switch

5.5 循环
5.5.1 while
5.5.2 do/while
5.5.3 for
5.5.4 for/in

5.6 跳转
5.6.1 标签语句
5.6.2 break语句
5.6.3 continue语句
5.6.4 return语句
5.6.5 throw语句
5.6.6 try/catch/finally语句

5.7 其他语句类型
5.7.1 with语句(p1113)
5.7.2 debugger语句
5.7.3 “use strict”
“use strict”是ECMAScript 5引入的一条指令。使用”use strict”指令的目的是说明后续的代码将会解析为严格代码(strict code)。如果顶层(不在任何函数内的)代码使用”use strict”指令,那么它们就是严格代码。如果函数体定义所处的代码是严格代码或者函数体使用”use strict”指令,那么函数体的代码也是严格代码。

6 对象
属性名是字符串,因此可以把对象看成是从字符串到值的映射。JavaScript对象还可以从一个称为原型的对象继承属性。对象的方法通常是继承的属性。这种原型式继承是JavaScript的核心特征。

除了字符串、数字、true、false、null和undefined之外,JavaScript中的值都是对象。尽管字符串、数字和布尔值不是对象,但它们的行为和不可变对象非常类似。

除了包含属性之外,每个对象还拥有三个相关的对象特性:
>对象的原型(prototype)指向另一个对象,本对象的属性继承自它的原型对象
>对象的类是一个标识对象类型的字符串
>对象的扩展标记指明了是否可以向该对象添加新属性

三类JavaScript对象和两类属性:
>内置对象(native object)是由ECMAScript规范定义的对象或类(TMD,到底对象还是类)。如数组、函数、日期和正则表达式都是内置对象。
>宿主对象(host object)是有JavaScript解释器所嵌入的宿主环境(比如Web浏览器)定义的。
>自定义对象是由运行中的JavaScript代码创建的对象(到底是指类型定义还是实例,简直操蛋)
>自有属性是直接在对象中定义的属性(这个时候的对象就是指类)
>继承属性是在对象的原型对象中定义的属性。

6.1 创建对象
可以通过对象直接量、关键字new和Object.create()函数来创建对象。
6.1.1 对象直接量
花括号语法。
6.1.2 通过new创建对象
JavaScript语言核心中的原始类型都包括内置构造函数。如:

var o = new Object();	//和{}一样
var a = new Array();	//和[]一样
var d = new Date();
var r = new RegExp();

6.1.3 原型
每个JavaScript对象(null除外)都和另一个对象相关联。这个对象就是原型,每一个对象都从原型继承属性。

所有通过对象直接量创建的对象都具有同一个原型对象,并可以通过JavaScript代码Object.prototype获得对原型对象的引用。通过关键字new和构造函数调用创建的对象的原型就是构造函数的prototype属性的值。同使用{}创建对象一样,通过new Object()创建的对象也继承自Object.prototype。同样,通过new Array()创建的对象的原型就是Array.prototype,通过new Date()创建的对象的原型就是Date.prototype。(对象实例有原型的概念,对象的类有一个叫prototype的属性,原型是一个实例,不是类,对象实例生成时,继承这个实例,实际非常诡异…)

6.1.4 Object.create()
Object.create()是一个静态函数,第一个参数是要创建的对象的原型。

var o = Object.create({x:1,y:2});

可以传入参数null来创建一个没有原型的新对象。

6.2 属性的查询和设置
6.2.1 作为关联数组的对象
点与方括号用法老调重弹。
6.2.2 继承

var o = {}; 	//o从Object.prototype继承对象和方法
o.x = 1;	//给o定义一个属性x
var p = Object.create(o); //p继承o和Object.prototype
p.y = 2;	//给p定义一个属性y
var q = Object.create(p); //q继承p、o和Object.prototype
q.z = 3;	//给q定义一个属性z
var s = q.toString(); //toString继承自Object.prototype
q.x + q.y	//3,x和y分别继承自o和p

js_prototype

var s = q.toString() 首先在q内找,再到q的原型p中找,再到p的原型o中找,再到o的原型Object.prototype中找,找到toString()方法。

q.x + q.y,在q中寻找x和y,再到q的原型p中找到y,继续到p的原型o中找到x。

var o = {}; 	
o.x = 1;	
var p = Object.create(o); 
p.y = 2;	
var q = Object.create(p); 
q.z = 3;	
var s = q.toString();

o.x = 10;
p.x = 100;

o.xy = 1000;

console.log(q.x); 	// 100
console.log(q.y);	// 2
console.log(q.z);	// 3
console.log(q.xy);	// 1000

对象o是在最后添加了xy属性,但是q.xy输出了o的xy属性值;x被修改了两次,输出了最近的值。这个不难理解,因为这些修改的都是原型,这个对于q来说,可以动态扩充,这个就是JavaScrip核心特征。

6.2.3 属性访问错误
访问不存在的属性不会出错(返回undefined),但是如果访问一个不存在的对象则会出错。

6.3 删除属性(方法的定义也是对象的属性)
delete运算符只能删除自有属性,不能删除继承属性(要删除继承属性必须从定义这个属性的原型对象上删除它,而且这会影响到所有继承自这个原型的对象)…

6.4 检查属性
in运算符的左侧是属性名(字符串),右侧是对象。如果对象的自有属性或继承属性中包含这个属性则返回true。

对象的hasOwnProperty()方法用来检测给定的名字是否是对象的自有属性。

propertyIsEnumerable()是hasOwnProperty()的增强版,只有检测到自有属性且这个属性的可枚举性为true时它才返回true(首先是自有,然后是可枚举的,可枚举啥概念??非方法?)。

6.5 枚举属性
for/in循环可以在循环中遍历对象中所有可枚举的属性(包括自有属性和继承的属性),把属性名称赋值给循环变量。对象继承的内置方法不可枚举,但在代码中给对象添加的属性都是可枚举的。

有许多工具库给Object.prototype添加了新的方法或属性,这些方法和属性可以被所有对象继承并使用。然而在EC5标准之前,这些新添加的方法是不能定义为不可枚举的,因此都可以在for/in循环中枚举出来。需要过滤:

for(p in o){
	if(!o.hasOwnProperty(p)) continue; //跳过继承的属性
}

for(p in o){
	if(type o[p] === "function") continue; // 跳过方法
}

EC5定义了两个用以枚举属性名称的函数。第一个是Object.keys(),反返回一个数组,这个数组由对象中可枚举的自有属性的名称组成,第二枚举属性的函数是Object.getOwnPropertyNames(),它返回对象的所有自有属性的名称,而不仅仅是可枚举的属性。

6.6 属性getter和setter
p132
6.7 属性的特性
除了包含名字和值之外,属性还还包括一些标识它们可写,可枚举和可配置的特性。
p134

6.8 对象的三个属性
每一个对象都有与之相关的原型(prototype)、类(class)和可扩展性(extensible attribute)。
6.8.1 原型属性
在EC5中,将对象作为参数传入Object.getPrototypeOf()可以查询它的原型。要想检测一个对象是否是另一个对象的原型(或处于原型链中),请使用isPrototypeOf()方法。如:p.isPrototypeOf(o)来检测p是否是o的原型。

Mozilla实现的JavaScript对外暴露了一个专门命名的__proto__属性,用以直接查询/设置对象的原型。

6.8.2 类属性
创建对象的类型。p139
6.8.3 可扩展性

6.9 序列化对象
6.10 对象的方法

7 数组
数组继承自Array.prototype中的属性,它定义了一套丰富的数组操作方法。
7.1 创建数组
如果省略数组直接量中的某个值,省略的元素将被赋予undefined值:

var count = [1,,3];
var undefs = [,,];

数组直接量的语法允许有可选的结尾的逗号,所以[,,]只有两个元素而非三个。

var a = new Array();
var a = new Array(10);

注:数组中没有存储值,甚至数组的索引属性等还未定义。

7.2 数组元素的读和写
数组是对象的特殊形式。JavaScript将指定的数字索引值转换成字符串–索引值1变成”1″–然后将其作为属性名来使用。

数组的特别之处在于,当使用小于2的32次方的非负数作为属性名时数组会自动维护其length属性值。(2的32次方的值是数组的索引,其它的也可以作为数组索引,但是不体现到length属性值,跟一般的对象属性没有什么不一样)

事实上数组索引仅仅是对象属性名的一种特殊类型,这意味着JavaScript数组没有越界错误的概念。当试图查询任何对象中不存在的属性时,不会报错,只会得到undefined值。

7.3 稀疏数组
如果数组是稀疏的,length属性值大于元素的个数(有length个元素,但是有些是根本不存在的,这跟值为undefined是有却别的)。

a = new Array(5); 	//数组没有元素,但a.length是5
a = []			//空数组,length=0
a[100] = 0;		//赋值添加一个元素,但是设置length为1001

7.4 数组长度
数组的两个特殊行为:
1) 如果为一个数组元素赋值,它的索引i大于或等于现有数组的长度时,length属性的值将设置为i+1。
2) 设置length属性为一个小于当前长度非负整数n时,当前数组中那些索引值大于或等于n的元素将从中删除。

7.5 数组元素的添加与删除
添加:直接赋值或调用push()。也可用unshift()在首部插入。可以像删除对象属性一样使用delete运算符来删除数组元素。

删除数组元素与为其赋undefined值是类似的。注:对一个数组元素使用delete不会修改数组的length属性,也不会将元素从高索引处一下来填充已删除属性留下的空白。如果从数组中删除一个元素,它就变成稀疏数组。(如果期望删除了元素索引从新排列,需要使用splice()方法)。

7.6 数组遍历
for循环,假设数组是非稀疏的,如果要排除null,undefined和不存在的元素:

//排除null,undefined和不存在的元素
for(var i = 0; i < a.length; i++) {
	if(!a[i]) continue;
}

//排除undefined
for(var i = 0; i < a.length; i++) {
	if(a[i] === undefined) continue;
}

//排除不存在
for(var i = 0; i < a.length; i++) {
	if(!(i in a)) continue;
}

可以使用for/in循环处理稀疏数组。循环每次将一个可枚举的属性名赋值给循环变量。不存在的索引将不会遍历到。for/in循环能够枚举继承的属性名,如果添加到Array.prototype中的方法。由于这个原因,在数组上不应该使用for/in循环,除非使用额外的检测方法来过滤不需要的属性:

for(var i in a) {
	if(!a.hasOwnProperty(i)) continue; //跳过继承而来的属性
}

如果算法依赖于遍历的顺序,最好不要使用for/in。

EC5定义了一些遍历数组元素的新方法,按照索引的顺序按个传递给定义的一个函数,这些方法中最常用的就是forEach()方法:

var data = [1,2,3,4,5];
var result = 0;
data.forEach(function(x){
	result + = x*x;
});
console.log(result);

7.7 多维数组
7.8 数组方法
EC3在Array.prototype中定义的数组函数。
7.8.1 join()
数组变成字符串。

//数组拆分成字符串
var a = [1,2,3];
a.join(); 	// "1,2,3"

a.join("-");	// "1-2-3"

//字符串组装成数组
var s = "1-2-3";
s.split("-"); 	//[1,2,3]

7.8.2 reverse()
这个方法是在原来数组中使用替换方法进行翻转。
7.8.3 sort()
按照字母表顺序返回排序后的数组。如果数组包含undefined元素,它们会被排到数组的尾部。

如果希望以非字母顺序进行数组排序,必须给sort()方法传递一个比较函数….
7.8.4 concat()
7.8.5 slice()
返回数组片段。
7.8.6 splice()
Array.splice()方法是在数组中插入或删除元素的通用方法。不同于slice()和concat(),splice()会修改调用的数组。

splice()第一个参数指定插入和(或)删除的起始位置,第二个参数指定应该从数组中删除的元素的个数。如果省略第二个参数,从起始点开始到数组末尾的所有元素都将被删除。splice()返回一个有删除元素组成的数组,或者如果没有删除元素就返回一个空数组。

var a = [1,2,3,4,5,6,7,8];
a.splice(4); 	//返回[5,6,7,8],a是[1,2,3,4]
a.splice(1,2);	//返回[2,3],a是[1,4]
a.splice(1,1);	//返回[4],a是[1]

splice()的前两个参数指定了需要删除的数组元素。紧跟其后的任意个参数指定了需要插入到数组中的元素(从指定位置前插入,删除是从指定位置(包含)开始删除):

var a = [1,2,3,4,5];
a.splice(2,0,'a','b'); 	//返回[],a是[1,2,'a','b',3,4,5]
a.splice(2,2,[1,2],3);	//返回['a','b'],a是[1,2,[1,3],3,3,4,5]

注意:[1,2]是作为整体插入的。

7.8.7 push()和pop()
7.8.8 unshift()和shift()
7.8.9 toString()和toLocalString()

7.9 ECMAScript 5中的数组方法
forEach() map() filter() every() some() reduce() reduceRight() indexOf() lastIndexOf()
7.10 数组类型
EC5中使用Array.isArray()来判断是否是数组。

Array.isArray([]) // true
Array.isArray({}) // false

7.11 类数组对象
7.12 作为数组的字符串

8 函数
除了实参之外,每次调用还会拥有另一个值–本次调用的上下文–this关键字的值。如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用的上下文,也就是该函数的this的值。用于初始化一个新创建的对象的函数称为构造函数。

在JavaScript里,函数即对象,程序可以随意操控它们。比如,JavaScript可以把函数赋值给变量,或者作为参数传递给其他函数。因为函数就是对象,所以可以给它们设置属性,调用它们的方法。

8.1 函数定义
如果return语句没有一个与之相关的表达式,则它返回undefined值。如果一个函数不包含return语句,那它就只执行函数体中的每条语句,并返回undefined值给调用者。
8.2 函数调用
8.2.1 函数调用
EC3和EC5对函数调用的规定,调用上下文是全局对象。在严格模式下,调用上下文是undefined。

以函数形式调用的函数通常不使用this关键字。不过,”this”可以用来判断当前是否是严格模式:

var strict = (function(){ return !this; }());

8.2.2 方法调用
和变量不同,关键字this没有作用越的限制,嵌套的函数不会从调用它的函数中继承this(内部不指向外部,它有自己的this)。如果嵌套函数作为方法调用,其this的值指向调用它的对象。如果嵌套函数作为函数调用,其this值不是全局对象就是undefined。不要误以为调用嵌套函数时this会指向调用外层函数的上下文。如果需要访问这个外部函数的this值,需要将this的值保持在一个变量中,这个变量和内部函数都在同一个作用越中。通常使用变量self来保存this。
8.2.3 构造函数调用
使用new调用。构造函数通常不使用return关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这个情况下,构造函数调用表达式的计算结果就是这个新对象的值。然而如果构造函数显式地使用return语句返回一个对象,那么调用表达式的值就是这个对象。如果构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果。

8.2.4 间接调用
函数对象也包含方法。其中的两个方法call()和apply()可以用来间接地调用函数。两个方法都运行显式指定调用所需要的this值,也就是说,任何函数可以作为任何对象的方法来调用。

8.3 函数的实参和形参
实际,JavaScript函数调用时不检查传入形参的个数。
8.3.1 可选形参
当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值。(无法在参数中放置默认值)

function getPropertyNames(o,/* optional */ a) {
	if(a === undefined) a = []; //如果未定义
	for(var property in o) a.push(property);
	return a;
}

a = a || [];  //最常见方式

8.3.2 可变长的实参列表:实参对象
在函数体内,标识符arguments是指向实参对象的引用,实参对象是一个类数组对象。
8.3.3 将对象属性用做实参
参数顺序无关。传入一个对象,这个方式很常见。

$.ajax({
	url:'',
	method:''
});

8.3.4 实参类型
不检查类型。需要自己实现。

8.4 作为值的函数
p178
8.5 作为命名空间的函数
就是匿名函数。
8.6 闭包
函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为闭包。
8.7 函数属性、方法和构造函数
8.7.1 length属性
在函数体中,arguments.length表示传入函数的实参的个数。而函数本身的length属性表示形参的个数,就是定义时候的参数个数。

function test(x,y,z){}
console.log(test.length); // 输出3

由于直接函数调用,内部this指向全局对象(window)。可以通过函数内部的arguments.callee来获取当前运行的函数的引用。
8.7.2 prototype属性
8.7.3 call()方法和apply()方法
可以将call()和apply()方法看做是某个对象的方法,通过调用方法的形式来间接调用函数。

以对象o的方法来调用函数f():

f.call(o)
f.apply(o)

对应call(),第一个上下文对象后的参数就是函数的实参;对apply来说就是一个实参数组。
8.7.4 bind()方法
在对象上绑定一个方法。
8.7.5 toString()方法
8.7.6 Function()构造函数
Function()构造函数创建的函数并不使用词法的作用域。可以认为是在全局作用域中执行eval()。(很少使用)
8.7.7 可调用的对象

8.8 函数式编程

9 类和模块
如果两个对象继承自同一个原型,往往意味着它们是由同一个构造函数创建并初始化的。JavaScript中类的一个重要特征是动态可继承,可以将类看做是类型。
9.1 类和原型
在JavaScript中,类的所有实例对象都从同一个原型对象上继承属性。
9.2 类和构造函数
使用new调用构造函数会自动创建一个新对象,因此构造函数本身只需要初始化这个新对象的状态即可。调用构造函数的一个重要特征是,构造函数的prototype属性被用做新对象的原型。

定义构造函数即是定义类,并且类名首字母要大写,而普通的函数和方法都是首字母小写。

function Range(from, to) {
	this.from = from;
	this.to = to;	
}

Range.prototype = {
	includes: function(x) { return this.from <= x && x <= this.to; },
	foreach: function(f) {
		for(var x = Math.ceil(this.from); x <= this.to; x++) f(x);
	},
	toString: function() { return "("+this.from + "..."+this.to+")"; }
};

var r = new Range(1,3);
console.log(r.includes(2));

构造函数调用和普通函数调用时不尽相同的。构造函数就是用来构造新对象的,它必须通过关键字new调用,如果将构造函数用做普通函数的话,往往不会正常工作。

9.2.1 构造函数和类的标识
当使用instanceof运算符来检测对象是否属于某个类时会用到构造函数。假设这里有一个r对象,想知道r是否是Range对象,这样写:r instanceof Range,实际上instanceof运算符并不会检查r是否是由Range()构造函数初始化而来,而会检查r是否继承自Range.prototype。不过,instanceof的语法则强化了构造函数是类的共有标识的概念。
9.2.2 constructor属性
每个JavaScript函数都自动拥有一个prototype属性。这个属性的值是一个对象。这个对象包含唯一一个不可枚举属性constructor。constructor属性的值是一个函数对象,指向函数本身。(注意看,函数原始的prototype对象,它有一个constructor属性,它执行函数体本身):

function Range(from, to) {
	this.from = from;
	this.to = to;	
}
console.log(Range.prototype.constructor === Range); 	//true
var f = new Range(1,3);
console.log(f.constructor === Range);			//true

由于f对象是继承Range.prototype,自然r就有一个叫contructor的属性。但是:

function Range(from, to) {
	this.from = from;
	this.to = to;	
}
Range.prototype = {};
console.log(Range.prototype.constructor === Range); 	//false
var f = new Range(1,3);
console.log(f.constructor === Range);			//false

Range.prototype被覆盖,自然什么就没有了,所以在自定义的时候,为了兼容性,一般需要带上constructor:

Range.prototype = {
	constructor:Range,
	inc:function(){}
};

9.3 JavaScript中Java式的类继承
9.4 类的扩充
9.5 类和类型
9.5.1 instanceof运算符
尽管instanceof运算符的有操作数是构造函数,但计算过程实际上是检测了对象的继承关系,而不是检测创建对象的构造函数(检测prototype,一直往上,如果对象间接继承,也返回true)
9.5.2 constructor属性
9.5.3 构造函数的名称
注意instanceof 和 constructor比较的都是一个当前生成的对象,在多上下文中,这个对象是不一样的,所以会存在问题。实际,如果比较构造函数的名称,就不存在这个问题。

可以调用对象的toString方法转换成字符串,然后提取名称(来自constructor属性),不是所有对象都有constructor属性,也不是多少对象都有名字。
9.5.4 鸭式辩型

P217开始…….
9.6 JavaScript中的面向对象技术
9.7 子类
9.8 ECMAScript 5 中的类
9.9 模块

jQuery中的Deferred

官方参考:http://api.jquery.com/category/deferred-object/

从jQuery 1.5.0开始,jQuery提供了Deferred对象。defer的意思是“延迟”,那么deferred大概就是被延迟的意思,Deferred对象实际上就是一种回调函数解决方案。

jQuery中的ajax方法:

$.ajax({
	url:"getData.php",
	success:function(){},
	error:function(){}
});

在Ajax请求成功和失败时分别调用对应的方法。jQuery中最为强大的特征是链式操作,但是如果在jQuery 1.5.0之前,$.ajax是不支持链式操作的,因为它会返回XHR对象。但是在jQuery 1.5.0之后,就可以如下使用:

$.ajax("getData.php").done(function(){}).fail(function(){});

这个写法和前一个例子实现了相同的逻辑,不过这里的是链式操作。

在第一个例子中,$.ajax成功时调用success指定的方法,但是如果希望成功时想执行多个方法是做不到的。但是使用如下方法可以做到:

$.ajax("getData.php")
.done(function(){})
.done(function(){})
.fail(function(){})

可以在成功或失败时绑定多个回调函数。

同时发起多个Ajax请求的情况也是很常见的,如果希望多个请求都成功了,就调用指定的回调函数,这个传统做法也是实现不了的,jQuery中可以这样做:

$.when($.ajax("getData1.php"),$.ajax("getData2.php"))
.done(function(){})
.fail(function(){})

这个写法的意思是:如果两个Ajax请求都成功,执行done绑定的函数,只有有一个失败或都失败,执行fail绑定的函数。

以上针对的都是Ajax这种操作,实际上,可以扩展到一般情况,比如针对一个耗时操作,希望它执行完毕后调用回调函数:

var big = function(){
	var defer = $.deferred();
	//耗时操作
	defer.resolve();

	return defer;
}

var df = big();
$.when(df)
.done(function(){})
.fail(function(){})

这样,耗时操作执行完毕就可以执行对应回调了。这个例子实际就是jQuery 1.5.0之后$.ajax实现的基本原理(jQuery 1.5.0之后ajax实现被重写,内部使用$.deferred)。实际这个过程不难理解,$.deferred()获取一个Deferred对象,耗时操作完毕后调用它的resolve()标志Deferred对象是已经解决的(后续执行回调的切入点),$.when函数接收Deferred对象为参数,它会检查这个对象的状态(是否被resolve),然后对应执行相应的回调函数。

这里需要对resolve()做一些说明:
deferred对象有三种执行状态—-未完成,已完成和已失败。如果执行状态是”已完成”(resolved),deferred对象立刻调用done()方法指定的回调函数;如果执行状态是”已失败”,调用fail()方法指定的回调函数;如果执行状态是”未完成”,则继续等待,或者调用progress()方法指定的回调函数(jQuery1.7版本添加)

jQuery中的Ajax操作这个过程是自动完成的。但是如果是自定义的方式,我们就需要自己去调用deferred.resolve()方法和deferred.reject()方法(把未完成状态改为已失败)。

到这里,原理上的东西应该已经说明白了,但是,还有一个叫promise的东西。考虑如上的代码,它返回的是Deferred对象,这个对象状态被改变时执行相应的回调,但是我们完全可以对返回的Deferred对象再次调用deferred.reject()方法使得原本应该是成功的,变成失败,然后调用失败对应的回调函数,意思就是说,状态的改变可以在外部被再次改变,为了改变这个缺陷,引入了promise对象,它几乎跟deferred对象一样,只是被剔除了可以改变状态的方法,比如resolve()和reject()等方法,使得状态不会在外部被再次改变。所以以上代码可以改为:

var big = function(){
	var defer = $.deferred();
	//耗时操作
	defer.resolve();

	return defer.promise();
}

var df = big();
$.when(df)
.done(function(){})
.fail(function(){})

Deferred对象的promise()返回一个promise对象,是一个被处理过的(阉割了方法)的Deferred对象,后续操作跟之前的一样。

另外,jQuery中还提供了一些便利的操作手法:

var fn = function(df){
	//
	df.resolve();
}

var defer = $.Deferred(fn);

$.when(defer).done().fail();

就是$.Deferred可以接受一个函数,这个函数接受当前这个对象作为参数,玛尼,这个绝对是淫技。

———————————-
AngularJS中的$q服务,实现了类似的东西:

//AngularJS
var defer = $q.defer();
defer.resolve();
var promise = defer.promise();

$q.all([defer1,defer2,defer3]).then(function(){});

//jQuery
$.when(defer1,defer2,defer3).then(function(){});

jQuery中的$.when对应AngularJS中的$q.all。then方法一样意思。AngularJS中的$http服务,和jQuery中的Ajax用法是一样的:

// AngularJS
$http({
	method:'POST',
	url:'getData.php',
	params:{}
}).success(function(d){});

//jQuery
$.ajax(
	url:'getData.php',
	type:'POST',
	data:{},
	success:function(){}
);

$.ajax({
	url:'getData.php',
	type:'POST',
	data:{}	
}).done(function(d){});

———————————-

函数简单说明:

deferred.always()	//当Deferred(延迟)对象解决或拒绝时,调用添加处理程序。
deferred.done()		//当Deferred(延迟)对象解决时,调用添加处理程序。
deferred.fail()		//当Deferred(延迟)对象拒绝时,调用添加处理程序。
deferred.isRejected()	//确定一个Deferred(延迟)对象是否已被拒绝。
deferred.isResolved()	//确定一个Deferred(延迟)对象是否已被解决。
deferred.notify()	//根据给定的 args参数 调用Deferred(延迟)对象上进行中的回调 (progressCallbacks)。
deferred.notifyWith() 	//根据给定的上下文(context)和args递延调用Deferred(延迟)对象上进行中的回调(progressCallbacks )。
deferred.pipe()		//实用的方法来过滤 and/or 链Deferreds。
deferred.progress()	//当Deferred(延迟)对象生成进度通知时,调用添加处理程序。
deferred.promise()	//返回Deferred(延迟)的Promise(承诺)对象。
deferred.reject()	//拒绝Deferred(延迟)对象,并根据给定的args参数调用任何失败回调函数(failCallbacks)。
deferred.rejectWith()	//拒绝Deferred(延迟)对象,并根据给定的 context和args参数调用任何失败回调函数(failCallbacks)。
deferred.resolve()	//解决Deferred(延迟)对象,并根据给定的args参数调用任何完成回调函数(doneCallbacks)。
deferred.resolveWith()	//解决Deferred(延迟)对象,并根据给定的 context和args参数调用任何完成回调函数(doneCallbacks)。
deferred.state()	//确定一个Deferred(延迟)对象的当前状态。
deferred.then()		//当Deferred(延迟)对象解决,拒绝或仍在进行中时,调用添加处理程序。
jQuery.Deferred()	//一个构造函数,返回一个链式实用对象方法来注册多个回调,回调队列, 调用回调队列,并转达任何同步或异步函数的成功或失败状态。
jQuery.when()		//提供一种方法来执行一个或多个对象的回调函数, Deferred(延迟)对象通常表示异步事件。
deferred.promise()	//返回一个 Promise 对象用来观察当某种类型的所有行动绑定到集合,排队与否还是已经完成。

文件上传 与 图片客户端预览

图片客户端预览:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="txt/html;charset=utf-8" />
<title>Javascript实现IE,firefox客户端图片预览</title>
<script>
//使用IE条件注释来判断是否IE6,通过判断userAgent不一定准确
if (document.all) document.write('<!--[if lte IE 6]><script type="text/javascript">window.ie6= true<\/script><![endif]-->');

function change(picId,fileId) {
	var pic = document.getElementById(picId);
  	var file = document.getElementById(fileId);
	
	//chrome,firefox7+,opera,IE10
  	if(window.FileReader) {
   		oFReader = new FileReader();
   		oFReader.readAsDataURL(file.files[0]);
   		oFReader.onload = function (oFREvent) {
			pic.src = oFREvent.target.result;
		};  
  	} else if (document.all) {
		//IE8-
   		file.select();
		//IE下获取实际的本地文件路径
   		var reallocalpath = document.selection.createRange().text;
		
		//IE6浏览器设置img的src为本地路径可以直接显示图片
   		if (window.ie6) {
			pic.src = reallocalpath; 
		} else { 
			//非IE6版本的IE由于安全问题直接设置img的src无法显示本地图片,但是可以通过滤镜来实现
    		pic.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod='image',src=\"" + reallocalpath + "\")";
			//设置img的src为base64编码的透明图片,要不会显示红xx
    		pic.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==';
   		}
  	} else if (file.files) {
		//firefox6-
   		if (file.files.item(0)) {
    		url = file.files.item(0).getAsDataURL();
    		pic.src = url;
   		}
  	}
}
</script>
</head>
<body>
<form name="form1" enctype="multipart/form-data">
    <table>
        <tr>
            <td>选择图片:</td>
            <td >
            	<input type="file" name="file1" id="file1" onchange="change('pic1','file1')">
            </td>
            <tr>
            	<td>预览:</td>
            <td>
            	<img src="images/px.gif" id="pic1" >
            </td>
        </tr>
    </table>
</form>
</body>
</html>

Javascript实现图片客户端预览。这段实现代码对大部分浏览器应该都是可用的,特别是针对IE做的兼容,实在让人觉得IE无比的恶心。(实际测试IE9无法工作,实际IE9没有实现HTML5的File API)。HTML5引入的FileReader对象,目前大部分主流浏览器都支持,它可以读取文件或任意Blob的内容。

先搞清楚Blob:
Blob是对大数据块的不透明引用或者句柄(Binary Large Object)。Blob是不透明的,能对它们进行直接操作的就只有获取它们的大小(字节)、MIME类型以及将它们分割成更小的Blob。

在HTML5中,元素上的files属性是一个FileList对象。该对象是一个类数组对象,其元素要么是0,要么是用户选择的多个File对象。一个File对象就是一个Blob,除此,还多了name和lastModifiedDate属性:

<script>
function fileinfo(files){
	for(var i=0;i<files.length;i++){
		var f = files[i];
		console.log(f.name,f.size,f.type,f.lastModifiedDate);
	}
}
</script>

<input type="file" accept="image/*" multiple onchange="fileinfo(this.files) "/>

注意这里的files属性。它就是文件选择框选择的文件(可以多选)。文件类型的input元素多了一个multiple属性,弹出来的选择框可以选择多个文件,对应保存到files这个类数组中。(IE8及以下对这个multiple属性视乎都不支持,为了可以使用需要技巧,WordPress中使用的文件上传插件http://www.plupload.com/)。除了Multple属性,HTML5还带来了accept属性,比如值允许图片,值可以为image/*,或者用逗号分隔多个值。

File文件域继承自Blob,参考:
js_file_blob

那么如何读取这个Blob的内容呢,这个就是FileReader对象的作用了:

//方法
abort
readAsText(file,[encoding])
readAsBinaryString(file)
readAsDataURL(file)

//事件
onabort
onerror
onloadstart
onload
onloadend
onprogress(在应用进度条时有用)

//属性
result

注意三个方法可以取回Blob的内容,不过要注意的是无论读取成功或失败,方法并不会返回读取结果,这一结果存储在result属性中(异步进行,一般在事件onload中取result内容)。这里主要关注readAsDataURL(file)方法,这个方法就是读取返回的内容换成用户URL的数据,比如一张图片,就转换成如下格式:

data:image/jpeg;base64,xxxxx

基本是data:开头,紧接Mime类型,然后是base64,表示之后的数据是base64编码,对于图片,可以直接赋值给img标签的src属性,图片就可以显示出来。

上面讨论过读取内容会填充到result中,它是异步进行,那么在什么时机获取这个result呢,这个可以通过onload事件实现,通过绑定一个回调函数(onload触发时,result已经被填充)获取result内容。

fr.onload = function(){
	var fc = this.result;
}

//
fr.onload = function(e) {
	var fc = e.target.result;
}

文件一旦开始读取,无论成功或失败,实例的 result 属性都会被填充。如果读取失败,则 result 的值为 null ,否则即是读取的结果,绝大多数的程序都会在成功读取文件的时候,抓取这个值。(onload事件的回调函数函数中判断一下是否为null)

如果要使用Ajax批量上传文件,服务器接收一下,以下是一段范本:

<?php 
if(isset($_POST['name'])){
    file_put_contents("D:/".$_POST['name'],$_POST['data']);
    
    exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>客户端文件上传测试</title>
<style type="text/css">
body { font:12px Helvetica, arial, sans-serif }
</style>

<!-- JS  -->
<script type="text/javascript" src="jquery.js"></script>

<!--[if lte IE 8]>
<script type="text/javascript">
var fuckIe=true;
</script>
<![endif]-->

<script type="text/javascript">
$(function(){
	var result = {};
	
	$("#file1").change(function() {
		if(typeof fuckIe !== "undefined"){
			alert("玛尼,IE6 7 8不支持当前功能。珍惜生命,请远离IE。");
			return;
		}
	
		var file = document.getElementById("file1");
		
		var files = {};
		for(var i=0;i<file.files.length;i++){
			var name = file.files[i].name;
			// 一次文件选择框只能选择相同目录中的文件,应该不存在同名情况,不过这里还是进行了去重逻辑
    		if(typeof files[name] === 'undefined'){
        		files[name] = file.files[i];
    		}
        	
        	// 可用参数  这里的大小 和 类型可以用来限制用户选择的文件
    		//console.log(file.files[i]);
        	//console.log("文件名称 - "+file.files[i].name);
			//console.log("文件大小 - "+file.files[i].size);
			//console.log("文件类型 - "+file.files[i].type);
			//console.log("修改时间 - "+file.files[i].lastModifiedDate);
		}
		
		// 把文件内容记录到全局数据,可以多次跨目录多选,这里根据文件名去重,严格的可以对文件内容取签名去重
		for(var name in files){
			if(result[name]){ continue; }
			result[name] = new FileReader();
			result[name].readAsDataURL(files[name]);
		}
	});

	function formatResult(){
		if(typeof fuckIe !== "undefined"){
			alert("玛尼,IE6 7 8不支持当前功能。珍惜生命,请远离IE。");
			return false;
		}
		
		var fr = {};
		for(var r in result){
			if(typeof result[r].result == 'undefined'){
				fr = {};
				alert("请等待文件读取完毕...");
				return;
			}
			fr[r] = result[r];
		}

		return fr;
	}
	$("#upload").click(function(){
		var fr = formatResult();
		if(!fr){ return; }
		result = {};
		for(var r in fr){
			$.post("index.php",{"name":r,"data":fr[r].result},function(){
			});
			$("#file1").val('');
		}
	});

	$("#review").click(function(){
		var fr = formatResult();
		if(!fr){ return; }
		$("#imglist").html('');
		for(var r in fr){
			$("#imglist").append('<li><img width="200" src="'+fr[r].result+'" /></li>');
		}
	});
});
</script>

</head>
<body>
<table>
    <tr>
        <td>
            <input type="file" name="file1" id="file1" accept="images/*" value="请选择文件" multiple="multiple" />
        </td>
        <td>
            <input type="button" id="upload" value="上传" cantrigger='' />
        </td>
        <td>
            <input type="button" id="review" value="预览" cantrigger='' />
        </td>
    </tr>
</table>
<br />

<ul id="imglist">

</ul>
</body>
</html>

不得不说的是,在IE中,文件多选框难产(IE高版本可能支持input类型为file的表单的mutiple属性,未测试),FileReader对象大概也是IE9以上才有(Win7默认为IE8浏览器),所以,为了兼容IE,需要做很多工作,这个就是为何第三方的文件上传插件存在的价值,比如Wordpress中使用的plupload(http://www.plupload.com/),它使用Flash和Silverlight,对于无法弹出多选框的浏览器(主要是IE),通过Flash或Silverlight来实现,而上传文件大概也是对应的。

那么,也就是说,如果你不打算兼容IE 6 7 8,应该就可以不需要使用第三方的插件了。

<?php
if (isset ( $_POST ['name'] )) {
    list ( , $data ) = explode ( ',', $_POST ['data'] );
    file_put_contents ( "D:/" . $_POST ['name'], base64_decode ( $data ) );
    
    header ( 'Content-type: text/json' );
    echo json_encode ( [ 
        'success' => 1,
        'err' => '' 
    ] );
    exit ();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>客户端文件上传测试</title>
<style type="text/css">
body {
	font: 12px Helvetica, arial, sans-serif
}
</style>

<!-- JS  -->
<script type="text/javascript" src="jquery.js"></script>

<!--[if lte IE 8]>
<script type="text/javascript">
var fuckIe=true;
</script>
<![endif]-->

<script type="text/javascript">
$(function(){
    var result = {};
     
    $("#file1").change(function() {
        if(typeof fuckIe !== "undefined"){
            alert("玛尼,IE6 7 8不支持当前功能。珍惜生命,请远离IE。");
            return;
        }
        var file = document.getElementById("file1");
        
      	for(var i=0;i<file.files.length;i++){
       		var name = file.files[i].name;
          	var reader = new FileReader();
          	reader.readAsDataURL(file.files[i]);

          	(function(name){
            	reader.onload = function(){
               		$("#imglist").append('<li><img alt="'+name+'" width="200" src="'+this.result+'" /></li>');
              	};
          	})(name);        
       	}
    });
    
    $("#upload").click(function(){
    	var ilst = $("#imglist img");
    	$("#file1").val('');
    	ilst.each(function(){
        	var $this = $(this);
        	var name = $this.attr('alt');
        	var data = $this.attr('src');

        	$.post('file_up.php',{"name":name,"data":data},function(d){
				if(d['success'] > 0) {
					$this.parents("li").remove();
				} else {
					alert(name+"上传失败");
				}
            },'json');
        });
    });
});
</script>

</head>
<body>
	<table>
		<tr>
			<td><input type="file" name="file1" id="file1" accept="images/*"
				value="请选择文件" multiple="multiple" /></td>
			<td><input type="button" id="upload" value="上传" cantrigger='' /></td>
		</tr>
	</table>
	<br />

	<ul id="imglist">

	</ul>
</body>
</html>

Javascript碎片笔记

JavaScript只有一个单一的数字类型。它内部被表示为64位的浮点数。值NaN是一个数值,它表示一个不能产生正常结果的运算结果。NaN不等于任何值,包括它自己。可以用函数isNaN(number)检测NaN。

值Infinity表示所有大于1.79xxxe+308的值。

转义字符允许把那些正常情况下不被允许的字符插入到字符串中,比如反斜线、引号和控制符。\u约定允许指定用数字表示的字符码位。

"A" === "\u0041"

字符串有一个length属性。

字符串是不可变的。一旦字符串被创建,就永远无法改变它。通过+运算符去链接其它的字符串会得到一个新字符串。

被当做假:
false
null
undefined
空字符串’ ‘
数字 0
数字 NaN

除此,所有值都被当做真,包括true 字符串”false”以及所有对象。

for (myvar in obj) {
	if (obj.hasOwnProperty(myvar)) {
		...
	}
}

通常须通过检查object.hasOwnProperty(var)来确定这个属性名就是该对象的成员,还是从其原型链里找到的。

return语句如果没有指定返回表达式,其返回值是undefined。JavaScript不允许在return(break)关键字和表达式之间换行。

JavaScript的简单类型包括数字、字符串、布尔值(true和false)、null值和undefined值。其它所有值都是对象。数字、字符串和布尔值类似对象,因为它们有方法,但它们是不可变的。

对象是属性的容器,其中每个属性都有名字和值。属性的名字可以是包括空字符串在内的任意字符串。属性值可以是除undefined值之外的任何值。(不能没有名字 和 不能没有值)

var sum = function(){
	var i, sum = 0;
	for(i = 0; i< arguments.length;i++){
		sum += arguments[i];
	}
}

arguments并不是一个真正的数组。arguments有一个length属性,但它缺少所有的数组方法。

一个函数总是返回一个值。如果没有指定返回值,则返回undefined。

Function.prototype.method = function(name,func){
	this.prototype[name]  = func;
	return this;
}
//改进
Function.prototype.method = function(name,func){
	if(!this.prototype[name]){
		this.prototype[name]  = func;
	}
	return this;
}

Number.method('integer',function(){
	return Math[this<0?'ceiling':'floor'](this);
});

String.method('trim',function(){
	return this.replace(/^\s+|\s+$/g,'');
}

作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量(除了this和arguments)。

判断变量为数组:

var is_array = function (value) {
	return value &&
		typeof value === 'object' &&
		value.constructor === Array;
};

//以下是最可靠的方法
var is_array = function(value){
	return value &&
		typeof value === 'object' &&
		typeof value.length === 'number' &&
		typeof value.splice === 'function' &&
		!(value.propertyIsEnumerable('length'));
}

——————-
注意:现在大部分浏览器都应该支持Array.isArray()方法,为了兼容性,可用jQuery中提供的$.isArray()。

Javascript 数组常用函数 (去重 是否有重复值 交集 并集)

自己写了几个函数,可能不是很高效,但至少是可用的…

Array.prototype.unique = function(){ 
	var uniq = [],hash = {};  
    for(var i = 0; i < this.length; i++){  
        if(!hash[this[i]]){  
        	uniq.push(this[i]);
        	hash[this[i]] = true; 
        }  
    }  
    return uniq; 
};

Array.prototype.isUnique = function(){ 
	var uniq = true, hash = {};
	
    for(var i = 0; i < this.length; i++){  
        if(!hash[this[i]]){
        	hash[this[i]] = true; 
        }else{
        	uniq = false;
        	break;
        }
    }  
    return uniq; 
};

Array.prototype.has = function(search){
	for(var i = 0; i < this.length; i++){
		if(this[i] == search){
			return true;
	  	}
	}
    return false;
};

Array.prototype.intersect = function(arr){
	if(arr.constructor == Array){
		var unique = this.unique();

		var hash = {};  
	    for(var i = 0; i < arr.length; i++){
	    	if(!hash[arr[i]]){
	        	hash[arr[i]] = true; 
	        }
	    }
	    var inter = [];
	    for(var i=0; i<unique.length; i++){
			if(hash[unique[i]]){
				inter.push(unique[i]);
			}
		}
		return inter;
	}else{
		return [];
	}
};

Array.prototype.merge = function(arr){
	if(arr.constructor == Array){
		for(var i = 0; i < arr.length; i++){
			this.push(arr[i]);
		}
		return this.unique();
	}else{
		return this;
	}
};