JavaScript 里的函数既可以作为参数传递给一个函数,也可以作为函数返回值返回,其地位跟别的变量一样并无二致,也就是说
———— JavaScript 的函数是一等公民
———— JavaScript 的函数是一等公民
说JS函数是一等公民的大意是说:函数在这个JS里面可以作为参数传递给另一个函数或者作为返回值返回、还可以保存在变量或者其他数据结构里(比如数组这样)
我们称具有以函数作为参数或者返回值是参数的函数为
高阶函数
在数学上,相信你也很熟悉:
y = g(f(x))
,这时候 y 就是我所说的高阶函数# 回调函数 ↵
函数作为参数传递的时候,通常称为 回调函数
这种高阶函数的调用方式你一定很熟悉:
00setTimeout(function(){ 01 console.log('After 200 ms'); 02}, 200);
函数最平凡的意味是流程
———— 可能是一个计算过程,亦或者是一次 http 请求 等等
———— 可能是一个计算过程,亦或者是一次 http 请求 等等
把函数作为参数传递,无疑是想要高阶函数在适当的时刻执行这个流程,并且,期望能给什么参数给回调函数。
异步函数总是需要一个参数作为回调函数,在适当的时候调用。
比如利用
$.ajax
封装一个 login 的 http 操作00function login(user, cb){ 01 $.ajax({ 02 url: 'http://xxxx.com/api/login', 03 data: user, 04 dataType: 'json', 05 success: function(res){ 06 cb(null, res); 07 }, 08 error: function(err){ 09 cb(err); 10 } 11 }); 12} 13 14login({ 15 id: 'eczn', 16 pwd: '0v0,i_am_pwd' 17}, function(err, res){ 18 if (err) { 19 alert('err'); 20 return 21 } 22 23 if (res.code == 200){ 24 alert('login success'); 25 storageAuth(res.data.auth); // 存储登录凭证 用来访问其他api 26 } else { 27 // 帐号密码错误 28 } 29});
更一般的情况:
00function todo(data, cb){ 01 // DELAY 表示延迟,这是异步操作 02 // DELAY 之后执行cb,可以认为是某个异步过程完成后回调一个函数 03 setTimeout(cb, DELAY); 04}
# 传递上下文 ↵
上下文即
this
将函数作为参数传递给另外一个函数可以很轻易的将当前 this 传递到另一个函数里。
00var todo = function(){ 01 // to do something ... 02 // such as: 03 return this.x + this.y; 04} 05 06setTimeout(todo.bind(this), 300);
传递上下文尤其是在
相邻组件间通信
里面很重要,使用他可以让别的组件访问当前组件的状态。# 访问闭包 ↵
一个函数里的变量外部无法访问,但是可以通过设置函数的方式来访问到,比如:
00function Rectangle(width, height){ 01 let HowToGetArea; 02 03 return { 04 setGetArea: function(cb){ 05 HowToGetArea = cb; 06 }, 07 getArea: () => HowToGetArea(width, height) 08 } 09} 10 11let Box_A = Rectangle(4, 8); 12 13Box_A.setGetArea((x, y) => x * y); 14Box_A.getArea(); 15// => 16// 32 17 18Box_A.setGetArea((x, y) => x + y); 19Box_A.getArea(); 20// => 21// 12
# 偏函数 ↵
当函数作为函数值返回的时候,我们可以实现 偏函数
偏函数其实就是固定 (或者说预置) 参数
———— 或者说对于二元函数 f(x, y) 让 y 恒等于 k,从而得到: f(x, k) 这是数学上的偏函数
我在 This, Function 曾提到过的偏函数 logger:
00function logger(logType){ 01 return console.log.bind(console, logType); 02} 03 04var info = logger('[INFO]'); 05var error = logger('[ERROR]'); 06 07info('this is an info'); 08// => 09// [INFO] this is an info 10 11error('this is an error'); 12// => 13// [ERROR] this is an error
再来看看另外一个更明显的例子:
00let add = (x, y) => x + y; 01 02add(2, 3); 03// => 04// 5 05 06function addX(x){ 07 return add.bind(this, x); 08} 09 10let add4 = addX(4); 11 12add4(4); 13// => 14// 8 15 16add4(0); 17// => 18// 4 19 20add4(1); 21// => 22// 5
# 创建闭包 ↵
当某个函数作为函数值返回的时候,无形中创建了闭包。
00function Rectangle(width, height){ 01 // width, height 闭包 02 return { 03 getArea: () => width * height, 04 getColor: () => color, 05 setWH: (newWidth, newHeight) => { 06 width = newWidth, height = newHeight; 07 } 08 } 09} 10 11let box_A = Rectangle(1, 2); 12let box_B = Rectangle(2, 3); 13 14box_A.getArea(); 15// => 16// 2 17 18box_B.getArea(); 19// => 20// 6 21 22box_B.setWH(4, 5); 23box_B.getArea(); 24// => 25// 20
# 惰性求值 ↵
00function add(x, y){ 01 let cache; 02 03 return function(){ 04 // 如果事先有缓存计算结果 05 if (cache) { 06 return cache; 07 } else { 08 // 没有缓存结果(第一次算) 09 let result = x + y; 10 cache = result; // 缓存结果 11 return result; 12 } 13 } 14} 15 16var sum = add(1, 3); 17 18sum(); 19// => 20// 4 21 22sum(); 23// => from cache 24// 4
显然,示例中的
x + y
并不需要多长执行时间,因此惰性求值还有cache缓存结果显得非常多余。但是如果某个计算操作需要很长的时间,可以先不求值,用闭包先保存好参数,需要用的时候再求值,甚至求值的结果还可以缓存下来,下次计算的时候会马上得到结果。