高级技巧
高级函数
安全的类型检测
- typeof操作符在检测数据类型时,可能会得到不靠谱的结果
- instanceof操作符在存在多个全局作用域,也就是页面包含多个iframe的情况下,也会出现问题
- 在任何值上调用Object原生的toString()方法,都会返回一个[object NativeConstructorName]格式的字符串
-
原生数组的构造函数名与全局作用域无关,因此使用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对象是正确类型的实例,如果不是,那么会创建新的实例并返回
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
-
使用构造函数窃取模式的继承且不适用原型链,这个继承可能被破坏
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
-
上面的代码中,Polygon构造函数是作用域安全的,然而Rectangle构造函数则不是。如果构造函数窃取结合使用原型链或者寄生组合则可以解决这个问题
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
惰性载入函数
-
惰性载入表示函数执行的分支仅会发生一次。
-
第一种实现惰性载入的方法,在函数被调用时再处理函数。在第一次调用的过程中,该函数会覆盖为另一个按何时方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了
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]; break; } catch (ex){ //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 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."); }; } })();
-
函数绑定
-
一个简单的bind()函数接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去
function bind(fn, context){ return function(){ return fn.apply(context, arguments); }; }
-
当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数
var handler = { message: "Event handled", handleClick: function(event){ alert(this.message); } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler))
-
ECMAScript5为所有函数定义了一个原生的bind()方法,进一步简单了操作,不用再自己定义bind()函数了,而是可以直接在函数上调用这个方法
var handler = { message: "Event handled", handleClick: function(event){ alert(this.message + ":" + event.type); } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));
函数的柯里化
- 用于创建已经设置好了一个或者多个参数的函数。函数柯里化的基本方法和函数绑定是一样的。使用一个闭包返回一个函数,两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数
-
柯里化函数通常由一下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数
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); }; }
-
ECMAScript5的bind()方法也实现函数柯里化,只要在this的值之后再传入另一个参数即可
var handler = { message: "Event handled", handleClick: function(name, event){ alert(this.message + ":" + name + ":" + event.type); } }; var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));
防篡改对象
不可扩展对象
- 默认情况下,所有对象都是可以扩展的,也就是说,任何时候都可以向对象中添加属性和方法
-
Object.preventExtensions()方法可以改变这个行为,不能再给对象添加属性和方法
var person={name:"Nicholas"}; Object.preventExtensions(person); person.age=29; alert(person.age);//undefined
-
Object.isExtensible()方法还可以确定对象是否可以扩展
var person={name:"Nicholas"}; alert(Object.isExtensible(person));//true Object.preventExtensions(person); alert(Object.isExtensible(person));//true
密封的对象
-
封闭对象不可扩展,而且已有成员[Configurable]特性将被设置为false,这就意味着不能删除属性和方法,因为不能使用Object.defineProperty()把数据属性修改为访问器属性
var person = { name: "Nicholas" }; Object.seal(person); person.age = 29; alert(person.age); //undefined delete person.name; alert(person.name); //"Nicholas"
-
Object.isSealed()方法可以确定对象是否被密封了,因为被密封的对象不可扩展,所以用Object.isExtensible()检测密封的对象也会返回false
var person = { name: "Nicholas" }; alert(Object.isExtensible(person)); //true alert(Object.isSealed(person)); //false Object.seal(person); alert(Object.isExtensible(person)); //false alert(Object.isSealed(person)); //true
冻结的对象
-
冻结的对象既不可扩展又是密封的,而且对象数据属性的[Writable]特性会被设置为false,如果定义[Set]函数,访问器属性仍然是可写的。ECMAScript5丁意思的Object.freeze()方法可以用来冻结对象
var person = { name: "Nicholas" }; Object.freeze(person); person.age = 29; alert(person.age); //undefined delete person.name; alert(person.name); //"Nicholas" person.name = "Greg"; alert(person.name); //"Nicholas"
-
Object.isFrozen()方法用于检测冻结对象,因为冻结对象既是密封的又是不可扩展的,所以Object.isExtensible()和Object.isSealed()检测冻结对象将分别返回false和true
var person = { name: "Nicholas" }; alert(Object.isExtensible(person)); //true alert(Object.isSealed(person)); //false alert(Object.isFrozen(person)); //false Object.freeze(person); alert(Object.isExtensible(person)); //false alert(Object.isSealed(person)); //true alert(Object.isFrozen(person)); //true
高级定时器
重复的定时器
- 使用setInterval()创建的定时器确保了定时器代码规则地插入队列中
- 使用setInterval()时,仅当没有该定时器的任何其他代码实现时,才将定时器代码添加到队列中,这确保了定时器加入到队列中的最小时间间隔为指定间隔
- 重复定时器有两个问题:某些间隔会被跳过;多个定时器的代码执行之间的间隔可能会比预期小
-
为了避免setInterval()的重复定时器的两个问题,使用链式setTimeout()调用
setTimeout(function(){ //处理中 setTimeout(arguments.callee, interval); }, interval);
-
这个模式主要用于重复定时器
setTimeout(function(){ var div = document.getElementById("myDiv"); left = parseInt(div.style.left) + 5; div.style.left = left + "px"; if (left < 200){ setTimeout(arguments.callee, 50); } }, 50)
Yielding Processes
- 脚本长时间运行的问题通常是由两个原因之一造成的:过长的、过深嵌套的函数调用或者是进行大量处理的循环
- 两个重要问题:该处理是否必须同步完成;数据是否必须按顺序完成
- 如果两个问题回答都是否,可以使用定时器分隔这个循环,即数组分块技术
-
小块小块地处理数组,通常每次一小块,基本的思路是为要处理的项目创建一个队列,然后使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器
setTimeout(function(){ //取出下一个条目并处理 var item = array.shift(); process(item); //若还有条目,再设置另一个定时器 if(array.length > 0){ setTimeout(arguments.callee, 100); } }, 100);
-
数组分块模式中,array变量本质上就是一个列表,包含了要处理的项目。使用shift()方法获取队列中要处理的项目,然后将其传递给某个函数。如果在队列中还有其他项目,则设置另一个定时器,并通过arguments.callee调用同一个匿名函数,要实现数组分块非常简单,可以使用下面的函数
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()方法接收三个参数:要处理的项目的数组,用于处理项目的函数,以及可选的运行该函数的环境
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);
函数节流
-
函数节流背后的基本思想是指,某些代码不可以在没有间断的情况连续重复执行。第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,它会清除前一次的定时器并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器。目的是只有在执行函数的请求停止了一段时间之后才执行。
var processor = { timeoutId: null, //实际进行处理的方法 performProcessing: function(){ //实际执行的代码 }, //初始处理调用的方法 process: function(){ clearTimeout(this.timeoutId); var that = this; this.timeoutId = setTimeout(function(){ that.performProcessing(); }, 100); } }; //尝试开始执行 processor.process();
自定义事件
- 观察者模式由两类对象组成:主体和观察者。主体负责发布事件,同时观察者通过订阅这些事件来观察该主体
-
自定义事件背后的概念是创建一个管理事件的对象,让其他对象监听事件
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类型有一个单独的属性handlers ,用于储存事件处理程序。
-
还有三个方法:
-
addHandler() ,用于注册给定类型事件的事件处理程序
- addHandler() 方法接受两个参数:事件类型和用于处理该事件的函数。当调用该方法时,会进行一次检查,看看 handlers 属性中是否已经存在一个针对该事件类型的数组;如果没有,则创建一个新的。然后使用 push() 将该处理程序添加到数组的末尾。
-
fire() ,用于触发一个事件
- 如果要触发一个事件,要调用 fire() 函数。该方法接受一个单独的参数,是一个至少包含 type属性的对象。 fire() 方法先给 event 对象设置一个 target 属性,如果它尚未被指定的话。然后它就查找对应该事件类型的一组处理程序,调用各个函数,并给出 event 对象。
-
removeHandler() ,用于注销某个事件类型的事件处理程序。
- removeHandler() 方法是 addHandler() 的辅助,它们接受的参数一样:事件的类型和事件处理程序。这个方法搜索事件处理程序的数组找到要删除的处理程序的位置。如果找到了,则使用 break操作符退出 for 循环。然后使用 splice() 方法将该项目从数组中删除。
-
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!"});
拖放
-
基本实现过程
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); } } }();
修缮拖动功能
-
为防止出现上图情况。计算元素左上角和指针位置之间的差值。这个差值应该在 mousedown 事件发生的时候确定,并且一直保持,直到 mouseup 事件发生。通过将 event的 clientX 和 clientY 属性与该元素的 offsetLeft 和 offsetTop 属性进行比较,就可以算出水平方向和垂直方向上需要多少空间
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); } } }()
添加自定义事件
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;
}();
-
这段代码定义了三个事件: dragstart 、 drag 和 dragend 。它们都将被拖动的元素设置为了 target ,并给出了 x 和 y 属性来表示当前的位置。它们触发于 dragdrop 对象上,之后在返回对象前给对象增加 enable() 和 disable() 方法。这些模块模式中的细小更改令 DragDrop 对象支持了事件
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 + ")"; });
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。