Skip to content

Latest commit

 

History

History
258 lines (196 loc) · 8.89 KB

File metadata and controls

258 lines (196 loc) · 8.89 KB
layout title
default
{"site.name" => nil}

22高级技巧

高级函数

安全的类型检测

typeof并不能够满足,有时对正则typeof也会返回function。

instanceof在页面上存在多个全局作用域的时候也是问题多多,比如多个frame,他们就不行了。

JSON对象也有这个问题。

解决方法很单一:

Object.prototype.toString.call(obj)就行了。

  • 数组的就是[object array]
  • 函数的就是[object function]
  • 正则表达式就是[object RegExp]

如果不是原生的话,任何构造函数都会是[object Object]

作用域安全的构造函数

这个很有意思

function Person(name,job){
  this.name = name;
  this.job = job;
}

这个方法new的话不会有问题,但是如果直接调用的话,里面的this就会变成window,所以安全的写法应该是进行一次判定。

function Person(name,job){
  if(this instanceof Person){
    this.name = name;
    this.job = job;
  }else{
    return new Person(name,job);
  }
}

这样子的话在通过构造函数窃取时会出现问题

function Perple(d){
  Person.call(this,2,3);
  this.d = d;
}

这里在new Perple的话,因为了一层判断,就会返回一个new Person,实际上没用..

所以解决方法是加上Perple.prototype = new Person();

这样instanceof就可以进行判断了。赞~~

惰性载入函数

惰性载入表示函数执行的分支仅仅会发生一次,比如一些创建xhr对象,每次都会进行if的一些判断,看是否支持,每次创建的时候都会进行判断,实际上没有必要

两种写法

  createXHR = function(){
    if (){
      createXHR = function(){}
    }else{
      createXHR = function(){}
    }
    return createXHR();
  }

这种写法是在第一次调用的时候就进行替换,最后执行一下,下次的话,就会直接执行新函数了

  createXHR = (function(){
    if(){
      return function(){}
    }else {
      return function(){}
    }
  })();

这种写法就是在申明的时候就进行一次判断,这样以后的执行都不再需要判断了。

函数绑定

就像是settimeout的函数一般会用一个匿名函数调用一样,因为这里是有函数绑定的问题的。

function User(){
  this.x = '1';
  this.y = function(){
    console.log(this.x);
  }
}
var user = new User();
setTimeout(user.y,1000);
setTimeout(function(){
  user.y();
},1000);

这里的问题就是没有保存user.y的环境,所以this对象的指向会错。我们可以使用一个闭包来修正这个问题。所以才会有bind方法这种东西存在。

Function.prototype.bind = function(obj){
  var fn = this;
  return function(){
    fn.apply(obj,arguments);    
  }
}

这样子就是最简单的bind方法了,注意apply接收的参数只要是arrayLike的就好了,所以我们可以直接将arguments传给他。之所以polyfill使用了一对concat,slice之类的是因为要支持bind传参的功能,就像下面这样。

Function.prototype.bind = function(obj){
  var fn = this;
  var oldArgs = [].slice.call(arguments,1);
  return function(){
    var newArgs = oldArgs.concat([].slice.call(arguments));
    fn.apply(obj,newArgs);    
  }
}

但是还要支持new方法呢,就得在其中插入一层原型链,如下:

Function.prototype.bind = function(obj){
  var fn = this;
  var oldArgs = [].slice.call(arguments,1);
  function Temp(){}
  Temp.prototype = fn.prototype;
  var newFn = function(){
    var newArgs = oldArgs.concat([].slice.call(arguments));
    fn.apply(this instanceof Temp ? this : obj,newArgs);    
  }
  newFn.prototype = new Temp();
  return newFn;
}

注意绑定过的已经无法再次bind了。因为绑定后得到的已经不是原函数了,而是一个处理原来方法的闭包而已。

函数柯里化

柯里化一般就是传入要柯里化的函数和必要参数,然后返回一个新的闭包函数来处理下参数。等于就是分次给函数添加参数,最后执行的过程。

调用之后通过传给函数部分参数来得到一个新函数,用来处理接下来的参数,有种预加载的用处。

上面的bind其实我已经做过了柯里化的过程了。

