《JavaScript 语言精粹》函数篇

137 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

《JavaScript 语言精粹》 第四章函数读完后的一些笔记摘录

本章主要讲解了函数得相关属性,函数的创建、调用、参数、返回值、作用域、闭包、柯里化、递归、回调、模块化、缓存、类型的扩充(原型上加属性)、异常处理(try catch)、级联(链式调用)

函数

  • 函数也是对象,也有一个原型对象 Function.prototype,该原型对象的原型对象指向的是 Object.prototype
  • 这个原型的对象上还有一个constructor属性,该属性值就是该函数本身
  • 函数创建时会有两个隐藏属性:函数的上下文和实现函数的行为代码
  • 函数可以被保存在变量、对象、数组中
  • 函数拥有方法
  • 函数可以变成参数,传递给其他函数,还可以作为函数的返回值返回
  • 函数在执行时候会有两个附带的参数: thisarguments

函数字面量

  • 创建函数关键字 function
  • 函数名称,可用于递归,function name
  • 函数参数,参数之间用逗号分隔, function name (a,b,c)
  • 函数主体用一对花括号包围 function name (a, b , c) { return a + b + c }

函数的调用

  • 方法调用
  • 构造函数调用
  • 函数调用
  • 改变 this 指向调用 apply, call, bind
  1. 方法调用
    当函数作为一个对象的属性时,我们称为这个函数时这个对象的方法,对象调用方法的时候函数内的 this 指向当前调用它的对象,调用形式有两种:点 . 和 方括号 []
    var obj = {
        name: 'abc',
        getName: function(){
            console.log(this.name)
        },
        "set-name": function(name) {
            this.name = name
        }
    }
    
    // 方法调用
    obj.getName()  // abc
    obj['set-name']('ccc')
    obj.getName()  // ccc
  1. 函数调用
    定义一个函数之间调用,这个时候函数内部的this指向的window
    function fn(){
        console.log(this)
    }
    
    fn() // window

某些情况下当执行对象方法后得到一个函数返回值 ,再执行这个函数,函数的this并不是指向这个对象,我们来看下

    var obj = {
        getFn: function(){
            function b(){
                console.log(this)
            }
            
            return b
        }
    }
    
    var c = obj.getFn()
    c() // window

原因是 c 执行的时候其实是执行得 window.c,全局变量调用方法,this 执行全局变量。想要 b 获取到 obj 对象,就想要一个变量来储存,而这一行为就造就了闭包

    var obj = {
        getFn: function(){
            var that = this
            function b(){
                console.log(that)
            }
            
            return b
        }
    }
    
    var c = obj.getFn()
    c() // obj
  1. 构造函数调用
    一般如果在一个函数前面使用 new 操作符来调用的话,就会把这个函数当做是一个构造函数。new 出来的新对象的原型对象指向的就是这个构造函数的prototype属性

扩展:new 操作符的执行过程:

  • 在内存中创建一个新对象
  • 这个新对象的内部[[Prototype]]即(__proto__)特性被赋值为构造函数的prototype属性
  • 构造函数内部的this被赋值给这个新的对象(this指向新对象)
  • 执行构造函数的内部代码(给这个新对象添加属性)
  • 如果构造函数返回非空对象,则返回该对象;否则就返回刚刚创建的新对象
    function myNew() {
      if (arguments.length === 0) {
        throw 'error';
      }
      let obj = {};
      let constructor = Array.prototype.shift.call(arguments);
      if (typeof constructor !== 'function') {
        throw 'error constructor is not function';
      }

      obj.__proto__ = Object.create(constructor.prototype);

      let result = constructor.apply(obj, arguments);

      let flag =
        result && (typeof result === 'object' || typeof result === 'function');

      return flag ? result : obj;
    }
  1. apply, call, bind 调用
    apply, call, bind 都是用来改变 this 指向的方法,bind是返回一个函数,第一个是被绑定的对象,第二个参数如果是apply调用的就是一个数组,其他的都是一个参数列表。

参数 arguments

  • 一个对象,拥有length属性的类数组
  • 不能调用任何数组方法
  • 函数可以通过arguments来获取被调用时传递的参数列表
  • 包括那些没有被分配给函数声明时定义的形式参数的多余参数

