多条语句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,引用了一个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)
总结
当遇见一个变量时,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
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: 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的出现是为了弥补JS在scope方面的缺陷:JS只有全局作用域(global scope)、函数作用域(function scope),从ES6开始才有块级作用域(block scope)。对比现在流行的其他面向对象的语言可以看出,JS在访问控制这方面是多么的脆弱!那么如何实现作用域的隔离呢?在JS中,只有function,只有function,只有function才能实现作用域隔离,因此如果要将一段代码中的变量、函数等的定义隔离出来,只能将这段代码封装到一个函数中。
在我们通常的理解中,将代码封装到函数中的目的是为了复用。在JS中,当然声明函数的目的在大多数情况下也是为了复用,但是JS迫于作用域控制手段的贫乏,我们也经常看到只使用一次的函数:这通常的目的是为了隔离作用域了!既然只使用一次,那么立即执行好了!既然只使用一次,函数的名字也省掉了!这就是IIFE的由来。
根据最后表示函数执行的一对()位置的不同,常见的IIFE写法有两种,示例如下:
**列表1:**IIFE写法一
(function foo(){
var a = 10;
console.log(a);
})();
**列表2:**IIFE写法二
(function foo(){
var a = 10;
console.log(a);
}());
这两种写法效果完全一样,使用哪种写法取决于你的风格,貌似第一种写法比较常见。
其实,IIFE不限于()的表现形式[1],但是还是遵守约定俗成的习惯比较好。
根据《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
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