匿名函数:

说明: 匿名函数就是没有名字的函数,闭包是可以访问一个函数作用域里变量的函数


// 普通函数
function userInfo(name){
    return name
}
alert(userInfo('李满满'))
// 匿名函数
var userInfo = function(name){
    return name
}
alert(userInfo('李满满'))
// 自我调用
(function(name){alert(name)})('李满满')
// 闭包函数
function userInfo(name){
    var userName = name
    return function(){
        alert(userName)
    }
}
userInfo('李满满')()


深入闭包:

说明: 闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量

特性: 闭包可以把局部变量驻留在内存中,可以避免使用全局变量(函数内部使用全局变量会导致全局变量污染),使用闭包可以实现类似全局变量的效果但局部变量函数又可以和外部环境保持隔离


// 累加效果 - 全局实现
var count = 1
function add(){
    this.count += 1
}
add()
alert(count)
// 累加效果 - 闭包实现
function add(){
    var count = 1
    return function(){
        count += 1
        return count
    }
}
res = add()
alert(res())
alert(res())

注意: 由于闭包里作用域返回的局部变量资源不会被立即销毁回收,所以可能会占用更多内存,过度的使用会导致性能下降,所以在非常有必要的时候才使用闭包或者在是使用完毕后设置为null标记回收


闭包THIS:

说明: 在没有绑定任何运行环境的情况下,闭包由于不属于任何对象的属性和方法,所以通常运行时指向window,当然我们也可以通过冒充对象,改变指向,或是bind来绑定运行环境


var userInfo = {
    sayEnv: function(){
        return function(){
            alert(this)
        }
    }
}
// 此时闭包中的this指向window
userInfo.sayEnv()()
// 冒充对象,让闭包this指向userInfo
userInfo.sayEnv().call(userInfo)
// 借用this,让闭包this指向userInfo
var userInfo = {
    sayEnv: function(){
        var _this = this
        return function(){
            alert(_this)
        }
    }
}
userInfo.sayEnv()()
// 使用bind,让闭包this指向userInfo
var userInfo = {
    sayEnv: function(){
        return function(){
            alert(this)
        }.bind(this)
    }
}
userInfo.sayEnv()()


内存泄漏:

说明: 虽然多个浏览器对于垃圾回收机制不同的处理,导致内存泄漏的问题,常常是由于闭包循环引用对象或变量,导致在内存中长期驻留,强烈推荐大家循环引用时赋值给变量然后在最后将循环引用的变量设置为null标记删除来避免内存泄漏的问题


块作用域:

说明: Js中没有块儿级作用域的特性,所以出了块儿范围的变量i还可以在块儿外部访问,此时可通过模仿块级作用域来避免这个问题


for(var i=1; i<=Math.floor(Math.random()*(10+1)); i++){}
// i出了块儿范围应该被销毁,但是却没有销毁,而且可以被访问
console.log(i)
/*
 * 模仿块儿级作用域(私有作用域),让变量出了作用范围就销毁
 */
(function(){
    for(var i=1; i<=Math.floor(Math.random()*(10+1)); i++){}
})()
console.log(i)

说明: 使用了块儿级作用域(私有作用域)后,匿名函数中定义的任何变量,都会在执行结束时销毁,因此使用此种方式可以减少闭包占用的内存问题,因为没有指向匿名函数的引用,只要函数执行完毕,就可以立即毁其作用域链了


私有变量:

说明: 任何函数中定义的变量,都可以认为是私有变量,因为不能在外部访问,因此可通过对构造函数定义对外公开访问这些私有属性的方法来实现私有变量的效果

function User(){
    // 构造函数中私有变量
    var name= '李满满'
    // 私有变量对外访问接口
    this.getName = function(){
        return name
    }
    // 私有变量对外设置接口
    this.setName = function(value){
        name = value
    }
}
limanman = new User()
console.log(limanman.getName())
limanman.setName('刘珍珍')
console.log(limanman.getName())

问题: 如上使用构造函数内部声明私有变量外加实例对外访问设置接口来实现封装会导致所有的实例都会创建一遍接口对象而其实所有的实例的访问设置接口都一样的,此时我们可以通过原型对象实现自动继承优化


// 放在块儿级作用域
(function(){
    var name = ''
    // 巧用全局构造函数
    User = function(value){
        name = value
    }
    // 设置原型对象的获取接口
    User.prototype.getName = function(){
        return name
    }
    // 设置原型对象的设置接口
    User.prototype.setName = function(value){
        name = value
    }
})()
// 实例化全局构造函数User
limanman = new User('李满满')
console.log(limanman.getName())
limanman.setName('刘珍珍')
console.log(limanman.getName())

说明: 使用了原型模式+块儿作用域方法实现了访问设置接口共享于多个实例之间,而不需要每次实例化都重新创建,而且块儿级作用域内的东西对外都是隔离的


模块模式:

说明: 上面采用的都是构造函数的方式创建私有变量和特权方法,而对象字面量的方式常采用模块模式来创建


function user(value){
    name = value
    return {
            getName: function(){
                return name
        },
            setName: function(value){
                name = value
        }
    }
}
limanman = user('李满满')
console.log(limanman.getName())
limanman.setName('刘珍珍')
console.log(limanman.getName())