闭包
闭包是一个难懂又必须征服的概念。闭包的形成与变量的作用域以及变量的生存周期密切相关。
变量的作用域
当在函数中声明一个变量的时候,如果该变量前面没有var关键字,这个变量就会成为全局变量,这当然是一种容易造成命名冲突的做法。
另一种就是用var关键字声明函数中的变量,这个时候的变量就是局部变量,只有在该函数内部才能访问到这个变量,在函数外部访问不到的
代码
var func = function () {var a = 1console.log(a)}func()console.log(a)
输出
1Uncaught ReferenceError: a is not defined
代码
var func = function () {a = 1console.log(a)}func()console.log(a)
输出
11
变量的生命周期
除了变量的作用域之外,另外一个跟闭包有关的概念是变量的生存周期。
对于全局变量来说,全局变量的生存周期当然是永久的,除非我们主动销毁这个全局变量。
而对于在函数内用 var 关键字声明的局部变量来说,当退出函数时,这些局部变量即失去了 它们的价值,它们都会随着函数调用的结束而被销毁:
代码
var func = function () {var a = 1console.log(a)}func()
输出
1
闭包代码
var func = function () {var a = 1;return function(){a++console.log(a)}}var f = func()f()f()f()f()
输出
2345
闭包的作用
封装变量
闭包可以帮助把一些不需要暴露在全局的变量封装成“私有变量”。假设有一个计算乘积的
简单函数
代码
var mult = function () {var a = 1for (var i = 0; i < arguments.length; i++) {a = a * arguments[i]}return a}mult(1,2,3,4,5)
输出
120
加入缓存代码
var cache = {}var mult = function () {var args = Array.prototype.join.call(arguments, ',')if (cache[args]) {return cache[args]}var a = 1for (var i = 0; i < arguments.length; i++) {a = a * arguments[i]}return (cache[args] = a)}console.log(mult(1, 2, 3))console.log(mult(1, 2, 3))console.log(cache)console.log(mult(3, 2, 1))console.log(cache)
输出
66{1,2,3: 6}6{1,2,3: 6, 3,2,1: 6}
上面的代码暴露在了全局作用域中
var mult = (function () {var cache = {}return function () {var args = Array.prototype.join.call(arguments, ',')if (cache[args]) {return cache[args]}var a = 1for (var i = 0; i < arguments.length; i++) {a = a * arguments[i]}return (cache[args] = a)}})()console.log(mult(1, 2, 3))console.log(mult(1, 2, 3))console.log(mult(3, 2, 1))
输出
666
延续局部变量的寿命
var report = function (src) {var img = new Image()img.src = src}report('http://xxxx')
问题
但是通过查询后台的记录我们得知,因为一些低版本浏览器的实现存在 bug,在这些浏览器 下使用 report 函数进行数据上报会丢失 30%左右的数据,也就是说,report 函数并不是每一次 都成功发起了 HTTP 请求。丢失数据的原因是 img 是 report 函数中的局部变量,当 report 函数的 调用结束后,img 局部变量随即被销毁,而此时或许还没来得及发出 HTTP 请求,所以此次请求 就会丢失掉。
现在我们把 img 变量用闭包封闭起来,便能解决请求丢失的问题:
var report = (function () {var imgs = []return function (src) {var img = new Image()imgs.push(img)img.src = src}})()
闭包与内存管理
闭包是一个非常强大的特性,但人们对其也有诸多误解。一种耸人听闻的说法是闭包会造成内存泄露,所以要尽量减少闭包的使用。
局部变量本来应该在函数退出的时候被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去。从这个意义上看,闭包的确会使一些数据无法被及时 销毁。使用闭包的一部分原因是我们选择主动把一些变量封闭在闭包中,因为可能在以后还需要 使用这些变量,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的,这里并不能说成是内存泄露。如果在将来需要回收这些变量,我们可以手动把这些变量设为 null。
跟闭包和内存泄露有关系的地方是,使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些 DOM 节点,这时候就有可能造成内存泄露。但这本身并非闭包的问题,也并非 JavaScript 的问题。在 IE 浏览器中,由于 BOM 和 DOM 中的对象是使用 C++以 COM 对象 的方式实现的,而 COM 对象的垃圾收集机制采用的是引用计数策略。在基于引用计数策略的垃圾回收机制中,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收,但循环引用造成的内存泄露在本质上也不是闭包造成的。
同样,如果要解决循环引用带来的内存泄露问题,我们只需要把循环引用中的变量设为 null 即可。将变量设置为 null 意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运 行时,就会删除这些值并回收它们占用的内存。
高阶函数
高阶函数值指至少满足下列条件之一的函数。
- 函数可以作为参数被传递
- 函数可以作为返回值输出
函数可以作为参数被传递
回调函数
```javascript var getUserInfo = function (userId, callback) { $.ajax(‘http://xxxxxxx?userId=‘ + userId, function (data) { callback(data) }) }
getUserInfo(123, function (data) { console.log(data) })
回调函数的应用不仅只在异步请求中,当一个函数不适合执行一些请求时,我们也可以把这 些请求封装成一个函数,并把它作为参数传递给另外一个函数,“委托”给另外一个函数来执行。<br />比如,我们想在页面上创建100个div节点,然后把这些节点设置为隐藏<a name="m5593"></a>##### 原来代码```javascriptvar appendDiv = function () {for (var i = 0; i < 100; i++) {var div = document.createElement('div')dix.innerHTML = idocument.body.appendChild('div')div.style.display = 'none'}}appendDiv()
把div.style.display = 'none'写在方法里面显得有些不合理
委托后代码
var appendDiv = function (callback) {for (var i = 0; i < 100; i++) {var div = document.createElement('div')dix.innerHTML = idocument.body.appendChild('div')if (typeof callback === 'function') {callback(div)}}}appendDiv(function (node) {node.style.display = 'none'})
Array.prototype.sort
[1, 2, 3, 4, 5, 6].sort(function (a, b) {return a - b})// 从大到小排序
逗号运算符
- 上面的代码我写错了
- 逗号运算符,返回最后一个
[(1, 2, 3, 4, 5, 6)].sort(function (a, b) {return a - b})// 最后返回的是:6
[1, 2].reduce((t,c) => (t+=c, t), 0)
函数作为返回值输出
判断数类型
代码
```javascript var isString = function (obj) { return Object.prototype.toString.call(obj) === ‘[object String]’ }
var isArray = function (obj) { return Object.prototype.toString.call(obj) === ‘[object Array]’ }
var isNumber = function (obj) { return Object.prototype.toString.call(obj) === ‘[object Number]’ }
- 我们发现大多数的代码都是相同的<a name="lUMXh"></a>##### 优化后的代码```javascriptvar isType = function (type) {return function (obj) {return Object.prototype.toString.call(obj) === '[object ' + type + ']'}}var isString = isType('String')
高阶函数的其他应用
函数节流
- throttle
分时函数
一个例子是创建 WebQQ 的 QQ 好友列表。列表中通常会有成百上千个好友,如果一个好友 用一个节点来表示,当我们在页面中渲染这个列表的时候,可能要一次性往页面中创建成百上千 个节点。
在短时间内往页面中大量添加 DOM 节点显然也会让浏览器吃不消,我们看到的结果往往就 是浏览器的卡顿甚至假死。
代码
var arr = []for (var i = 1; i < 1000; i++) {arr.push(i)}var renderFriendList = function (data) {for (var i = 0; i < data.length; i++) {var div = document.createElement('div')div.innerHTML = idocument.body.appendChild(div)}}renderFriendList(arr)
- 优化,每200毫秒创建8个节点
优化后代码
var timeChunk = function (arr, fn, count) {var objvar tvar len = arr.lengthvar start = function () {for (var i = 0; i < Math.min(count || 1, arr.length); i++) {var obj = arr.shift()fn(obj)}}return function () {t = setInterval(function () {if (arr.length === 0) {return clearInterval(t)}start()}, 200)}}
测试代码
var ary = []for (var i = 1; i <= 1000; i++) {ary.push(i)}var renderFriendList = timeChunk(ary,function (n) {var div = document.createElement('div')div.innerHTML = ndocument.body.appendChild(div)},8)renderFriendList()
惰性加载函数
