JS高级技巧学习

高级技巧

因为全部的函数都是对象,因此使用函数的指针很是简单javascript

安全类型检测

  1. typeof 操做符有一些没法预知的行为,检测数据类型时有时候会获得不靠谱的结果
  2. instanceof 操做符在存在多个全局做用域的状况下,也会有问题

场景:假设一个页面有多个 iframejava

array 是 window 的属性数组

/* value是一个数组的状况下,且必须与Array在同一个全局做用域下才会返回true 若是value是在另外一个ifream下定义的数组,那么返回false */

var isArray = value instanceof Array;
复制代码
  1. 检测原生 JSON 对象

任何值上面调用 object 的 toString()方法都会返回一个[object NativeConstructorName]格式的字符串,每一个类在内部都有一个[[class]]属性,指定了上述字符串中构造函数名浏览器

alert(Object.prototype.toString.call(value)); // "[object 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 isRegExp(value) {
  return Object.prototype.toString.call(value) == "[object RegExp]";
}
复制代码

这一技巧也被被普遍的应用于检测原生 JSON 对象,Object 的 toString 方法不能检测非原生构造函数的构造函数名,所以开发人员定义的任何的构造函数都将返回 [obje0ct object]安全

var isNativeJSON=window.JSON && Object.prototype.toString.call(JSON)==“[Onject JSON]”
复制代码

做用域安全的构造函数

当没有使用 new 操做符来建立的时候,this 对象是在运行时候绑定的,直接调用的话,this 会映射到 window 全局对象上构造函数当作普通函数去调用,这个问题是 this 晚绑定产生的,这里的 this 解析成了 window 对象bash

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}
var p = Person("cc", 18, "coder");
console.log(window.name);
console.log(window.age);
console.log(window.job);
复制代码

如下方式不管 Person 是否使用 new 操做符调用,都会返回一个新的实例对象,这就避免了在全局对象上意外的设置属性闭包

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);
  }
}
复制代码

使用这个模式能够锁定构造函数的做用域。若是使用了构造函数窃取模式的继承且不使用原型链,那么这个继承有可能会被破坏app

//多边形类 ,此构造函数的做用域是安全的
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.width = width;
  this.height = height;
  this.getArea = function() {
    return this.width * this.height;
  };
}

var rect = new Rectangle(5, 10);
console.log(rect.sides); //undefined
console.log(rect.getArea()); //50
console.log(rect); //Rectangle {width: 5, height: 10, getArea: ƒ}
复制代码

解决办法是 结合使用原型链或者寄生组合

//多边形类
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);
  //原型链,rect是Rectangle实例,也是Polygon的实例,call执行,sides被添加
  this.width = width;
  this.height = height;
  this.getArea = function() {
    return this.width * this.height;
  };
}
Rectangle.prototype = new Polygon();

var rect = new Rectangle(5, 10);
console.log(rect.sides); //undefined
console.log(rect.getArea()); //50
console.log(rect);
复制代码

惰性载入函数

产生背景:大多数浏览器之间的行为差别,致使多数 js 代码包含了大量的 if 语句,将执行引导到正确的代码中,因此若是 if 没必要每次执行,那么代码能够运行的更快一些。ide

function createXHR() {
  if (typeof XMLHttpRequest != "undefined") {
    //...
    return new XMLHttpRequest();
  } else if (typeof ActiveXObject != "undefined") {
    //...
    return new ActiveXObject(arguments.callee.activeXString);
  } else {
    throw new Error("error message");
  }
}
复制代码

解决方案就称为惰性载入的技巧: 表示函数执行的分支只会发生一次函数

这两种方式都能避免执行没必要要的代码,惰性载入函数的优势只执行一次 if 分支,避免了函数每次执行时候都要执行 if 分支和没必要要的代码,所以提高了代码性能,至于那种方式更合适,就要看您的需求而定了。

  1. 在函数被调用时再处理函数
function createXHR() {
  if (typeof XMLHttpRequest != "undefined") {
    createXHR = function() {
      //...
      return new XMLHttpRequest();
    };
  } else if (typeof ActiveXObject != "undefined") {
    createXHR = function() {
      //...
      return new ActiveXObject(arguments.callee.activeXString);
    };
  } else {
    createXHR = function() {
      throw new Error("error message");
    };
  }
  return createXHR();
}
复制代码
  1. 在声明函数时就指定适当的函数 这样第一次调用函数时就不会损失性能,而在代码首次加载的时候会损失一点性能,具体使用可根据本身的具体需求而定
var createXHR = (function() {
  if (typeof XMLHttpRequest != "undefined") {
    return function() {
      //...
      return new XMLHttpRequest();
    };
  } else if (typeof ActiveXObject != "undefined") {
    return function() {
      //...
      return new ActiveXObject(arguments.callee.activeXString);
    };
  } else {
    throw new Error("error message");
  }
})();
复制代码

函数绑定

该技巧经常和回调函数和事件处理程序一块儿使用,以便将函数做为变量传递的同时保留代码执行环境

//事件兼容封装
var EventUtil = {
  addHandler: function(element, type, handler) {
    if (element.addEventListener) {
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
      element.attachEvent("on" + type, handler);
    } else {
      element["on" + type] = handler;
    }
  },
  //获取事件对象
  getEvent: function(event) {
    return event ? event : window.event;
  },
  //获取目标元素
  getTarget: function(event) {
    return event.target || event.srcElement;
  },
  //阻止事件默认行为
  preventDefault: function(event) {
    if (event.preventDefault) {
      event.preventDefault();
    } else {
      event.returnValue = false;
    }
  },
  //解除监听
  removeHandler: function(element, type, handler) {
    if (element.removeEventListener) {
      element.removeEventListener(type, handler, false);
    } else if (element.detachEvent) {
      element.detachEvent("on" + type, handler);
    } else {
      element["on" + type] = null;
    }
  },
  //阻止冒泡
  stopPropagation: function(event) {
    if (event.stopPropagation) {
      event.stopPropagation;
    } else {
      event.cancelBubble = true;
    }
  }
};
复制代码

