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)
(0)

相关推荐

  • JavaScript——函数

    一.函数的定义与调用(与python类同)  // 无参函数 function f1() { console.log("Hello world!"); }; f1(); // 有参 ...

  • JavaScript 之 作用域

    学习目标:能够说出Javascript的两种作用域 能够区分全局变量和局部变量 能够说出如何在作用域链中查找变量的值 1.作用域 <script> //1.javaScript作用域:就是 ...

  • 闭包理解

    闭包 刚学过的闭包,分享一下闭包的理解 1.什么是闭包? 闭包就是一个函数,也可以说闭包是一个引用关系,可以理解为一个作用域可以访问另一个函数的局部变量. 代码: function fn() { va ...

  • ES6中的函数(函数参数、默认值、箭头函数)

    一.函数参数的解构赋值 function foo([a,b]) { console.log(a+b); // 3 } foo([1,2]); function bar({c,d}) { console ...

  • 「学习笔记」JavaScript基础

    前言 最近一直在跟着黑马教程学习JavaScript内容,遂把这一阶段的学习内容整理成笔记,巩固所学知识,同时也会参考一些博客,书籍上的内容,查漏补缺,给自己充充电

  • 从一个超时程序的设计聊聊定时器的方方面面

    目录 如何设计一个靠谱的超时程序 JS引擎的运行机制是怎样的? 如何避免程序卡顿? 如何判断H5程序是从后台台恢复过来的? 如何理解定时器的丢弃行为? 在开发中如何选择使用合适的定时器? 有没有一键回 ...

  • 1.变量:var,let,const

    var在ECMAScript的所有版本中都可以使用,而const和let只能在ES6及更晚的版本中使用. var,let,const三个关键字的区别 var: 1)声明作用域:在函数内部,使用var定 ...

  • JavaScript 基础二

    函数 函数:函数就是封装了一段可以重复执行的代码块. function fn(){ console.log('我是函数') } fn(); function getSum(a,b){ return a ...

  • 深入理解JavaScript作用域和作用域链

    目录 前言 作用域(Scope) 1.什么是作用域 2.全局作用域和函数作用域 3.块级作用域 作用域链 1.什么是自由变量 2.什么是作用域链 3.关于自由变量的取值 作用域与执行上下文 解释阶段: ...

  • Python 中的函数装饰器和闭包

    函数装饰器可以被用于增强方法的某些行为,如果想自己实现装饰器,则必须了解闭包的概念. 装饰器的基本概念 装饰器是一个可调用对象,它的参数是另一个函数,称为被装饰函数.装饰器可以修改这个函数再将其返回, ...

  • javascript中的闭包这一篇就够了

    前端技术优选 今天 以下文章来源于程序员成长指北 ,作者koala 程序员成长指北专注 Node.js 技术栈分享,从 前端 到 Node.js 再到 后端数据库,祝您成为优秀的高级 Node.js ...

  • 中考数学倒计时23:几何图形中的函数问题(超高难度)

    (1)D是BC中点时,点E也为AB中点, 所以连接CE可得斜边中线CE=AE, 证明△FAE和△CAE全等即可得到EF: (2)过点E作AC的垂线, 如果过A作EF的垂线,可以得到AG为EF的一半, ...

  • Excel中通过函数提取不重复值及计算不重复值个数,简单高效!

    Excel中通过函数提取不重复值及计算不重复值个数,简单高效!

  • c++继承关系中成员函数的重载、重写、重定义之间的区别

    c++继承关系中成员函数的重载、重写、重定义之间的区别

  • 动点问题中的函数图象及规律探索问题

    [方法技巧] 动点问题中函数图象的题目的解决方法是:先根据动点运动规律找出所求与动点运动之间的关系,进而获取相应函数的解析式及函数值变化规律,达到求解的目的. 考查的重点是分段函数解析式的求解. 探索 ...

  • Excel中通过函数将含单位的文本值直接求和,简单到没朋友!

    Excel中通过函数将含单位的文本值直接求和,简单到没朋友!

  • VBA中OFFSET函数的实际利用

    今日继续讲VBA实用代码的第十讲,相信大家通过这系列的文章学到了很多,今日着重讲解OFFSET函数在VBA中的利用. 1 Range("A32", Range("A32& ...

  • Go 中的函数是一等公民,这到底在说什么?

    来自群友的问题 请问下各位大佬,这是什么语法,为什么不需要参数的? 对于有些人来说这根本不是问题,但有些人却想不明白.我提到,在 Go 语言中,函数是一等公民,但对方不清楚这到底在说什么.看来有必要解 ...