前端之旅Web前端之路Web 前端开发

JS红宝书·读书笔记 -- 下篇

2017-04-27  本文已影响443人  Yeaseon
JavaScript 高级程序设计

个人博客:https://yeaseonzhang.github.io

花了半个多月的时间,终于又把“JS红宝书”又撸了一遍。

第一次读“JS红宝书”还是2015年初学JS的时候,那时候只是把语法部分读了一遍,还有一些浏览器相关知识做了下了解,大概也就读了半本的样子,
就开始了用JS进行开发了,在成长的道路上遇见了JQuery,当时真的是感觉到JQuery太友好了,慢慢放下了原生开发。

现在呢,更多的时候是在用框架进行开发,越来越觉得自己的JS基础很缺乏,然后就开启了“JS红宝书”二刷之路。

下面就把书中自己觉得重要的、没有掌握的知识整理出来。因为我觉得还是会三刷“JS红宝书”,希望把这本700多页的书越读越薄,勉励。

章节

事件


事件流

事件冒泡

IE的事件流叫做事件冒泡,即事件开始时由最具体的元素接受,然后逐级向上传播到较为不具体的节点。

事件捕获

Netscape 团队提出的事件流叫做事件捕获,事件捕获的用意在于在事件到达预定目标之前捕获它。

DOM事件流

“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段处于目标阶段事件冒泡阶段

事件处理程序

DOM0 级事件处理程序

每个元素(包括windowdocument)都有自己的事件处理程序,这些属性通常全部小写。

var btn = document.getElementById('myBtn');
btn.onclick = function () {
    console.log('clicked');
}

DOM 0级方法指定的事件处理程序被认为是元素的方法,因此,这个时候的事件处理程序是在元素的作用域中运行,也就是说程序中的this可以引用当前元素。

var btn = document.getElementById('myBtn');
btn.onclick = function () {
    console.log(this.id);  // 'myBtn'
}

以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。

DOM2 级事件处理程序

定义了两个方法用于处理指定和删除事件处理程序的操作。所有的DOM节点中都包含这两个方法,接受三个参数:事件名事件处理程序布尔值。最后这个布尔值如果是true,表示在捕获阶段调用事件处理程序;false表示在冒泡阶段调用事件处理程序,默认是false

通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除。如果通过addEventListener()添加的匿名函数将无法移除。

btn.addEventListener('click', function () {  //匿名函数
    ...
})

:大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段(false),这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需要,我们不建议在事件捕获阶段注册事件处理程序。

IE事件处理程序

这两个方法接受两个参数:事件名(带on)和事件处理函数。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
    alert("Clicked");
});

:在IE 中使用attachEvent()与使用DOM0 级方法的主要区别在于事件处理程序的作用域。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
    alert("Clicked");
});
btn.attachEvent("onclick", function(){
    alert("Hello world!");
});

:与DOM方法不同的是,这些事件处理程序不是以添加它们的顺序执行,而是以相反的顺序被触发。单击这个例子中的按钮,首先看到的是"Hello world!",然后才是"Clicked"。

事件对象

在触发DOM上的某个事件时,会产生一个事件对象event

DOM中的事件对象

event对象成员

| 属性/方法 | 类型 | 读/写 | 说明 |
| : - : | : -- : | : -- : | : -- : | : -- : |
| bubbles | Boolean | 只读 | 表明事件是否冒泡 |
| cancelable | Boolean | 只读 | 表明是否可以取消事件的默认行为 |
| currentTarget | Element | 只读 | 其事件处理程序当前正在处理事件的那个元素 |
| defaultPrevented | Boolean | 只读 | 为true表示已经调用preventDefault() |
| detail | Integer | 只读 | 与事件相关的细节信息 |
| eventPhase | Integer | 只读 | 调用事件处理程序的阶段:1 捕获,2 处于目标,3 冒泡 |
| preventDefault() | Function | 只读 | 取消事件的默认行为。如果cancelabletrue,则可以使用这个方法 |
| stopImmediatePropagation() | Function | 只读 | 取消事件的进一步冒泡或捕获,同时阻止任何事件处理程序被调用 |
| stopPropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡。如果bubblestrue,则可以使用这个方法 |
| target | Element | 只读 | 事件的目标 |
| trusted | Boolean | 只读 | 为true表示事件是浏览器生成,false是开发人员创建 |
| type | String | 只读 | 被触发的事件类型 |
| view | AbstractView | 只读 | 与事件关联的抽象视图。等同于发生事件的window对象 |

在事件处理程序内部,对象this 始终等于currentTarget 的值,而target 则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则thiscurrentTargettarget 包含相同的值。

document.body.onclick = function(event){
    alert(event.currentTarget === document.body); //true
    alert(this === document.body); //true
    alert(event.target === document.getElementById("myBtn")); //true
};

调用event方法

var link = document.getElementById("myLink");
link.onclick = function(event){
    event.preventDefault();
};

跨浏览器的事件对象

