JavaScript中的函数

  • A+
所属分类:Web前端
摘要

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


函数的五种声明方式

具名函数

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 } 

函数的本质

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

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

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 

this与arguments

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

在普通模式下,如果 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): ƒ] 

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} 

作用域

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

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

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

我们来看看几个例子:

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 就形成一个闭包。