# 闭包和高阶函数
# 闭包
# 作用域
- 在函数中搜索一个变量时,如果该函数内没有声明这个变量,那么搜索的过程会随着代码执行环境创建的作用域链往外层逐层搜索,一直搜索到全局对象为止。
- 在函数内用var关键字声明的局部变量,在退出函数时,这些局部变量失去了它们的价值,它们会随着函数调用的结束而被销毁。
# 作用
- 封装变量。将一些不需要暴露在全局的变量封装成“私有变量”
- 延续局部变量的寿命(使用img进行数据上报时丢失数据)
# 内存管理
- 把变量放闭包中和全局作用域,对内存方面的影响是一致的
- 如果需要回收这些变量,可以手动把这些变量设为null
- 使用闭包比较容易形成循环引用。如果闭包的作用域链中保存着一些节点,这时候就可能造成内存泄漏。但这并非闭包的问题,也并非JavaScript的问题。BOM和DOM的对象是使用C++以COM对象的方式实现的,而COM对象的垃圾收集机制采用的是引用计数策略。在基于引用计数策略的垃圾回收机制中,如果两个对象之间形成了循环引用,那么两个对象都无法被回收。解决办法就是将循环引用中的变量设为null
# 高阶函数
# 函数作为参数传递
- 回调函数
- Array.prototype.sort
# 函数作为返回值输出
- 判断数据的类型
var Type = {};
for(var i = 0; type; type = ['String', 'Array', 'Number'][i++];) {
  (function(type) {
    Type['is' + type] = function(obj) {
      return Object.prototype.toString.call(obj) === '[Object ' + type + ']'
    }
  })(type)
}
Type.isArray([])
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- getSingle
var getSingle = function(fn) {
  var ret;
  return function() {
    return ret || (ret = fn.apply(this, arguments))
  }
}
1
2
3
4
5
6
2
3
4
5
6
- 实现AOP
把一些跟核心业务逻辑模块无关的功能抽离出来,再通过“动态织入”的方式掺入业务逻辑模块中。比如日志统计、安全控制、异常处理等。
Function.prototype.before = function(befornFn) {
  var _self = this;
  return function() {
    beforeFn.apply(this, arguments);
    return _self.apply(this, arguments);
  }
}
Function.prototype.after = function(afterFn) {
  var _self = this;
  return function() {
    var ret = _self.apply(this, arguments);
    afterFn.apply(this, arguments);
    return ret;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 函数柯里化(function currying)
一个currying的函数首先接受一些参数,接受这些参数之后,该函数不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中保存起来。
- uncurrying
Function.prototype.uncurrying = function() {
  var self = this;
  return function() {
    var obj = Array.prototype.shift.call(arguments);
    return self.apply(obj, arguments)
  }
}
var push = Array.prototype.push.uncurrying();
(function() {
  push(arguments, 4);
  console.log(arguments); // [1, 2, 3, 4]
})(1, 2, 3);
// 扩展
for(var i = 0, fn; ary = ['push', 'shift', 'forEach']; fn = ary[i++]) {
  Array[fn] = Array.prototype[fn].uncurrying();
}
var obj = {
  length: 3,
  "0": 1,
  "1": 2,
  "2": 3
};
Array.push(obj, 4);
console.log(obj.length) // 4
// 简化
Function.prototype.uncurrying = function() {
  var self = this;
  return function() {
    return Function.prototype.call.apply(self, arguments)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- 节流
var throttle = function(fn, timeout) {
  var _self = fn;
  var firstTime = true;
  var timer;
  return function() {
    var args = arguments;
    var _me = this;
    // 第一次调用,不需要延迟执行
    if (firstTime) {
      firstTime = false;
      return _self.apply(_me, args);
    }
    if (timer) {
      return;
    }
    timer = setTimeout(function() {
      clearTimeout(timer);
      timer = null;
      _self.apply(_me, args);
    }, timeout || 500);
  }
}
window.onresize = throttle(function() {
  console.log('执行了')
}, 500)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- 分时函数 
- 惰性加载函数 
// 浏览器差异,嗅探
// 1.每次都需要执行一次if else
var addEvent = function(elem, type, handler) {
  if (window.addEventListener) {
    return elem.addEventListener(type, handler, false);
  }
  if (window.attachEvent) {
    return elem.attachEvent('on' + type, handler);
  }
}
// 2. 先行定义,如果没有使用过的话就是多余的操作
var addEvent = (
  if (window.addEventListener) {
    return function(elem, type, handler) {
      elem.addEventListener(type, handler, false);
    }
  }
  if (window.attachEvent) {
    return function(elem, type, handler) {
      elem.attachEvent('on' + type, handler);
    }
  }
)()
// 3. 惰性载入,第一次使用后会重写函数
var addEvent = function(elem, type, handler) {
  if (window.addEventListener) {
    addEvent = function(elem, type, handler) {
      elem.addEventListener(type, handler, false);
    }
  } else if (window.attachEvent) {
    addEvent = function(elem, type, handler) {
      elem.attachEvent('on' + type, handler);
    }
  }
  addEvent(elem, type, handler)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42