var EventUtil = {
    addHandler: function(element, type, handler){
        //省略的代码
    },
    getEvent: function(event){
        return event ? event : window.event;
    },
    getTarget: function(event){
        return event.target || event.srcElement;
    },
    preventDefault: function(event){
        if (event.preventDefault){
            event.preventDefault();
        } else {
            event.returnValue = false;
        }
    },
    removeHandler: function(element, type, handler){
        //省略的代码
    },
    stopPropagation: function(event){
        if (event.stopPropagation){
            event.stopPropagation();
        } else {
            event.cancelBubble = true;
        }
    }
};

事件类型

UI 事件

焦点事件

鼠标与滚动事件

页面上的所有元素都支持鼠标事件。除了mouseentermouseleave,所有鼠标事件都会冒泡,也可以被取消,而取消鼠标事件将会影响浏览器的默认行为。

只有在同一个元素上相继触发mousedownmouseup 事件,才会触发click 事件;如果mousedownmouseup 中的一个被取消,就不会触发click 事件。

触摸设备

iOS和Android设备的相关事件:

HTML5事件

设备事件

触摸与手势事件

内存和性能

事件委托

例如在<ul>为添加一个click事件,所有<li>子元素点击事件都会冒泡到<ul>上。

表单脚本


表单基础知识

提交表单

<input type="submit" value="Submit Form">

重置表单

<input type="reset" value="Reset Form">

表单字段

每个表单都有elements属性,该属性是表单中所有表单元素的集合。

var form = document.getElementById("form1");
//取得表单中的第一个字段
var field1 = form.elements[0];
//取得名为"textbox1"的字段
var field2 = form.elements["textbox1"];
//取得表单中包含的字段的数量
var fieldCount = form.elements.length;

文本框脚本

过滤输入

屏蔽特定的字符,需要检测keypress事件对应的字符编码。

EventUtil.addHandler(textbox, 'keypress', function (event) {
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    var charCode = EventUtil.getCharCode(event);

    if (!/\d/.test(String.fromCharCode(charCode))) {
        EventUtil.preventDefault(event);
    }
})

HTML5约束验证API

输入模式

HTML5为文本字段新增了pattern属性。

<input type="text" pattern="\d+" name="count">
检测有效性

使用checkValidity()方法可以检测表单中的某个字段是否有效。是否有效的判断依据是一些<input>的约束条件。

if (document.forms[0].elements[0].checkValidity()){
    //字段有效,继续
} else {
    //字段无效
}

也可以检测整个表单是否有效

if(document.forms[0].checkValidity()){
    //表单有效,继续
} else {
    //表单无效
}
禁用验证
<form method="post" action="signup.php" novalidate>
    <!--这里插入表单元素-->
</form>

HTML5 脚本编程


跨文档消息传递

跨文档消息传送(cross-document messaging)简称XDM。其核心方法是postMessage()方法。

postMessage()方法接受两个参数:一条消息和一个表示消息接收方来自哪个域的字符串。

// 注意:所有支持XDM的浏览器也支持iframe的`contentWindow`属性
var iframeWindow = document.getElementById('myframe').contentWindow;
iframeWindow.postMessage('A secret', 'https://yeasoenzhang.github.io');

尝试向内嵌框架中发送一条消息,并指定框架中的文档必须来源于https://yeasonzhang.github.io域。

接收到XDM消息时,会触发window对象的message事件,这个事件是以异步形式触发。
传递的onmessage处理程序的事件对象包含三个重要信息:

EventUtil.addHandler(window, "message", function(event){
    //确保发送消息的域是已知的域
    if (event.origin == "https://yeasonzhang.github.io"){
        //处理接收到的数据
        processMessage(event.data);
        //可选:向来源窗口发送回执
        event.source.postMessage("Received!", "http://p2p.wrox.com");
    }
});

XDM 还有一些怪异之处。首先,postMessage()的第一个参数最早是作为“永远都是字符串”来实现的。但后来这个参数的定义改了,改成允许传入任何数据结构。可是,并非所有浏览器都实现了这一变化。为保险起见,使用postMessage()时,最好还是只传字符串。如果你想传入结构化的数据,最佳选择是先在要传入的数据上调用JSON.stringify(),通过postMessage()传入得到的字符串,然后再在onmessage 事件处理程序中调用JSON.parse()

原生拖放

拖放事件

拖动某个元素时,将依次触发的事件:

当某个元素被拖动到一个有效的放置目标时,会依次触发下列事件:

dataTransfer对象

dataTransfer对象,它是事件对象的一个属性,用于被拖动元素向放置目标传递字符串格式的数据。该对象有两个主要方法:

//设置和接收文本数据
event.dataTransfer.setData("text", "some text");
var text = event.dataTransfer.getData("text");

//设置和接收URL
event.dataTransfer.setData("URL", "http://www.wrox.com/");
var url = event.dataTransfer.getData("URL");

