复习js基础篇-函数

193 阅读8分钟

函数,本质上是一种代码的分组形式。

通过这种分组形式赋予某组代码一个名字,方便后期重用时调用。

函数的声明

// 一般函数声明通常由以下几部分组成:
function sum (a,b) {
  var c = a + b;
  return c;
}
// function 子语句
// 函数名称 sum
// 参数 0个或多个参数
// 函数执行的代码块,即函数体 ,这里 var c = a + b;
// return 子句,函数通常都会有返回值,如果函数没有显示的返回值,会默认它的返回值为undefined.

函数的调用及参数arguments

// 函数的调用很简单,只需在函数名后面加一对括号。
// 如果设定了参数,但是在调用时忘记传相关的参数,js引擎会自动将其设为undefined.
sum(1) // NaN
sum(1,3) // 4
// 对多传的参数,会被默认忽略掉。
sum(1,3,4,5) // 4
// 函数内部有一个 arguments 数组,返回函数接收的所有参数。
// arguments.length 返回接收的参数数量
2020_06_16_oDYRy1

变量作用域

不能为变量定义特定的块作用域 但是可以定义其所属的函数域,也就是说,如果变量是在某个函数中定义,那么它在函数以外的地方是不可见的。 如果该变量是定义在if或者for 这样的代码块中的,它在代码之外都是可见的。

js 术语 全局变量 指的是所有函数之外的变量,与之相对的是局部变量,指的是在某个函数中定义的变量。其中,函数内的代码可以像访问自己的局部变量那样访问全局变量,反之则不能。

// f() 可以访问变量 g ;
// f() 以外,变量 l 是不存在的
var g =1;
function f(){
	var l = 2;
	g++;
	return g;
}
f() // 2
f() // 3
l // l is not defined 

// 如果声明一个变量时没有使用 var 语句,该变量就会被默认为全局变量。
function b(){ pig = 2; }
pig // pig is not defined
b() // pig 变量在函数b调用前是不存在的,调用时,变量pig 被创建,并赋予全局作用域。使得可以在该函数以外的地方可以访问
pig // 2

2020_06_16_c7umyX

var a = 4;
function f(){
	console.log(a);
	var a =1 ;
	console.log(a);
} 
f() // undefined 1
// 第一个输出是因为函数的作用域始终高于全局作用域,所以局部变量a 会覆盖掉所有和它同名的全局变量。尽管第一次输出时,a变量没有被正式定义(即该值为undefined),但是该变量本身已经存在本地空间了。

2020_06_16_sVWhfq

函数也是数据

函数也是一种数据类型,只不过这种特殊的数据类型有两个重要的特性:

  1. 他们所包含的是代码;
  2. 他们是可执行的,或者说是可调用的。

函数也是赋值给变量的一种数据,所以命名规则和一般变量相同,不能以数字开头,可以由任意的字母、数字和下划线组合而成。

// 两者本质上是相同的
function f() { return 1; }
var f = function() {return 1;} // 函数标识法

匿名函数

函数本质上和其他变量并无区别,因此可以在没有名字的情况下被使用。

一般的优雅用法:

  1. 将匿名函数作为参数传递给其他函数,接收方函数就能利用接收的匿名函数完成某些事情;
  2. 定义某个匿名函数来执行某些一次性的任务。

回调函数

当将函数 A 传递给函数 B,并由 B 来执行 A 时,A 就成了一个回调函数。

如果这时 A 还是一个无名的函数,我就称它为匿名回调函数。

应该什么时候使用回调函数?

  1. 可以在不做命名的情况下传递函数,意味着可以节省全局变量;
  2. 可以将一个函数的调用操作委托给另一个函数,意味着可以节省一些代码;
  3. 有助于提升性能。
function multiply(a,b,c,callback) {
	var i,arr = [];
	for(i=0; i<3; i++){
		arr[i] = callback(arguments[i] * 2);
	}
	return arr;
}
// 回调函数 addOne
function addOne(a) {
  return a + 1;
}
var myArr = multiply(1,2,3,addOne); // [3,5,7]
// 还可以使用匿名函数来代替addOne,这样可以节省一个额外的全局变量,而且也更易于根据需求随时调整代码
var xArr = multiply(1,2,3,function(a){return a + 2;}); // [4,6,8]

2020_06_16_Hl7sCx

自调函数

定义后自行调用

好处在于不会产生任何的全局变量,

缺点是这样的函数无法重复执行。

所以匿名自调函数最适合执行一些一次性的或初始化的任务。

// 匿名函数放进一对括号中,然后外面再紧跟一对括号即可。第二个括号起到的就是立即调用的作用。也是向匿名函数传递参数的地方
(function(name){
  console.log('自调'+ name); // 自调完美
})('完美')

2020_06_16_ugg5of

私有函数

在一个函数内部定义的另一个函数,就是私有函数

