作用域和闭包
问题:
-
说一下对变量提升的理解
-
说明this几种不同的使用场景
-
创建10个<a>,点击的时候弹出来对应的序号
-
如何理解作用域
-
实际开发中闭包的应用
知识点:
1.执行上下文(execution context)简称EC
概念:执行上下文,简单的理解就是会提前做的一些“准备工作”。存在变量提升的现象。
变量:来看三个小例子,当你直接去打印一个不存在的变量,他自然会报错,但当你在打印一个变量时,没有提前声明,在你需要打印的这句话下面再声明,此时,就会显示出准备工作的作用。会将他提前声明,默认值为undefined。
this:无论在哪里去读取this,都会有值,但是在不同的地方不同。这个比较复杂。
函数声明:
此处有分别为两点,一个是函数,另外一个是函数表达式。
函数声明是会被提前的,但是函数表达式不会。
每一个script里面的变量,函数都会被提前,同时还会一开始就确定this和arguments的值。
- 范围:一段<script>或者一个函数
- 全局:变量定义,函数声明 一段<script>
- 函数:变量定义,函数声明,this,arguments 函数执行之前生成
我们在日常的开发中,最好做到:先定义后执行
函数覆盖:
函数声明和变量声明都会被提升,但是函数声明会覆盖变量声明。
但是当变量已经被赋值的时候,最终的值就会为变量的值。
变量的重复声明是无用的,但是函数的重复声明会覆盖前面的声明
我们在日常开发中,应该避免在同一作用域内重复声明。
2.this
this是一个有趣的点,this要在执行时才能确认值,定义时无法确认。
(1)作为构造函数执行
在构造函数中,会有一个this为空一开始,最后会返回那个被赋好值的this.把this看作一个变量,属性可以扩展。
(2)作为对象属性执行
当作为一个对象有某一个属性时,this就指向那个对象。
(3)作为普通函数执行
作为普通函数,this就指向window
(4)Call apply bind
call、apply、bind的作用是改变函数运行时this的指向。
call第一个参数是this指向,后面为参数,
apply第一个参数是this指向,后面为参数数组,
bind有点像call,同样第一个参数是this指向,从第二个参数开始是接收的参数列表。
但他是方法.bind(对象)。这样子的。
需要注意的是:.bind必须是一个函数表达式,不能是一个函数声明
//构造函数
function Foo(name) {
this = {}
this.name = name
return this
}
var f = new Foo('zhangsan')
//对象属性
var obj = {
name: 'A',
printName: function () {
console.log(this.name)
}
}
obj.printName()
//此时函数作为一个对象的属性,this就是指向该对象
//普通函数
function fn() {
console.log(this) //this === window
//当作为普通函数执行,this就是指向window
}
fn()
//call apply bind
function fn1 (name,age) {
alert(name)
console.log(this)
}
fn1.call({x:100}, 'zhangsan', 20)
//call 第一个参数说明this指向谁,后面的参数为函数需要的参数
//apply几乎一样,只是把后面的参数当作数组来传递
fn1.apply({x:100}, ['zhangsan', 20])
var fn2 = function (name,age) {
alert(name)
console.log(this)
}.bind({y:200})
//定义函数的时候,直接就在后面调用一个bind绑定一个this的对象,然后在后面就直接是执行
fn2('zhangsan',20)
//.bind必须是一个函数表达式,不能是一个函数声明
3.作用域
(1)没有块级作用域
在ES6就有了,let,{}会形成块级作用域。此处我们都讲的是var。
(2)只有函数和全局作用域
这里即使是在函数内部声明,也同样会在外面读到。
if (true) {
var name = 'zhangsan'
}
console.log('name')
//name
var name;
if (true) {
name = 'zhangsan'
}
console.log('name')
//name
4.作用域链
当前作用域没有定义的变量,为自由变量
当前找不到就到函数定义的父级作用域里面找到变量
//作用域链
var a = 100
function fn() {
var b = 200
console.log(a) //自由变量 到父级作用域找,函数定义时的
console.log(b)
}
fn()
//100
//200
var a = 100
function F1() {
b = 200
function F2() {
var c = 300
console.log(a) //自由变量
console.log(b) //自由变量
console.log(c)
}
F2()
}
F1()
5.闭包
函数内部还有函数。
闭包使用的场景
(1)函数作为返回值。
里面的变量若为自由变量,就到父级作用域中去找,函数定义时的父级作用域。
bar函数作为返回值,赋值给f1变量。
(2)函数作为参数传递
作为参数传递时候,
function F1() {
var a = 100
return function bar() {
console.log(a) //自由变量,父级作用域寻找,函数定义时的父级作用域
}
}
var f1 = F1()
var a = 200
f1()
//100
function F1() {
var a = 100
return function () {
console.log(a) //自由变量,父级作用域寻找,函数定义时的父级作用域
}
}
var f1 = F1()
function F2(fn) {
var a = 200
fn()
}
F2(f1)
//100
解答:
- 说一下对变量提升的理解
1.变量定义时,会自动提升到全局变量
2.函数声明(注意和函数表达式的区别),函数声明会提前,函数表达式不会提前
3.<scipt></scirpt>和函数中,要用到的变量都会被提前 - 说明this几种不同的使用场景
1.作为构造函数执行 构造函数里面的this一开始为空,后来赋值后返回
2.作为对象属性执行 哪个对象使用就指向谁
3.作为普通函数执行 指向window
4.call,apply,bind 三个分别可以改变this的指向作用域。 - 创建10个<a>,点击的时候弹出来对应的序号
var i, a for(i = 0; i < 10 ; i++){ a = document.createElement('a') a.innerHTHML = i + '</br>' a.addEventListener('click', function (e) { e.preventDefault() alert(i) }) document.body.appendChild(a) } //自执行函数,就是不用调用,只要定义完成,就立即执行 var i for(i = 0; i < 10 ; i++){ (function (i) { var a = document.createElement('a') a.innerHTHML = i + '</br>' a.addEventListener('click', function (e) { e.preventDefault() alert(i) }) document.body.appendChild(a) })(i) }
- 如何理解作用域
1.自由变量,在这个作用域里找不到的变量
2.作用域链条,即自由变量的查找
3.闭包的两个场景(函数作为返回值,函数作为参数) - 实际开发中闭包的应用
封装变量,收敛权限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) firstLoad(10) firstLoad(20) firstLoad(20) //你在函数外面,根本不可能修改_list的值