不过,保存在dataTransfer对象中的数据只能在drap事件处理程序中读取。如果在ondrop 处理程序中没有读到数据,那就是dataTransfer 对象已经被销毁,数据也丢失了。

drapEffect 与 effectAllowed

dateTransfer对象有两个属性:

dropEffect,属性可以知道被拖动的元素能够执行那种放置行为。

要使用dropEffect属性,必须在ondragenter事件处理程序中针对放置目标来设置。

effectAllowed属性表示允许拖动元素的哪种dropEffect

必须在ondragstart 事件处理程序中设置effectAllowed 属性。

可拖动

HTML5为所有元素规定了draggable属性,表示元素是否可以拖动。只有图像和链接的draggable默认是true

<!-- 让这个图像不可以拖动 -->
![](smile.gif)
<!-- 让这个元素可以拖动 -->
<div draggable="true">...</div>

其他成员

HTML5规定了dateTransfer对象还应该包含下列方法和属性。

错误处理与调试


错误处理

try-catch 语句

try {
    // 可能出错的代码
} catch (err) {
    // 处理发生的错误
}
finally子句

只要代码中包含finially子句,无论try还是catch语句中的return语句都将被忽略。

错误类型
try {
    someFunction();
} catch (error) {
    if (error instanceof TypeError) {
        //...
    } else {
        //
    }
}

抛出错误

try-catch 语句相配的还有一个throw 操作符,用于随时抛出自定义错误。抛出错误时,必须要给throw 操作符指定一个值,这个值是什么类型,没有要求。

throw 12345;
throw "Hello world!";
throw true;
throw { name: "JavaScript"};

遇到throw操作符时,代码会立即停止执行。只有当try-catch语句捕获到被抛出值,代码才会继续执行

自定义错误类型

可以利用原型链通过继承Error创建自定义错误类型。需要为新创建的错误类型指定namemessage属性

function CustomError (message) {
    this.name = 'CustomError';
    this.message = message;
}

CustomError.prototype = new Error();

throw new CustomError('Error msg');

Error事件

任何没有通过try-catch处理的错误都会触发window对象的error事件。

在任何Web浏览器中,onerror事件处理程序都不会创建event对象,但它可以接受三个参数:错误消息、错误所在的URL和行号。

要指定onerror 事件处理程序,必须使用如下所示的DOM0 级技术,它没有遵循“DOM2 级事件”的标准格式(addEventListener)。

window.onerror = function(message, url, line){
    alert(message);
};

只要发生错误,无论是不是浏览器生成的,都会触发error事件,然后让浏览器的默认机制发挥作用,这时候我们需要阻止浏览器的默认行为(return false)。

window.onerror = function (message, url, line) {
    console.log(message);
    retrun false;
}

常见的错误类型

在数据检测的时候,基本类型的值应该使用typeof来检测,对象的值应该使用instanceof

JSON


解析与序列化

JSON对象

JSON对象有两个方法:stringifyparse()

var book = {
    title: "Professional JavaScript",
    authors: [
        "Nicholas C. Zakas"
    ],
    edition: 3,
    year: 2011
};
var jsonText = JSON.stringify(book);

以上就把Javascript对象序列化为一个JSON字符串(没有空格和缩进)

{"title":"Professional JavaScript","authors":["Nicholas C. Zakas"],"edition":3,"year":2011}

如果传给JSON.parse()的字符串不是有效的JSON,会抛出错误。

序列化选项

JSON.stringify()除了要序列化的JS对象外,还可以接受两个参数,一个是过滤器(数组或函数),第二个参数是一个选项,表示是都在JSON字符串中保留缩进。

过滤结果
var book = {
    "title": "Professional JavaScript",
    "authors": [
        "Nicholas C. Zakas"
    ],
    edition: 3,
    year: 2011
};
var jsonText = JSON.stringify(book, ["title", "edition"]);

第二个参数中包含两个字符串"title", "edition",所以只会返回对应的属性

{"title":"Professional JavaScript","edition":3}

过滤器为函数

var book = {
    "title": "Professional JavaScript",
    "authors": [
        "Nicholas C. Zakas"
    ],
    edition: 3,
    year: 2011
};
var jsonText = JSON.stringify(book, function(key, value){
    switch(key){
        case "authors":
            return value.join(",")
        case "year":
            return 5000;
        case "edition":
            return undefined;
        default:
            return value;
    }
});

:返回undefined删除该属性,上例的edition属性就会被删除。

字符串缩进

JSON.stringify()方法的第三个参数用于控制结果中的缩进和空白符。可以是数字,表示缩进的空格数;也可以是字符串,将该字符串作为缩进的表示。

toJSON()方法

解析选项

JSON.parse()方法也可以接受第二参数,该参数是一个函数(被称为还原函数),传入函数的参数均为key, value

如果还原函数返回undefined,则表示要从结果中删除响应的键。

Ajax与Comet


XMLHttpRequest 对象

XHR的用法

XHR对象的属性

同步请求