如下时间处理程序,在点击 button 后会弹出 undefined,这个问题在于没有保存 handler.handleClcik()的环境,因此 this 指向了 DOM 按钮而非 handler,咱们能够采用闭包来修正这个问题

var handler = {
  message: "event handler",
  handleClick: function(event) {
    alert(this.message);
  }
};

var btn = document.getElementById("btn");
EventUtil.addHandler(btn, "click", handler.handleClick);

//闭包修正
EventUtil.addHandler(btn, "click", function(event) {
  handler.handleClick();
});
复制代码

可是大多数使用可能致使代码的难以调试和理解。因此下面咱们使用 bind 解决,大多数的 js 库实现了一个能够将函数绑定到指定环境的函数,通常叫作 bind()

// 自定义bind函数接受一个函数和一个环境
function bind(fn, context) {
  return function() {
    return fn.apply(context, arguments);
  };
}

var handler = {
  message: "event handler",
  handleClick: function(event) {
    alert(this.message);
  }
};

var btn = document.getElementById("btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));
复制代码

ES5 为全部函数提供了一个原生 bind 方法进一步简化了操做,可是被绑定函数比普通函数相比有更多的开销,须要更多的内存

var handler = {
  message: "event handler",
  handleClick: function(event) {
    alert(this.message);
  }
};

var btn = document.getElementById("btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));
复制代码

函数柯里化

用于建立已设置好了一个或多个参数的函数,函数柯里化的基本方法和函数绑定是同样的:使用一个闭包返回一个函数

如下 curriedAdd 函数虽然并非柯里化的函数,可是很好的展示了其概念

function add(x, y) {
  return x + y;
}
function curriedAdd(y) {
  return add(5, y);
}
console.log(add(1, 2)); //3
console.log(curriedAdd(5)); //10
复制代码

柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数并且返回结果的新函数的技术。

咱们改造下,实际上就是把 add 函数的 x,y 两个参数变成了先用一个函数接收 x 而后返回一个函数去处理 y 参数。如今思路应该就比较清晰了,就是只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

// 普通的add函数
function add(x, y) {
  return x + y;
}

// Currying后
function curriedAdd(x) {
  return function(y) {
    return x + y;
  };
}

add(1, 2); // 3
curriedAdd(1)(2); // 3
复制代码

柯里化函数一般的动态建立方式

/* arguments是一个关键字,表明当前参数,在javascript中虽然arguments表面上以数组形式来表示,但实际上没有原生数组slice的功能,这里使用call方法算是对arguments对象不完整数组功能的修正。 slice返回一个数组,该方法只有一个参数的状况下表示除去数组内的第一个元素。就本上下文而言,原数组的第一个参数是“事件名称”,具体像“click”,"render"般的字符串,其后的元素才是处理函数所接纳的参数列表。 */
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);
  };
}
function add(x, y) {
  return x + y;
}
var curryAdd = curry(add, 5);
console.log(curryAdd(1)); //6

var curryAdd2 = curry(add, 1, 5); //两个参数都提供了,就无需在传递了
console.log(curryAdd2()); //6

// ------支持多参数传递---------
function progressCurrying(fn, args) {
  var _this = this;
  var len = fn.length;
  var args = args || [];

  return function() {
    var _args = Array.prototype.slice.call(arguments);
    Array.prototype.push.apply(args, _args);

    // 若是参数个数小于最初的fn.length,则递归调用,继续收集参数
    if (_args.length < len) {
      return progressCurrying.call(_this, fn, _args);
    }

    // 参数收集完毕,则执行fn
    return fn.apply(this, _args);
  };
}
复制代码

用于函数绑定的一部分构造更复杂的 bind 函数

function bind(fn, context) {
  var args = Array.prototype.slice.call(arguments, 2);
  return function() {
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(innerArgs);
    return fn.apply(context, finalArgs);
  };
}
复制代码

柯里化好处:

  1. 参数复用
//将第一个参数reg进行复用,这样别的地方就可以直接调用hasNumber,hasLetter等函数,让参数可以复用,调用起来也更方便
// 正常正则验证字符串 reg.test(txt)

// 函数封装后
function check(reg, txt) {
  return reg.test(txt);
}

check(/\d+/g, "test"); //false
check(/[a-z]+/g, "test"); //true

// Currying后
function curryingCheck(reg) {
  return function(txt) {
    return reg.test(txt);
  };
}

var hasNumber = curryingCheck(/\d+/g);
var hasLetter = curryingCheck(/[a-z]+/g);

hasNumber("test1"); // true
hasNumber("testtest"); // false
hasLetter("21212"); // false
复制代码

缺点:

存取arguments对象一般要比存取命名参数要慢一点
一些老版本的浏览器在arguments.length的实现上是至关慢的
使用fn.apply( … ) 和 fn.call( … )一般比直接调用fn( … ) 稍微慢点
建立大量嵌套做用域和闭包函数会带来花销,不管是在内存仍是速度上
复制代码

防篡改对象

开发人员可能意外修改别人的代码,因此 ES5 提供了防篡改对象定义, 一旦把对象定义为防篡改。就没法撤销了

var person = { name: "cc" };

object.preventExtensions(person);
person.name = "bb";
console.log(person.name);
复制代码

未完持续中......

相关文章
相关标签/搜索