不知不觉写到第10篇了。这篇写起来很忐忑,终于和高级搭上边了(呵呵),这篇咱们 主要 说一下 JS 方法的部分高级用法(我知道的),笔者水平有限,不免有错。废话很少少,进入正文。javascript
咱们在看一些别人写的优秀的代码,特别是组件时,咱们常常能发现有init或initializa这样的方法,它通常执行的都是初始化。那初始化通常都有几种呢,咱们来一一介绍:java
初始化对象,顾名思义,就是对带着一堆具体逻辑的对象进行初始化。直接上代码:编程
```javascript var Wr = { name: "WeiRan", age: 21, init: function() { // body ... console.log("初始化完成!"); } } Wr.init(); //初始化完成! ```
这种方法,有个弊端就是可能会污染全局做用域。这样有时候就能够采用另外一种方法来初始化:数组
```javascript ({ name: "WeiRan", age: 21, init: function() { // body ... console.log("初始化完成!"); } }).init(); //初始化完成! ```
这种方法的有点和上篇所说的即时函数的有点是相同的:能够在执行一次性的初始化任务时保护全局命名空间,若是初始化任务更加复杂,他会使整个初始化过程显得更有结构化。浏览器
值得注意的是,这种模式主要适用于一次性的任务,并且在init完毕之后也没有该对象的访问(若是想有,也能够有,在init方法底部加个return this便可)缓存
初始化分支也叫作加载时分支[load-time branching] ,是一种优化模式。当知道某些属性在整个程序生命周期中都不会发生变化时,该模式就显得很重要了。浏览器特性嗅探就是一个典型的例子,一般状况下在site上写JavaScript要考虑到兼容性,例如addEventListener在早期的IE上就是不支持的,在IE上支持的是attachEvent(更早的甚至是on+type),而若是咱们每次绑定事件的时候都要作个判断,这样会产生大量的冗[rǒng]余代码,可是初始化分支能够很好的解决这个问题。代码以下:markdown
```javascript //初始化分支 if(typeof window.addEventListener === "function"){ utils.addListener = function(el, type, fn){ el.addEventListener(type, fn, false); } utils.removeListener = function(el, type, fn){ el.removeEventListener(type, fn, false); } } // if IE else if(typeof document.attachEvent === "function"){ utils.addListener = function(el, type, fn){ el.attachEvent('on' + type, fn); } utils.removeListener = function(el, type, fn){ el.detachEvent('on' + type, fn); } } //long long ago Browser else{ utils.addListener = function(el, type, fn){ el['on' + type] = fn; } utils.removeListener = function(el, type, fn){ el['on' + type] = null; } } ```
在上一篇中,我写了一个叫作暴露接口的用法,在 @北川 的纠正下,我发现我写的不是很严谨,对于用法也写的太含糊,特地去详细了解了下 Module模式。在这里谢谢北川的指正。关于详细的Module模式,我会在后面专门写一篇。在回到咱们的正文,在模块化编程中(利用r.js 或者 seaJS),咱们常常要对某个模块进行初始化,而这时,这种模式就显得更有用了。代码以下:闭包
```javascript function Wr(n){ var name; return { init : function(n){ name = n; }, getName : function(){ console.log(name); } } } var wr1 = new Wr(); wr1.init("Weiran"); wr1.getName(); //WeiRan ```
上面init很简单,可是具体到工做中,可能初始化作的就不会这么简单,总之它很好的提升了模块的可扩展性。让咱们的代码结构更加清晰。app
对于一些操做很是很是复杂的函数,咱们不必每次都去真的执行一遍。举个例子,咱们在百度搜索一个关键词,百度并非真的去搜一遍,而是去看之前有木有查过,而后才返回结果(这就是有时候,百度的结果有点旧了)。在函数中也是同样,对于逻辑极度复杂,可是参数变化不大的函数(结果也不常常变化),咱们能够缓存计算结果。代码以下:模块化
```javascript var fun1 = function(param){ if(!fun1.cache[param]){ var result = {}; // 复杂的逻辑 ... fun1.cache[param] = result; //把计算结果缓存 } return fun1.cache[param]; } ```
上面的代码假定该函数只须要一个参数,若是有更多及更复杂的参数,一般的作法是将参数序列化。代码以下:
```javascript var func2 = function(){ //JSON 序列化 var cacheKey = JSON.stringify(Array.prototype.slice.call(arguments)), result; if(!func2.cache[cacheKey]){ result = {}; // 复杂的逻辑 func2.cache[cacheKey] = result; } return func2.cache[cacheKey]; }; func2.cache = {}; ```
值得注意的是,在JSON序列化的过程当中,由于本质上都是被序列化成了字符串,全部对象也被序列化成了字符串,对于两个不一样对象但具备相同的属性,这两个对象就会被当成同一个,共享一条缓存条目。
柯里化(curry),貌似很高端的样子,那他是什么呢,咱们先假设有一个需求(这里我借用腾讯的案例。):
若是使用curry怎么实现,废话很少说,直接上代码:
```javascript var curry = function(fn){ var args = []; return function(){ if(arguments.length === 0){ return fn.apply(this,args); } args.push.apply(args, arguments); return fn; } } var exc = curry(function(){ var nums = [].slice.call(arguments), result = 0; nums.forEach(function(val){ result += val; }); return result; }); exc(500); exc(1000); console.log(exc()); //1500 ```
初看上去仍是有点绕的,咱们切开来看,咱们传给curry一个匿名函数,curry返回一个通过处理的函数。而重点来了,咱们来看curry是怎么处理的,首先是个判断,判断这个函数接受的参数是不是空[arguments.length === 0] 若是不为空,把当前的参数push到args里面去,这里要注意的是curry 返回的是一个方法,因此就造成了一个闭包,而闭包的一个特性就是能够访问返回他的函数的变量和方法,故这个方法能够改变args的值(把当前参数push到返回他的函数的args里去),若是为空的话,他才会执行fn并把参数数组经过apply的形式传给fn。
这种方式的最大好处就是能够延迟到最后一刻才一块儿计算, 在不少场合能够避免无谓的计算, 节省性能, 也是实现惰性求值的一种方案.
若是以上还不够明确,咱们在来看一个例子:
```javascript //Curry var curry = function(fn){ var slice = Array.prototype.slice, storedArgs = slice.call(arguments, 1); return function(){ var newArgs = slice.call(arguments); var args = storedArgs.concat(newArgs); return fn.apply(null, args); }; } var result = curry(function(x, y){ return x + y; }, 5)(10); console.log(result); //15 ```
上面的代码,须要注意的是 storedArgs = slice.call(arguments, 1) 这段代码的意思是把fn以外的参数存储起来(咱们第一次传个curry的那个5),而后咱们调用这个通过curry处理事后的函数时,他会把咱们新传的参数和原来咱们传的那个5结合组成新的参数数组进行处理[var args = storedArgs.concat(newArgs)],具体处理的代码是fn.apply(null, args)。
当咱们发现正在调用一个函数,而且传递的大部分参数都是相同的,那么该函数使用curry化是一个很好的方案。能够经过将一个curry化,从而动态产生一个新的函数,这个新的函数会保存重复的参数(所以没必要每次都传递这些参数),而且还会使用预填充原始函数所指望的完整参数列表。
原本准备也把反柯里化写了,奈何最后发现,对于这东西,本身还不知道怎么把它完整的表诉清楚,可能仍是本身没彻底吃透吧,因此也暂时不写了,JS的路还很长啊。
若是你在文中发现错误,欢迎指正。