JavaScript笔记 函数详解

函数的定义和调用

多条语句1,组合成一个"语句块"集体使用

	//定义一个函数,函数就是一组语句的集合
	function haha(){
    
    
		console.log(1);
		console.log(2);
		console.log(3);
		console.log(4);
	}
	//调用函数
	haha();

在这里插入图片描述

定义一个函数,用关键字function来定义,function就是英语功能的意思.表示这里面定义的语句,完成了一些功能.function后面有一个空格,后面就是函数名字,函数的名字也是关键字,命名规范和变量命名是一样的.名字后面一对圆括号,里面放置参数,然后就是大括号,大括号里面是函数的语句

	function 函数名(){
    
    
}

函数如果不掉用,那么里面的语句就一辈子不会执行,不调用等于白写
调用一个函数的方法非常简单,函数名后面加一个(),()是一个运算符,表示执行一个函数

	函数名();

一旦调用了函数,函数内部的语句就会执行

能够感觉到,函数就是一些语句的集合,让语句成为一个军团,集体作战,要不出动都不出动,要出动就全出动,得到调用才出动
函数的意义1:在出现大量程序相同的时候,可以封装为一个function,这样只用调用一次,就能执行很多语句

函数的参数

定义在函数内部的语句都是相同的,但实际上我们可以通过参数这个东西来让语句有差别
定义函数的时候,内部语句可能有一些悬而未决的量,就是变量,这些变量,我们要求在定义的时候都罗列在小括号中:

	function fun(a){
    
    
		console.log("我第"+a+"次说爱你");
		}

调用的时候,要把这个变量的真实的值,一起写在括号里,这样随着函数的调用,这个值页传给了a

	fun(88);

在这里插入图片描述

罗列在function小括号中的参数,叫做形式参数;调用时传递的数值,叫做实际参数。
在这里插入图片描述

参数可以有无数个,用逗号隔开。

	//有多少形式参数都可以,都罗列出来
	function fun(a,b){
    
    
		console.log(a + b);
	}
	
	fun(3,5);	//输出8
	fun(8,11);	//输出19

函数的意义2:我们在调用一个函数的时候,不用关心函数内部的实现细节,甚至这个函数是你上网抄的,可以运用。所以这个东西,给我们团队开发带来了好处。

定义函数的时候,参数是什么类型的没写,不需要指定类型:

	function sum(a,b){
    
    
	console.log(a + b);
		}

也就是说调用的时候,传进去什么什么类型,就是a、b什么类型

	sum("5",12);

在这里插入图片描述

我们还可以发现,定义的时候和调用的时候参数个数可以不一样多,不报错。

	sum(10);

在这里插入图片描述

	sum(10,20,32,23,22,2,4);

在这里插入图片描述

只有前两个参数被形参接收了,后面的参数无视了

函数的返回值

函数可以通过参数来接收东西,更可以通过return的语句来返回值,“吐出”东西。

	function sum(a,b){
    
    
		return a + b;		//现在这个函数的返回值就是a+b的和
	}
	
	console.log(sum(3,8));  //sum没有输出功能,就要用console.log输出
	//sum(3,8)实际上就成为了一个表达式,需要计算
	//计算后就是11,console.log(11);

函数只能有唯一的return,有if语句除外

程序遇见了return,将立即返回结果,返回调用它的地方,而不执行函数内的剩余的语句。

	function fun(){
    
    
		console.log(1);
		console.log(2);
		return;			   //返回一个空值
		console.log(3);  //这行语句不执行,因为函数已经return了
	}
	
	fun();

在这里插入图片描述

函数有一个return的值,那么现在这个函数,实际上就是一个表达式,换句话说这个函数就是一个值。
所以这个函数,可以当做其他函数的参数。

sum(3,sum(4,5));

程序从最内层做到最外层, sum(3,9) 12在这里插入图片描述

函数可以接受很多值,返回一个值,
函数的意义3:模块化编程,让复杂的逻辑变得简单。

函数表达式

定义函数除了使用function之外,还有一种方法,就是函数表达式。就是函数没有名字,称为“匿名函数”,为了今后能够调用它,我们把这个匿名函数,直接赋值给一个变量。

	var haha = function(a,b){
    
    
	return a + b;3	}

以后想调用这个函数的时候,就可以直接使用haha变量来调用。

	console.log(haha(1,3));

