JS学习篇(三) 函数与闭包

本文主要介绍了JS函数的多种灵活用法。包括匿名函数、默认参数、处理参数个数不确定的两种方法(arguments对象和剩余参数语法)。还详细讲解了闭包,指出嵌套函数形成闭包可访问外部函数参数和变量,以及作用域链规则。此外,介绍了ES6的箭头函数,用于简洁声明和解决闭包this传递问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个JS函数的定义由function关键字开头,然后加上函数名参数列表函数体

  • 函数名与变量等JS标识符命名规则一致,以字母、下划线或者$开头,后面可以跟数字;
  • 参数列表是一个包含在括号中,以逗号分离的列表,例如:(a, b, c);
  • 函数体是函数的主体内容,以大括号{}将JS语句包含起来,可通过return返回结果;

最简单的函数定义如下所示(函数名自定义):

function foo(){}

可以添加一些语句到函数体中:

function foo(){
    console.log("Hello World!");
}

再添加一些参数,并返回两个参数的总和:

function foo(a, b){
    return a + b;
}

var sum = foo(1, 2);
console.log(sum);   //输出3

 上面定义了一个求和的函数,并使用函数名加参数列表(可为空)调用它。

 

OK,一般的函数定义和调用比较简单,但在JS中函数的灵活用法有很多,可以再看看下面。

匿名函数

定义函数时不一定需要函数名:

var test = function(){}

像上面这样的匿名函数,以后可以通过test变量来调用它。

另外,可以定义一个匿名函数并立即运行它:

(function(){ return 1; })();

 如果不需要重复调用这个函数并立即执行的话,可以像上面这样定义匿名函数。

但是直接定义一个匿名函数,却不将它赋值给某个变量或者立即执行,那么它会报错

function() {return 1;}   // SyntaxError: function statement requires a name

默认参数

JS函数中的参数默认值是undefined,有时候给参数一个默认值是比较好的选择,这样不用判断一个值是否为未定义而手动设置初始值。

不使用默认参数的话,只能像下面那样判断参数b是否为undefined(外部调用是未传入此参数),如果是则赋值为1,否则直接传入b的值:

function multiply(a, b) {
  b = (typeof b !== 'undefined') ?  b : 1;

  return a*b;
}

multiply(5); // 5

使用默认参数可以这样:

function multiply(a, b = 1) {
  return a*b;
}

multiply(5); // 5

上面设置默认参数的方式,可以设后面多个参数。

参数个数不确定

函数的参数个数不确定的情况下,有两种处理方法:

第一种,arguments对象:

function myConcat(separator) {
   var result = ''; // 把值初始化成一个字符串,这样就可以用来保存字符串了!!
   var i;
   // iterate through arguments
   for (i = 1; i < arguments.length; i++) {
      result += arguments[i] + separator;
   }
   return result;
}

arguments对象是一个类数组的对象,但它不是数组。因为它有length属性,也可以通过下标索引的方式来访问各参数,所以叫类数组的对象。简单来说,它的值是根据调用函数时的任意数量参数来确定的。

可以给这个函数传递任意数量的参数,它会将各个参数连接成一个字符串“列表”:

// returns "red, orange, blue, "
myConcat(", ", "red", "orange", "blue");

// returns "elephant; giraffe; lion; cheetah; "
myConcat("; ", "elephant", "giraffe", "lion", "cheetah");

// returns "sage. basil. oregano. pepper. parsley. "
myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley");

第二种,剩余参数语法:

function multiply(multiplier, ...theArgs) {
  return theArgs.map(x => multiplier * x);
}

var arr = multiply(2, 1, 2, 3);
console.log(arr); // [2, 4, 6]

在这个例子,theArgs参数是一个真数组,它包含了从第二个参数开始的所有参数(除了第一个)。

那么上面这两种方法有什么不同?

剩余参数与arguments对象的三个不同点:

1. 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参。

2. arguments对象不是一个真正的数组,而剩余参数是真正的 Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sortmapforEachpop

3. arguments对象还有一些附加的属性 (如callee属性)。

 

