JS高级函数
安全的类型检测方式
在使用类型检测typeof的时候,常常会碰到各种各样的问题,这些原因呢,有可能是因为浏览器的不同,也可能是因为浏览器版本不同,也有可能因为所运行的框架不同。比如typeof函数在safari中对正则表达式的返回值是“function”。那么,下面提供一种绝对安全可靠的类型检测方式(如果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]";
}
作用域安全的构造函数
在JS中使用原型链或者寄生组合的方式可以解决构造函数的作用域混用问题。例如:
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;
};
}
var rect = new Rectangle(5, 10);
alert(rect.sides); //undefined
我想要在Rectangel中调用Polygon方法来为Rectangle对象添加新的属性sides,但是我却发现this在被当做参数传入时并不是polygon的实例,而是rectangle的实例。而如何才能让Rectangle继承Polygon的属性呢?那么就使用原型链继承的方式来解决该问题。
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
通过 Rectangle.prototype = new Polygon(); 将原型链路连接到polygon后,那么rectangle的this也是polygon的实例,也就可以实现该功能了。一般情况下,也是用该方法去实现继承的。
惰性载入函数
通过创建一个匿名的、自执行的函数,用以确定应该使用哪个函数分支去实现,以此来增加运算性能。具体遇到性能问题的时候再对此进行一些优化研究。
函数绑定
该方法通常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境,可以用于创建始终在指定环境中运行的函数。具体如何使用,等到碰到使用场景再来细细研究。
函数柯里化
函数柯里化主要是为了绑定函数的时候,通过某些方式还可以在返回的函数中加入一些自定义的参数。具体如何使用,等到拥有了使用场景再来细细研究。
防篡改对象
数据或资源的共享是JS的大问题,为了防止某些函数无意或者恶意篡改,应该会存在相应的方法防止篡改。
- 不可扩展对象:Object.preventExtensions() 不可以对其添加属性和方法
- 密封对象: Object.seal() 不可以拓展,并且已有成员的configurable设置为false
- 冻结对象:不可扩展且密封,冻结对象,此外对象的writable属性为false
高级定时器
JS是单线程运行环境,因此很多有趣的功能并不是通过多线程实现的,而是使用定时器实现,即设定某个时间之后将某些代码加入到队列中,一旦空闲迅速执行,也就是setTimeout仅仅是设定了一个函数执行的最早时间而已。
重复定时器: 有setInterval()可以设定重复的计时器,但是有时候会产生一些不稳定的问题。可以使用链式的setTimeout的方式来进行解决。链式setTimeout方法的标准如下:
setTimeout(function(){
// 处理中的程序
setTimeout(arguments.callee, interval);
}, interval);
Yielding Processes: 由于内存和处理器的使用对于JS来说有明显的限制,因此当脚本运行时间过长就会出现一个浏览器错误的对话框。因此为了解决这个问题,我们可以使用定时器来分割运行的程序(比如说一个需要大量计算的循环),然后在每个循环中设置定时器去执行,这样会避免这样的问题。定时器标准函数chunk() 的标准如下:
function chunk(array, process, context){
setTimeout(function(){
//可更改的区域
var item = array.shift();
process.call(context, item);
//可更改的区域
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
chunk()函数接收是哪个参数:要处理的项目的数组,用于处理项目的函数,以及可选的运行该函数的环境
小例子:
<!doctype html>
<html>
<head>
<title>Array Chunking Example</title>
</head>
<body>
<div id="myDiv" style="background:red;"></div>
<script type="text/javascript">
var data = [12,123,1234,453,436,23,23,5,4123,45,346,5634,2234,345,342];
function chunk(array, process, context){
setTimeout(function(){
var item = array.shift();
process.call(context, item);
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
function printValue(item){
var div = document.getElementById("myDiv");
div.innerHTML += item + "<br>";
}
chunk(data, printValue);
</script>
</body>
</html>
函数节流:DOM操作比起来非DOM操作需要更多的时间和CPU,尤其是在resize事件中。最好去限制该时间的响应频率,可以通过throttle()的方法实现。
function throttle(method, scope) {
clearTimeout(method.tId);
method.tId= setTimeout(function(){
method.call(scope);
}, 100);
}
该函数接收两个参数:要执行的函数以及在哪个作用域中去执行,这样可以控制事件的响应频率,非常方便可以用在一些超级频繁触发的事件中,比如说:onmousemove事件。
自定义事件和拖放实现代码
事件的两个关联对象,主体和观察者。可以通过自定义的基本模式来自定义事件。具体如何使用,等到拥有了使用场景再来细细研究。
自定义事件的可以用在拖放上来实时显示目标对象的位置和事件。而在普通事件和自定义事件的标准定义上,推荐两个JS的标准代码:EventUtil.js 和 EventTarget.js代码供使用。(该代码摘抄自 Nicholas C.Zakas 所著的 Professional JavaScript For Web Developers)。
EventUtil.js
var EventUtil = {
addHandler: function(element, type, handler){
if (element.addEventListener){
element.addEventListener(type, handler, false);
} else if (element.attachEvent){
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
getButton: function(event){
if (document.implementation.hasFeature("MouseEvents", "2.0")){
return event.button;
} else {
switch(event.button){
case 0:
case 1:
case 3:
case 5:
case 7:
return 0;
case 2:
case 6:
return 2;
case 4: return 1;
}
}
},
getCharCode: function(event){
if (typeof event.charCode == "number"){
return event.charCode;
} else {
return event.keyCode;
}
},
getClipboardText: function(event){
var clipboardData = (event.clipboardData || window.clipboardData);
return clipboardData.getData("text");
},
getEvent: function(event){
return event ? event : window.event;
},
getRelatedTarget: function(event){
if (event.relatedTarget){
return event.relatedTarget;
} else if (event.toElement){
return event.toElement;
} else if (event.fromElement){
return event.fromElement;
} else {
return null;
}
},
getTarget: function(event){
return event.target || event.srcElement;
},
getWheelDelta: function(event){
if (event.wheelDelta){
return (client.engine.opera && client.engine.opera < 9.5 ? -event.wheelDelta : event.wheelDelta);
} else {
return -event.detail * 40;
}
},
preventDefault: function(event){
if (event.preventDefault){
event.preventDefault();
} else {
event.returnValue = false;
}
},
removeHandler: function(element, type, handler){
if (element.removeEventListener){
element.removeEventListener(type, handler, false);
} else if (element.detachEvent){
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
setClipboardText: function(event, value){
if (event.clipboardData){
event.clipboardData.setData("text/plain", value);
} else if (window.clipboardData){
window.clipboardData.setData("text", value);
}
},
stopPropagation: function(event){
if (event.stopPropagation){
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
该JS文件主要是为跨浏览器的注册和删除各种事件提供一个通用的接口,也可以对事件的冒泡进行设置,是一个比较强大的事件处理的通用代码。
EventTarget.js
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);
}
}
};
该JS文件主要是通过自定义事件的方式来定义一些自定义功能,比如说拖动HTML元素触发某些事件等等。其中addHandler添加事件,fire触发事件,remove删除事件。
那么在通过调用这两个标准库,就可以实现鼠标拖拽元素后在浏览器的DOM元素中打印出其位置信息的功能。
<!DOCTYPE html>
<html>
<head>
<title>Drag and Drop Example</title>
<script type="text/javascript" src="EventUtil.js"></script>
<script type="text/javascript" src="EventTarget.js"></script>
</head>
<body>
<div id="status"></div>
<div id="myDiv1" class="draggable" style="top:100px;left:0px;background:red;width:100px;height:100px;position:absolute"></div>
<div id="myDiv2" class="draggable" style="background:blue;width:100px;height:100px;position:absolute;top:100px;left:100px"></div>
<script type="text/javascript">
var DragDrop = function(){
var dragdrop = new EventTarget(),
dragging = null,
diffX = 0,
diffY = 0;
function handleEvent(event){
//get event and target
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
//determine the type of 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){
//assign location
dragging.style.left = (event.clientX - diffX) + "px";
dragging.style.top = (event.clientY - diffY) + "px";
//fire custom event
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;
}
};
//public interface
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 + ")";
});
</script>
</body>
</html>
效果图如下:
小结
JS的高级技巧,主要是适用于系统优化,数据安全等等方面,这些功能已经在某些成熟框架中进行了优化,但是如果你们了解这些特殊高级功能,那么在做一些前端或者后端的功能创新方面,会有更多的工具和方法。注意,这些都是基于JS的单线程运行大环境的。通过各种灵活的函数定义方式和定时器,可以实现很多复杂的功能,比如说模仿多线程等等,JS的高级编码模式真的是非常强大。