如果现在这个函数表达式中的function不是匿名的,而是有名字的:

	var haha = function xixi(a,b){
    
    
		return a + b;
	}

那么JS表现非常的奇怪,在外部只能用haha()来调用,xixi()非引发错误!

在这里插入图片描述

也就是说,JS这个奇怪的特性,给我们提了个醒,定义函数,只能用这两种方法,但是不能杂糅:

	function haha(){
    
    
		2
	}
	var haha = function(){
    
    
		2
	}

错误的

	var xixi = function haha(){
    
    
		2
	}

函数声明的提升(预解析)

	//先调用
	fun();	
	//然后定义
	function fun(){
    
    
		alert("我是函数,我执行了!");
	}

不会引发错误,alert能够弹出。
JS在执行前,会有一个预解析的过程,把所有的函数声明,都提升到了最最开头,然后再执行第一行语句。
所以,function定义在哪里,都不重要,程序总能找到这个函数。

函数声明会被提升,但是函数表达式却不会被提升

	fun();
		var fun = function(){
    
       //函数表达式,而不是function定义法
		alert("我是函数,我执行了!");
	}

在这里插入图片描述

又给我们提了个醒,没有极特殊的理由,都要使用function haha(){} 来定义函数,而不要使用var haha= function(){}

函数优先

	aaa();	//现在这个aaa到底是函数,还是变量5呢?
	//函数优先,遇见同名标识符,预解析阶段一定把这个标识符给函数
	
	var aaa = 5;		//定义一个变量,是5
	
	function aaa(){
    
    
		alert("我是aaa函数,我执行了");
	}

面试题

在这里插入图片描述

函数优先,现在foo这个标识符冲突了,一个函数叫做foo,一个变量也叫作foo。预解析阶段,如果遇见标识符冲突,这个标识符给函数。
函数优先 : 如果同一个标识符,在程序中又是变量的名字,又是函数的名字,解析器会把标识符给函数

	a();
	var a = 1;
	function a(){
    
    
		alert("我是函数");
	}