xhr.open("get", "example.txt", false);
xhr.send(null);
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
    alert(xhr.responseText);
} else {
    alert("Request was unsuccessful: " + xhr.status);
}

var xhr = createXHR();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
            alert(xhr.responseText);
        } else {
            alert("Request was unsuccessful: " + xhr.status);
        }
    }
};
xhr.open("get", "example.txt", true);
xhr.send(null);

HTTP 头部信息

默认情况下,在发送XHR请求的同时,还会发送下列头部信息:

自定义请求头部信息,使用setRequestHeader()方法,该方法接受两个参数:头部字段的名称和头部字段的值。

要成功发送请求头部信息,必须在调用open()方法之后且调用send()方法之前调用serRequestHeader()

var xhr = createXHR();
xhr.onreadystatechange = function(){
    // ...
};
xhr.open("get", "example.php", true);
xhr.setRequestHeader("MyHeader", "MyValue");
xhr.send(null);

建议使用自定义的头部字段名称,不要使用浏览器正常发送的字段名称,否则有可能会影响服务器的响应。有的浏览器允许开发人员重写默认的头部信息,但有的浏览器则不允许这样做。

调用XHR对象的getResponseHeader()方法,接受一个参数:头部字段名称。就能取得相应的响应头部信息。
调用getAllResponseHeaders()方法可以取得包含所有头部信息的字符串。

GET请求

使用GET请求经常会发生一个错误,就是查询字符串的格式有问题。查询字符串中每个参数的名称和值都必须使用encodeURIComponent()进行编码,然后才能放到URL的末尾。

function addURLParam(url, name, value) {
    url += (url.indexOf("?") == -1 ? "?" : "&");
    url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
    return url;
}

var url = "example.php";
//添加参数
url = addURLParam(url, "name", "Nicholas");
url = addURLParam(url, "book", "Professional JavaScript");
//初始化请求
xhr.open("get", url, false);

POST请求

如果我们希望用XHR模仿表单提交,需要将Content-Type头部信息设置为application/x-www-form-urlencoded(表单提交的内容类型)

function submitData(){
    var xhr = createXHR();
    xhr.onreadystatechange = function(){
        if (xhr.readyState == 4){
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
                alert(xhr.responseText);
            } else {
                alert("Request was unsuccessful: " + xhr.status);
            }
        }
    };
    xhr.open("post", "postexample.php", true);
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    var form = document.getElementById("user-info");
    xhr.send(serialize(form));
}

XMLHttpRequest 2 级

FormData

FormData为序列化表单以及创建于表单格式相同的数据提供了便利。

var data = new FormData();
data.append('name', 'Yeaseon');

append方法可以将表单的字段和值,传入FormData对象中。也可以预先填入表单中的字段:

var data = new FormData(document.form[0]);

FormData的方便就在于不必手动修改XHR对象的请求头部。

超时设定

XHR对象添加了一个timeout属性,表示请求在等待多少毫秒之后终止。如果规定时间内浏览器没有收到响应,就会触发timeout事件,进而调用ontimeout事件处理程序。

var xhr = createXHR();
xhr.onreadystatechange = function(){
    // ...
};
xhr.open("get", "timeout.php", true);
xhr.timeout = 1000; //将超时设置为1 秒钟
xhr.ontimeout = function(){
    alert("Request did not return in a second.");
};
xhr.send(null);

超时之后请求终止,但是此时的readyState可能已经变为了4,就意味着会调用onreadystatechange事件。

可是,如果在超时终止请求之后再访问status 属性,就会导致错误。为避免浏览器报告错误,可以将检查status 属性的语句封装在一个try-catch语句当中。

overrideMimeType()方法

用于重写XHR响应的MIME类型。因为返回响应的MIME 类型决定了XHR 对象如何处理它,所以提供一种方法能够重写服务器返回的MIME 类型是很有用的。

var xhr = createXHR();
xhr.open("get", "text.php", true);
xhr.overrideMimeType("text/xml");
xhr.send(null);

进度事件

有以下6个进度事件:

progress事件
onprogress事件处理程序会接收到一个event对象,target属性指向XHR对象,包含着三个额外的属性:

跨资源共享

IE对CORS的实现

微软在IE8中引入了XDR类型,类似与XHR对象,两者的不同之处:

请求返回之后,就会触发load事件,响应数据也会保存在responseText属性中:

var xdr = new XDomainRequest();
xdr.onload = function () {
    console.log(xdr.responseText);
};
xdr.onerror = function(){
    alert("An error occurred.");
};
xdr.open('get', 'http://..../xxx/');
xdr.send(null);

在请求返回之前可以调用abort()方法终止请求。

xdr.abort();