私有函数在外部函数之外是不可见的。

好处:

  1. 有助于确保全局名字,命名冲突机会很小
  2. 私有性,可以选择只将一些必要的函数暴露给外部,并保留属于自己的函数。

返回函数的函数

函数始终都会有一个返回值,即便不是显式返回,也会隐式返回一个undefined

既然函数能返回一个唯一值,那么这个值就有可能是另一个函数

调用:只需将返回值赋值给某一个变量,然后像函数一样调用即可。

function f(){
	alert(1);
	return function (){
		alert(2);
	}
}
// 调用
var pig = f(); // 弹出1;
pig(); //2
// 也可以这样调用
f()(); // 依次弹出1,2

闭包

闭包突破作用域链

如何突破作用域链呢?只需将它们升级为全局变量(不使用var 语句)或者通过F传递或者返回给全局空间即可。

由于函数通常都会将自身的参数视为局部变量,因此创建返回函数时,也可以令其返回父函数的参数

1.作用域链:

  • 函数作用域,在函数内定义的变量在函数外是不可见的。

  • 但该变量是在某个代码块中定义的(某个if或者for语句中),它在代码块外是可见的。

var a = 1; // a 全局变量
function f() {
 var b = 1; // b的作用域就在f()内,在f()内,a和b都是可见的,在f()外,a是可见的,b是不可见的。
	function n() {
		var c = 3; // c的作用域就在n()内,在n()内,a、b、c都是可见的,在n()外,a和b是可见的,c是不可见的。
	}
}

2.词法作用域:

  • 每个函数都有一个自己的词法作用域,即每个函数在被定义时都会创建一个属于自己的环境(即作用域)。
function f1(){
	var a = 1; // 局部变量a 
  console.log(a) // 1
	return f2();	
}
function f2(){ // 函数定义时是不执行的,所以此时f2内的 a 是未定义的,只能访问自身作用域和全局作用域的内容,也就是f1(),f2()之间不存在共享的词法作用域。
  console.log(2)
	return a;
}
f1();

2020_06_17_LhsACH

3.闭包

  • 如何突破作用域链呢?只需将它们升级为全局变量(不使用var 语句)或者通过F传递或者返回给全局空间即可。
// f()函数含有一个局部变量b,它在全局空间是不可见的。
function f(){
  var b="b";
  return function(){
    return b;
  }
}
b // b is not defined

var pig = f(); // pig()函数有自己的私用空间,同时可以访问f()和全局空间,所以b对于pig函数来说是可见的。
pig(); // 'b'
// 因为f()是可以在全局空间中被调用的,是一个全局函数,所以我们可以将它的返回值复制给一个全局变量,从而生成一个可以访问f()函数私有空间的新全局函数pig(),这样当运行pig()时,就可以访问到变量b了。
// 同样是闭包,但是f() 不再返回函数,而是直接在函数体内容创建一个新的函数
var pig;
function f(){
  var b='b';
  pig = function(){
    return b;
  }
}
f()
pig() // "b"

4.循环中的闭包

// 通常会按顺序输出0,1,2,但结果全是3,为什么不是2 呢
// 因为我们在这里创建了三个闭包,他们都指向的了一个共同的变量i,闭包不会记录他们的值,他们所拥有的只是一个i的连接即引用。因此只能返回i的当前值。由于循环结束时,i的值为3.所以三个函数都指向了这个值。
function f(){
  var arr = [];
  var i;
  for( i = 0; i < 3; i++){
    arr[i] = function(){
      return i;
    }
  }
  return arr;
}
var pig = f();
pig[0](); // 3
pig[1](); // 3
pig[2](); // 3

// 解决上面的问题,只需输出三个不同的变量。换种闭包形式
function f(){
  var arr = [];
  var i;
  for( i = 0; i < 3; i++){
    arr[i] = (function(x){ // 将i传递给一个自调函数,在该函数中,i就被赋值给了局部变量x,这样每次迭代中的x就会拥有各自不同的值了。
      return x;
    })(i)
  }
  return arr;
}
// 这样就可以获得预期的结果:
var pig = f();
pig[0](); // 0
pig[1](); // 1
pig[2](); // 2
// 或者,在函数内容部定义一个函数,将i值进行本地化。
function f(){
  function mackX(x){
    return function(){
      return x
    }
  }
  var arr = [];
  var i;
  for( i = 0; i < 3; i++){
    arr[i] = mackX(i);
  }
  return arr;
}
var pig = f();
pig[0](); // 0
pig[1](); // 1
pig[2](); // 2

迭代器

闭包实现迭代器的功能

function setup(arr){
	var i = 0;
  return function(){
    return arr[i++];
  }
}
var next = setup(['a','b','c','d']);
next(); // 'a'
next(); // 'b'
next(); // 'c'
next(); // 'd'
next(); // undefined