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)
    
  • 相关阅读:
    HDU 2089 不要62
    HDU 5038 Grade(分级)
    FZU 2105 Digits Count(位数计算)
    FZU 2218 Simple String Problem(简单字符串问题)
    FZU 2221 RunningMan(跑男)
    FZU 2216 The Longest Straight(最长直道)
    FZU 2212 Super Mobile Charger(超级充电宝)
    FZU 2219 StarCraft(星际争霸)
    FZU 2213 Common Tangents(公切线)
    FZU 2215 Simple Polynomial Problem(简单多项式问题)
  • 原文地址:https://www.cnblogs.com/LqZww/p/13951820.html
Copyright © 2011-2022 走看看