函数

函数的概念:

  函数包含一组数据,它们是JavaScript的基础模块单元,用于代码复用、信息隐藏(封装)和组合调用(继承)。

  函数用于指定对象的行为。

  所谓编程,就是将一组需求分解成一组函数与数据结构的技能。

函数对象:

  对象的角度(__proto__):JavaScript中的函数就是对象。对象是“键/值”对的集合并拥有一个连到原型对象的隐藏连接(这个隐藏连接就是__proto__)。对象字面量产生的对象连接到Object.prototype。函数对象连接到Function.prototype(该原型对象本身连接到Object.prototype)。每个函数在创建时会附加两个隐藏属性:函数的上下文(this:指向window)和实现函数行为的代码注释1

  函数的角度(prototype):每个函数对象在创建时也随机配有一个prototype属性,它的值是一个拥有constructor属性并且值为该函数的对象。这和隐藏连接到Function.prototype完全不同。

   因为函数是对象,所以他们可以像任何其他的值一样被使用。函数可以保存在变量、对象和数组中。函数可以被当做参数传递给其他函数,函数也可以再返回函数。而且,因为函数是对象,所以函数拥有方法(静态方法和动态方法)。

    function fn() {
      // 动态方法,需要new一个实例对象来调用
      this.play = function () {
        console.log('玩耍')
      }
    }
    let f = new fn()
    f.play() // 玩耍
    fn.play() // Uncaught TypeError: fn.play is not a function

    // 静态方法,不需要new一个实例,直接调用
    fn.eat = function () {
      console.log('吃饭')
    }
    fn.eat(); // 吃饭

函数字面量:

  函数对象可以通过函数字面量来创建:

    let add = function fn(a, b) {
      return a + b
    };

    console.log(add(10, 20))
    console.log(add(101, 201))

  函数字面量包括4个部分:

    1、保留字function

    2、函数名,就是fn,它可以被省略。函数可以用它的名字来递归地调用自己,这个名字可以被调试器和开发工具用来识别函数。如果没有给函数命名,就是匿名函数

    3、函数的参数,多个参数用逗号分隔。这些参数被定义为函数中的变量。它们不像普通的变量那样被初始化为undefined,而是在函数被调用时初始化为实际提供的参数的值

    4、函数体,函数被调用的时候执行

  函数字面量可以出现在任何允许表达式出现的地方。函数也可以被定义在其他函数中。一个内部函数除了可以访问自己的参数和变量,同时它也能自由访问把它嵌套在其中的复函数的参数与变量(作用域链:内部函数可以访问到外部函数的变量,而外部函数不可以访问内部函数的变量)。通过函数字面量创建的函数对象包含一个连到外部上下文的连接,这就是闭包。

调用:

  调用一个函数会暂停当前函数的执行,传递控制权和参数给新函数(JavaScript是单线程,当前函数执行完了再去执行下一个函数,就是入栈和出栈的概念)。除了声明时定义的形式参数,每个函数还接收两个附加的参数:this和arguments。参数this在面向对象编程中非常重要,它的值取决于调用的模式。在JavaScript中一共有4中调用模式:方法调用模式、函数调用模式、构造器调用模式、apply调用模式。这些模式在如何初始化关键参数this上存在差异。

  调用运算符就是函数名后面的小括号。小括号里可以有零个或多个用逗号隔开的表达式(表达式:有可能是别的函数运行的结果放在这里当做参数使用,所以这里叫表达式),每个表达式产生一个参数值。每个参数值被赋予函数声明时定义的形式参数名。当实参的个数和形参的个数不一致时,不会导致运行错误。如果实参个数多于形参,超出的实参就相当于没传;如果实参比形参少,那剩下的形参默认为undefined。对参数值不会进行类型检查:任何类型的值都可以被传递给任何参数(JavaScript是弱类型的语言)。

方法调用模式:

  当一个函数被保存为对象的一个属性时,我们称它为一个方法。当一个方法被调用时,this被绑定到该对象。如果调用表达式包含一个提取属性的动作(即包含一个.点表达式或[]下标表达式),那么它就是被当做一个方法来掉调用。

    let obj = {
      value: 100,
      eat: function (val) {
        console.log(this.value += typeof val === 'number' ? val : 1)
      }
    }
    obj.eat()
    document.writeln(obj.value)


    obj.eat(3000)
    document.writeln(obj.value)

  方法可以使用this访问自己所属的对象,所以它能够从对象中取值或对对象进行修改。this到对象的绑定发生在调用的时候(JavaScript的作用域在执行的时候确定)。这个“超级”延迟绑定使得函数可以对this高度复用。通过this可取得对它们所属对象的上下文的方法称为公共方法

