这一系列文章都讲述的是关于使用 JavaScript 操作文件相关的知识,其中最重要的是 File 对象,而实际上 file 对象只是 blob 对象的一个更具体的版本,blob 存储着大量的二进制数据,并且 blob 的 size 和 type 属性,都会被 file 对象所继承。

所以,可以说,在大多数情况下,blob 对象和 file 对象可以用在同一个地方,例如,可以使用 FileReader 借口从 blob 读取数据,也可以使用 URL.createObjectURL() 从 blob 创建一个新的 URL 对象。

Slicing (分割)

通过 Blob 对象可以做的一件有趣的事情就是可以创建一个子 Blob 对象,其实就是可以将其分割(file 对象也可以)。由于每个 Blob 对象都是通过指针指向数据的而不是指向数据本身,因此可以快速的创建指向其他子部分的新的 Blob 对象,这里就需要使用 slice() 方法了。是不是和 JavaScript 的 slice() 方法很象,其实差不多。

此方法接受三个参数,起始偏移量,结束偏移量,还有可选的 mime 类型。如果 mime 类型,没有设置,那么新的 Blob 对象的 mime 类型和父级一样。

不过目前浏览器实现此方法还没有统一,火狐使用的是 mozSlice() ,Chrome 使用的是 webkitSlice() ,其他浏览器则正常的方式 slice() 。重写的兼容各个浏览器的例子如下:

function sliceBlob(blob, start, end, type) {

    type = type || blob.type;

    if (blob.mozSlice) {
        return blob.mozSlice(start, end, type);
    } else if (blob.webkitSlice) {
        return blob.webkitSlice(start, end type);
    } else {
        throw new Error("This doesn't work!");
    }
}

当要上传大文件的时候,此方法非常有用,可以将大文件分割分段,然后各自上传,因为分割之后的 Blob 对象和原始的是独立存在的。例如,Flickr 的工程师就使用此方法将照片中的需要使用的 exif 信息截取出来,而不是等到其传到服务器上之后,才处理的,并且一旦选择要上传照片,则同时传输文件数据和 Exif 数据,这样几乎就可以在上传照片的时候同时显示照片的信息了。

使用旧方法创建 Blob 对象

当 File 对象开始出现在浏览器中的时候,开发者们意识到 Blob 对象太强大了,都想可以在用户不干预的情况下,创建 Blob 对象,毕竟任何数据都可以用 Blob 对象表示,不用非要和文件产生关系。通过 BlobBuilder 创建一个包含有特定数据的 Blob 对象,然后浏览器快速响应即可。(不过目前其还不统一,Firefox:MozBlobBuilder, Internet Explorer 10:MSBlobBuilder,Chrome:WebKitBlobBuilder)。

例子:

var builder = new BlobBuilder();
builder.append("Hello world!");
var blob = builder.getBlob("text/plain");

BlobBuilder() 创建一个新实例,并且使用一个 append() 方法,将字符串(或者 ArrayBuffer 或者 Blob,此处用 string 举例)插入,一旦数据插入成功,就可以使用 getBlob() 方法设置一个 mime 。

并且 BlobBuilder() 还可以处理动态数据,例如 worker 中的数据等,这里就不翻译了。

使用新方法创建 Blob 对象

因为开发者一直想要能够直接创建 Blob 对象,因此浏览器实现了 BlobBuilder(); Blob 作为构造函数而存在,而且,此构造函数也已经被作为标准了,其也是今后创建 Blob 对象的方式。

Blob()–构造函数,接受两个参数,第一个为一个数据序列,可以是任意格式的值,例如,任意数量的字符串,Blobs 以及 ArrayBuffers。第二个参数,是一个包含了两个属性的对象,其两个属性分别是:

type — MIME 的类型。

endings — 决定 append() 的数据格式,(数据中的 \n 如何被转换)可以取值为 “transparent” 或者 “native”(t* 的话不变,n* 的话按操作系统转换;t* 为默认) 。

例子:

var blob = new Blob(["Hello world!"], { type: "text/plain" });

