Andre Huang


  • 首页

  • 归档

XMLHttpRequest 对象以及 AJAX 技术

发表于 2016-03-16

XMLHttpRequest 对象

AJAX技术的核心。

是通过 MSXML 库中的一个 ActiveX 对象实现。

在 IE 中可能会遇到三种不同版本的 XHR 对象: MSXML2.XMLHttp、MSXML2.XMLHttp.3.0 和 MSXML2.XMLHttp.6.0

创建 XHR 对象的方法:

function createXHR() {
if (typeof XMLHttpRequest != "undefined") {
// 首先检测原生 XHR 对象是否存在,如果存在则返回它的新实例。
return new XMLHttpRequest();
} else if (typeof ActiveXObject != "undefined") {
// 如果原生对象不存在,则检测 ActiveX 对象
if (typeof arguments.callee.activeXString != "string") {
var version = [ "MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp" ],
i, len;
for (i = 0, len=version.length; i < len; i++) {
try {
new ActiveXObject(version[i]);
arguments.callee.activeXString = version[i];
break;
} catch (ex) {
// 跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
} else {
// 如果两种对象都不存在,就抛出错误
throw new Error("No XHR object available.");
}
}

由于其他浏览器中对 XHR 的实现与 IE 最早的实现是兼容的,所以可以在所有的浏览器中以相同方式使用上面创建的 xhr 对象。

XHR 的用法

open()

接受 3 个参数:

  1. 要发送的请求的类型(”get”、”post” 等)
  2. 请求的URL
  3. 表示是否异步发送请求的布尔值

p.s.
1、URL 可以是相对地址(一般推荐),也可以是绝对路径

2、调用 open()方法只是启动一个请求以备发送,并没有真正发送出去

只能向同一个域中使用相同端口和协议的 URL 发送请求。
如果 URL 与启动请求的页面有任何差别,都会引发安全错误。

send()

在使用 open() 方法启动请求之后,可以调用 send() 方法

接受一个参数: 作为请求主体发送的数据(不需要的话则必须传入 null,因为对于部分浏览器来说是该参数是必须的)

服务器响应后,JS 代码继续执行,并且响应的数据会自动填充 XHR 对象的属性。相关属性如下:

  • responseText:作为响应主体被返回的文本
  • responseXML:如果响应的内容是”text/xml”或”application/xml”,这个属性中将保存包含着响应数据的 XML DOM 文档
  • status:响应的 HTTP 状态
  • statusText:HTTP 状态的说明

返回之后,通过检测 status 来决定下一步的操作,而不要依赖 statusText,因为后者在跨浏览器使用时不太可靠。
无论内容类型是什么,响应主体的内容都会保存在 responseText 属性中;而对于非 XML 数据而言,responseXML 属性的值将为 null。

发送异步请求时,通常会检测 XHR 对象的 readyState 属性,这个属性表示请求/响应过程的当前活动阶段。可能值如下:

  • 0: 未初始化。尚未调用 open()方法
  • 1: 启动。已经调用 open()方法,但还没调用 send()方法
  • 2: 发送。已经调用了 send()方法,但尚未接收到响应
  • 3: 接收。已经接收到部分响应数据
  • 4: 完成。已经接收全部响应数据,而且已经可以在客户端中使用了

readystatechange 事件,可以用来检测每次状态变化后 readyState 的值。通常只关注值为 4 的时候。
不过,必须在调用 open()之前指定 onreadystatechange 事件处理程序才能确保跨浏览器兼容性。

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);

(To be continued…)

超时调用和间隔调用

发表于 2016-03-16

setTimeout() 和 setInterval() 都是 window 对象的方法。

超时调用(setTimeout)

顾名思义,setTimeout()通过设置超时值,以在指定的时间(超时值)过后的来执行某段代码。

setTimeout()接收两个参数:

  • 包含 JavaScript 代码的字符串(跟eval()解析的字符串一样) / 一个函数(可以是一个函数体,也可以是一个函数名)
  • 超时值(单位:毫秒)

方法会返回一个ID,作为计划执行代码的唯一标识符。可以通过 clearTimeout(ID)方法在指定时间过去之前来取消超时调用。

var timer = setTimeout(function(){
console.log("hello");
}, 1000);
clearTimeout(timer);

其中,一般以函数作为第一个参数,使用字符串的话可能会导致性能损失,故不推荐传递字符串

注意:尽管设置了超时值,但是经过该时间后,也不一定会执行指定的代码段。因为 JavaScript 是一个单线程的解释器,一定时间内只能执行一段代码。为了控制要执行的代码,JS 有一个任务队列,这些任务会按照它们在队列中的顺序执行。setTimeout()方法只是延迟了将某个任务添加到队列的时间。因此,当任务队列为空,添加的代码便会立刻执行;否则,则需等队列前面的代码全部执行完了以后再执行。

间歇调用(setInterval)

setInterval()方法会根据设置的时间间隔来重复执行代码,直到间歇调用被取消或者是页面被卸载才停止。
接收的参数和 setTimeout()相同。
同样,也会返回一个 ID,可以通过 clearInterval(ID)来取消。

var num = 0;
function increaseNum(){
num++;
// 当 num 等于 10 时,取消后续的调用
if(num === 10) {
clearInterval(timer);
alert("DONE~");
}
};
var timer = setInterval(increaseNum, 1000);

一般来说,使用 setTimeout 来模拟 setInterval 是一种最佳模式。因为后一个间歇调用可能会在前一个间歇调用结束之前启动。而使用超时调用,则完全可以避免。如:

var num = 0;
function increaseNum(){
num++;
if(num < 10) {
setTimeout(increaseNum, 1000);
} else {
alert("DONE~");
}
}
setTimeout(increaseNum, 1000);
// 从中可看出,使用超时调用,没有必要跟踪其 ID,因为每次执行完代码,如果不再设置一次超时调用的话,调用就自动停止

(To be continued…)

JavaScript数据类型的判定

发表于 2016-03-13

js数据类型

基本类型

因为我们可以操作保存在变量中的实际的值,所以这些类型是按值访问的

  • String
  • Number
  • Boolean
  • Null
  • Undefined

引用类型

引用类型的值是保存在内存中的对象
在操作对象时,事实上是在操作对象的引用,而不是实际的对象
因此可以说引用类型的值是按引用访问(地址指针)

  • Object

# Undefined 类型
Undefined 类型只有一个值——undefined
值得注意的是:包含 undefined 值的变量与尚未定义的变量还是不一样的,如下

var message; // 声明时默认取得了 undefined 值
// 下面这个变量没有声明
// var age;
alert(message); // "undefined"
alert(age); // 报错

但如果对 age(未声明变量) 使用 typeof 的话,也会返回 undefined

# Null 类型
类似 Undefined,Null 类型也是只有一个值——null。
null表示一个空对象指针,因此当使用 typeof null 时,会返回“object”
事实上,undefined 是派生自 null 值得,因此有如下判断时返回 true

alert(null == undefined); // true

只要意在保存对象的变量还没有真正保存对象,就应该明确地让该变量保存 null 值。
这样做不仅可以体现 null 作为空对象指针的惯例,而且有助于进一步区分 null 和 undefined。

typeof 操作符

typeof 可以检测原始值类型,也就是基本类型。

alert( typeof "abc" === "string" ); // true
alert( typeof 123 === "number" ); // true
alert( typeof true === "boolean" ); // true
alert( typeof undefined === "undefined" ); // true
alert( typeof null === "null" ); // true

但是,但基本类型,在相同值的情况下,如果是利用构造函数来定义的话,typeof 返回的结果都只会是 object

alert( typeof new String("abc") ); // "object"
alert( typeof new Number(123) ); // "object"
alert( typeof new boolean(true) ); // "object"

因此,typeof 具有局限性。

typeof 只有一个实际的应用——检测一个对象是否已经定义或者是否已经赋值。而不是检查对象的类型。

instanceof 区别引用类型

对于引用类型的数据,单纯使用 typeof 是无法达到目的的。所以,要用 instanceof 操作符来区别数组、函数和对象

// 对象
(a instanceof Object) && !(a instanceof Array) && !(a instanceof Function)
// 函数
(a instanceof Object) && (a instanceof Function)
// 数组
(a instanceof Object) && (a instanceof Array)

但 instanceof 也有它严重的局限性:不能跨帧使用。

假设一个浏览器帧A(frame A)里的一个对象被传入到帧B(frame B)中,两个帧中都定义了 Person 构造函数,如果来自帧A 的对象是帧A 中Person的实例,则有:

personA instanceof frameAPerson   //true
personA instanceof frameBPerson   //false

因为每个帧都有 Person 的拷贝,它被认为是该帧中的Person的拷贝实例,尽管两个定义是一样的。
同样的问题也出现在其他两个非常重要的内置类型中:数组和函数,所以检测这两个内置类型一般不用 instanceof。
而且 instanceof 对于对象的整个原型链都能检测到,例如:

var now = new Date();
 
now instanceof Date;    //true
now instanceof Object;  //true

因此,用 instanceof 检测某一对象是否属于特定类型并非最佳。

仅仅用来比较来自同一个 JavaScript 上下文的自定义对象。正如 typeof 一样,避免其他的用途。

使用内置对象的 class 属性来判断

使用 Object.prototype.toString.call() 来判断

[[Class]]是一个内部属性,所有的对象(原生对象和宿主对象)都拥有该属性,通过该属性的值可以用来判断一个原生对象属于哪种内置类型,而这个属性只能通过Object.prototype.toString 方法来访问

在 ES5 中,对于 toString() 方法被调用时,执行的步骤如下:

  1. 如果this的值为undefined,则返回”[object Undefined]”
  2. 如果this的值为null,则返回”[object Null]”
  3. 让O成为调用ToObject(this)的结果
  4. 让class成为O的内部属性[[Class]]的值
  5. 返回三个字符串”[object “, class, 以及 “]”连接后的新字符串

所以判断变量类型的方法可以如下:

// 直接传入待判定变量和匹配类型
// 返回布尔值
function isType(data, type) {
return Object.prototype.toString.call(data).slice(8,-1) === type ? true : false;
}
// 直接传入某个变量
// 返回该变量的类型
function getType(data) {
return Object.prototype.toString.call(data).slice(8,-1);
}

浏览器渲染页面

发表于 2016-03-12

浏览器渲染过程

  1. 获取 HTML、css、js文件
  2. 将HTML代码形成DOM Tree,解析样式为 CSSOM Tree
  3. 合并 DOM 和 CSSOM,去除不可见元素以及设置了display:none属性的元素,构建 render tree
  4. 对 render tree 的每个元素,其实也是 DOM 元素,计算其形状和位置,进行布局
  5. 将每个节点元素转化为实际像素绘制在视口上,也称“栅格化”

p.s.

不可见元素:<html>、<head>、<meta>、<link>、<style>、<script>等

render tree(渲染树):在 Webkit 中这些对象被称为渲染器或渲染对象,而在 Gecko 中称之为“frame”。在渲染树中,每一段文本字符串都表现为独立的渲染器。每一个渲染对象都包含与之对应的 DOM 对象,或者文本块,还加上计算过的样式。换言之,渲染树是一个文档对象模型的直观展示。

重绘

      当render tree中元素的某些属性需要更新,而这些元素只影响外观、风格,不影响元素在网页中的位置(如:background-color、border-color、visibility等),浏览器就会重新构造样式,也就是重绘

重排

  1. DOM 操作(元素添加,删除,修改,或者元素顺序的改变)
  2. 内容变化,包括表单域内的文本改变
  3. CSS 属性的计算或改变
  4. 添加或删除样式表
  5. 更改“类”的属性
  6. 浏览器窗口的操作(缩放,滚动)
  7. 伪类激活(:hover)(在 IE8 中响应速度等性能问题更为明显)

重绘&重排补充知识

触发重排的操作

1. DOM 元素的几何属性变化

当DOM元素的几何属性变化时,渲染树中的相关节点就会失效,浏览器会根据DOM元素的变化重建构建渲染树中失效的节点。之后,会根据新的渲染树重新绘制这部分页面。而且,当前元素的重排也许会带来相关元素的重排。例如,容器节点的渲染树改变时,会触发子节点的重新计算,也会触发其后续兄弟节点的重排,祖先节点需要重新计算子节点的尺寸也会产生重排。最后,每个元素都将发生重绘。可见,重排一定会引起浏览器的重绘,一个元素的重排通常会带来一系列的反应,甚至触发整个文档的重排和重绘,性能代价是高昂的。

2. DOM 树的结构变化

当DOM树的结构变化+时,例如节点的增减、移动等,也会触发重排。浏览器引擎布局的过程,类似于树的前序遍历,是一个从上到下从左到右的过程。通常在这个过程中,当前元素不会再影响其前面已经遍历过的元素。所以,如果在body最前面插入一个元素,会导致整个文档的重新渲染,而在其后插入一个元素,则不会影响到前面的元素。

3. 获取某些属性

浏览器引擎可能会针对重排做了优化。比如Opera,它会等到有足够数量的变化发生,或者等到一定的时间,或者等一个线程结束,再一起处理,这样就只发生一次重排。但除了渲染树的直接变化,当获取一些属性时,浏览器为取得正确的值也会触发重排。这样就使得浏览器的优化失效了。这些属性包括:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)。所以,在多次使用这些值时应进行缓存。

4. 其他操作(如:调整浏览器窗口大小)

优化实践

改变样式

改变样式的优化方案,就是将多次改变属性的操作合并成一次,如:

// JS:
var changeDiv = document.getElementById(‘changeDiv’);
changeDiv.style.color = ‘#093′;
changeDiv.style.background = ‘#eee';
changeDiv.style.height = ‘200px';

  • 结合 css 的 class 选择器样式:

    // 可以合并为:
    // CSS:
    div.changeDiv {
    background: #eee;
    color: #093;
    height: 200px;
    }
    // JS:
    document.getElementById(‘changeDiv’).className = ‘changeDiv';
  • 直接使用 cssText 属性:

    document.getElementById(‘changeDiv’).cssText = 'background: #eee; color: #093; height: 200px;';

批量修改 DOM

需要对 DOM 进行一系列操作,比如增加删除子节点之类的操作,可以通过下列步骤减少重绘和重排,优化性能:

  1. 使元素脱离文档流
  2. 对其应用多重改变
  3. 把元素带回文档中

这样,整个过程只需要两次重排,中间怎么操作都不会触发。

那么就有第一个问题了:怎么让 DOM 脱离文档?

有三种基本方法:

  • 隐藏元素,应用修改,重新显示
    这个方法原理是利用渲染树的节点不包括隐藏元素,因此,通过改变 display 属性,临时从文档中移除指定元素

  • 使用文档片段,在 DOM 之外构建一个子树,再把它拷贝回文档
    文档片段通过 document.createDocumenyFragment() 创建。它是个轻量级的 document 对象,用来更新和移动节点。当你附加一个片段到文档节点时,实际上被添加进文档的是片段的子节点,而不是片段本身。

  • 将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后再替换原始元素
    直接上例子:

    var old = document.getElementById('test');
    var clone = old.cloneNode(true);
    foo(clone, data); // 更新指定节点数据的通用函数
    old.parentNode.replaceChild(clone, old);

推荐尽可能地使用文档片段,因为它们产生的 DOM 遍历和重排次数最少。

缓存布局信息

浏览器尝试通过队列化修改和批量执行的方式最小化重排次数。当查询布局信息时,比如获取偏移量(offsets)、滚动位置(scroll values)或计算出的样式值时,浏览器为了返回最新值,会刷新队列并应用所有变更。
所以,最好的做法是尽量减少布局信息的获取次数,获取后把它赋值给局部变量,然后再操作局部变量。
例子:
把 myElement 元素沿对角线移动,每次移动一个像素,从 (100, 100) 到 (500, 500)。
低效的实现方法:

myElement.style.left = 1 + myElement.style.offsetLeft + 'px';
myElement.style.top = 1 + myElement.style.offsetTop + 'px';
if (myElement.offsetLeft >= 500) {
stopAnimation();
}

这个方法之所以低效,是在于元素每次移动时都会去查询元素的偏移量,导致浏览器需要刷新渲染队列而不利于优化。
好的做法是,获取一次起始位置的值,然后将其赋值给一个变量,然后在动画循环中,直接使用该变量而不再查询偏移量:

var current = myElement.offsetLeft; // 坐标x,y值相同,只需要用一个变量即可
current++;
myElement.style.left = current + 'px';
myElement.style.top = current + 'px';
if (myElement.offsetLeft >= 500) {
stopAnimation();
}

让元素脱离动画流

手风琴导航示例
用图片这种类似手风琴效果的交互模式为例。它通常包括展开区域的集合动画,并将页面其他部分推向下方。而在这个过程中,会导致一次代价昂贵的大规模重排(@: 是一次还是多次?),使得页面感觉卡顿,渲染树中需要重新计算的节点越多,情况越糟糕。

使用下列步骤可以避免页面的大部分重排:

  1. 使用绝对位置定位页面上的动画元素,将其脱离文档流。
  2. 让元素动起来。当它扩大时,会临时覆盖部分页面。但这只是页面一个小区域的重绘过程,不会产生重排并重绘页面的大部分内容。
  3. 当动画结束时恢复定位,从而只会下移一次文档的其他元素

参考:

  • 《高性能JavaScript》
  • 浏览器的重绘与重排

Hello World

发表于 2016-01-01

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

$ hexo new "My New Post"

More info: Writing

Run server

$ hexo server

More info: Server

Generate static files

$ hexo generate

More info: Generating

Deploy to remote sites

$ hexo deploy

More info: Deployment

1…78
Andre Huang

Andre Huang

40 日志
19 标签
GitHub
© 2018 Andre Huang
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.3