javascript 闭包

240 阅读3分钟

闭包:创建闭包的常见方式,就是在一个函数内部创建另一个函数;例如:①

    /**	 ①
    	如下例子的value1和value2是内部函数(匿名函数)中的代码,这两行代码访问了外部函数中的变量propertyName;即使这个内部函数(匿名函数)被返回了,在其它地方调用了,它也还可以访问变量propertyName。
        这是因为:内容内部函数(匿名函数)的作用域链中包含createComparisonFunction()的作用域。
        	为什么会包含createComparisonFunction()的作用域:根据作用域链的定义,当某个函数被调用时,它会创建一个对应的作用域;
            在作用域链中,它的外部函数是第二位执行。外部函数的外部函数时第三位执行,直到作用域链的终点全局执行环境。
            
            所以在匿名函数从createComparisonFunction()中被返回后,它的作用域链被初始化为包含createComparisonFunction()函数的活动对象和全局变量对象。
            这样匿名函数就能访问createComparisonFunction()中定义的对象,并且函数执行完后,其活动对象也不会被销毁;
            直到匿名函数被销毁,createComparisonFunction()中定义的对象才会被销毁,例如:②
    */
    function createComparisonFunction(propertyName) {
    	return function(object1, object2) {
        	const value1 = object1[propertyName]
        	const value2 = object2[propertyName]
            return 'value1: ' + value1 + ';value2:' + value2
        }
    }
    
    /**	②
    */
    let compareNames = createComparisonFunction('name')
    const result = compareNames({ name: 'red' }, { name: 'black' })
    console.log(result)
    compareNames = null

1、闭包与变量

	闭包这种作用域链的配置机制有一个值得注意的副作用,即闭包只能取得函数中任何变量的最后一个值。例子:①
    
    解决方案:我们可以通过创建另一个匿名函数强制让闭包的行为符合预期。例子:②
    
    // ①
    function createFunctions() {
    	var result = new Array()
        
        for (var i = 0; i < 10; i++) {
        	result[i] = function() {
            	return i
            }
        }
        return result
    }
    const result1 = createFunctions()
    console.log(result1[0](), result1[1](), result1[2]()) // 10 10 10
    
    // ②
    function  createFunctions2() {
    	var result = new Array()
        
        for (var i = 0; i < 10; i++) {
            result[i] = function(num) {
                return num
            }(i)
        }
        return result
    }

2、关于this对象

	当某个函数被作为某个对象的方法调用时,this等于那个对象;在全局函数中,this等于window。
    但是,在闭包中使用this对象也可能会导致一些问题。例子:①
    	在这个例子中,当执行object2.getNameFun()()时,this最终指向的是window而不是object2
        
        解决方案:把this对象保存在一个闭包能访问到的变量里,就可以让闭包访问到该对象了;(或者通过apply,call)例子:②
    
    // ①
    var name2 = 'This Window'
    var object2 = {
    	name2: 'My object',
        getNameFun: function () {
        	return function() {
            	return this.name2
            }
        }
    }
    console.log(object2.getNameFun()())
    
    // ②
    var name21 = 'This Window'
    var object21 = {
        name21: 'My Object',
        getNameFun: function () {
        	// 通过创建that变量解决
            var that = this
            return function () {
                return that.name21
            }
        },
        getNameApply: function () {
            return function () {
                return this.name21
            }
        }
    }
    console.log(object21.getNameFun()())
    console.log(object21.getNameApply()())
    
    var apply21 = object21.getNameApply().apply(object21, [])
    console.log(apply21)

3、内存泄漏

	具体来说,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁;例子:①
    
    	该例子,创建了一个作为element元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用。
        由于匿名函数保存了一个对assignHandler()的活动对象的引用,因此就会导致无法减少element的引用数。
        只要匿名函数存在,element的引用至少也是1,因此它所占用的内存就永远不会被回收。
        
        解决方案:②
        必须要记住:闭包会引用包含函数的整个活动对象,而其中也包含element,即使不直接引用,包含函数的活动对象中也仍然会保存一个引用。
        把element设置为null,就能解除对DOM对象的引用
    
    // ①
    function assignHandler () {
        var element = document.getElementById('xxx')
        element.onclick = function () {
            console.log(element.id)
        }
    }

    // ②
    function assignHandler2 () {
        var element = document.getElementById('xxx')
        var id = element.id
        element.onclick = function () {
            console.log(id)
        }
        element = null
    }