作用:

  • 预加载,动态创建新函数,比如get,post这两种方法的实现都是通过柯里化的形式传一个type,然后动态生成的新函数,因为这两个函数的差异很小。
  • 延迟执行,累计传入的参数,最后执行。
  • 通过一个通用的函数来创建出更具体的功能
  • 那种浏览器能力检测返回新函数的惰性载入函数的机制也是一种柯里化的过程

其实柯里化是函数的部分执行,这个其实应该是个静态的方法,与一切外部无关的东西,上面的bind的柯里化才是真实的柯里化。

防篡改对象

目前我们可以通过设置属性的configurable,writable,enumerable,value,get,set来改变属性的行为。我们还可以通过ES5的一些方法来让对象无法篡改,但是一旦设置了,就无法撤销了。

不可扩展

Object.preventExtensions可以将对象设置为不可扩展。这个方法IE9才支持,opera压根就没有。用Object.isExtensible来检测。

设置了之后,就无法再改回去了。注意只是新属性不能被添加。原有属性想怎么整怎么整。

密封的对象

Object.seal方法可以把对象密封,就是无法扩张的情况下也无法删除。但是原有属性的值是可以修改的。

Object.isSealed方法可以确定对象是否被密封了。注意这个对象也是不可扩展的。

冻结的对象

最严格的级别是冻结对象,使用Object.freeze可以冻结。

Object.isFrozen用于检测

tudo://608页的访问器属性仍然是可写的。

高级定时器

就是说定时器并不能保证到了时间立即执行,只能说是时间到了,就会添加进队列中

重复的定时器

setInterval本身是有些错误的,因为有可能还没执行好,就又被添加进执行序列了,所以一般情况下使用setTimeout来模拟。避免连续运行。

yield processing

js的能力其实是被浏览器限制的,防止恶意程序猿来让计算机崩溃。所以在一些过分超长的行为发生的时候,我们需要用数据分块的技术。来将数据一小块一小块的进行处理。

就是说我们在有些时候可以通过设置定时器的事情来将一件事情分成很多次的执行,这样子避免了因为js的同步执行问题导致的系统的阻塞,实现如下:

function chunk (array, process, context){
  setTimeout(function(){
    var item  = array.shift();
    process.call(context, item);
    if(array.length > 0) {
      setTimeout(arguments.callee,100);
    }
  },100)
}

函数节流

就是说我们在一些会被不断调用的方法执行时,必须进行调用的处理。例如resize方法被调动的次数会相当频繁,如果涉及到了DOM操作的话,对于浏览器的压力其实非常大的。所以我们可以使用一种节流的方式来做。比如如下:

function debounce(fn,context){
  clearTimeout(fn.tId);
  fn.tId  = setTimeout(function(){
    fn.call(context);
  },100)
}

这样子就可以在持续触发的时候只执行最后一次的。

还有throttle的写法,都已经深入研究过了,参见我的另一篇文章

自定义事件

自定义事件其实是一种观察者模式。是用来解耦的一个东西。

本来觉得事件系统是个好神秘的东西,那次勐喆一说。原来就是继承了一层原型,然后on其实就是绑定了一个特别的属性而已,emit就是属性的调用。

function MyEvent(){
  this.handlers = [];
}
MyEvent.prototype={
  addHandler:function(name,handler){
    this.handlers[name] = handler;
  },
  fire:function(name){
    this.handlers[name]();
  }
}

这个其实就是个最简单的事件了,但是我们想要让一个东西继承这个事件,不能直接A.prototype = new MyEvent()的。因为这样的话,原型的属性handlers是会被相互影响的。我觉得可以使用借用构造函数。我来看下别人是怎么写的。

_.extend(Something, MyEvent.prototype);

别人用的underscore的extends,好吧,等于就是把prototype上的东西复制了过来,因为使用的Events的prototype里面的每个方法都检测了是否拥有handlers,所以没有出问题。我们想使用的话只能用借用构造函数和原型继承一起的形式。这样才能保证原型上的属性被单独地继承了。

拖放

这里还稍微解释了拖放的原理,就是鼠标事件的时候重新设置下元素的定位,通过event.cilentX和event.cilentY来进行定位。 tudo:EventUtil是个什么东西?