XDR对象也支持timeout属性以及ontimeout事件处理程序

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.onerror = function(){
    alert("An error occurred.");
};
xdr.timeout = 1000;
xdr.ontimeout = function(){
    alert("Request took too long.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);

为了支持POST请求,XDR对象提供了contentType属性,用来表示发送数据的格式。

var xdr = new XDomainRequest();
xdr.onload = function () {
    //
}
xdr.onerror = function () {
    //
}
xdr.open('post', 'http://www.somewhere-else.com/page/');
xdr.contentType = 'application/x-www-form-urlencoded';
xdr.send('name1=value1&name2=value2');

其他浏览器对CORS的实现

与IE中的XDR对象不同,通过跨域XHR对象可以访问statusstatusText属性,并且支持同步请求。同时也有一些限制:

var xhr = createXHR();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
            alert(xhr.responseText);
        } else {
            alert("Request was unsuccessful: " + xhr.status);
        }
    }
};
xhr.open("get", "http://www.somewhere-else.com/page/", true);
xhr.send(null);

其他跨域技术

图像Ping

var img = new Image();
img.onload = img.onerror = function(){
    alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";

JSONP

JSONPJSON with padding的简写。JSONP只不过时被包含在函数调用中的JSON

callback({"name": "Yeaseon"});

JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是请求中指定的。下面是一个经典的JSONP请求

http://freegeoip.net/json/?callback=handleResponse

这里指定的回调函数的名字叫做handleResponse

JSONP是通过动态<script>元素来使用的,使用时可以为src属性指定一个跨域URL

function handleResponse(response){
    alert("You’re at IP address " + response.ip + ", which is in " +
        response.city + ", " + response.region_name);
}
var script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);

服务器发送事件

SSE支持短轮询、长轮训和HTTP流,而且能在断开连接时自动确定何时重新连接。

SSE API

要预订新的事件流,首先要创建一个新的EventSource对象,并传入一个入口点:

var source = new EventSource('myevents.php');

传入的URL必须与创建对象的页面同源。
EventSource的实例有一个readyState属性:0表示正连接到服务器,1表示打开了连接,2表示关闭了连接。
EventSource实例还有三个事件:

服务器发回的数据以字符串形式保存在event.data中。
默认情况下,EventSource对象会保持与服务器的活动连接。如果想强制立即断开连接并且不在重新连接,可以调用close()方法。

Web Sockets

由于 Web Sockets 使用了自定义的协议,所以 URL 模式也略有不同。未加密的连接不再是 http:// ,而是 ws:// ;加密的连接也不是 https:// ,而是 wss:// 。

Web Sockets API

创建一个WebSockets实例对象:

var socket = new WebSocket("ws://www.example.com/server.php");

WebSocket也有一个表示当前状态的readyState属性:

发送和接收数据

向服务器发送数据,使用send()方法并传入任意字符串:

var socket = new WebSocket('ws:// www.example.com/server.php');
socket.send('Hello World');

Web Sockets只能发送纯文本数据,对于复杂的数据结构,在发送之前,必须进行序列化。

var message = {
    time: new Date(),
    text: 'Hello world',
    clientId: 'adfalsf39'
};

socket.send(JSON.stringify(message));

当服务器向客户端发来消息时,WebSocket对象就会触发message事件。这个message事件与其他传递消息的协议类似,也是把返回的数据保存在event.data属性中。

socket.onmessage = function (event) {
 var data = event.data;
 // ....
}

send()类似,event.data中返回的数据也是字符串,需要手工解析这些数据。

其他事件

WebSocket对象还有其他三个事件,在连接生命周期的不同阶段触发:

WebSocked对象不支持DOM 2级事件监听:

var socket = new WebSocket("ws://www.example.com/server.php");
socket.onopen = function(){
    alert("Connection established.");
};
socket.onerror = function(){
    alert("Connection error.");
};
socket.onclose = function(){
    alert("Connection closed.");
};

高级技巧


高级函数

安全的类型检测

用于区分原生和非原生JavaScript对象,通过Object.prototype.toString()

function isArray(value){
    return Object.prototype.toString.call(value) == "[object Array]";
}

function isFunction(value){
    return Object.prototype.toString.call(value) == "[object Function]";
}

function isRegExp(value){
    return Object.prototype.toString.call(value) == "[object RegExp]";
}

作用域安全的构造函数