可以看到,此方法比 BlobBuilder() 简单多了。

但是,此 Blob 构造函数还没有被一些浏览器实现,目前只有某些版本的 Chrome 和 Firefox 实现了。而且剩余的浏览器也没有确定什么时候实现,但是其现在作为 File API 的一部分,将来应该会被统一实现的。

全文完。

会使用传统的方式上传图片,也能够用 js 从本地读取图片,现在讨论一下一种更简单的方法来上传文件。那就是 URL 对象 。

什么是 URL 对象(object url)

URL 对象就是一个指向磁盘上文件的 url,例如,想要在浏览器中显示一个文件,而这个文件存在与用户的电脑上,通常的办法是先上传到服务器,然后在现实,但这里却不需要服务器的干预,只需要加载图片即可。上一节的讨论可以讲文件加载进一个 file 对象。将图像文件数据读取为 data URI 然后将此 data URI 加入一个 img 对象的属性中即可,但是既然图像已经存在于硬盘上,为什么还要将它读取为其他格式,然后再来使用呢?为何要多此一举呢?如果使用 URL 对象的话,那么就可以直接将硬盘里的文件付给 img 对象了。

如何工作?

File API 对全局的 URL 对象,定义了两个方法,一个是 createObjectURL() ,接受一个指向文件的参数,然后返回 URL 对象 ,这就使得浏览器能够创建和管理一个指向本地文的 URL ;另一个方法是 revokeObjectURL() ,使得浏览器可以销毁传递给它的 URL 对象,以释放内存,并且页面一旦关闭,所有的 url 对象都会被销毁。不过这样做的好处就是不需要他们的时候,浏览器可以及时释放内存。

示例:

那该怎么做呢?首先给用户一个选择文件的方式,然后将其引用到一个变量 file。然后可以使用下面的代码:

var URL = window.URL || window.webkitURL,
    imageUrl,
    image;

if (URL) {
    imageUrl = URL.createObjectURL(file);
    image = document.createElement("img");

    image.onload = function() {
        URL.revokeObjectURL(imageUrl);
    };

    image.src = imageUrl;
    document.body.appendChild(image);
}

示例首先创建了一个本地 URL 变量(假设此 URL 变量正常可用),接着代码直接将本地的文件创建了一个 URL 对象然后将其存储在 imageUrl 中,然后后创建一个 img 对象,给 img 对象一个事件,等 img 对象加载完成之后,销毁 imageUrl ,然后将此 imageUrl 付给 img 对象的 src 属性,再将其插入到页面,此时,就可以看到图像在页面上显示了。

为什么在图像加载完成之后就销毁次数据呢,因为此数据在这里只会被使用一次,所以手动的释放它以节省内存占用,但是如果还有其他用处的话,就不应该手动销毁它。

安全问题和其他注意事项

初一看,此功能有点恐怖,因为竟然可以直接加载用户的磁盘文件。当然了,这样有一些安全隐患,不过 URL 本身并没有多大的问题,因为它都是动态分配,并且在其他计算机上无法访问。

对于不同的源 File API 是不允许被使用的,当 URL 对象被创建的时候,它就被绑定到此 javascript 运行的页面,所以不可能从 www.yimity.com 访问到 post.yimity.com 页面的 URL 对象,但是两个页面都来源于 www.yimity.com ,并且一个页面是另一个页面的 iframe 的话,那就可以访问到了。

URL 对象的储存时间很短,只在文档创建他们的期间存在,一旦页面被关闭,则他们都会被销毁。所以他们不会被存储在客户端。因为页面一关闭,他们都是无用的数据了。

在 GET 请求中可以使用 URL 对象,就和正常的脚本,图像一样。但是在 post 请求中确实不可用的,例如给 form post 属性。

下一节

下一节将会有更多的关于如何处理文件数据的方式。

主题名称: iSimple

主题链接: https://yimity.com/2012/06/01/isimple.html

