zoukankan      html  css  js  c++  java
  • 初涉JavaScript模式 (10) : 函数 【进阶用法】

    写在前面

    不知不觉写到第10篇了。这篇写起来很忐忑,终于和高级搭上边了(呵呵),这篇我们 主要 说一下 JS 方法的部分高级用法(我知道的),笔者水平有限,难免有错。废话不多少,进入正文。

    初始化

    我们在看一些别人写的优秀的代码,特别是组件时,我们经常能发现有init或initializa这样的方法,它一般执行的都是初始化。那初始化一般都有几种呢,我们来一一介绍:

    初始化对象

    初始化对象,顾名思义,就是对带着一堆具体逻辑的对象进行初始化。直接上代码:

    ```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]余代码,但是初始化分支可以很好的解决这个问题。代码如下:

    ```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很简单,但是具体到工作中,可能初始化做的就不会这么简单,总之它很好的提高了模块的可扩展性。让我们的代码结构更加清晰。

    缓存函数结果

    对于一些操作非常非常复杂的函数,我们没必要每次都去真的执行一遍。举个例子,我们在百度搜索一个关键词,百度并不是真的去搜一遍,而是去看以前有木有查过,然后才返回结果(这就是有时候,百度的结果有点旧了)。在函数中也是一样,对于逻辑极度复杂,但是参数变化不大的函数(结果也不经常变化),我们可以缓存计算结果。代码如下:

    ```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的路还很长啊。

    如果你在文中发现错误,欢迎指正。

    作者:未然丶
  • 相关阅读:
    毕设(五)ListView
    毕设(四)ListBox
    毕设(三)NotifyIcon
    hdu 1.2.3
    ZOJ 1789 The Suspects
    ZOJ 2833 Friendship
    git
    yum wget rpm
    上传绕过
    LAMP 和 LNMP
  • 原文地址:https://www.cnblogs.com/ahjx777/p/3533619.html
Copyright © 2011-2022 走看看