js高级程序设计-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 是个什么东西?


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 981909093@qq.com

文章标题:js高级程序设计-22高级技巧

文章字数:2.2k

本文作者:泽鹿

发布时间:2019-08-28, 16:45:23

最后更新:2019-08-28, 16:45:23

原始链接:http://panyifei.github.io/2019/08/28/读书笔记/Javascript高级程序设计/22高级技巧/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