主题描述: iSimple . 单栏主题,支持后台选项,包括自定义站点统计,支持评论框右侧 125*125 像素的广告,评论错误/正确提示,评论 Ajax 支持,SEO 友好,支持自定义菜单,支持IE8以上,及 Chrome,Firefox,Opera等现代浏览器。不需要任何插件支持。

主题版本: 1.0

主题作者: 一米

发布日期:06月01日。

主题预览:https://yimity.com/

介绍:

iSimple. 单栏主题,支持后台选项,包括自定义站点统计,支持评论框右侧 125*125 像素的广告,评论错误/正确提示,评论 Ajax 支持,SEO 友好,支持自定义菜单,支持IE8以上,及 Chrome,Firefox,Opera等现代浏览器。不需要任何插件支持。即使 JS 加载失败,功能同样正常可用。文章样式已经定义好,几乎所有的文章发表即可正常显示。支持 10 中文章格式。支持一键切换评论模式,WP 原始评论模式和多说评论系统。后台有说明。支持响应式设计,移动设备访问更舒服,类似阅读模式。支持发布 .mp3 结尾文件直接显示播放器。全站 placeholder 支持。极度个性化的 404 页面。

后续升级计划:

增加白色内容背景选择。
优化响应式设计。
优化代码。
其他

安装:

1.将本文件解压,得到 iSimple 文件夹及其内部文件,将此文件夹上传到 WordPress 程序中的 wp-content/themes 文件夹。

2.进入博客后台,外观-主题, 选择 iSimple 主题,并且激活。

使用:

1. 上传成功,并激活成功即可使用。

2. 在 外观-主题选项中,点击“主题选项”即可设置相关的选项,不要忘记点击“更新选项”哦。

4. 后台所有输入框 HTML 都是可用的,但是建议该用的时候用,不该用的就不要用。

5. 统计代码 可以使用单选框选择是否可用,选中状态则起作用,否则统计代码即使在后台能看到,但不会起作用。

7. 多语言支持。目前只有中文简体及英语。

9. 其他任何问题,请联系 [email protected]。谢谢。

10. 主题为 1.0 版,如有相关建议或 Bugs 也请发邮件,谢谢。

当然,此份主题不是免费的,但是只出售 10 份,每份 99 元人民币。同时附赠价格同样 99 元的 BreakUp airSquare 一份。

购买者,享受不限时一对一服务,但是也只是我一人,当然了,大家都有工作,最好是晚上,最最好是周末把。

最后,当然是走支付宝,你放心,我放心。

更新日志:

更新内容 , 时间 , 版本
增加 slimbox 功能 , 2012.6.1 , 1.0
优化评论框样式 , 2012.6.4 , 1.0.1
优化响应式设计样式 , 2012.6.4 , 1.0.2
修复一处文章内容样式及翻译错误 , 2012.6.5 , 1.0.3
增加评论分页功能 , 2012.6.6 , 1.1.3
修改音频播放器样式, 2012.7.30 , 1.1.4
将文章底部时间改为多少时间以前(类似新浪微薄), 2012.9.11 , 1.2.5

获取光标位置,在很多场景中都十分有用,但是这个东西又不是那么好弄,在学习的过程中,找到这个文章,貌似被墙了,所以转载过来,也做个记录。

在任何编辑器中,获取光标位置都是非常重要的,很多人可能认为较难,其实只要处理好浏览器的兼容,还是比较容易实现的。

下面我们一起来看看如何获取到 Textarea 元素中的光标位置(测试地址1(墙) 测试地址2)。

首先,我们用 rangeData 对象作为数据存储,并获得焦点:

var rangeData = {start: 0, end: 0, text: "" };
textarea.focus();

对于非 IE 浏览器获取选区的起始和末尾位置其实非常容易:

rangeData.start= el.selectionStart;
rangeData.end = el.selectionEnd;

通过截取我们可以得到光标的选区内容:

rangeData.text = (rangeData.start != rangeData.end) ? el.value.substring(rangeData.start, rangeData.end): "";

而对于 IE 浏览器处理起来就比较麻烦了,但我们依旧可以获取到选区:

oS = document.selection.createRange();