闭包

闭包,这个名词第一次听有点头大,以为又是什么复杂的设计,经过了解之后,发现其实闭包就是函数,不过它是一个特殊的函数。

介绍闭包之前需要介绍JS里函数另一个特性——嵌套。

JS支持嵌套函数,也就是函数里面包含另一个函数的定义:

function addSquares(a, b) {
  function square(x) {
    return x * x;
  }
  return square(a) + square(b);
}
a = addSquares(2, 3); // returns 13
b = addSquares(3, 4); // returns 25
c = addSquares(4, 5); // returns 41

由于内部函数形成了闭包,因此可以调用外部函数并为外部函数和内部函数指定参数:

function outside(x) {
  function inside(y) {
    return x + y;
  }
  return inside;
}
fn_inside = outside(3); // Think of it like: give me a function that adds 3 to whatever you give it
result = fn_inside(5); // returns 8

result1 = outside(3)(5); // returns 8

注意上面这里有两种方式调用,一种是先调用outside获取内部函数并村委fn_inside,然后再调用fn_inside来获取结果;第二种是直接电泳outside(3)(5)来获取结果。

由此可见,一个函数内部的另一个函数(嵌套函数)就是一个闭包,一个闭包可以“继承”容器函数的参数和变量。换句话说,内部函数包含外部函数的作用域。

总结如下:

1. 内部函数只可以在外部函数中访问,或者通过外部函数暴露给更外部(此处只outside函数外部的作用域)。

2. 内部函数形成了一个闭包:它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。

作用域链

由于嵌套函数的层数不限,从里到外形成了一个作用域链:{最里层函数,较内层函数,...,较外层函数,最外层函数}。

当在这个作用域链中定义了同名的变量,那么会发送命名冲突,更近的作用域有更高的优先权,所以最近的优先级最高,最远的优先级最低。链的第一个元素就是最里面的作用域,最后一个元素便是最外层的作用域。

看下面例子:

function outside() {
  var x = 5;
  function inside(x) {
    return x * 2;
  }
  return inside;
}

outside()(10); // returns 20 instead of 10

很明显,outside定义的变量x与inside定义的变量x发生了命名冲突,那么该选择哪个变量来做最后的运算呢?这里是作用域链出场的地方了,根据上面的规则作用域链是{insideoutside, 全局对象},inside处于最高优先级位置,因此x变量应该是10,结果是10*2即20。

箭头函数

ES6规范新作了一个新的函数定义语法,箭头函数。它主要有两个应用点,简洁函数声明和闭包this传递。

简洁的函数

一般它是用在简短的函数上,以是整个函数的定义更加简洁,它抛弃了function关键字,也不需要函数名,甚至还可以连函数体的大括号都可以省略!

下面看一个例子:

var a = [
  "Hydrogen",
  "Helium",
  "Lithium",
  "Beryllium"
];

var a2 = a.map(function(s){ return s.length });

console.log(a2); // logs [ 8, 6, 7, 9 ]

var a3 = a.map( s => s.length );

console.log(a3); // logs [ 8, 6, 7, 9 ]

上面a2和a3的结果是一样的,但是a3使用了箭头函数,同时函数体的大括号也省略了,这种情况下直接返回箭头右边的表达式的结果。

闭包this传递

在箭头函数出现之前(ES6前),由于每个函数都有自己的this值,所以闭包的this关键字不能访问到外部函数的this:

function Person() {
  this.age = 0;

  setInterval(function growUp() {
    // 这里的this是growUp这个闭包,而不是外部Person函数的this
    this.age++;
  }, 1000);
}

var p = new Person();

为了解决上面的问题,需要先将this赋值给其他变量,再在闭包里访问改变了来达成目的:

function Person() {
  var self = this; // Some choose `that` instead of `self`. 
                   // Choose one and be consistent.
  self.age = 0;

  setInterval(function growUp() {
    // 此处self是Person函数的的this
    self.age++;
  }, 1000);
}

上面是我总结了关于JS中函数应用的关键点,更多其他的特性功能以后再补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值