作用域与闭包

作用域

全局作用域只有一个,每个函数又都有作用域(环境)。

  • 编译器运行时会将变量定义在所在作用域
  • 使用变量时会从当前作用域开始向上查找变量
  • 作用域就像攀亲亲一样,晚辈总是可以向上辈要些东西

使用规范

作用域链只向上查找,找到全局window即终止,应该尽量不要在全局作用域中添加变量。

函数被执行后其环境变量将从内存中删除。下面函数在每次执行后将删除函数内部的total变量。

function count() {
    let total = 0
}

count()

函数每次调用都会创建一个新作用域

let site = 'mkimq'

function a() {
    let web = 'mkimq.com'

    function b() {
        let url = 'wwww.mkimq.com'
        console.log(web)
        console.log(site)
    }

    b()
}

a()

如果子函数被使用时父级环境将被保留

function fn() {
	let n = 1

	return function() {
		let b = 1

		return function() {
			console.log(++n)
			console.log(++b)
		}
	}
}

const a = fn()()

a()
a()

构造函数也是很好的环境例子,子函数被外部使用父级环境将被保留

function User() {
	let a = 1
	this.show = function() {
		console.log(a++)
	}
}

const a = new User()

a.show()
a.show()

const b = new User()
b.show()

let/const

使用 let/const 可以将变量声明在块作用域中(放在新的环境中,而不是全局中)

{
    let a = 1
}

console.log(a)

if (true) {
    var i = 2
}

console.log(i)

在没有let/const 的历史中使用以下方式产生作用域

var arr = []

for (var i = 0; i < 10; i++) {
    (function(a) {
        arr.push(() => a)
    })(i)
}

console.log(arr[3]())

闭包使用

闭包指子函数可以访问外部作用域变量的函数特性,即使在子函数作用域外也可以访问。如果没有闭包那么在处理事件绑定,异步请求时都会变得困难。

  • JS中的所有函数都是闭包
  • 闭包一般在子函数本身作用域以外执行,即延伸作用域

基本示例

function fn() {
	let name = 'mkimq'

	return function() {
		return name
	}
}

const mk = fn()
console.log(mk())

闭包问题

  1. 内存泄漏

闭包特性中上级作用域会为函数保存数据,从而造成的如下所示的内存泄漏问题

<body>
    <div desc="mkimq">在线文档</div>
    <div desc="url">站点</div>
</body>
<script>
let divs = document.querySelectorAll('div')
divs.forEach(function(item) {
    item.addEventListener('click', function() {
        console.log(item.getAttribute('desc'))
    })
})
</script>

下面通过清除不需要的数据解决内存泄漏问题

let divs = document.querySelectorAll('div')
divs.forEach(function(item) {
    const desc = item.getAttribute('desc')
    item.addEventListener('click', function() {
        console.log(desc)
    })
    item = null
})
  1. this指向

this 总是指向调用该函数的对象,即函数在搜索this时只会搜索到当前活动对象。

贡献者: mankueng