函数调用模式:

  当一个函数不是一个对象的方法时,那么它就是被当做一个函数来调用:

    function add(a, b) {
      return a + b
    }
    let sum = add(3, 4)
    console.log(sum)

  以此模式调用函数时,this被绑定到全局对象。外部函数被调用的时候,this应该绑定到外部函数的this变量,但是内部函数的this在JavaScript语言设计的时候没有按照这样的思路来,所以不能共享该方法对对象的访问权(this指向)。

  解决方案:给该函数定义一个that变量,赋值为this,内部函数可以访问到外部函数的变量,就可以访问到that,其实就是访问到this。

    let obj = {
      value: 0,
      increment: function (val) {
        this.value += typeof val === 'number' ? val : 1
      }
    }
    obj.increment()
    document.writeln(obj.value) // 1
    obj.increment(2)
    document.writeln(obj.value) // 3

    function add(a, b) {
      return a + b
    }

    obj.double = function () {
      let that = this
      let helper = function () {
        that.value = add(that.value , that.value)
      }
      helper()
    }
    obj.double()
    document.writeln(obj.value) // 6

  定义在double方法中的helper函数就是私有方法,它只能被double方法调用。helper函数中的this指向window,它是无法通过this直接访问到obj对象中的value值的,但是double方法是obj对象调用的,所以该方法中的this指向obj对象,然后在该函数中定义that赋值为this,就可以在helper函数中通过that,拿到obj中的value值并进行操作,这是非常常用的方法。如果这里不想使用that变量,还可以用将helper函数定义为箭头函数,箭头函数的this正好指向外层函数的this,这里就正好指向obj了。还有call方法可以改变this指向,在调用helper函数的时候,调用call方法,即helper.call(this),这里传入的this就是赋给that的this,此时helper函数中的this指向为obj。

构造器调用模式:

   JavaScript是一门基于原型继承的语言,这意味着对象可以直接从其他对象继承属性。构造器调用模式就是在一个函数(构造函数)前面使用new操作符来调用,这个操作在背地里会创建一个连接到该函数的prototype的新对象,同时this会被绑定到那个新对象上。

  new操作符干了哪些事:

    1、let obj{}  在内存中开辟一块新的空间,创建了一个对象

    2、this=obj  将this的指向改为该对象

    3、将构造函数上的属性和方法添加到新对象身上

    4、return this  将该对象返回

    function Fn(person) {
      this.name = '孙艺珍'
      this.name1 = person
      this.age = 20
      this.love = function () {
        console.log('love')
      }
    }
    let f = new Fn('小明同学')
    console.log(f.name) // 孙艺珍
    console.log(f.name1) // 小明同学
    console.log(f.age) // 20
    f.love() // love

apply调用模式:

  改变this指向,如果第一个参数传入null则不改变this指向,如果apply和call只传入一个参数它俩没有任何区别。

    let obj = {
      paly: function () {
        console.log(this)
      }
    }

    obj.paly() // {paly: ƒ}
    obj.paly.apply(this) // window
    obj.paly.call(window) // window
    obj.paly.call() // window
    obj.paly.call(null) // window
    function fn() {
      console.log(this)
    }

    fn() // window
    let obj = { name: 'syz' }
    fn.apply(obj) // { name: 'syz' }
    fn.call(obj) // { name: 'syz' }

参数:

  arguments是函数被调用时,传递给函数的参数列表,是一个伪数组。

  伪数组:有length且大于0,但是没有数组的方法。

  将伪数组转为数组:Array.from(伪数组)  Array.prototype.slice.call(伪数组)  Array.prototype.concat.apply([],伪数组)

    function add(a, b) {
      console.log(arguments) // [3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]
      console.log(arguments.length) // 2
      console.log(typeof arguments) // object
      console.log(Object.prototype.toString.call(arguments)) // [object Arguments]
    }

    add(3, 4)

返回:

  return可以使函数提前返回,并且可以返回一个值。

  如果return后面没有内容,则返回值是undefined

  如果函数调用时在前面加了new操作符,当返回值是一个对象时,返回该对象,当返回值不是一个对象时,返回this,就是new出来的实例对象----构造函数中一般不写return

    function fn() {
      return 'aaa'
    }
    function ff() {
      this.age = 100
      // return {} 如果这里返回一个对象,则ff()、new ff()、f的返回值都是该对象;如果返回值不是对象,则new ff()、f返回this(实例对象);如果没有返回值,则ff()返回undefined
    }
    let f = new ff()

    console.log(fn()) // aaa
    console.log(ff()) // undefined
    console.log(new ff()) // {age: 100}
    console.log(f) // {age: 100}

异常:

  异常是干扰程序正常运行的事故。

    // 用throw抛出一个异常对象
    let add = function (a, b) {
      if (typeof a !== 'number' || typeof b !== 'number') {
        throw {
          name: 'TypeError',
          message: 'add needs numbers'
        }
      }
      return a + b
    }
    // console.log(add('a',3))

    let try_it = function () {
      try {
        add(5, '6,')
      } catch (e) {
        document.writeln(e.name + ':' + e.message)
        console.log(e, e.name + ':' + e.message)
      }
    }
    try_it()

扩充类型的功能:

  js允许给语言的基本类型扩充功能,比如给Object.prototype添加方法,所有的对象都可以使用。这样的方式对函数、数组、字符串、数字、正则、布尔值都适用。

    Function.prototype.method = function (name, func) {
      if (!this.prototype[name]) this.prototype[name] = func
      return this
    }

    // 添加一个取整方法
    Number.method('integer', function () {
      return Math[this > 0 ? 'ceil' : 'floor'](this)
    })

    console.log((10.1).integer()) // 11 向上取整

    // 添加一个去除字符串两侧空格方法
    String.method('trim1', function () {
      return this.replace(/^\s+|\s+$/g, '')
    })

    console.log('"' + '  neat  '.trim1() + '"') // "neat"

  注意:这个方法最好是中午用,因为早晚会出事的。知道js有这个功能就行了

递归:

  直接或间接地调用自己的一种函数。

 

注释1:JavaScript创建一个函数对象时,会给该对象设置一个“调用”属性。当JavaScript调用一个函数时,可理解为调用此函数的“调用”属性。

posted @ 2020-09-25 20:37  吴小明-  阅读(175)  评论(0编辑  收藏  举报