高级程序设计(第3版)第二十二章高级技巧/笔记

高级技巧

  • 高级函数
    • 安全的类型检测
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。  

 

  • 高级定时器
    1. 浏览器负责进行排序,指派某段代码在某个时间点运行 的优先级
    2. 在 JavaScript中没有任何代码是立刻执行的,但一旦进程空闲则尽快执行
    3. 定时器对队列的工作方式是,当特定时间过去后将代码插入。
    4. 设定一个 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); 
        } 
   } 
}();     
View Code
    • 添加自定义事件二刷预定

//这一章看得我眼珠子都要掉了,难顶,缓一缓再二刷叭~

原文地址:https://www.cnblogs.com/isremya/p/13443280.html