前言
this这个玩意说实话,要是真的详细解释的话,三天三夜都说不完,而在各种应接不暇的书籍当中,对this也有较多的详解。可以说this在JavaScript当中简直是无处不在,应用范围十分广泛,下面我们就来好好了解一下this
“这个”this
定义
我们都知道js执行的时候会产生一个全局上下文,而函数在执行的时候也会产生函数的执行上下文。当一个函数被调用时,会创建一个执行上下文,这个执行上下文包含 函数在哪里被调用(调用栈),函数的调用方法以及传入的参数,this就是用来记录这个执行上下文信息的一个属性。
this“归属”
那么this的归属是谁呢?
简单一句话来概括,当前方法的最直接的环境上下文,采用就近原则。也就是函数的执行在调用栈中处在哪里,函数的this指向哪里 。(不考虑严格模式)
全局上下文
console.log(this === window) //true
var a = 4
console.log(this.a) //4
在浏览器中,无论是否在严格模式下,我们的this在全局上下文都是指向我们的window(因为在全局上下文被调用),而由于在node环境当中没有window这个概念,所以打印结果就会是undefined
函数上下文
function foo(){
//use strict 严格模式
console.log(this.a) //2
}
var a = 2
foo()
foo()函数在全局被调用,所以foo函数当中的this同样也是指向window,那么this.a相当于是window.a,输出的结果自然是2。
在这里是默认绑定,而在严格模式下,this和他调用的作用域没有关系,this就会被绑定为undefined
我们再看一段代码,深刻理解一下this的指向
var a = 1
function baz(){
//此刻调用的位置是调用栈的全局 this 指向全局window
let a = 2
console.log('this.a');
bar()
}
function bar(){
//调用位置是调用栈的 baz -> bar this指向baz
let a = 3
console.log('this.a');
foo()
}
function foo(){
//调用位置是调用栈的 baz -> bar -> foo this指向bar
let a = 4
console.log('this.a');
}
baz() // baz调用
代码分析
我们来分析一下上述代码,看看三个打印,思考一下,打印出来的this.a是什么。
在全局上下文当中,我们定义了baz()、bar()、foo()三个函数,baz()在全局上下文中被调用,所以它的this指针指向window;而bar()在baz()当中被调用,this指向的是baz函数的执行上下文;而foo()同理,它内部的this指向了bar()的执行上下文,那么这么层层嵌套下来,最终他们的this都等同于指向了window,也就是说打印的时候this.a等同于window.a(但是事实上不是这样,this实际上指向所在方法被调用的函数上下文),所以打印结果都为 1。
当然,在JavaScript当中我们还有几种方法人为的绑定this,分别是apply(),bind() call()
,在后续中我们将会说到。
对象中的this
function foo() {
console.log(this.a);
}
var obj1 = {
a: 2,
obj2: obj2
}
var obj2 = {
a: 4,
foo: foo //key属性:foo 引用foo this绑定到对象obj1
}
obj1.obj2.foo() //4 函数foo 在哪被引用,this指向哪 this指向obj2 a = 4
代码分析
我们定义了函数foo()和两个对象obj1,obj2,将obj2作为obj1的key属性,将obj2对象作为value值,而foo作为obj2的key属性,引用foo,将函数foo作为其value值。然后利用obj1.obj2.foo()调用这个foo(). 打印的结果是4,这是为什么呢?
【隐式绑定】
这就是我们的隐式绑定规则了,当函数引用时有上下文对象,那么就会隐式绑定,把函数的this绑定带该上下文上面。将函数foo()当做引用属性添加到了obj2对象当中去,但是严格意义上来说foo函数是不属于obj2对象的,但是执行的时候,foo()函数在obj2的执行上下文被调用,因此你可以说函数被调用时“拥有”或者“包含”函数引用。所以foo()函数的this指向的是obj2,那么打印出来的结果就为2了
【知识点】
将方法的调用最内层包裹在一个指定上下文里,若是后面再进行绑定,均不能插入到这一层环境中间。上述例子中,foo()的this已经绑定了obj2,若是将obj2整体绑定到别的对象当中,foo()函数的this依然指向obj2.
【隐式丢失】
function foo(){
console.log(this.a);
}
var obj = {
a:2,
foo:foo //隐式绑定
}
var bar = obj.foo // 函数别名 实际上调用的是函数本身
var a = 4
bar() // 4 调用的是全局下面的a
代码分析
分析上述代码,定义了obj对象以及foo()函数,obj对象将foo作为函数引用,也就是隐式绑定。而后我们在外部定义了变量a和bar,那么最后打印的结果会是什么呢?
在这里我们需要注意的是,var bar = obj.foo
这个函数表达式,因为bar
只是绑定了fn
函数的引用,因此bar
只是一个函数的调用,也就是说实际上是在调用foo()函数本身,会应用默认绑定
绑定到了全局window,所以打印结果会是4.
这种情况也会出现在回调函数当中,所以我们应当注意,莫要踩坑!
【显式绑定】
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
}
foo.call(obj) //人为绑定
实际上就是利用我们的call(),apply(),bind()
函数来指定this的执行上下文,用法也比较简单,但是这种经常会在面试当中提到,后续我会详细讲解他们三者的区别。
构造函数
我们都知道构造函数在创建一个对象的时候都要经历三部曲
构造函数三部曲
1 创建this对象
2 挂上对象该有的属性值,name属性传值,对象的隐式原型绑定构造函数显示原型
3 返回this对象
我们来看简单的实例
function constructor(){
this.a = 1;
}
var obj = new constructor();
console.log(obj.a); //1
当一个函数用作构造函数时,使用new关键字时,它的this
被绑定到正在构造的新对象中。所以自然而然就会打印出1了。
总结
this的变化无穷无尽,也是耗费了数以万计的程序员才总结出来的。就算把我给搞秃头了,都说不完this的故事,以上是我对于this的浅解,总之一句话,谁包住了this,那么就得对this负责,当然,不考虑严格模式哈。
我是小白,浅解this,如有错误,还请各位指出,谢谢!