闭包和作用域
1、执行上下文
在一段 JS 脚本(即一个`<script>`标签中)执行之前,会先创建一个**全局执行上下文**环境,先把代码中即将执行的(内部函数的不算,因为不知道函数何时执行)变量、函数声明(和“函数表达式”的区别)都拿出来。变量先暂时赋值为`undefined`,函数则先声明好可使用。这一步做完了,然后再开始正式执行程序。这是在代码执行之前才开始的工作。
另外,一个函数在执行之前,也会创建一个**函数执行上下文**环境,跟**全局上下文**差不多,不过**函数执行上下文**中会多出`this` `arguments`和函数的参数。
总结一下
- 范围:一段`<script>`或者一个函数
- 全局:变量定义,函数声明
- 函数:变量定义,函数声明,this,arguments
#参考实例
console.log(a) // undefined var a = 100 fn('zhangsan') // 'zhangsan' 20 function fn(name) { age = 20 console.log(name, age) var age }
2、 this
定义:`this`的值是在执行的时候才能确认,定义的时候不能确认!
因为`this`是执行上下文环境的一部分,而执行上下文需要在代码执行之前确定,而不是定义的时候。
var a = {
name: 'A',
fn: function () {
console.log(this.name)
}
} //this作为对象属性执行
a.fn() // this === a
a.fn.call({name: 'B'}) // this === {name: 'B'}
var fn1 = a.fn
fn1() // this === window
`this`执行会有不同,主要集中在这几个场景中
- 作为构造函数执行
- 作为对象属性执行
- 作为普通函数执行
- 用于`call` `apply` `bind`
//作为构造函数执行,Foo是构造函数 function Foo (name) { this.name = name console.log(this.name) } var fn1 = new Foo("zhangsan") fn1(); ----this指向fn1 //作为一个对象属性 var a = { name: 'A', fn: function () { console.log(this.name) } } a.fn(); ---this指向 a //作为普通函数执行,函数inner独立调用(不论这个函数在哪调用),this默认指向到window function outer(){ function inner(){ console.log(this === window); } inner(); } outer(); ---this指向window //call、apply、bind调用:三者的第一个参数都是this要指向的对象; //bind 只是返回函数,还未调用,所以如果要执行还得在后面加个();call、apply 是立即执行函数; func.call(obj,value1,value2); //call 后面的参数用逗号隔开 func.apply(obj,[value1,value2]); //apply 后面的参数以数组的形式传入 func.bind(obj,value1,value2)(); func.bind(obj)(value1,value2); //bind则可以在指定对象的时候传参,和 call 一样,以逗号隔开,也可以在执行的时候传参,写到后面的括号中
3、作用域
JS 没有块级作用域,只有全局作用域和函数作用域。
作用域就是一个独立的地盘,让变量不会外泄、暴露出去。下面的`name`就被暴露出去了,因此,JS 没有块级作用域
if (true) {
var name = 'zhangsan'
}
console.log(name)
全局作用域就是最外层的作用域,如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么他们就全部都在全局作用域中。这样的坏处就是很容易装车。
函数作用域是放在`(function(){....})()`中的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。
作用域链是向父级作用域寻找,一层一层向上寻找,直到找到全局作用域还是没找到当前的自由变量,就宣布放弃的这种一层一层的关系。
var a = 100 function F1() { var b = 200 function F2() { var c = 300 console.log(a) console.log(b) //‘a','b'都为【自由变量】 console.log(c) } F2() } F1()
4、闭包
自由变量将从作用域链中去寻找,但是【依据的是函数定义时的作用域链,而不是函数执行时】,这就是闭包。
闭包主要有两个应用场景:
- 函数作为返回值
- 函数作为参数传递
#函数作为返回值 function F1() { var a = 100 return function () { console.log(a) } } var f1 = F1() var a = 200 f1() #函数作为参数传递 function F1() { var a = 100 return function () { console.log(a) } } function F2(f1) { var a = 200 console.log(f1()) } var f1 = F1() F2(f1)
5、实际开发中闭包的使用场景
闭包的实际应用,主要是用来封装变量。即把变量隐藏起来,不让外面拿到和修改。
在 isFirstLoad 函数外面,根本不可能修改掉 _list 的值。
function isFirstLoad() { var _list = [] return function (id) { if (_list.indexOf(id) >= 0) { return false } else { _list.push(id) return true } } } // 使用 var firstLoad = isFirstLoad() firstLoad(10) // true firstLoad(10) // false firstLoad(20) // true
6、创建 10 个`<a>`标签,点击的时候弹出来对应的序号
#错误的写法 【监听的i是全局作用域的变量:“自由变量“,因此每次点击都是10】 var i, a for (i = 0; i < 10; i++) { a = document.createElement('a') a.innerHTML = i + '<br>' a.addEventListener('click', function (e) { e.preventDefault() alert(i) //自由变量,要去父作用域获取值 }) document.body.appendChild(a) } #正确的写法【每个i都生成一个函数】 var i for (i = 0; i < 10; i++) { (function (i) {
//生成了一个函数作用域 var a = document.createElement('a') a.innerHTML = i + '<br>' a.addEventListener('click', function (e) { e.preventDefault() alert(i) }) document.body.appendChild(a) })(i) }