JavaScript高级程序设计读书笔记22

第22章 高级技巧

高级函数

安全的类型检测

  • typeof在Safari中对正则表达式也返回function。
  • instanceof在存在多个全局作用域时也会把同种却不同作用域中构造函数的实例识别为不同的实例
1
2
3
4
5
6
7
8
9
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]";
}

作用域安全的构造函数

  • 当使用new操作符,构造函数内用到的this对象会指向新创建的对象实例
  • 作用域安全的构造函数在进行任何更改前,首先确认this对象是正确类型的实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
}
var person1 = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //""
alert(person1.name); //"Nicholas"
var person2 = new Person("Shelby", 34, "Ergonomist");
alert(person2.name); //"Shelby"
  • 不过在使用了这样作用域安全的构造函数后,如果使用基于构造函数窃取的继承且不使用原型链,就会出现问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Polygon(sides){
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function(){
return 0;
};
} else {
return new Polygon(sides);
}
}
function Rectangle(width, height){
//this对象并非Polygn的实例,所以会创建并返回一个新的Polygn对象
Polygon.call(this, 2);
this.width = width;
this.height = height;
this.getArea = function(){
return this.width * this.height;
};
}
var rect = new Rectangle(5, 10);
alert(rect.sides); //undefined
  • 使用原型链或者寄生组合可以解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Polygon(sides){
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function(){
return 0;
};
} else {
return new Polygon(sides);
}
}
function Rectangle(width, height){
Polygon.call(this, 2);
this.width = width;
this.height = height;
this.getArea = function(){
return this.width * this.height;
};
}
Rectangle.prototype = new Polygon();
var rect = new Rectangle(5, 10);
alert(rect.sides); //2