防止构造函数内this指针的指向被改变(指向window

function Person (name, age, job) {
    if (this instanceof Person) {
        this.name = name;
        this.age = age;
        this.job = job;
    } else {
        return new Person(name, age, job);
    }
}

惰性载入函数

function createXHR(){
    if (typeof XMLHttpRequest != "undefined"){
        return new XMLHttpRequest();
    } else if (typeof ActiveXObject != "undefined"){
        if (typeof arguments.callee.activeXString != "string"){
            var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
                i,len;
            for (i=0,len=versions.length; i < len; i++){
                try {
                    new ActiveXObject(versions[i]);
                    arguments.callee.activeXString = versions[i];
                    break;
                } catch (ex){
                    //跳过
                }
            }
        }
        return new ActiveXObject(arguments.callee.activeXString);
    } else {
        throw new Error("No XHR object available.");
    }
}

第一种改法:

function createXHR () {
    if (typeof XMLHttpRequest != 'undefined') {
        createXHR = function () {
            return new XMLHttpRequest();
        };
    } else if (typeof ActiveXObjext != 'undefined') {
        createXHR = function () {
            if (typeof arguments.callee.activeXString != 'string') {
                var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
                    i,len;
                for (i = 0; len = versions.length; i < len; i++) {
                    try {
                        new ActiveXObject(versions[i]);
                        arguments.callee.activeXString = versions[i];
                        break;
                    } catch (e) {
                        // skip
                    }
                }
            }
            return new ActiveXObject(arguments.callee.activeXString);
        };
    } else {
        createXHR = function () {
            throw new Error('No XHR object available.');
        }
    }
    return createXHR();
}

第二种改法:

var createXHR = (function () {
    if (typeof XMLHttpRequest != 'undefined') {
        return function () {
            return new XMLHttpRequest();
        };
    } else if (typeof ActiveXObjext != 'undefined') {
        return function () {
            if (typeof arguments.callee.activeXString != 'string') {
                var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
                    i,len;
                for (i = 0; len = versions.length; i < len; i++) {
                    try {
                        new ActiveXObject(versions[i]);
                        arguments.callee.activeXString = versions[i];
                        break;
                    } catch (e) {
                        // skip
                    }
                }
            }
            return new ActiveXObject(arguments.callee.activeXString);
        };
    } else {
        return function () {
            throw new Error('No XHR object available.');
        }
    }
})();

函数绑定

bind()函数,语法如下:

function bind (fn, context) {
    return function () {
        return fn.apply(context, arguments);
    }
}

离线应用与客户端存储


离线检测

navigator.onLine属性可以判断设备否能访问网络。

HTML5定义两个事件:onlineoffline,当网络状态变化时,分别触发这两个事件:

EventUtil.addHandler(window, 'online', function () {
    console.log('online');
});
EventUtil.addHandler(window, 'offline', function () {
    console.log('offline');
});

数据存储

Web存储机制

Web Storage规范包含两种对象的定义:sessionStorageglobalStorage。这两个对象在支持的浏览器中都是以windows对象属性的形式存在。

Storage类型

Storage类型提供最大的存储空间来存储名值对。

sessionStorage对象

sessionStorage对象存储特定于某个会话的数据,也就是该数据只保持到浏览器关闭。存储在sessionStorage中的数据可以跨越页面刷新而存在。

//使用方法存储数据
sessionStorage.setItem("name", "Nicholas");
//使用属性存储数据
sessionStorage.book = "Professional JavaScript";

//使用方法读取数据
var name = sessionStorage.getItem("name");
//使用属性读取数据
var book = sessionStorage.book;

//使用delete 删除一个值——在WebKit 中无效
delete sessionStorage.name;
//使用方法删除一个值
sessionStorage.removeItem("book");

可以通过结合length属性和key()方法来迭代sessionStorage中的值:

for (var i = 0, len = sessionStorage.length; i < len; i++) {
    var key = sessionStorage.key(i);
    var value = sessionStorage.getItem(key);
    console.log(key + ' = ' + value);
}

还可以使用for-in循环来迭代sessionStorage中的值:

for (var key in sessionStorage) {
    var value = sessionStorage.getItem(key);
    console.log(key + ' = ' + value);
}
globalStorage对象

这个对象的目的是跨越会话存储数据,,但有特定的访问限制。要使用globalStorage,首先要指定哪些域可以访问该数据。

// 保存数据
globalStorage['wrox.com'].name = 'Yeaseon';

// 获取数据
var name = globalStorage['wrox.com'].name;

上例,访问的是针对域名wrox.com的存储空间。globalStorage对象不是Storage的实例,
而具体的globalStorage['wrox.com']才是。这个存储空间对于wrox.com及其所有子域都是可以访问的。

globalStorage["www.wrox.com"].name = "Yeaseon";
globalStorage["www.wrox.com"].book = "Professional JavaScript";
globalStorage["www.wrox.com"].removeItem("name");
var book = globalStorage["www.wrox.com"].getItem("book");
localStorage对象

localStorage对象是HTML5规范中作为持久保存客户端数据的方案,并且取代globalStorage。要访问同一个localStorage对象,页面必须来自同一个域名(子域名无效),必须同源。

//使用方法存储数据
localStorage.setItem("name", "Nicholas");
//使用属性存储数据
localStorage.book = "Professional JavaScript";

//使用方法读取数据
var name = localStorage.getItem("name");
//使用属性读取数据
var book = localStorage.book;
storage事件

Storage对象进行任何修改,都会在文档上触发storage事件。这个事件的event对象有以下属性。

在这四个属性中,IE8 和Firefox 只实现了domain 属性。在撰写本书的时候,WebKit 尚不支持
storage 事件

IndexedDB

Indexed Database API,简称IndexedDB,是在浏览器中保存结构化数据的一种数据库。IndexedDB设计的操作完全是异步进行。

var indexedDB = window.indexedDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB;
数据库

IndexedDB就是一个数据库,它最大的特色就是使用对象保存数据,而不是使用表来保存数据。

indexDB.open(),传入一个数据库参数。如果该数据库存在就会发送一个打开它的请求;如果该数据库不存在,就会发送一个创建并打开它的请求。请求会返回一个IDBRequest对象,这个对象上可以添加onerroronsuccess事件处理程序。

var request, database;

request = indexedDB.open('admin');
request.onerror = function (event) {
    console.log(event.target.errorCode);
};
request.onsuccess = function (event) {
    database = event.target.result;
};

event.target都指向request对象,因此他们可以互换使用。
发生错误了,event.target.errorCode中将会保存一个错误码:

指定数据库版本号,通过setVersion()方法:

if (database.version != '1.0') {
    request = database.setVersion('1.0');
    request.onerror = function (event) {
        console.log(event.target.errorCode);
    };
    request.onsuccess = function (event) {
        console.log(''Database name: ' + database.name + ', Version: ' + database.version);
    }
} else {
    console.log(''Database name: ' + database.name + ', Version: ' + database.version);
}
对象存储空间

假设要保存的用户记录由用户名、密码等组成,那么保存一条记录的对象应该类似:

var user = {
    username: '007',
    firstname: 'James',
    lastname: 'Bond',
    password: 'foo'
}

如果使用username属性作为这个对象存储空间的键,这个username必须全局唯一,而且大部分时候都要通过这个键来访问数据。

var store = db.createObjectStore('users', { keyPath: 'username' });

其中第二个参数中的keyPath属性,就是空间中将要保存的对象的一个属性,而这个属性将作为存储空间的键来使用。

通过add()put()方法来向存储空间添加数据。着两个方法都接收一个参数,就是要保存的对象。

//users 中保存着一批用户对象
var i=0,
    request,
    requests = [],
    len = users.length;
while(i < len){
    request = store.add(users[i++]);
    request.onerror = function(){
        //处理错误
    };
    request.onsuccess = function(){
        //处理成功
    };
    requests.push(request);
}
事务

在数据库对象上调用transaction()可以创建事务,任何时候,只要想读取或修改数据,都要通过事务来组织所有操作。

// 创建事务
var transaction = db.transaction();

可以传入要访问的一或多个对象存储空间。

var transaction = db.transaction('users');

var transaction = db.transaction(['users', 'anotherStore']);

前面这些事务都是以只读方式访问数据。要修改访问方式,必须在创建事务时传入第二个参数,这个参数表示访问模式:

在Chrome中叫做webkitIDBTransaction,可以使用一下代码兼容:

var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;

这样就能方便的指定transaction()第二个参数:

var transaction = db.transaction('users', IDBTransaction.READ_WRITE);

取得事务的索引后,使用objectStore()方法并传入存储空间的名称,就可以访问指定的存储空间。然后通过如下方法操作对象:

var request = db.transaction('users').objectStore('users').get('007');
request.onerror = function (event) {
    console.log('Did not get the object!');
};
request.onsuccess = function (event) {
    var result = event.target.result;
    console.log(result.firstName);  // 'James'
};

也可以针对事务对象本身进行事件处理,存在两个事件onerroroncomplete

transaction.onerror = function (event) {
    // 整个事务都被取消了
}
transaction.oncomplete = function (event) {
    // 整个事务都成功完成了
}

:在oncomplete事件的事件对象中访问不到get()请求返回的数据,必须在onsuccess事件中处理。

键范围
var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;

有四种定义键范围的方法:

var onlyRange = IDBKeyRange.only("007");

//从键为"007"的对象开始,然后可以移动到最后
var lowerRange = IDBKeyRange.lowerBound("007");
//从键为"007"的对象的下一个对象开始,然后可以移动到最后
var lowerRange = IDBKeyRange.lowerBound("007", true);

//从头开始,到键为"ace"的对象为止
var upperRange = IDBKeyRange.upperBound("ace");
//从头开始,到键为"ace"的对象的上一个对象为止
var upperRange = IDBKeyRange.upperBound("ace", true);

//从键为"007"的对象开始,到键为"ace"的对象为止
var boundRange = IDBKeyRange.bound("007", "ace");
//从键为"007"的对象的下一个对象开始,到键为"ace"的对象为止
var boundRange = IDBKeyRange.bound("007", "ace", true);
//从键为"007"的对象的下一个对象开始,到键为"ace"的对象的上一个对象为止
var boundRange = IDBKeyRange.bound("007", "ace", true, true);
//从键为"007"的对象开始,到键为"ace"的对象的上一个对象为止
var boundRange = IDBKeyRange.bound("007", "ace", false, true);

新型的API


Page Visibility API

Page Visibility API 是为了让开发人员知道页面是否对用户可见推出的。

Geolocation API

Geolocation API 在浏览器中的实现是navigator.geolocation对象。

调用这个方法就会触发请求用户共享地理定位信息的对话框。这个方法接收三个参数:成功回调,可选的失败回调和可选的选项对象。

成功回调会接收到一个Position对象参数,该对象有两个属性:coordstimestamp

coords对象中包含于位置相关的信息:

navigator.geolocation.getCurrentPosition(function (position) {
    drawMapCenteredAt(position.coords.latitude, position.coords.longitude);
});

失败回调在被调用的时候也会接受到一个参数,这个参数是一个对象,包含连个属性:messagecodecode保存着一个数值,表示错误的类型:用户拒绝共享(1)、位置无效(2)或者超时(3)。

navigator.geolocation.getCurrentPosition(function (position) {
    drawMapCenteredAt(position.coords.latitude, position.coords.longitude);
}, function (error) {
    console.log('Error code:' + error.code);
    console.log('Error message:' + error.message);
});

第三个参数是一个可选对象,用于设定信息的类型。可以设置的选项有三个:

navigator.geolocation.getCurrentPosition(function (position) {
    drawMapCenteredAt(position.coords.latitude, position.coords.longitude);
}, function (error) {
    console.log('Error code:' + error.code);
    console.log('Error message:' + error.message);
}, {
    enableHighAccuracy: true,
    timeout: 5000,
    maximumAge: 25000
});

File API

File API 在表单中的文件输入字段的基础上,又添加了一些直接访问文件信息的接口。HTML5在DOM中为文件输入元素添加了一个files集合。每个File对象都有下列只读属性。

FileReader 类型

FileReader 类型实现的是一种异步文件读取机制。可以把FileReader想象成XMLHttpRequest

由于读取过程是异步的,所以FileReader提供了三个事件:

progress事件,每50ms就会触发一次,通过事件对象可以获得与XHRprogress事件相同的信息:

由于种种原因无法读取文件,都会触发error事件,相关信息都会保存到FileReadererror属性中。error.code即错误码:

文件加载成功后会触发load事件。

var filesList = document.getElementById('files-list');
EventUtil.addHandler(filesList, 'change', function (event) {
    var info = '',
        output = document.getElementById('output'),
        progress = document.getElementById('progress'),
        files = EventUtil.getTarget(event).files,
        type = 'default',
        reader = new FileReader();

    if (/image/.test(files[0].type)) {
        reader.readAsDateURL(files[0]);
        type = 'image';
    } else {
        reader.readAsText(files[0]);
        type = 'text';
    }

    reader.onerror = function () {
        output.innerHTML = 'Could not read file, error code is ' + reader.error.code;
    };

    reader.onprogress = function () {
        if (event.lengthComputable) {
            progress.innerHTML = event.loaded + '/' + event.total;
        }
    };

    reader.onload = function () {
        var html = '';
        switch (type) {
            case 'image':
                html = '<img src=\"' + reader.result + '\">';
                break;
            case 'text':
                html = reader.result;
                break;
        }
        output.innerHTML = html;
    };
});

读取拖放的文件

从桌面上把文件拖放到浏览器中会触发drop 事件。而且可以在event.dataTransfer. files中读取到被放置的文件,当然此时它是一个File 对象,与通过文件输入字段取得的File 对象一样。

var droptarget = document.getElementById('droptarget');

function handleEvent(event) {
    var info = '',
        output = document.getElementById('output');
        files, i, len;
    EventUtil.preventDefault(event);
    if (event.type == 'drop') {
        files = event.dataTransfer.files;  //转换成File对象
        i = 0;
        len = files.length;

        while (i < len) {
            info += files[i].name + ' (' + files[i].type + ', ' + files[i].size + ' bytes)<br>';
            i++;
        }
        output.innerHTML = info;
    }
}
// 阻止默认事件,只有 drop 事件会被处理
EventUtil.addHandler(droptarget, "dragenter", handleEvent);
EventUtil.addHandler(droptarget, "dragover", handleEvent);
EventUtil.addHandler(droptarget, "drop", handleEvent);

使用XHR上传文件

创建一个FormDate对象,通过它调用append()方法并传入相应的File对象作为参数,再把FormData对象传递给XHRsend()方法。

var droptarget = document.getElementById('droptarget');

function handleEvent(event) {
    var info = '',
        output = document.getElementById('output'),
        data, xhr,
        files, i, len;

    EventUtil.preventDefault(event);

    if (event.type == 'drop') {
        data = new FormData();
        files = event.dataTransfer.files;
        i = 0;
        len = files.length;

        while (i < len) {
            data.append('file' + i, files[i]);
            i++;
        }

        xhr = new XMLHttpRequest();
        xhr.open('post', 'FileAPIExapleUpload.php', true);
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
                console.log(xhr.responseText);
            }
        };
        xhr.send(data);
    }
}
EventUtil.addHandler(droptarget, "dragenter", handleEvent);
EventUtil.addHandler(droptarget, "dragover", handleEvent);
EventUtil.addHandler(droptarget, "drop", handleEvent);
上一篇下一篇

猜你喜欢

热点阅读