学习笔记——JS函数

1. 函数的定义和调用

多条语句作何成一个“语句块”集体使用

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

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

    function 函数名(){
        
    }

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

    函数名();

一旦调用了函数,函数内部的语句就会执行
能够感觉到,函数就是一些语句的集合,让语句成为一个军团,集体作战,要不出动都不出动,要出动就全出动,得到调用才出动

函数的意义1:
  在出现大量程序相同的时候,可以封装为一个function,这样只用调用一次,就能执行很多语句

2.函数的参数

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

    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);

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

3.函数的返回值

函数可以通过参数来接收东西,更可以通过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:
  模块化编程,让复杂的逻辑变得简单。

4.函数的表达式

定义函数除了使用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
	}

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

	//先调用
	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,就又变成变量了。遇见func定义,程序会无视,因为已经预解析了。知道a()运行的时候,a就是变量,无法运行,报错。

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


函数是一个引用类型
我们之前说的,基本类型: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);

6.函数作用域

1>函数能封闭住作用域

一个变量如果定义在了一个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没有定义的。
  • d定义在全局范围内的,没写在任何function里面的,叫做全局变量,都认识。

2>作用域链

当遇见一个变量时,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

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

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

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

absdf = 123;  

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

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

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

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

5>全局变量的作用

全局变量挺有用的,有两个功能:
**功能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

7.函数的定义也有作用域

//这个函数返回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{
		
		}
		();   //可以运行
	}
	
	();   //不能运行,因为小函数定义在了大函数里面,离开大函数没有作用域。

8.闭包

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

	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

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

9.IIFE及时调用表达式

1>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。

3>为什么需要IIFE?

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

4>IIFE的常见形式

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

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

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

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

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

5>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

6> 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以后也许有更好的访问控制手段(模块?类?),有待研究。

10.通过数组来观察闭包

	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](); /# 学习目标:

<font color=#999AAA >提示:这里可以添加学习目标
例如:一周掌握 Java 入门知识
<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">

# 学习内容:

<font color=#999AAA >提示:这里可以添加要学的内容
例如:
1、 搭建 Java 开发环境
2、 掌握 Java 基本语法
3、 掌握条件语句
4、 掌握循环语句
<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">

# 学习时间:

<font color=#999AAA >提示:这里可以添加计划学习的时间
例如:
1、 周一至周五晚上 7 点—晚上92、 周六上午 9-上午 113、 周日下午 3-下午 6<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">

# 学习产出:

<font color=#999AAA >提示:这里统计学习计划的总量
例如:
1、 技术笔记 22CSDN 技术博客 33、 学习的 vlog 视频 1/ 0
	arr[3](); // 3
	arr[6](); // 6
	arr[9](); // 9	
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值