函数,本质上是一种代码的分组形式。
通过这种分组形式赋予某组代码一个名字,方便后期重用时调用。
函数的声明
// 一般函数声明通常由以下几部分组成:
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 返回接收的参数数量
变量作用域
不能为变量定义特定的块作用域 但是可以定义其所属的函数域,也就是说,如果变量是在某个函数中定义,那么它在函数以外的地方是不可见的。 如果该变量是定义在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
var a = 4;
function f(){
console.log(a);
var a =1 ;
console.log(a);
}
f() // undefined 1
// 第一个输出是因为函数的作用域始终高于全局作用域,所以局部变量a 会覆盖掉所有和它同名的全局变量。尽管第一次输出时,a变量没有被正式定义(即该值为undefined),但是该变量本身已经存在本地空间了。
函数也是数据
函数也是一种数据类型,只不过这种特殊的数据类型有两个重要的特性:
- 他们所包含的是代码;
- 他们是可执行的,或者说是可调用的。
函数也是赋值给变量的一种数据,所以命名规则和一般变量相同,不能以数字开头,可以由任意的字母、数字和下划线组合而成。
// 两者本质上是相同的
function f() { return 1; }
var f = function() {return 1;} // 函数标识法
匿名函数
函数本质上和其他变量并无区别,因此可以在没有名字的情况下被使用。
一般的优雅用法:
- 将匿名函数作为参数传递给其他函数,接收方函数就能利用接收的匿名函数完成某些事情;
- 定义某个匿名函数来执行某些一次性的任务。
回调函数
当将函数 A 传递给函数 B,并由 B 来执行 A 时,A 就成了一个回调函数。
如果这时 A 还是一个无名的函数,我就称它为匿名回调函数。
应该什么时候使用回调函数?
- 可以在不做命名的情况下传递函数,意味着可以节省全局变量;
- 可以将一个函数的调用操作委托给另一个函数,意味着可以节省一些代码;
- 有助于提升性能。
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]
自调函数
定义后自行调用
好处在于不会产生任何的全局变量,
缺点是这样的函数无法重复执行。
所以匿名自调函数最适合执行一些一次性的或初始化的任务。
// 匿名函数放进一对括号中,然后外面再紧跟一对括号即可。第二个括号起到的就是立即调用的作用。也是向匿名函数传递参数的地方
(function(name){
console.log('自调'+ name); // 自调完美
})('完美')
私有函数
在一个函数内部定义的另一个函数,就是私有函数
私有函数在外部函数之外是不可见的。
好处:
- 有助于确保全局名字,命名冲突机会很小
- 私有性,可以选择只将一些必要的函数暴露给外部,并保留属于自己的函数。
返回函数的函数
函数始终都会有一个返回值,即便不是显式返回,也会隐式返回一个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();
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