同时还可获取 Textarea 元素的选区:

// 为了使 oR 与 oS 在同一等级上比较,请勿使用:oR = textarea.createTextRange()
oR = document.body.createTextRange();
oR.moveToElementText(textarea);

如果光标在 Textarea 元素内,很自然 oS.text 就是我们需要的选区内容:

rangeData.text = oS.text;

并且我们可以通过 oS.getBookmark() 方法获取到选区的位置数据,该位置数据可以通过 moveToBookmark() 方法设置回去。

    getBookmark: Retrieves a bookmark (opaque string) that can be used with moveToBookmark to return to the same range.

    moveToBookmark: Moves to a bookmark.

我们用 rangeData.bookmark 来记录该位置数据:

rangeData.bookmark = oS.getBookmark();

下面是最重要的步骤:我们比较 oR 与 oS 的选区起始位置(使用 object.compareEndPoints(sType, oRange) 方法比较),如果 oR 的起始位置在 oS 之前,我们向前移动 oS 的起始位置1个字符(使用 object.moveStart(sUnit [, iCount]) 方法移动),一直当 oS 的起始位置在 oR 之前停止,移动的位置,则是选区的起始位置。

    compareEndPoints: Compares an end point of a TextRange object with an end point of another range.

    moveStart: Changes the start position of the range.
for (i = 0; oR.compareEndPoints('StartToStart', oS) < 0 && oS.moveStart("character", -1) !== 0; i ++) {}
rangeData.start = i;

但由于在 IE 中,Textarea 元素中的所有换行符都占 1 个字符,可以通过 alert(textarea.value.length) 查看,故要对上面的代码做部分处理:

for (i = 0; oR.compareEndPoints('StartToStart', oS) < 0 && oS.moveStart("character", -1) !== 0; i ++) {
    // Why? You can alert(textarea.value.length)
    if (textarea.value.charAt(i) == '\n') {
        i ++;
    }
}
rangeData.start = i;

既然得到了选区的起始位置和选区字符串的字符,很自然我们可以计算得到选区的末尾位置:

rangeData.end = rangeData.text.length + rangeData.start;

获取 Textarea 的光标位置的 getCursorPosition 函数方法整理如下:

/**
* getCursorPosition Method
*
* Created by Blank Zheng on 2010/11/12.
* Copyright (c) 2010 PlanABC.net. All rights reserved.
*
* The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license.
*/
function getCursorPosition(textarea) {
    var rangeData = {text: "", start: 0, end: 0 };
        textarea.focus();
    if (textarea.setSelectionRange) { // W3C
        rangeData.start= textarea.selectionStart;
        rangeData.end = textarea.selectionEnd;
        rangeData.text = (rangeData.start != rangeData.end) ? textarea.value.substring(rangeData.start, rangeData.end): "";
    } else if (document.selection) { // IE
        var i,
            oS = document.selection.createRange(),
            // Don't: oR = textarea.createTextRange()
            oR = document.body.createTextRange();
        oR.moveToElementText(textarea);

        rangeData.text = oS.text;
        rangeData.bookmark = oS.getBookmark();

        // object.moveStart(sUnit [, iCount])
        // Return Value: Integer that returns the number of units moved.
        for (i = 0; oR.compareEndPoints('StartToStart', oS) < 0 && oS.moveStart("character", -1) !== 0; i ++) {
            // Why? You can alert(textarea.value.length)
            if (textarea.value.charAt(i) == '\n') {
                i ++;
            }
        }
        rangeData.start = i;
        rangeData.end = rangeData.text.length + rangeData.start;
    }

    return rangeData;
}

得到 Textarea 元素光标位置,当Textarea 中的光标丢失了,再设置回来就简单多了:

/**
* setCursorPosition Method
*
* Created by Blank Zheng on 2010/11/12.
* Copyright (c) 2010 PlanABC.net. All rights reserved.
*
* The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license.
*/
function setCursorPosition(textarea, rangeData) {
    if(!rangeData) {
        alert("You must get cursor position first.")
    }
    if (textarea.setSelectionRange) { // W3C
        textarea.focus();
        textarea.setSelectionRange(rangeData.start, rangeData.end);
    } else if (textarea.createTextRange) { // IE
        var oR = textarea.createTextRange();
        // Fixbug :
        // In IE, if cursor position at the end of textarea, the setCursorPosition function don't work
        if(textarea.value.length === rangeData.start) {
            oR.collapse(false)
            oR.select();
        } else {
            oR.moveToBookmark(rangeData.bookmark);
            oR.select();
        }
    }
}

测试地址1 (墙) 测试地址2

扩展阅读:
《TextRange Prototype》
《TextArea Cursor Position with JavaScript》

文章转载自:PlanABC – 怿飞’s Blog

继承链(原型链)以及构造函数

JavaScript 对象都有一个 prototype 属性,这个属性的作用就是为了方便的进行 继承。一个对象的原型属性,可以被设置为另外一个对象创建的继承链的实例,也就是说,对象 B在创建的时候,可以继承对象 A 的 prototype 属性。

function Shape(name) {
  this.x = 0;
  this.y = 0;
  this.name = name;
  console.log('Shape constructor called');
}

Shape.prototype = {
  move: function(x, y) {
    this.x += x;
    this.y += y;
  },

  toString: function() {
    return 'name: ' + this.name + ', at x: ' + this.x + ', y:' + this.y;
  }
};

// Rectangle
function Rectangle(name) {
  this.name = name;
  console.log('Rectangle constructor called');
}

Rectangle.prototype = new Shape();

var rect = new Rectangle('Player 1');
rect.move(1, 1);
console.log(rect.toString());
console.log(rect instanceof Rectangle);

此代码执行后会得到:

Shape constructor called
Rectangle constructor called
name: Player 1, at x: 1, y:1
true

结果表明,Shape 和 Rectangle 这两个构造函数( constructor )都被调用了。这是因为这句 Rectangle.prototype = new Shape();–new Rectangle() 并不会使其父对象的构造函数自动被调用,这也就是为什么两个构造函数都有 this.name = name; 了。

这里的 Shape.prototype 的方法 rect.move 和 rect.toString,当代码执行到这里的时候,如果构造函数 Rectangle 中有此方法,则就执行此方法,如果构造函数 Rectangle 中 没有此方法,则会向上寻找到 Rectangle.prototype ,如果找到,则执行,否则则返回 undefined 。如果构造函数 Rectangle 中和 Rectangle.prototype 都有此方法的话,则优先使用构造函数 Rectangle 中的方法,而不是 Rectangle.prototype 的。

调用父方法

但是,如果想要 Rectangle 使用一个不同的 move 方法,而又想重用原始的 Shape 方法的话,这时最好的方法就是使用 Function.prototype.apply:

Rectangle.prototype.move = function(x, y) {
  console.log('Super method called');
  Shape.prototype.move.apply(this, arguments);
};

虽然 Shape.prototype.move.apply 看起来很复杂, 但是如果把它拆解开来看的话,其实也比较简单。

1. 想要使用 Shape 调用 move 方法。
2. 此方法被存储在 Shape.prototype.move 中。
3. 由于是一个函数,所以有很多方法可以调用。
4. apply 方法可以调用一个函数而不用创建一个新实例。
5. 可以使用一些参数。

当函数执行的时候,arguments 对象,会被解释器所创建,其实就是传入的参数列表,类似参数的数组。而 this 则又是两一个深奥的东西了,那么下次再说。

FileReader 对象被用来,通过浏览器来读取文件数据,在上篇文章中,讨论了可以使用 FileReader 用多种格式来方便的读取文件数据,在很多方面,FileReader 和 XMLHttpRequest 非常类似。

Progress events (进度事件)

实际上,Progress 事件是一个单独定义的很普通的规范,这些事件被定义来大概的显示数据传输的进度,例如,当从服务器传送数据开始发生的时候,同样的可以是FileReader 对象开始从磁盘读取数据事件发生的时候。

总共有六个事件:

loadstart – 当加载数据开始的时候,此事件通常都是使用在整个过程的最开始。
progress – 当数据读取的时候,可以获得数据读取进度。
error – 当数据加载失败的时候。
abort – 当数据加载被 abort() 方法取消的时候,(在 FileReader 和 XMLHttpRequest 都可用)。
load – 当所有数据都被成功加载的时候。
loadend – 当数据完成传输的时候,一般情况下,都是在出错,取消,或者成功加载后。所以其不区分成功与否。只要过程完成就好。

error 和 load 已经在上篇文章中讨论过了,这里将讨论其他的一些事件。

跟踪进度

当想要知道文件的读取进度的时候,就应该使用 progress 事件,此事件对象,包含了三个属性,来表示文件开始传输之后的状态。

lengthComputable ─ true或者false,表示请求数据的大小是否已知。
loaded ─ 目前接收到的字节数。
total ─ 整个请求中期望传输的字节数。
注意:当lengthComputable为false时,total属性值为0。loadstart事件只会被激发一次。在loadstart之后,进度事件可能被激发0次或多次。

这些数据的作用是可以使 进度条(progress Html5)使用 progress 事件的信息进行进度展示。例如使用 Html5 的 progress 标签来展示数据读取进度。

var reader = new FileReader(),
     progressNode = document.getElementById("my-progress");

reader.onprogress = function(event) {
    if (event.lengthComputable) {
        progressNode.max = event.total;
        progressNode.value = event.loaded;
    }
};

reader.onloadend = function(event) {
    var contents = event.target.result,
        error    = event.target.error;

    if (error != null) {
        console.error("File could not be read! Code " + error.code);
    } else {
        progressNode.max = 1;
        progressNode.value = 1;
        console.log("Contents: " + contents);
    }
};

reader.readAsText(file);

相同的例子就是 Gmail 使用的拖拽上传,当把文件拖拽到 Gmail 里之后,就会立即出现进度条,显示上传进度。

处理错误

虽然是从本地上传文件,但有时候还是会失败。File API 定义了四种错误类型。

NotFoundError – 找不到文件。
SecurityError – 文件本身或者读取存在危险,一般情况下,如果读取此文件是危险的或者已经读取了太多次此文件,就会出现该错误。
NotReadableError – 文件存在,但是没有权限读取。
EncodingError – 编码错误,只要是作为 data URI 读取的时候,读取结果的长度超过了浏览器支持的最大长度。

当读取文件时有错误发生时,FileReader 对象的错误属性,就会成为上面提到的一个错误类型的一个实例。在规范中,浏览器都会用一个代码表明错误的属性和值。

FileError.NOT_FOUND_ERR -- NotFoundError.
FileError.SECURITY_ERR -- SecurityError.
FileError.NOT_READABLE_ERR -- NotReadableError.
FileError.ENCODING_ERR for -- EncodingError .
FileError.ABORT_ERR -- 当 abort() 方法调用的时候。

可以通过下面的代码测试不同的错误:

var reader = new FileReader();

reader.onloadend = function(event) {
    var contents = event.target.result,
        error    = event.target.error;

    if (error != null) {
        switch (error.code) {
            case error.ENCODING_ERR:
                console.error("Encoding error!");
                break;

            case error.NOT_FOUND_ERR:
                console.error("File not found!");
                break;

            case error.NOT_READABLE_ERR:
                console.error("File could not be read!");
                break;

            case error.SECURITY_ERR:
                console.error("Security issue with file!");
                break;

            default:
                console.error("I have no idea what's wrong!");
        }
    } else {
        progressNode.max = 1;
        progressNode.value = 1;
        console.log("Contents: " + contents);
    }
};

reader.readAsText(file);

下节

FileReader 对象有很多的功能的对象的集合,大部分都和 XMLHttpRequest 类似。通过这三篇文章,应该能够通过 javascript 读取文件并把文件发送到服务器了。 但是 File API 系统,要比上面这三篇文章讲到的多得多,下篇文章将会有更给力的功能来进行讨论。