this
JS中的this,老生常谈了,经常做经常错。这边做一个this的总结: this,指向使用它的函数。如果没有函数调用,默认就是windows。 一下是this会被使用的各种情况,进行总结:
- fn() this => window/global
- obj.fn() this => obj
- fn.call(xx) this => xx
- fn.apply(xx) this => xx
- fn.bind(xx) this => xx
- new Fn() this => 形成的实例
- ()=>{} this => 这个函数外面的this
bind,call,apply区别
正如上面的式子所表现的,这三个方法们都可以用来绑定this,事实上在大量的源码中也能看见用call,apply绑定this来调用函数。接下来分别说一下这三个方法的用途
- bind: 这个方法不会立即调用这个函数,而是会生成一个新的函数,并将传入的第一个参数作为this传入函数
name="znz"
function demo01(){
console.log(this.name);
}
demo01() // znz
const demo02 = demo01.bind({name:"xiao"})
demo02() // xiao
- call:这个方法在函数调用的时候,将传入的第一个参数作为this,第二个参数作为函数中真正的第一个参数调用
function demo1(a){
console.log("this.name",this.name) // this.name znz
console.log("name",a.name) // name xiao
}
demo1.call({'name':"znz"}, {"name":"xiao"})
- apply:这个方法跟call一样,在函数调用的时候传入第一个参数作为this调用,不同点在于第二参数需要传入一个数组作为参数。数组中元素按照顺序当做函数的参数顺序执行
function demo1(){
console.log("this.name",this.name) // this.name znz
for (const key in arguments) {
console.log("name",arguments[key].name) // name xiao name nan
}
}
demo1.apply({'name':"znz"}, [{"name":"xiao"},{"name":"nan"}])
执行上下文
对于this的多变,js中有一个抽象概念专门用来阐述————执行上下文:
- 全局执行上下文: 一般来说,代码如果不在函数中时,就处在全局执行上下文中,此时this就绑定为window。一般来说,js每次执行,只会产生一个执行上下文
- 函数执行上下文:
每当一个调用一个函数的时候,js就会进入一个新的执行上下文,此时this就看给它绑定的是什么,如果没有绑定那就是window,如果是函数本身调用的就是函数本身。 - eval执行上下文:
效果同函数执行上下文,此时它会创建属于自身的执行上下文
执行栈
执行栈是一种按照LIFO数据结构编写的栈,它的基本特征是后进先出。 如下代码:
let a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
此时代码的执行顺序为:
- 首先将全局执行上下文入栈
- 发现first函数,将first执行上下文入栈
- 发现second函数,将second执行上下文入栈
- second函数执行完毕,second执行上下文弹栈
- first函数执行完毕,first执行上下文弹栈
- 全局执行上下文弹栈,代码执行完毕
执行顺序
执行栈的执行过程分为创建阶段和执行阶段
创建阶段
在创建阶段中,以下三个量会被定义完成
this,激活变量,词法环境
- this:
之前说执行上下文中已经提到,在全局执行上下文中,是window,在函数执行上下文中,是看谁调用的它 - 激活变量:
如果是window,那么就是全局的变量。而对于函数,当函数被调用但是还没有执行的时候,它会创建一个叫active object的对象来进行存储诸如实参,形参,局部变量等各种类型数据,然后将它推到这个函数执行上下文的顶部 - 词法环境:
也就是作用域链,每个函数在执行创建时,它会将它所在的父变量对象传给一个叫[scope]的变量,当函数执行时,就会查找当前的激活变量对象是否有这个值,如果没有,它就会顺着[scope]中的值找到父变量对象,在父变量对象中寻找需要的值,一直找到全局对象(全局对象存储在
[scope][1][global])为止。这种一层层寻找变量的方法被称为作用域链。
执行阶段
函数根据代码执行,在执行阶段,函数会不断改变激活变量的里面存储的值,直到函数执行结束,整个执行上下文会被全部摧毁
对于let/const/var的解释
现在,可以完整的解释为什么let/const对比var直接的不同点了。
在es6中,js执行的时候,新加了一个[scope][1][script]变量。在var在全局执行上下文声明时,我们会在全局变量中设置变量,但是执行let/const时,我们并不会在全局变量中声明,这就很有效的解决了全局变量这个问题。而在函数中声明变量时,var会将变量进行一个初始化并赋值一个undefined,而let/const并不会赋值,他们会一直等到定义被执行的时候才会进行初始化,所以这就是let/const会出现暂时性的死区的原因。