在这里插入图片描述
在执行var a = 1之前,函数已经把function a()预解析了,程序就已经知道页面上有一个函数叫做a。但是开始执行程序之后,定义了一个变量a,所以标识符a,就又变成变量了。遇见function定义,程序会无视,因为已经预解析了。直到a()运行的时候,a就是变量,无法运行,报错。

	function fun(){
    
    
	var a = 1;
	function a(){
    
    
		alert("我是函数");
	}
	
	a();

在这里插入图片描述

但是函数表达式是不会预解析的,所以预解析的就是变量a的定义,就是undefined,undefined是无法执行的。

	a();
	var a = 1;
	var a = function(){
    
    
		alert("我是函数");
	}

在这里插入图片描述
函数是一个引用类型
我们之前说的,基本类型:number、string、boolean、undefined、null
引用类型也有很多种:object、function、array、RegExp、Math、Date。

	function fun(){
    
    
	
	}
	
	console.log(typeof fun);

在这里插入图片描述
函数也是一种类型。这个类型就叫做function,是引用类型的一种。
基本类型保存值,引用类型保存地址
我们现在变量a = 6 ; 那么这个a变量里面存储的是6这个数值;
而a = function(){} 那么这个a标签里面存储的是function的内存地址。

在这里插入图片描述
a、b指向的是内存中的同一个地址,所以a就是b,b就是a。

	//定义了一个变量a,引用了一个funciton
	//这个a变量存储的是这个匿名函数的内存地址
	var a = function(){
    
    
	alert("我是一个函数,我执行了");
	}
	//就是把匿名函数的地址也给了b
	var b = a;
	//给b添加一个属性, 以后讲对象的时候,我们再看
	b.haha = 1;
	//输出a的haha属性,你会发现a也有这个属性了:
	console.log(a.haha);
	//b的haha属性和a的同步更改的,因为都是指向的同一个对象
	b.haha++;
	b.haha++;
	b.haha++;
	console.log(a.haha);

函数作用域

函数能封闭住定义域

一个变量如果定义在了一个function里面,那么这个变量就是一个局部变量,只在这个function里面有定义。出了这个function,就如同没有定义过一样。

	<script type="text/javascript">
	function fn(){
    
    
		var a = 1;	//定义在一个函数里面的变量,局部变量,只有在函数里面有定义
		console.log("我是函数里面的语句,我认识a值为" + a);
	}
	
	fn();
	console.log("我是函数外面的语句,我不认识a" + a);
	</script>

在这里插入图片描述

a被var在了function里面,所以现在这个a变量只在红框范围内有定义:
在这里插入图片描述

JavaScript变量作用域非常的简单,没有块级作用域,管理住作用域的只有一个东西:函数。

如果一个变量,没有定义在任何的function中,那么它将在全部程序范围内都有定义:

	var a = 1;	//定义在全局范围内的一个变量,全局变量,在程序任何一个角落都有定义
	
	function fn(){
    
    
		console.log("我是函数里面的语句,我认识全局变量a值为" + a);
	}
	
	fn();
	console.log("函数外面的语句也认识a值为" + a)

在这里插入图片描述

总结

  • 定义在function里面的变量,叫做局部变量,只在function里面有定义,出了function没有定义的。
  • 定义在全局范围内的,没写在任何function里面的,叫做全局变量,都认识。

作用域链

当遇见一个变量时,JS引擎会从其所在的作用域依次向外层查找,查找会在找到第一个匹配的标识符的时候停止。

	function outer(){
    
    
		var a = 1;		//a的作用域就是outer
		inner();
	function inner(){
    
    
		var b = 2;		//b的作用域就是inner
		console.log(a);	//能够正常输出1,a在本层没有定义,就是找上层
		console.log(b);   //能够正常输出2
	}
	}
	
	outer();
	console.log(a);		//报错,因为a的作用域outer

在这里插入图片描述

多层嵌套,如果有同名的变量,那么就会发生“遮蔽效应”:

	var a = 1;		//全局变量
	function fn(){
    
    
		var a = 5;         //就把外层的a给遮蔽了,这函数内部看不见外层的a了。
		console.log(a);	//输出5,变量在当前作用域寻找,找到了a的定义值为5
	}
	fn();
	console.log(a);  //输出1,变量在当前作用域寻找,找到了a的定义值为1

在这里插入图片描述

作用域链:一个变量在使用的时候得几呢?就会在当前层去寻找它的定义,找不到,找上一层function,直到找到全局变量,如果全局也没有,就报错。

	var a = 1;		//全局变量
	var b = 2;		//全局变量
	function outer(){
    
    
		var a = 3;		//遮蔽了外层的a,a局部变量
	function inner(){
    
    
		var b = 4;  //遮蔽了外层的b,b局部变量
		console.log(a);   //① 输出3,a现在在当前层找不到定义的,所以就上一层寻找
		console.log(b);   //② 输出4
	}
	inner();		//调用函数
	console.log(a);	//③ 输出3
	console.log(b); //④ 输出2 b现在在当前层找不到定义的,所以就上一层寻找
	}
	outer();		//执行函数,控制权交给了outer
	console.log(a);	// ⑤ 输出1
	console.log(b); // ⑥ 输出2

不写var就自动成全局变量了

	function fn(){
    
    
		a = 1;		//这个a第一次赋值的时候,并没有var过,
					//所以就自动的在全局的范围帮你var了一次
	}
	
	fn();
	console.log(a);

这是JS的一个机理,如果遇见了一个标识符,从来没有var过,并且还赋值了:

absdf = 123;

那么就会自动帮你在全局范围内定义var absdf;
告诉我们一个道理,变量要老老实实写var。

函数的参数,会默认定义为这个函数的局部变量

	function fn(a,b,c,d){
    
    
	}

a,b,c,d就是一个fn内部的局部变量,出了fn就没有定义。

全局变量的作用

全局变量挺有用的,有两个功能:

**功能1:**通信,共同操作同一个变量
两个函数同时操作同一个变量,一个增加,一个减少,函数和函数通信。

	var num = 0;
	
	function add(){
    
    
		num++;
	}
	
	function remove(){
    
    
		num--;
	}

**功能2:**累加,重复调用函数的时候,不会重置

	var num = 0;
	function baoshu(){
    
    
		num++;
		console.log(num);
	}
	
	baoshu();	//1
	baoshu();	//2
	baoshu();	//3

如果num定义在baoshu里面,每次执行函数就会把num重置为0:

	function baoshu(){
    
    
		var num = 0;
		num++;
		console.log(num);
	}
	
	baoshu();	//1
	baoshu();	//1
	baoshu();	//1

函数的定义也有作用域

//这个函数返回a的平方加b的平方
function pingfanghe(a,b){
    
    
	return  pingfang(a) + pingfang(b);
	//返回m的平方
	function pingfang(m){
    
    
		return Math.pow(m,2)
	}
}

// 现在相求4的平方,想输出16
pingfang(4);	//报错,因为全局作用域下,没有一个函数叫做pingfang

公式:

	function{
    
    
		function{
    
    
		
		}
		();   //可以运行
	}
	
	();   //不能运行,因为小函数定义在了大函数里面,离开大函数没有作用域。

闭包

一个函数可以把它自己内部的语句,和自己声明时所处的作用域一起封装成了一个密闭环境,我们称为“闭包”.

	function outer(){
    
    
		var a = 333;
		function inner(){
    
    
			console.log(a);
		}
		return inner;
	}
	
	var inn = outer();
	inn(); //弹出333

推导过程:

我们之前已经学习过,inner()这个函数不能在outer外面调用,因为outer外面没有inner的定义:

	function outer(){
    
    
		var a = 888;
		function inner(){
    
    
			console.log(a);
		}
	}
	//在全局调用inner但是全局没有inner的定义,所以报错
	inner();

但是我们现在就想在全局作用域下,运行outer内部的inner,此时我们必须想一些奇奇怪怪的方法。
有一个简单可行的办法,就是让outer自己return掉inner:

	function outer(){
    
    
		var a = 333;
		function inner(){
    
    
			console.log(a);
		}
	return inner;	//outer返回了inner的引用
	}
	
	var inn = outer();	//inn就是inner函数了
	inn();			//执行inn,全局作用域下没有a的定义
	//但是函数闭包,能够把定义函数的时候的作用域一起记忆住
	//能够输出333

这就说明了,inner函数能够持久保存自己定义时的所处环境,并且即使自己在其他的环境被调用的时候,依然可以访问自己定义时所处环境的值。


一个函数可以把它自己内部的语句,和自己声明时所处的作用域一起封装成了一个密闭环境,我们称为“闭包” (Closures)。

在这里插入图片描述

每个函数都是闭包,每个函数天生都能够记忆自己定义时所处的作用域环境。但是,我们必须将这个函数,挪到别的作用域,才能更好的观察闭包。这样才能实验它有没有把作用域给“记住”。
我们发现,把一个函数从它定义的那个作用域,挪走,运行。嘿,这个函数居然能够记忆住定义时的那个作用域。不管函数走到哪里,定义时的作用域就带到了哪里。这就是闭包。

闭包在工作中是一个用来防止产生隐患的事情,而不是加以利用的性质。
因为我们总喜欢在函数定义的环境中运行函数。从来不会把函数往外挪。那为啥学习闭包,防止一些隐患,面试绝对考。

闭包的性质

每次重新引用函数的时候,闭包是全新的。

	function outer(){
    
    
		var count = 0;
		function inner(){
    
    
			count++;
			console.log(count);
		}
		return inner;
	}
	
	var inn1 = outer();
	var inn2 = outer();
	
	inn1();	//1
	inn1();	//2
	inn1(); //3
	inn1(); //4
	inn2(); //1
	inn2(); //2
	inn1(); //5

无论它在何处被调用,它总是能访问它定义时所处作用域中的全部变量

IIFE及时调用表达式

FFIE定义

IIFE: Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。
对比一下,这是不采用IIFE时的函数声明和函数调用:

	function foo(){
    
    
		var a = 10;
		console.log(a);
	}
	foo();

下面是IIFE形式的函数调用:

	(function foo(){
    
    
		var a = 10;
		console.log(a);
	})();

函数的声明和IIFE的区别在于,在函数的声明中,我们首先看到的是function关键字,而IIFE我们首先看到的是左边的(。也就是说,使用一对()将函数的声明括起来,使得JS编译器不再认为这是一个函数声明,而是一个IIFE,即需要立刻执行声明的函数。
两者达到的目的是相同的,都是声明了一个函数foo并且随后调用函数foo。

为什么需要IIFE?

如果只是为了立即执行一个函数,显然IIFE所带来的好处有限。实际上,IIFE的出现是为了弥补JS在scope方面的缺陷:JS只有全局作用域(global scope)、函数作用域(function scope),从ES6开始才有块级作用域(block scope)。对比现在流行的其他面向对象的语言可以看出,JS在访问控制这方面是多么的脆弱!那么如何实现作用域的隔离呢?在JS中,只有function,只有function,只有function才能实现作用域隔离,因此如果要将一段代码中的变量、函数等的定义隔离出来,只能将这段代码封装到一个函数中。
在我们通常的理解中,将代码封装到函数中的目的是为了复用。在JS中,当然声明函数的目的在大多数情况下也是为了复用,但是JS迫于作用域控制手段的贫乏,我们也经常看到只使用一次的函数:这通常的目的是为了隔离作用域了!既然只使用一次,那么立即执行好了!既然只使用一次,函数的名字也省掉了!这就是IIFE的由来。

IIFE的常见形式

根据最后表示函数执行的一对()位置的不同,常见的IIFE写法有两种,示例如下:
**列表1:**IIFE写法一

	(function foo(){
    
    
		var a = 10;
		console.log(a);
	})();

**列表2:**IIFE写法二

	(function foo(){
    
    
		var a = 10;
		console.log(a);
	}());

这两种写法效果完全一样,使用哪种写法取决于你的风格,貌似第一种写法比较常见。
其实,IIFE不限于()的表现形式[1],但是还是遵守约定俗成的习惯比较好。

IIFE的函数名和参数

根据《You Don’t Know JS:Scope & Clouses》[2]的说法,尽量避免使用匿名函数。但是IIFE确实只执行一次,给IIFE起个名字有些画蛇添足了。如果非要给IIFE起个名字,干脆就叫IIFE好了。
IIFE可以带(多个)参数,比如下面的形式:

	var a = 2;
	(function IIFE(global){
    
    
		var a = 3;
		console.log(a); // 3
		console.log(global.a); // 2
	})(window);

	console.log(a); // 2

IIFE构造单例模式

JS的模块就是函数,最常见的模块定义如下:

	function myModule(){
    
    
		var someThing = "123";
		var otherThing = [1,2,3];
	
		function doSomeThing(){
    
    
			console.log(someThing);
		}
		
			function doOtherThing(){
    
    
			console.log(otherThing);
		}
		
		return {
    
    
			doSomeThing:doSomeThing,
			doOtherThing:doOtherThing
		}
	}
	
	var foo = myModule();
	foo.doSomeThing();
	foo.doOtherThing();
	
	var foo1 = myModule();
	foo1.doSomeThing();

如果需要一个单例模式的模块,那么可以利用IIFE:

	var myModule = (function module(){
    
    
		var someThing = "123";
		var otherThing = [1,2,3];
		
		function doSomeThing(){
    
    
			console.log(someThing);
		}
		
			function doOtherThing(){
    
    
			console.log(otherThing);
		}
		
		return {
    
    
			doSomeThing:doSomeThing,
			doOtherThing:doOtherThing
		}
	})();
	
	myModule.doSomeThing();
	myModule.doOtherThing();

IIFE小结
IIFE的目的是为了隔离作用域,防止污染全局命名空间。
ES6以后也许有更好的访问控制手段(模块?类?),有待研究。

通过数组来观察闭包

	function outer(){
    
    
		var a = 10;
		function inner(){
    
    
			a++;
			console.log(a);
		}
		return inner;
	}
	
	var inn = outer();
	inn();
	inn();
	
	var inn1 = outer();
	inn1();
	inn1();
	var arr = [];
	for(var i = 0; i < 10; i++){
    
    
		arr[i] = function(){
    
    
			console.log(i);
		};
	}
	console.log(arr);
	arr[0](); // 10 因为当我们for循环时每个元素对应的函数中只是记住了i这个变量  当去执行函数时  i已经变成10了
	arr[3](); // 10
	arr[6](); // 10
	arr[9](); // 10
	var arr = [];
	for(var i = 0; i < 10; i++){
    
    
	
		(function(m){
    
    
			arr[m] = function(){
    
    
			console.log(m); // 每次循环时我们都是新的IIFE 里面的m都是不同的m
				}
			})(i);
	}
	
	
	console.log(arr);
	arr[0](); // 0
	arr[3](); // 3
	arr[6](); // 6
	arr[9](); // 9	

猜你喜欢

转载自blog.csdn.net/weixin_44368963/article/details/108701982