zoukankan      html  css  js  c++  java
  • JavaScript中的函数

    概念

    函数就是封装了一段可被重复调用执行的代码块

    函数的使用分为两步:声明函数和调用函数。

    1. 函数声明
    function fn() {
      console.log("hi")
    }
    

    注意:

    • function 声明函数的关键字全部小写;
    • 函数不调用自己不会执行;
    1. 调用函数
    fn()
    

    注意:调用的时候一定要加小括号。


    函数的五种声明方式

    具名函数

    function f(x, y) {
      return x + y
    }
    console.log(f(1, 2))    // 3
    

    匿名函数

    var fn
    fn = function(x, y) {
      return x + y
    }
    console.log(fn(1, 2))    // 3
    

    具名函数赋值

    var x = function y(a, b) {
      return a + b
    }
    console.log(x(1, 2))    // 3
    console.log(y)    // y is not defined
    

    window.Function

    var fn = new Function('x', 'y', 'return x+y')
    console.log(fn(1, 2))    // 3
    

    箭头函数

    var fn1 = x => n * n
    var fn2 = (x, y) => x + y
    var fn3 = (x, y) => {
      return x + y
    }
    

    name属性

    function.name 属性返回函数实例的名称。

    我们来看看下面这几种情况:

    function fn() {}
    console.log(fn.name)    // fn
    

    let fn1 = function fn2() {}
    console.log(fn1.name)    // fn2
    

    let fn = new Function('x', 'y', 'return x+y')
    console.log(fn.name)    // anonymous
    

    console.log((() => {}).name)    // ""
    let fn = () => {}
    console.log(fn.name)    // fn
    

    函数的本质

    函数就是一段可以反复调用的代码块。函数是一个对象,这个对象可以执行一段代码,可以执行代码的对象就是函数。

    那为什么函数是一个对象呢?

    var f = {}
    f.name = 'f'
    f.params = ['x', 'y']
    f.functionBody = 'console.log("1")'
    f.call = function() {
      return window.eval(f.functionBody)
    }
    console.log(f)    // {name: "f", params: Array(2), functionBody: "window.runnerWindow.proxyConsole.log("1")", call: ƒ}
    f.call()    // 1
    

    函数的封装

    函数的封装是把一个或多个功能通过函数的方法封装起来,对外只提供一个简单的函数接口。

    下面我们来看看几个简单的例子:

    // 计算 1 ~ 100 之间的累加和
    function getNum() {
      var sum = 0
      for (let i = 1; i <= 100; i++) {
        sum += i
      }
      console.log(sum)
    }
    getNum()    // 5050
    

    // 求任意两个数的和
    function getSum(num1, num2) {
      console.log(num1 + num2)
    }
    getSum(1, 2)    // 3
    

    // 求任意两个数之间的和
    function getNum(start, end) {
      let sum = 0
      for (let i = start; i <= end; i++) {
        sum += i
      }
      console.log(sum)
    }
    getNum(0, 10)    // 55
    

    this与arguments

    this 就是 call 的第一个参数,可以用 this 得到。

    arguments 就是 call 除了第一个以外的参数,可以用 arguments 得到。arguments 对象中存储了传递的所有实参。
    arguments 展示形式是一个伪数组。
    伪数组具有以下这几个特点:

    1. 具有 length 属性;
    2. 按索引方式储存数据;
    3. 不具有数组的 push、pop 等等方法;

    在普通模式下,如果 this 是 undefined,浏览器会自动把 this 变为 window。

    在普通模式下:

    let fn = function() {
      console.log(this)    // window
      console.log(this === window)    // true
    }
    fn.call(undefined)
    

    在严格模式下:

    let fn = function() {
      'use strict'
      console.log(this)    // undefined
      console.log(this === window)    // false
    }
    fn.call(undefined)
    

    arguments:

    let fn = function() {
      console.log(arguments)
    }
    fn.call(undefined, 1, 2)    // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
    

    // 求任意个数中的最大值
    function fn() {
      let max = arguments[0]
      for (let i = 1; i < arguments.length; i++) {
        if (max < arguments[i]) {
          max = arguments[i]
        }
      }
      return max
    }
    console.log(fn(1, 2, 3, 4, 661, 434))    // 661
    

    call stack调用栈

    先进后出

    查看调用栈过程

    普通调用

    嵌套调用

    递归调用


    柯里化

    柯里化(Currying),又称部分求值(Partial Evaluation),是一种关于函数的高阶技术。它不会调用函数,它只是对函数进行转换。将 fn(a,b,c) 转换为可以被以 fn(a)(b)(c) 的形式进行调用。它是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

    我们先来看一个例子:

    function add(a, b, c) {
      return a + b + c
    }
    console.log(add(1, 2, 3))    // 6
    

    现在我们把上面代码修改成柯里化版本:

    function addCurry(a) {
      return function(b) {
        return function(c) {
          return a + b + c
        }
      }
    }
    console.log(addCurry(1)(2)(3))    // 6
    

    我们来把 addCurry(1)(2)(3) 换一个形式来表示:

    let a = addCurry(1)    // ƒ (b) { return function(c) { return a + b + c } }
    let b = a(2)    // ƒ (c) { return a + b + c }
    let c = b(3)
    console.log(c)    // 6
    

    下面我们再来看一个例子:

    let handleBar = function(template, data) {
      return template.replace('{{name}}', data.name)
    }
    handleBar('<p>Hello,{{name}}</p>', {
      name: 'zww'
    })    // <p>Hello,zww</p>
    
    handleBar('<p>Hello,{{name}}</p>', {
      name: 'lq'
    })    // <p>Hello,lq</p>
    

    上面这段代码导致的问题就是,如果我们经常要使用 template 模板,那么每次像上面这样写将会导致十分繁琐。我们可以将代码修改为柯里化版本:

    function handleBarCurry(template) {
      return function(data) {
        return template.replace('{{name}}', data.name)
      }
    }
    
    let h = handleBarCurry('<p>Hello,{{name}}</p>')
    h({ name: 'zww' })    // <p>Hello,zww</p>
    h({ name: 'lq' })    // <p>Hello,lq</p>
    

    这样就实现了 template 模板参数复用的效果了。

    张鑫旭 - JS中的柯里化(currying)
    现代JavaScript 教程 - 柯里化(Currying)
    Currying 的局限性
    JavaScript函数柯里化


    高阶函数

    高阶函数是至少满足下面一个条件的函数:

    1. 接受一个或多个函数作为输入;
    2. 输出一个函数;
    3. 同时满足上面两个条件;

    例如下面这些就是 JS 原生的高阶函数:

    1. Array.prototype.sort()
    2. Array.prototype.forEach()
    3. Array.prototype.map()
    4. Array.prototype.filter()
    5. Array.prototype.reduce()

    我们来实现找出数组中所有的偶数并相加:

    let array = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    let sum = 0
    for (let i = 0; i < array.length; i++) {
      if (array[i] % 2 === 0) {
        sum += array[i]
      }
    }
    console.log(sum)    // 20
    

    下面我们用高阶函数来实现上面的功能:

    let array = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    let sum = array.filter(function(x) {
      return x % 2 === 0
    }).reduce(function(p, n) {
      return p + n
    }, 0)
    console.log(sum)    // 20
    

    廖雪峰 - 高阶函数


    回调函数

    MDN 所描述的:被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数。

    简单的说就是被当作参数的函数就是回调。

    就像 array.sort(function() {})array.forEach(function() {})这些都是回调函数。

    function putMsg(msg, callback) {
      setTimeout(() => {
        console.log(msg)
        callback()
      }, 1000)
    }
    putMsg('hi', function() {
      console.log('msg')
    })
    

    上面代码将在 1 秒后打印 hi、msg。


    构造函数

    简单的说就是返回对象的函数就是构造函数,构造函数名字首字母一般大写。

    构造函数有两个特点:

    1. 函数体内部使用了 this 关键字,代表了所要生成的对象实例;
    2. 生成对象的时候,必须使用 new 命令;
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    let person = new Person('zww', 18)
    console.log(person)    // Person {name: "zww", age: 18}
    

    作用域

    作用域指的是您有权访问的变量集合。

    作用域决定了这些变量的可访问性(可见性)。

    函数内部定义的变量从函数外部是不可访问的(不可见的)。

    作用域分为全局作用域、局部作用域。变量也可以分为全局变量与局部变量。
    从执行效率来看全局变量与局部变量:

    1. 全局变量只有浏览器关闭的时候才会销毁,比较占内存资源;
    2. 局部变量当我们程序执行完毕就会销毁,比较节约内存资源;

    作用域链:内部函数访问外部函数的变量,采取的是链式查找的方式来决定取哪个值,这种结构称为作用域链。也就是所谓的就近原则。

    我们来看看几个例子:

    question one:

    var a = 1
    
    function f1() {
      var a = 2
      f2.call()
      console.log(a)    // 2
    
      function f2() {
        var a = 3
        console.log(a)    // 3
      }
    }
    f1.call()
    console.log(a)    // 1
    

    question two:

    var a = 1
    
    function f1() {
      f2.call()
      console.log(a)    // undefined
      var a = 2    // 变量提升!!!
    
      function f2() {
        var a = 3
        console.log(a)    // 3
      }
    }
    f1.call()
    console.log(a)    // 1
    

    question three:

    var a = 1
    
    function f1() {
      console.log(a)    // undefined
      var a = 2
      f2.call()
    }
    
    function f2() {
      console.log(a)    // 1
    }
    f1.call()
    console.log(a)    // 1
    

    question four:

    var liTags = document.querySelectorAll('li')
    for (var i = 0; i < liTags.length; i++) {
      liTags[i].onclick = function() {
        console.log(i)    // 点击第二个li时,打印6
      }
    }
    

    以上代码变量提升后可等价如下:

    var liTags
    var i
    liTags = document.querySelectorAll('li')
    for (i = 0; i < liTags.length; i++) {
      liTags[i].onclick = function() {
        console.log(i)
      }
    }
    

    闭包

    闭包指有权访问另一个函数作用域中变量的函数,简单的说就是,一个作用域可以访问另外一个函数内部的局部变量。

    闭包的主要作用:延伸了变量的作用范围。

    var a = 1
    
    function fn() {
      console.log(a)
    }
    

    这个函数 fn 与变量 a 就形成一个闭包。

    function f1() {
      var num = 10
      function f2() {
        console.log(num);
      }
      return f2
    }
    var f = f1()
    f()    // 10
    

    箭头函数

    ES6 中新增的定义函数的方式。

    箭头函数不绑定 this,箭头函数中的 this,指向的是函数定义位置的上下文 this。

    箭头函数有以下这些特点:

    1. 有更加简洁的语法;
    2. 不会绑定 this;
    let fn = () => {
      console.log(this)
    }
    fn()    // window
    
    // 如果使用 call,还是不会改变 this 指向。
    fn.call({
      name: 'zww'
    })    // window
    

    我们来看看下面这段代码的 this 是什么:

    function fn() {}
    setTimeout(function(a) {
      console.log(this)    // window
    }, 1000)
    

    很明显,上面代码执行 1s 后将会打印 window,那么我们应该怎样把 this 指向 fn 呢?

    可以使用 bind 来更改 this 的执行,代码如下:

    setTimeout(function(a) {
      console.log(this)    // ƒ fn() {}
    }.bind(fn), 1000)
    

    这样 this 就指向了 fn 了,下面我们再在 setTimeout 里面添加个 setTimeout:

    setTimeout(function(a) {
      console.log(this)    // ƒ fn() {}
      setTimeout(function(a) {
        console.log(this)    // window
      }, 1000)
    }.bind(fn), 1000)
    

    里面这个 setTimeout 所打印的 this 还是指向的 window,那应该怎么指向 fn 呢?

    没错,还是使用 bind,只不过里面直接传 this 即可:

    setTimeout(function(a) {
      console.log(this)    // ƒ fn() {}
      setTimeout(function(a) {
        console.log(this)    // ƒ fn() {}
      }.bind(fn), 1000)
    }.bind(fn), 1000)
    

    上面代码,我们还可以使用箭头函数来简化:

    setTimeout(function(a) {
      console.log(this)    // ƒ fn() {}
      setTimeout((a) => {
        return console.log(this)    // ƒ fn() {}
      }, 1000)
    }.bind(fn), 1000)
    
  • 相关阅读:
    随机发牌 代码
    网络传输 buf 封装 示例代码
    简易数据库实现 UNIX环境高级编程(APUE)第二十章 A Database Library
    状态机学习(六)解析JSON2
    又一篇四则运算代码
    c++ stl源码剖析学习笔记(三)容器 vector
    c++ stl源码剖析学习笔记(二)iterator
    Linux系统编程(16)——正则表达式入门
    Linux系统编程(15)——shell脚本语法
    Linux系统编程(14)——shell常用命令
  • 原文地址:https://www.cnblogs.com/LqZww/p/13951820.html
Copyright © 2011-2022 走看看