高级技巧
(一)高级函数
1.安全的类型检测
javascript内置的类型检测机制并非完全可靠,如typeof操作符。instanceof操作符在存在多个全局作用域(一个页面包含多个iframe)时会有问题。
//value要是一个数组,且与Array构造函数在同一个全局作用域 //如果value是另个iframe的数组,则返回false var isArray = value isinstanceof Array;
原生数组的构造函数名与全局作用域无关,使用toString()能保持返回一致的值。
//检测某个值是不是数组 function isArray(value){ return Object.Prototype.toString.call(value) == "object Array"; } //检测某个值是不是函数 function isFunction(value){ return Object.Prototype.toString.call(value) == "object Function"; } //检测某个值是不是正则表达式 function RegExp(value){ return Object.Prototype.toString.call(value) == "object RegExp"; } //检测某个值是不是原生JSON var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON) == "object JSON";
Object的toSting()方法不能检测非原生构造函数的构造函数名,返回[object Object]。
2.作用域安全的构造函数
当使用new调用时,构造函数内用的this对象会指向新创建的对象实例。
var person1 = new Person("Nicholas", 29, "Software Engineer"); alert(person1.name); //"Nicholas" var person2 = Person("Nicholas", 29, "Software Engineer"); alert(person2); //undefined alert(window.name); //"Nicholas"
当没有使用new操作符来调用该构造函数的情况时,由于该this对象是在运行时绑定的,所以直接调用Person (),this会映射到全局对象window。
原本对Person实例的属性被加到window对象,因为构造函数作为普通函数调用,忽略了new操作符。
作用域安全的构造函数,在进行任何修改前,先确定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); } }
如果使用构造函数窃取模式的继承而不使用原型链,这个继承会被破坏。
function Rectangle(width, height){ Polygon.call(this, 2); this.width = width; this.height = height; this.getArea = function(){ return this.width * this.height; }; }
因为Polygon的构造函数是作用域安全的,this对象并非Polygon的实例,所以会创建并返回一个新的对象。Rectangle实例没有sides属性。
构造函数窃取结合使用原型链或寄生组合可以解决。
3.惰性载入函数
惰性载入表示函数执行的分支仅会执行一次
方法一:在函数被调用时再处理函数,在第一次调用的过程中,该函数会被覆盖为另一个按合适方式执行的函数,任何对原函数的调用都不再经过执行的分支了。
方法二:在声明函数时就指定适当的函数。
优点:只在执行分支代码时牺牲一点系能。
4.函数绑定
在特定的this环境指定函数调用另一个函数。这技巧常常和回调函数与事件处理程序一起用,以便在将函数作为变量传递的同时保留代码执行环境。
bind()函数接收一个函数和一个环境,并返回一个在给定环境中调用给定函数的寒素,将所有参数原封传递过去。
function bind(fn, context){ return function(){ return fn.apply(context, arguments); }; }
支持原生bind()方法 IE9+ Firefox 4+ Chrome
主要用于事件处理程序和setTimeout()和setInterval()。
5.函数柯里化
用于创建已经设置好了一个或多个参数的函数。使用一个闭包返回一个函数,当函数被调用时,返回的函数还要设置一些传入的参数。
创建:调用另一个函数并未它传入要柯里化的函数和必要参数。
function curry(fn){ var args = Array.prototype.slice.call(arguments, 1); return function(){ var innerArgs = Array.prototype.slice.call(arguments), finalArgs = args.concat(innerArgs); return fn.apply(null, finalArgs); }; }
(二)防篡改对象
一旦把对象定义为防篡改,就无法取消。
1.不可扩展对象
Object.preventExtensions()方法,不能再给对象添加属性和方法,仍然可以修改和删除已有成员。
Object.inExtensible():确定对象是否可以扩展。
2.密封的对象
不可扩展,而且已有成员的[[Configurable]]特性被修改为false,不能删除属性和方法,但可以修改。
使用Object.seal()方法。
Object.isSealed() 检测是否被密封。
3.冻结的对象
不可扩展、密封而且对象数据的[[Writable]]特性被设置为false。
Object.freeze()
Object.isFrozen()方法 检测
(三)高级定时器
页面载入时,先值任何包含在<script>的代码,通常是页面生命周期后面要用的简单的函数和变量的声明。
1.重复的定时器
链式setTimeout()
setTimeout ( function)(){ //处理中 setTimeout(arguement.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);
2.Yielding Progresses
脚本长时间运行通常由两个原因造成:过长的、过深嵌套的函数调用或者进行大量处理的循环。后者是比较容易解决的问题。
数组分块:为要处理的项目创建一个队列,然后使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器。
setTimeout(function (){ //取出下一个条目并处理 var item = array.shift(); process(item); //若还有条目,再设置另一个定时器 if(array.length>0){ setTimeout (arguments.callee, 100); } }, 100)
function chunk(array, process, context){ setTimeout(function(){ var item = array.shift(); process.call(context, item); if (array.length > 0){ setTimeout(arguments.callee, 100); } }, 100); }
3.函数节流
代码不可以在没有间断的情况下连续重复执行。第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码。第二次调用时它会清楚前一次的定时器并设置另一个。如果前一个定时器已经执行,则这个操作没有意义。如果未执行,将其代替为新的定时器。目的是只有在执行函数的请求停止了一段时间之后才执行。
var processor = { timeoutId: null, //实际进行处理的方法 performProcessing: function(){ //实际执行的代码 }, //初始化进行调用的方法 process: function(){ clearTimeout(this.timeoutId); var that = this; this.timeoutId = setTimeout(function)(){ that.performProcessing(); }, 100); } };
简化,自动进行定时器的设置和清除
//参数:要执行的函数 在哪个作用域执行 function throttle(method, scope) { clearTimeout(method.tId); method.tId= setTimeout(function(){ method.call(scope); }, 100); }
节流在resize时间最常用。
(四)自定义事件
主体负责发布时间,观察者通过订阅时间来观察主体。主体不知道观察者的任何事,它可以独自存在并正常运作即使观察者不存在。
观察者知道主体并能注册事件的回调函数。
自定义事件:创建一个管理事件的对象,让其他对象监听那些事件。
代码中存在多个部分在特定时刻相互交互的情况下,自定义事件非常有必要、
(五)拖放
基本概念:创建一个绝对定位的元素,使其可以用鼠标移动。
1.修缮拖动功能
计算元素左上角和指针之间的差,这个值在mousedown事件发生时就确定,并一直保持知道mouseup发生。
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.enable();
公共方法enable()和disable()只是相应添加和删除所有的事件处理程序,这两个函数提供了额外的对拖放功能的控制手段。
2.添加自定义事件
先创建一个新的EventTarget对象,然后添加enable()和disable()方法,最后返回这个对象。