
本文是 重温基础 系列文章的第四篇。 今日感受:常怀感恩之心,对人对己。
本章节复习的是JS中的基础组件之一,函数,用来复用特定执行逻辑。
1.定义函数
定义函数有两种方法:函数声明 和 函数表达式 :
1.1 函数声明
也成为函数声明,通常格式为:
-
function f (a){ -
return a + 1; -
}
解释:这里声明一个函数 f ,并传入一个参数 a ,当函数执行以后,通过 return 关键字返回了 a+1的值。
参数:
当传入的参数是一个数字/字符串等具体的值的时候,若参数的值被改变,不会影响到全局或调用函数。
但如果参数是个对象,若函数内改变的这个参数的属性,则函数外部的这个参数原始的值会被修改。
-
var leo = { -
age:20 -
} -
function f(obj){ -
obj.age = 15; -
obj.name = 'leo'; -
} -
f(leo); -
console.log(leo); //{age: 15, name: "leo"}
1.2函数表达式
通过定义一个匿名的函数,来赋值给一个变量,通过这个变量来调用这个函数。
-
var f = function (a){ -
return a + 1; -
}
但是函数表达式也可以提供函数名,用于函数内部调用,并指代本身,也可以作为调试器堆栈跟踪中识别该函数。
-
var f = function g(a){ -
return n < 2 ? 1 : a*g(a-1); -
}
另外,函数表达式声明可以用来根据不同条件,来定义一个函数:
-
var f; -
if(a == 1){ -
f = function (){ -
return 'when a == 1'; -
} -
}else { -
f = function (){ -
return 'when a != 1'; -
} -
}
2.函数调用
函数定义完成后不会自动执行,需要我们通过函数名称来调用,才能真正执行:
-
var f = function (){ -
console.log('ok'); -
} -
f(); // 'ok'
另外,函数也可以调用自身,这就是递归过程:
-
function f (n){ -
if( n==0 || n==1) { -
return 1; -
}else { -
return n * f(n-1); -
} -
} -
// 三目运算 -
function f (n){ -
return (n==0 || n==1)?1: n*f(n-1); -
}
3.函数作用域
由于函数只在函数的内部有定义,所以函数内部定义的变量在函数外部不能访问,函数内部就是这个函数的作用域。
当一个父级函数内,还定义了一个子级函数,则这个子级函数可以访问父级函数定义的变量。
-
// 全局作用域 global scope -
var a = 1, b = 2; -
function f (){ -
return a + b; -
} -
f(); // 3 -
function g(){ -
var a1 = 'leo', b1 = 'pingan'; -
function hi (){ -
return a1 + '和' + b1 -
} -
return hi(); -
} -
g(); // 'leo和pingan'
3.1 闭包
闭包是 JavaScript 中最强大的特性之一,并且JS允许函数嵌套。
在一个函数内部在嵌套一个函数,而嵌套的这个函数对外面的函数是私有的,则形成一个闭包,闭包是一个可以自己拥有独立的环境和变量的表达式,通常是函数。
理解一下,前面说的内部函数可以调用外部函数的变量和方法,那么可以这么理解:闭包的函数继承了父级容器函数的参数和变量,即内部函数包含外部函数的作用域。
总结一下:
- 内部函数只能在外部函数中访问;
- 内部函数形成闭包:可以访问外部函数的参数和变量,但外部函数却不能使用这个内部函数的参数和变量;
-
function f(a) { -
function g(b){ -
return a + b; -
} -
return g; -
} -
var a1 = f(5); // ƒ g(b){ return a + b; } -
var a2 = a1(6); // 11 -
var a3 = f(5)(6); // 11
闭包可以给内部函数的变量提供一定的安全保障。
另外,闭包还有复杂的用法:
-
var f = function (name){ -
var age ; -
return { -
setName : function (newName){ -
name = newName; -
}, -
getName : function (){ -
return name; -
}, -
getAge : function (){ -
return age; -
}, -
setAge : function (newAge){ -
age = newAge; -
} -
} -
} -
var leo = f('leo'); -
leo.setName('pingan'); -
leo.setAge(20); -
leo.getName(); // 'pingan' -
leo.getAge(); // 20
3.2命名冲突
在同一个闭包作用域下若参数或变量名相同,产生冲突,则优先使用作用域最近:
-
function f(){ -
var a = 1; -
function g(a){ -
return a + 1; -
} -
return g; -
} -
f()(3); // 4
4.arguments对象
函数的实际参数会被保存在一个类数组对象 arguments 对象中,通过索引访问具体的参数:
-
var a = arguments[i]
arguments的索引从0开始,也有 arguments.length属性获取长度。
当我们不知道参数的数量的时候,可以使用 arguments.length来获取实际传入参数的数量,再用 arguments对象来获取每个参数。
例如:
-
// 拼接所有参数为一个字符串 -
// 参数 s 为分隔符 -
function f( s ){ -
var text = ''; -
for(var i = 0;i<= arguments.length; i++){ -
text += arguments[i] + s ; -
} -
return text; -
} -
f('--','leo','pingan','robin'); -
// "----leo--pingan--robin--undefined--" -
f('**','leo','pingan','robin'); -
// "****leo**pingan**robin**undefined**"
5.函数参数
ES6开始,新增两个类型的参数:默认参数和剩余参数:
5.1默认参数
若函数没有传入参数,则参数默认值为 undefined,通常设置参数默认值是这样做的:
-
// 没有设置默认值 -
function f(a, b){ -
b = b ? b : 1; -
return a * b; -
} -
f(2,3); // 6 -
f(2); // 2 -
// 设置默认值 -
function f(a, b = 1){ -
return a * b; -
} -
f(2,3); // 6 -
f(2); // 2
5.2剩余参数
可以将参数中不确定数量的参数表示成数组,如下:
-
function f (a, ...b){ -
console.log(a, b); -
} -
f(1,2,3,4); // a => 1 b => [2, 3, 4]
6.箭头函数
函数箭头表达式是ES6新增的函数表达式的语法,也叫胖箭头函数,变化:更简洁的函数和 this。
- 更简洁的函数
-
// 有1个参数 -
let f = v => v; -
// 等同于 -
let f = function (v){return v}; -
// 有多个参数 -
let f = (v, i) => {return v + i}; -
// 等同于 -
let f = function (v, i){return v + i}; -
// 没参数 -
let f = () => 1; -
// 等同于 -
let f = function (){return 1}; -
let arr = [1,2,3,4]; -
arr.map(ele => ele + 1); // [2, 3, 4, 5]
- this 注意这几点:
- 箭头函数内的
this总是指向定义时所在的对象,而不是调用时。 - 箭头函数不能当做构造函数,即不能用
new命令,否则报错。 - 箭头函数不存在
arguments对象,即不能使用,可以使用 rest参数代替。 - 箭头函数不能使用
yield命令,即不能用作Generator函数。
一个简单的例子:
-
function Person(){ -
this.age = 0; -
setInterval(() => { -
this.age++; -
}, 1000); -
} -
var p = new Person(); // 定时器一直在执行 p的值一直变化
参考资料
1.MDN 函数











