zoukankan      html  css  js  c++  java
  • 《JavaScript模式》第4章 函数

    @by Ruth92(转载请注明出处)

    第4章:函数

    一、JavaScript 中函数的两个重要特征

    1. 函数是第一类对象,可以作为带有属性和方法的值以及参数进行传递;
    2. 函数提供了局部作用域,而其他大括号并不能提供这种局部作用域。此外,声明的局部变量可被提升到局部作用域的顶部。

    二、创建函数的语法包括:

    1. 命名函数表达式(函数表达式的一种特殊情况);

       var add = function add(a,b) {
       	return a + b;
       };	// <—— 分号结尾
      
    2. 函数表达式,即匿名函数;

       // 该函数对象的 name 属性将会成为一个空字符串
       var add = function (a,b) {
       	return a + b;
       };	// <—— 分号结尾
      
    3. 函数声明,与其他语言中的函数的语法类似。

       function foo() { 
       	// 函数主体 
       }
      

    注意,函数字面量 是指函数表达式或命名函数表达式,但不建议使用。

    差别:

    1)语法差异:

    • 函数声明不需要分号结尾;
    • 函数表达式需要。

    2)用法差异:

    • 函数表达式应用场景:将函数对象作为参数传递,或者在对象字面量中定义方法。

        // 1. 作为参数传递
        callMe(function () {
          // 函数表达式(匿名函数)
        })
        
        callMe(function me() {
          // 命名函数表达式
        })
        
        // 2. 在对象字面量中定义方法
        var myObject = {
          say: function() {
            // 函数表达式
          }
        }
      
    • 函数声明只能出现在“程序代码”中,即仅能在其他函数体内部或全局空间中。其定义不能分配给变量或属性,也不能以参数形式出现在函数调用中。

        // 1.全局作用域
        function foo() {
          // 2.局部作用域
          function bar() {}
          return bar;
        }
      

    3)函数的提升:

    • 对于所有变量,无论在函数体的何处进行声明,都会在后台被提升到函数顶部。

    • 当使用函数声明时,函数定义和函数声明都被提升。

        function hoistMe() {
          console.log(typeof foo); // 'function'
          console.log(typeof bar);  // 'undefined'
        
          foo();  // 'local foo'
          bar();  // 'TypeError: bar is not a function'
        
          /*
           * 函数声明
           * 变量 'foo' 及其实现都被提升
           */
          function foo() {
            console.log('local foo');
          }
        
          /*
           * 函数表达式
           * 仅变量 'bar' 被提升,函数实现并未被提升
           */
          var bar = function() {
            console.log('local bar');
          }
        }
      

    三、函数的模式:

    1. API 模式——>可以为函数提供更好且更整洁的接口。

    • 回调模式:将函数作为参数进行传递;

    注意点:回调与作用域,当回调作为一个对象的方法时,调用回调 this 会发生变化

    使用场景:异步事件监听器、超时方法、库中的回调模式(有助于扩展及自定义库方法)。

    /**
     * 1.回调模式
     * 功能逻辑解耦
     */
    var findNodes() = function(callback) {
      var i = 10000,
          nodes = [],
          found;
    
      // 检查回调函数是否为可调用的
      if (typeof callback !== 'function') {
        callback = false;
      }
    
      while (i) {
        i -= 1;
    
        // 运行回调函数
        if (callback) {
          callback(found);
        }
    
        nodes.push(found);
      }
    
      return nodes;  
    }
    
    /**
     * 解决回调中存在的作用域问题——>当回调作为对象的方法时
     * 解决方法:绑定作用域,传递该回调函数所属的对象
     */
    var findNodes = function(callback, callback_obj) {
      //...
      if (typeof callback === 'function') {
        callback.call(callback_obj, found);
      }
    }
    
    //使用=>
    findNodes(myapp.paint, myapp);
    
    /**
     * 更好的方案:无需重复两次输入对象的名称
     */
    var findNodes = function(callback, callback_obj) {
      if (typeof callback === 'string') {
        callback = callback_obj[callback];
      }
    
      //...
      if (typeof callback === 'function') {
        callback.call(callback_obj, found);
      }
      //...
    }
    
    //使用=>
    findNodes('paint', myapp);
    
    
    /**
     * 应用场景:
     */
    // 异步事件监听器
    document.addEventListener('click', console.log, false); 
    
    // 超时
    var thePlotThickens = function() {};
    setTimeout(thePlotThickens, 500); //<=回调不带括号,带括号表示立即执行
    
    • 配置对象:有助于保持收到控制的函数的参数数量;

    缺点:需要记住参数名称;属性名称无法被压缩

    应用:当函数创建DOM元素时,该模式非常有用,如,可用于设置元素的CSS样式。

    /**
     * 配置对象
     */
    var conf = {
      username: 'batman',
      first: 'Bruce',
      last: 'Wayne'
    };
    
    addPerson(conf);
    
    • 返回函数:当一个函数的返回值是另一个函数时;

    闭包

    /**
     * 闭包
     */
    var setup = function() {
      var count = 0;
      return function() {
        return (count += 1);
      }
    }
    
    var next = setup();
    next(); // 1
    next(); // 2
    
    • Curry 化:是一个转化过程,即我们执行函数转换的过程。当新函数是基于现有函数,并加上部分参数列表创建时。

    函数应用:在一些纯粹的函数式编程语言中,函数并不描述为被调用(called 或 invoked),而是描述为应用(applied)。

    函数调用:实际上就是将一个参数集合应用到一个函数中。

    Curry过程:使函数理解并处理部分应用的过程。

    // 定义函数
    var sayHi = function(who) {
        return "Hello" + (who ? "," + who : "") + "!";
    };
    
    // 调用函数
    sayHi();    //'Hello'
    sayHi('world'); // 'Hello, world'
    
    // 应用函数1
    sayHi.apply(null, ["hello"]);   // 'Hello, hello!'
    
    // 应用函数2:使用 call() 方法比 apply() 更有效率,节省了一个数组
    sayHi.call(null, 'hello');  // 'Hello, hello!'
    
    /**
     * curry化的add()函数
     * 接受部分参数列表
     */
    // x 隐式地存储在闭包中,并且还将 y 作为局部变量复用
    function add(x,y) {
      if (typeof y === 'undefined') {  // 部分
        return function(y) {
          return x + y;
        }
      }
      // 完全应用
      return x + y;
    }
    
    typeof add(5);  // 'function'
    add(3)(4);  // 7
    // 创建并存储一个新函数
    var add2000 = add(2000);
    add2000(10);  // '2010'
    
    
    /**
     * 通用curry化函数示例
     */
    function schonofinkelize(fn) {
      var slice = Array.prototype.slice,
          stored_args = slice.call(arguments, 1);
      return function() {
        var new_args = slice.call(arguments),
            args = stored_args.concat(new_args);
        return fn.apply(null, args);
      }
    }
    
    // 普通函数
    function add(x, y) {
      return x + y;
    }
    
    // 将一个函数curry化以获得一个新的函数
    var newadd = schonofinkelize(add, 5);
    newadd(4);  // 9
    
    //另一种选择——直接调用新函数
    schonofinkelize(add, 6)(7);
    

    2. 初始化模式——>可以在不污染全局命名空间的情况下,使用临时变量以一种更加整洁、结构化的方式初始化以及设置任务。

    • 即时函数:只要定义之后就立即执行;

        /**
         * 即时函数
         */
        // 写法1——>JSLint偏好该写法
        (function() {
          //...
        }());
        
        // 写法2
        (function() {
        
        })();
        
        
        /**
         * 即时函数传参
         */
        (function(global) {
          // 通过 'global' 访问全局变量
        }(this));
        
        
        /**
         * 即时函数的返回值
         */
        // 写法1——>推荐
        var result = (function(){
          return 2 + 2;
        }());
        
        // 写法2
        var result = function() {
          return 2 + 2;
        }();
        
        // 写法3
        var result = (function(){
          return 2 + 2;
        })();
      
    • 即时对象初始化:匿名对象组织了初始化任务,提供了可被立即调用的方法;

    缺点:不利于压缩

    /**
     * 即时对象初始化
     */
    ({
      // 在这里可以定义设定值
      // 又名配置常数
      max 600,
      maxheight: 400,
    
      // 还可以定义一些实用的方法
      gimmeMax: function() {
        return this.maxwidth + 'x' + this.maxheight;
      },
    
      // 初始化
      init: function() {
        console.log(this.gimmeMax());
        // 更多初始化任务
      }
    }).init();
    
    // 语法
    ({...}).init();
    ({...}.init());
    
    // 返回对对象的引用:在init()函数尾部添加 "return this;"
    
    • 初始化时分支(加载时分支):是一种优化模式,帮助分支代码在初始化代码执行过程中仅检测一次,这与以后在程序生命期内多次检测相反。

        /**
         * 初始化时分支
         */
        // 之前
        var utils = {
          addListener: function(el, type, fn) {
            if (typeof window.addEventListener === 'function') {
              el.addEventListener(type, fn, false);
            } else if (typeof document.attachEvent === 'function') {
              el.attachEvent('on' + type, fn);
            } else {
              el['on' + type] = fn;
            }
          },
          removeListener: function(el, type, fn) {
            // 几乎一样...
          }
        };
        
        
        // 之后:可以在脚本初始化加载时一次性探测出浏览器特征
        
        // 接口
        var utils = {
          addListener: null,
          removeListener: null
        }
        // 实现
        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);
          };
        
          // 判断为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);
          }
        
          // 更早版本浏览器
        } else {
          utils.addListener = function(el, type, fn) {
            el['on' + type] = fn;
          };
          utils.removeListener = function(el, type, fn) {
            el['on' + type] = null;
          }
        }
      

    3. 性能模式——>可以加速代码运行

    • 备忘模式:使用函数属性以便使得计算过的值无须再次计算;

    默认:length 属性,包含了该函数期望的参数数量

    自定义属性:缓存函数结果(备忘)

    /**
     * 函数属性
     */
    var myFunc = functino(param) {
      if (!myFunc.cache[param]) {
        var result = {};
        //... 开销很大的操作 ...
        myFunc.cache[param] = result;
      }
    
      return myFunc.cache[param];
    };
    
    // 缓存存储
    myFunc.cache = {};
    
    
    /**
     * 优化点
     */
    var myFunc = functino() {
        
        // 1.参数优化:如果有更多及复杂的参数,可以将它们序列化为一个JSON字符串
        var cachekey = JSON.stringify(Array.prototype.slice.call(arguments)),
            result;
    
        // 2.使用 arguments.callee 来引用该函数,在ES5的严格模式中不支持
        var f = arguments.callee;
    
        if (!f.cache[cachekey]) {
            var result = {};
            //... 开销很大的操作 ...
            f.cache[cachekey] = result;
        }
    
        return f.cache[cachekey];
    };
    
    // 缓存存储
    myFunc.cache = {};
    
    • 自定义模式:以新的主体重写本身,以使得在第二次或以后调用时仅需执行更少的工作;

    缺点:

    当它重定义自身时已经添加到原始函数的任何属性都会丢失;

    此外,如果函数使用了不同的名称,比如分配给不同的变量或者以对象的方法来使用,那么重定义部分将永远不会发生,并且将会执行原始函数体

    /**
     * 自定义函数(惰性函数自定义):可以更新自身的实现
     * 当有一些初始化准备工作要做,并且仅需要执行一次,该模式就非常有用
     */
    var scareMe = function() {
      console.log('Boo!');
      scareMe = function() {
        console.log('Double boo!');
      };
    };
    
    scareMe();  // 'Boo!'
    scareMe();  // 'Double boo!'
    
    /**
     * 自定义函数的缺点:
     * 1)当它重定义自身时已经添加到原始函数的任何属性都会丢失;
     * 2)如果函数使用了不同的名称,比如分配给不同的变量或者以对象的方法来使用,那么重定义部分将永远不会发生,并且将会执行原始函数体
     */
    // 1.添加一个新的属性
    scareMe.property = "properly";
    
    // 2.赋值给另一个不同名称的变量
    var prank = scareMe;
    
    // 3.作为一个方法使用
    var spooky = {
      boo: scareMe
    };
    
    prank();  // 'Boo!'
    prank();  // 'Boo!'
    console.log(prank.property);  // 'properly'
    
    spooky.boo(); // 'Boo!'
    spooky.boo(); // 'Boo!'
    console.log(spooky.boo.property);  // 'properly'
    
    scareMe();  // 'Double boo!'
    scareMe();  // 'Double boo!'
    console.log(scareMe.property);  // 'undefined'
    

  • 相关阅读:
    Django使用redis
    Django中static media的简单配置
    套接字,TCP,UDP
    nginx常用配置
    使用systemctl管理nginx
    jumpserver 安装
    elasticsearch7.x集群安装(含head、bigdesk、kibana插件)
    codepush安装
    mysql优化后的主配置文件
    nginx优化、负载均衡、rewrite
  • 原文地址:https://www.cnblogs.com/Ruth92/p/5928858.html
Copyright © 2011-2022 走看看