返回

  • 函数返回使用 return 关键字
  • 若未指定返回内容,则返回undefined
  • return关键字后续的代码将不会被执行

异常

  • 函数执行中抛出异常可以使用 throw 关键字
  • 也可以使用 try catche 来捕获异常

扩展功能

  • Function.prototype 属性上定义属性和方法
  • 每个函数都可以获取到新添加的属性和方法

递归

  • 直接或者间接的调用自身的一种函数
  • 将问题分解为组相似的子问题
    // 斐波那契数列
    function fn(n) {
        if(n < 2){
            // 最小化的子问题
            return n
        } else {
            return fn(n-1) + fn(n-2)
        }
    }

作用域

  • 作用域控制变量与参数的可见性及生命周期
  • 函数中参数和变量,在函数外部是不可见的
  • 外部无法访问内部的变量
  • 内部可以访问外部任何变量

闭包

闭包指的是那些引用了另一个函数作用域的变量的函数,通常是在嵌套函数中实现的。

    function fn(){
        var value = 0
        return {
            getValue: function(){
                return value
            },
            setValue: function(){
                value += 1
            }
        }
    }
    
    var obj = fn()
    obj.getValue() // 0
    obj.setValue()
    obj.getValue() // 1

对象 objgetValue 方法可以访问到函数fn内部的变量value,这样的形式就是闭包。虽然对象obj在函数外部,但是依然具有对函数作用域变量的访问权限。

回调

回调函数在日常开发中用的比较多的,最多的就是用在异步请求中,当我们向服务器发送一个请求后,通过回调函数来处理返回的结果,这样即不会阻塞 JS 主线程的运行,也很好的处理异步请求返回的结果后的处理。

    ajax(data, function(res){
        // 处理返回结果
    })

模块

  • 模块就是对外暴露出一个接口,也可以理解成一个对象。
  • 可以用函数和闭包来创建被绑定对象与私有成员的关系
  • 私有化变量,隐藏内部状态和实现方式
  • 开发中一个 js 文件就可以看作是一个模块
  • 通常结合单例模式使用

级联

级联就是对象调用方法可以采用链式调用,用jquery来表现一下

    $('#app').addClass('active').css(...)

对象上有很多的方法,而在每个方法内部最终把指向对象的 this 返回出去就可以实现链式调用。

柯里化

  • 把多参数的函数转换成一系列单参数函数并且调用的技术
  • 函数的职责单一
  • 函数逻辑复用
  • 减少函数的传参 举一个简单的例子,拼接网站地址url,通常我们会这样做
    function concatUrl(a, b, c){
        return a + b + c
    }
    
    concatUrl('https://','lodash.com/','docs/4.17.15#curry')
    

通过柯里化我们可以这样写

    function concatUrl(a){
        return function (b, c){
            return a + b + c 
        }
    }
    
    var httpUrl = concatUrl('https://')
    
    httpUrl('lodash.com/','docs/4.17.15#curry')

通过上述柯里化操作,后面拼接的 url 就不用再传递 https://

记忆

函数可以将先前的操作结果记录在某个对象里面,避免重复操作,这样的优化叫记忆。其实就是缓存数据,判断时候有缓存数据,有就直接用不用再做多余的计算操作。
还是举例斐波那契数列,它是一个数字是之前两个数字的之和。求一个 5 的数列和一个 10 的数列,其实在求 10 的这个数列也求过了刚刚的 5 的数列,所以刚刚在求 5 的时候,把它缓存起来再算 10 的时候直接拿过来用就可以了,这样就减少了很多次的计算。所以在计算的时候把结果都缓存起来,下一次计算就会减少很多计算调用。

    var fibonacci = (function{ 
      var meno = [0, 1]
      var fib = function (n) {
        var result = meno[n]
        if (typeof result !== 'number') {
          result = fib(n - 1) + fib(n - 2)
          meno[n] = result
        }
      }
      return fib
    }())

编写一个带记忆功能的函数

    var memoizer = function (memo, formula) {
      var redur = function (n) {
        var result = memo[n];
        if (typeof result !== "number") {
          result = formula(redur, n);
          memo[n] = result;
        }
      };

      return redur;
    };