Javascript高级技巧(一)

TOC
  1. 1. 作用域安全的构造函数
  2. 2. 函数加载优化技巧
    1. 2.1. 解决方案:惰性载入(1)
    2. 2.2. 解决方案:惰性载入(2)
  3. 3. 函数绑定
    1. 3.1. 常见问题
    2. 3.2. 改进:
  4. 4. 函数curry化
  5. 5. 防篡改对象
    1. 5.1. 不可扩展对象:(第一个级别的保护)
    2. 5.2. 密封的对象(第二个级别的保护)
    3. 5.3. 冻结的对象(第三个级别的保护)

作用域安全的构造函数

这是常见的构造函数:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
}
var person = new Person("Nicholas", 29, "Software Engineer");     // new

假设:

var person1 = Person("test",22,"FE");

那么 这个新对象的属性将会被绑定在window对象上面, 如果当前环境存在 window.name的操作 那么可能就会造成错误.

解决方法:(域安全的构造函数)

先判断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, 不属于Polygon的this
               this.width = width;
               this.height = height;
               this.getArea = function(){
                    return this.width * this.height;
               };
          }
          var rect = new Rectangle(5, 10);
          alert(rect.sides); //undefined

改正方法: 利用原型链

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"){
            return new XMLHttpRequest();
        } else if (typeof ActiveXObject != "undefined"){
                    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){
                        //跳过
                }
            }
        }
        return new ActiveXObject(arguments.callee.activeXString);
        } else {
        throw new Error("No XHR object available.");
        }
    }

缺点:每一次判断和循环造成性能上的损失

解决方案:惰性载入(1)

执行代码的时候损失性能

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();
     }

解决方案:惰性载入(2)

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.");
                };
           }
           })();

函数绑定

常见问题

var handler = {
          message: "Event handled",
          handleClick: function(event){
               alert(this.message);
          }
     };
     var btn = document.getElementById("my-btn");
     EventUtil.addHandler(btn, "click", handler.handleClick);          // undefined, 出现了作用域问题

改进:

var handler = {
          message: "Event handled",
          handleClick: function(event){
               alert(this.message);
          }
     };
     var btn = document.getElementById("my-btn");
     EventUtil.addHandler(btn, "click", function(event){               // 使用了闭包  但是尽量少用闭包
          handler.handleClick(event);
     });

这个解决方案在 onclick 事件处理程序内使用了一个闭包直接调用 handler.handleClick()。

当然,这是特定于这段代码的解决方案。创建多个闭包可能会令代码变得难于理解和调试。因此,很多JavaScript 库实现了一个可以将函数绑定到指定环境的函数。这个函数一般都叫 bind()。一个简单的 bind()函数接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去。

语法如下:

function bind(fn, context){
     return function(){
          return fn.apply(context, arguments);
     };
}

这个函数似乎简单,但其功能是非常强大的。在 bind()中创建了一个闭包,闭包使用 apply()调用传入的函数,并给 apply()传递 context 对象和参数。注意这里使用的 arguments 对象是内部函数的,而非 bind()的。当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数。

bind()函数按如下方式使用:

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));

ECMAS5: 存在bind方法, 但IE9及以上支持

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));

函数curry化

函数柯里化(function currying) ,它用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和 函数绑定 是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。

function add(num1, num2){
               return num1 + num2;
          }
function curriedAdd(num2){          // 比如说说实现 计算 和 打印 功能的分开
               return add(5, num2);
          }
alert(add(2, 3));                     //5
alert(curriedAdd(3));             //8

柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。下面是创建柯里化函数的通用方式。

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);
               };
          }

防篡改对象

不过请注意:一旦把对象定义为防篡改,就无法撤销了

不可扩展对象:(第一个级别的保护)

默认情况下,所有对象都是可以扩展的。也就是说,任何时候都可以向对象中添加属性和方法。

var person = { name: "Nicholas" };
person.age = 29;

即使第一行代码已经完整定义 person 对象,但第二行代码仍然能给它添加属性。现在,使用Object.preventExtensions()方法可以改变这个行为,让你不能再给对象添加属性和方法。

例如:

var person = { name: "Nicholas" };
Object.preventExtensions(person);
person.age = 29;
alert(person.age); // undefined,非严格模式下静默失败,严格模式下抛出错误

虽然不能给对象添加新成员,但已有的成员则丝毫不受影响。你仍然还可以修改和删除已有的成员。另外,使用 Object.istExtensible()方法还可以确定对象是否可以扩展。

var person = { name: "Nicholas" };
alert(Object.isExtensible(person)); //true
Object.preventExtensions(person);
alert(Object.isExtensible(person)); //false

密封的对象(第二个级别的保护)

var person = { name: "Nicholas" };
Object.seal(person);
person.age = 29;
alert(person.age); //undefined
delete person.name;     // 无效 , 严格模式下抛出错误
alert(person.name); //"Nicholas"

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

冻结的对象(第三个级别的保护)

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"

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

访客评论