JavaScript 之 深入理解函数

90 阅读4分钟

函数定义

函数声明

函数名称和函数体提前,所以可以在声明一个函数前调用它

myFunc('k') // k
function myFunc(name){
  console.log(name);
}

函数表达式

函数表达式不支持函数提升,因为只有var 声明变量提前了,函数代码还是在原来位置

匿名函数表达式

var myFunc2 = function (){}

命名函数表达式

console.log(myFunc3);  // undefined
var myFunc3 =  function funcName(){}
console.log(myFunc3); // ƒ funcName(){}

函数生成器声明

function* generator(i) {
  yield i;
  yield i + 10;
}

const gen = generator(10);
console.log(gen.next().value); // 10
console.log(gen.next().value); // 20

函数生成器表达式

const foo = function*() {
  yield 'a';
  yield 'b';
  yield 'c';
};

let str = '';
for (const val of foo()) {
  str = str + val;
}

console.log(str); // abc

箭头函数表达式

var myFunc4 = () => {}

Function构造函数

var myFunc5 = new Function('name',`return name`)
myFunc5('jack') // jack

函数参数

形参和实参

形参是函数中定义的变量,实参是运行时函数调用传入的参数

function add(a,b){
  return a+b
}
add(1,2)
add(a,b) // 这里的 a 和 b 就是形参  
add(1,2) // 1 和 2 就是实参

默认参数

函数默认参数允许在没有值或undefined被传入时使用默认形参

function add(a = 1){
  return a
}

console.log(add(1));  // 1
console.log(add()); // 1
console.log(add(undefined)); // 1
console.log(add('')); //
console.log(add(null)); // null

剩余参数

函数的最后一个命名参数以...为前缀,则它将成为一个由剩余参数组成的真数组

function sum(...theArgs) {
  let total = 0;
  for (const arg of theArgs) {
    total += arg;
  }
  return total;
}

console.log(sum(1, 2, 3)); // 6

console.log(sum(1, 2, 3, 4)); // 10

arguments 对象

arguments对象是所有(非箭头)函数中都可用的局部变量arguments对象不是一个 Array 。它类似于Array

function fn(){
  console.log(arguments[0]);  // 1
  console.log(arguments[1]);  // 2
}

fn(1,2)

Function 类型

每个函数都是Function类型的实例,因此函数实际上是一个对象。函数名是指向函数对象的指针。

function func(name){
  console.log(name);
}

console.log(func.name);  // func
console.log(func.length); // 1
console.log(func.prototype); // 原型对象
console.log(typeof func); // function
console.log(func instanceof Function); // true

属性

  • name:函数的名字
  • length:该函数希望接收的参数个数,即形参的个数。
  • prototype:函数的原型

闭包

闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。

function fn(){
  var i = 0
  return function(){
    return i++
  }
}

var f2 = fn()
console.log(f2());  // 0
console.log(f2());  // 0

在上面这个例子中,fn()方法返回了一个匿名函数,在函数中执行 a+i,访问了外部的变量i。即使这个内部函数被返回了在其它地方调用,依然可以访问变量i

使用注意:

  • 由于闭包会将函数中的变量保存在内存中,因此内存消耗比其它函数大。过度使用闭包会导致内存占用过多
  • 闭包会改变父函数的变量,所以谨慎使用

高阶函数

高阶函数就是一个将函数作为参数或者返回值的函数。
JavaScript 的内置对象中同样存在着一些高阶函数,例如数组的mapfilterreduce方法等

比如我们想计算了一个数组元素的和。

最普通的方式

let arr = [1, 2, 3, 4]
let sum = 0
for (let index = 0; index < arr.length; index++) {
  sum += arr[index]
}
console.log(sum) // 10

使用 Array.prototype.reduce:

let arr = [1, 2, 3, 4]
let sum = arr.reduce(function (value, item) {
  return value + item
})
console.log(sum) // 10

创建一个自己的高阶函数,模拟实现 map() 方法 :

let arr = [1, 2, 3, 4]
Array.prototype.mapFn = function(callbackfn,thisArg){

  if (typeof callbackfn !== 'function') {
    throw new TypeError(`callbackfn is not function`)
  }

  let result = []
  const current = this
  for (let index = 0; index < current.length; index++) {
    result[index] = callbackfn.call(thisArg,current[index],index,current)
  }
  return result
};

let result = arr.mapFn((item)=>{
  return item * 2
})
console.log(result) // [2, 4, 6, 8]

我们在 Array 原型上面定义了 mapFn() 方法,它接受一个回调函数 callbackfnthisArg用作执行 callbackFn 函数时被用作 this 的值。 我们循环调用这个函数的数组,在每次循环的时候callbackfn方法接受当前元素、索引、调用mapFn()方法的数组作为参数,然后将 callbackfn方法存储在返回结果中 result。循环完毕后再返回出函数

柯里化函数

函数柯里化也是高阶函数的一个应用。其含义是把接收多个参数的函数变成一个可以接收单一参数的函数,并且返回接受余下的参数并返回结果的新函数

function add(y){
  return function(x){
    return x + y
  }
}

add(2)(1) // 3

柯里化工具函数封装

function curry(fn, args) {
  var args = args || []
  return function () {
    argsList = args.concat(Array.prototype.slice.call(arguments))
    if (argsList.length < fn.length) {
      // 传入的参数大于等于原始函数fn的参数个数,则直接执行该函数
      return curry.call(this, fn, argsList)
    } else {
      // 否则继续对当前函数进行柯里化,返回一个接受所有参数(的函数
      return fn.apply(this, argsList)
    }
  }
}

function add(x, y) {
  return x + y
}
const addCurry = curry(add)
console.log(addCurry(1)(2)) // 6
console.log(addCurry(1, 2)) // 6
console.log(addCurry(1, 2)) // 6