高级技巧
- 高级函数
- 安全的类型检测
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); } } //原本,像这种没有使用new就直接调用person的,this会被解析成window 对象 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); //返回一个新的Polygon对象,里面的属性方法不会成为Rectangle的属性和方法 this.width = width; this.height = height; this.getArea = function(){ return this.width * this.height; }; } ---------------------------------------------------- var rect = new Rectangle(5, 10); alert(rect.sides); //undefined ---------------------------------------------------- Rectangle.prototype = new Polygon(); //使得,一个Rectangle实例也同时是一个Polygon实例 var rect = new Rectangle(5, 10); alert(rect.sides); //2
- 惰性载入函数
- 惰性载入表示函数执行的分支仅会发生一次,避免了大量的if语句,避免了不必要的代码
//法一: //在第一次调用的过程中,该函数会被覆盖为另外一个按合适方式执行的函数, //这样任何对原函数的调用都不用再经过执行的分支了 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."); }; } })();
- 函数绑定
- 函数绑定要创建一个函数,可以在特定的 this 环境中 以指定参数调用另一个函数。
- 该技巧常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境
- 被绑 定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一点,所 以好只在必要时使用。
- 一个简单的 bind()函数接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数, 并且将所有参数原封不动传递过去
function bind(fn, context){ return function(){ return fn.apply(context, arguments); }; } //注意这里使用的 arguments 对象是内部函 数的,而非 bind()的。 //当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数 var handler = { message: "Event handled", handleClick: function(event){ alert(this.message + ":" + event.type); } }; //自定义的bind var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler)); //原生的 bind()方法 //不用 再自己定义 bind()函数了,而是可以直接在函数上调用这个方法 var btn = document.getElementById("my-btn"); EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));
- 函数柯里化
- 函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。
- 两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。
- 柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数
- 防篡改对象
- 不可扩展对象
- 默认情况下,所有对象都是可以扩展的。也就是说,任何时候都可以向对象中添加属性和方法
- Object.preventExtensions()方法可以改变这个行为,让你不能再给对象添加属性和方法。
- 虽然不能给对象添加新成员,但已有的成员则丝毫不受影响。你仍然还可以修改和删除已有的成员
- 另外,使用 Object.istExtensible()方法还可以确定对象是否可以扩展。
var person = { name: "Nicholas" }; Object.preventExtensions(person); person.age = 29; alert(person.age); //undefined //------------------------------------------------ var person = { name: "Nicholas" }; alert(Object.isExtensible(person)); //true Object.preventExtensions(person); alert(Object.isExtensible(person)); //false
- 密封的对象
- 密封对象不可扩展,而 且已有成员的[[Configurable]]特性将被设置为 false。这就意味着不能删除属性和方法,因为不能 使用 Object.defineProperty()把数据属性修改为访问器属性,或者相反。
- 属性值是可以修改的。
- 使用 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]]函数,访问器属性仍然是可写的。
- Object.freeze()方法可以用来冻结对象。
- Object.isFrozen()方法用于检测冻结对象
- 因为冻结对象既是密封的又是不可 扩展的,所以用 Object.isExtensible()和 Object.isSealed()检测冻结对象将分别返回 false 和 true。
- 高级定时器
- 浏览器负责进行排序,指派某段代码在某个时间点运行 的优先级
- 在 JavaScript中没有任何代码是立刻执行的,但一旦进程空闲则尽快执行
- 定时器对队列的工作方式是,当特定时间过去后将代码插入。
- 设定一个 150ms后执行的定时器不代表到了 150ms代码就立刻 执行,它表示代码会在 150ms后被加入到队列中
- 重复的定时器
- 定时器代码可能在代码再次被添加到队列之前还没有完成执行,结果导致定时器代码连续运行好几次, 而之间没有任何停顿。
- 当使用 setInterval()时,仅 当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中。
- 这种重复定时器的规则有两个问题:
- 某些间隔会被跳过
- 多个定时器的代码执行之间的间隔 可能会比预期的小。
- 解决:使用链式setTimeout() 调用
setTimeout(function(){ //处理中 setTimeout(arguments.callee, interval); }, interval); //这个模式链式调用了 setTimeout(),每次函数执行的时候都会创建一个新的定时器。 //第二个 setTimeout()调用使用了 arguments.callee 来获取对当前执行的函数的引用,并为其设置另外一个定时器。 //这样做的好处是,在前一个定时器代码执行完之前,不会向队列插入新的定时器代码,确保 不会有任何缺失的间隔。 //而且,它可以保证在下一次定时器代码执行之前,至少要等待指定的间隔,避免了连续的运行。
- Yielding Processes 二刷预定
- 脚本长时间运行的问题通常有两个原因
- 过长的、过深嵌套的函数调用或者是进行大 量处理的循环
- 当你发现某个循环占用了大量时间,同时对于“该处理是否必须同步完成?”和“数据是否必须按顺序完成?”两个问题,你的回答都是“否”时,你就可以 使用定时器分割这个循环。这是一种叫做数组分块(array chunking)的技术,小块小块地处理数组,通 常每次一小块。基本的思路是为要处理的项目创建一个队列,然后使用定时器取出下一个要处理的项目 进行处理,接着再设置另一个定时器。
//基本模式 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); }
- 函数节流
- 函数节流背后的基本思想是指,某些代码不可以在没有间断的情况连续重复执行。
//基本模式 var processor = { timeoutId: null, //实际进行处理的方法 performProcessing: function(){ //实际执行的代码 }, //初始处理调用的方法 process: function(){ clearTimeout(this.timeoutId); var that = this; this.timeoutId = setTimeout(function(){ that.performProcessing(); }, 100); } }; //尝试开始执行 processor.process();
- throttle()函数
- 可以自动进行定时器的设置和清除
- 接受两个参数:要执行的函数以及在哪个作用域中执行
- 节流在 resize 事件中是常用的
- 只要代码是周期性执行的,都应该使用节流,但是你不能控制请求执行的速率。
//简化版(使用 throttle()函数) function throttle(method, context) { clearTimeout(method.tId); method.tId= setTimeout(function(){ method.call(context); }, 100); }
- 自定义事件
function EventTarget(){ this.handlers = {}; //用于储存事件处理程序 } EventTarget.prototype = { constructor: EventTarget, addHandler: function(type, handler){ //法接受两个参数:事件类型和用于处理该事件的函数 if (typeof this.handlers[type] == "undefined"){ //handlers 属性中是否已经存在一个针对该事件类型的数组 this.handlers[type] = []; //;如果没有,则创建一个新的数组 } this.handlers[type].push(handler); //使用 push()将该处理程序添加到数组的末尾。 }, fire: function(event){ //接受一个单独的参数,是一个至少包含 type 属性的对象。 if (!event.target){ //给 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); } } };
- 拖放
拖放的基本概念很简单:创建一个绝对定位的元素,使其可以用鼠标移动
- 修缮拖动功能
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); } } }();
- 添加自定义事件二刷预定
//这一章看得我眼珠子都要掉了,难顶,缓一缓再二刷叭~