惰性载入函数

  • 大量的判断浏览器能力的函数需要被使用(通常是大量的if),然而这些判断一般其实不必每次都执行,在执行一次后,浏览器的能力就确定了,以后就应该不用在判断了。
  • 第一种方式就是在函数被调用时再处理函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function createXHR(){
if (typeof XMLHttpRequest != "undefined"){
createXHR = function(){
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != "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];
} catch (ex){
//skip
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
createXHR = function(){
throw new Error("No XHR object available.");
};
}
return createXHR();
}
  • 第二种实现惰性载入的方式是在声明函数时就指定适当的函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var createXHR = (function(){
if (typeof XMLHttpRequest != "undefined"){
return function(){
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != "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 (ex){
//skip
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
return function(){
throw new Error("No XHR object available.");
};
}
})();
var xhr1 = createXHR();

函数绑定

  • bind()函数可以将函数绑定到指定环境
1
2
3
4
5
function bind(fn, context) {
return function() {
return fn.apply(context, arguments);
};
}

函数柯里化

  • 函数柯里化:用来创建已经设置好一个或多个参数的函数
1
2
3
4
5
6
7
8
function curry(fn){
var args = Array.prototype.slice.call(arguments, 1);
return function(){
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(null, finalArgs);
};
}

防篡改对象

不可拓展对象

  • 不能给对象添加属性和方法。但是可以删除和修改已有成员。
  • preventExtensions()方法:对象不可扩展
  • Object.isExtensible():确定对象是否可以扩展

密封的对象

  • 密封对象不可扩展,已有成员的[[Configurable]]特性将被设置为false。不能删除属性和方法。
  • Object.frozen():密封对象
  • Object.isFrozen():确定对象是否被密封了

冻结的对象

  • frozen对象不可拓展又是密封的,而且对象属性的[[Writable]]特性会被设置为false。如果定义[[Set]]函数,访问器属性仍是可写的。
  • Object.frozen(person):冻结对象
  • Object.isFrozen(person):确定对象是否被冻结

高级定时器

  • 关于定时器要记住的最重要的事情是,指定的时间间隔表示何时将定时器的代码添加到队列,而不是何时执行代码。

重复的定时器

  • 使用setInterval创建定时器的目的是使代码规则的插入到队列中。这个方式的问题在于,存在这样一种可能,在上次代码还没执行完的时候代码再次被添加到队列。
  • 解决方式:使用链式setTimeout()
1
2
3
setTimeout(function(){ 
setTimeout(arguments.callee, interval);
}, interval);

Yielding Processes

  • 如果你的页面中要进行大量的循环处理,每次循环会消耗大量的时间,那就会阻塞用户的操作。
  • 解决方法:数组分块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function chunk(array, process, context){
setTimeout(function(){
var item = array.shift();
process.call(context, item);
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
var data = [12,123,1234,453,436,23,23,5,4123,45,346,5634,2234,345,342];
function printValue(item){
var div = document.getElementById("myDiv");
div.innerHTML += item + "<br>";
}
chunk(data, printValue);

函数节流

  • 这个是为了避免某个操作连续不停的触发,比如涉及到DOM操作,连续大量的DOM操作非常耗资源。
  • 解决方式:函数节流
  • 函数节流的基本思想是,每一次调用其实是设置一个真正调用的setTimeout操作,每次调用都会先清除当前的setTimeout再设置一个新的。如果短时间内大量调用,就回一直设置新的setTimeout而不执行setTimeout内的操作。只有停止调用足够长的时间,直到setTimeout时间到了,内部的真正操作才会执行一次。这个对onresize事件特别有用。
1
2
3
4
5
6
7
8
9
10
11
12
13
function throttle(method, context) {
clearTimeout(method.tId);
method.tId= setTimeout(function(){
method.call(context);
}, 100);
}
function reDiv(){
var div = document.getElementById("myDiv");
div.innerHTML += "qqqqqq" + "<br>";
}
window.onresize = function(){
throttle(reDiv);
};

自定义事件

  • 事件这样的交互其实就是观察者模式,这类模式由两类对象组成:主体和观察者。主体负责发布事件,观察者通过订阅这些事件来观察该主体。
  • 观察者知道主体并能注册事件的回调函数(事件处理程序)。涉及DOM上时,DOM元素便是主体,事件处理程序代码便是观察者。
  • 创建自定义事件实际上就是创建一个管理事件的对象,并在里面存入各种事件类型的处理函数,触发事件时,只要你给出事件类型,这个对象就会找到相应的事件处理程序并执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function EventTarget(){
this.handlers = {};
}
EventTarget.prototype = {
constructor: EventTarget,
addHandler: function(type, handler){
if (typeof this.handlers[type] == "undefined"){
this.handlers[type] = [];
}
this.handlers[type].push(handler);
},
fire: function(event){
if (!event.target){
event.target = this;
}
if (this.handlers[event.type] instanceof Array){
var handlers = this.handlers[event.type];
for (var i=0, len=handlers.length; i < len; i++){
handlers[i](event);
}
}
},
removeHandler: function(type, handler){
if (this.handlers[type] instanceof Array){
var handlers = this.handlers[type];
for (var i=0, len=handlers.length; i < len; i++){
if (handlers[i] === handler){
break;
}
}
handlers.splice(i, 1);
}
}
};
  • 使用EventTarget类型的自定义事件
1
2
3
4
5
6
7
8
function handleMessage(event){
alert("Message received: " + event.message);
}
var target = new EventTarget();
target.addHandler("message", handleMessage);
target.fire({ type: "message", message: "Hello world!"});
target.removeHandler("message", handleMessage);
target.fire({ type: "message", message: "Hello world!"});

拖放

  • 创建一个绝对定位的元素,使其可以用鼠标移动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var DragDrop = function(){
var dragging = null;
function handleEvent(event){
//获取事件和目标
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
//确定事件类型
switch(event.type){
case "mousedown":
if (target.className.indexOf("draggable") > -1){
dragging = target;
}
break;
case "mousemove":
if (dragging !== null){
//指定位置
dragging.style.left = event.clientX + "px";
dragging.style.top = event.clientY + "px";
}
break;
case "mouseup":
dragging = null;
break;
}
};
//公共接口
return {
enable: function(){
EventUtil.addHandler(document, "mousedown", handleEvent);
EventUtil.addHandler(document, "mousemove", handleEvent);
EventUtil.addHandler(document, "mouseup", handleEvent);
},
disable: function(){
EventUtil.removeHandler(document, "mousedown", handleEvent);
EventUtil.removeHandler(document, "mousemove", handleEvent);
EventUtil.removeHandler(document, "mouseup", handleEvent);
}
}
}();

修缮拖动方法

  • 原先的代码会让被拖动的元素的左上角在鼠标下方。
  • 现在对鼠标相对元素的位置记录后再次计算元素的绝对位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
var DragDrop = function(){
var dragging = null,
diffX = 0,
diffY = 0;
function handleEvent(event){
//获取事件和目标
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
//确定事件类型
switch(event.type){
case "mousedown":
if (target.className.indexOf("draggable") > -1){
dragging = target;
diffX = event.clientX - target.offsetLeft;
diffY = event.clientY - target.offsetTop;
}
break;
case "mousemove":
if (dragging !== null){
//指定位置
dragging.style.left = (event.clientX - diffX) + "px";
dragging.style.top = (event.clientY - diffY) + "px";
}
break;
case "mouseup":
dragging = null;
break;
}
};
//公共接口
return {
enable: function(){
EventUtil.addHandler(document, "mousedown", handleEvent);
EventUtil.addHandler(document, "mousemove", handleEvent);
EventUtil.addHandler(document, "mouseup", handleEvent);
},
disable: function(){
EventUtil.removeHandler(document, "mousedown", handleEvent);
EventUtil.removeHandler(document, "mousemove", handleEvent);
EventUtil.removeHandler(document, "mouseup", handleEvent);
}
}
}();

添加自定义事件

  • 新定义一个dragdrop变量,它是EventTarget类型的对象,在它上面我们可以添加事件处理函数或触发事件。在拖动开始时,过程中,结束时,都触发了自定义事件,这样有人想在这几个节点做什么就直接添加事件处理函数就可以了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
var DragDrop = function(){
var dragdrop = new EventTarget(),
dragging = null,
diffX = 0,
diffY = 0;
function handleEvent(event){
//获取事件和目标
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
//确定事件类型
switch(event.type){
case "mousedown":
if (target.className.indexOf("draggable") > -1){
dragging = target;
diffX = event.clientX - target.offsetLeft;
diffY = event.clientY - target.offsetTop;
dragdrop.fire({type:"dragstart", target: dragging, x: event.clientX, y: event.clientY});
}
break;
case "mousemove":
if (dragging !== null){
//指定位置
dragging.style.left = (event.clientX - diffX) + "px";
dragging.style.top = (event.clientY - diffY) + "px";
//触发自定义事件
dragdrop.fire({type:"drag", target: dragging, x: event.clientX, y: event.clientY});
}
break;
case "mouseup":
dragdrop.fire({type:"dragend", target: dragging, x: event.clientX, y: event.clientY});
dragging = null;
break;
}
};
//公共接口
dragdrop.enable = function(){
EventUtil.addHandler(document, "mousedown", handleEvent);
EventUtil.addHandler(document, "mousemove", handleEvent);
EventUtil.addHandler(document, "mouseup", handleEvent);
};
dragdrop.disable = function(){
EventUtil.removeHandler(document, "mousedown", handleEvent);
EventUtil.removeHandler(document, "mousemove", handleEvent);
EventUtil.removeHandler(document, "mouseup", handleEvent);
};
return dragdrop;
}();
DragDrop.enable();
DragDrop.addHandler("dragstart", function(event){
var status = document.getElementById("status");
status.innerHTML = "Started dragging " + event.target.id;
});
DragDrop.addHandler("drag", function(event){
var status = document.getElementById("status");
status.innerHTML += "<br>Dragged " + event.target.id + " to (" + event.x + "," + event.y + ")";
});
DragDrop.addHandler("dragend", function(event){
var status = document.getElementById("status");
status.innerHTML += "<br>Dropped " + event.target.id + " at (" + event.x + "," + event.y + ")";
});