有关this的文章也是看了一篇有一篇,总觉得会了,但是时间久了就又忘了,这次彻底走一遍,后面就按照这个看了。
首先,我们来回顾一下执行上下文的内容
执行上下文的创建阶段,分别生成变量对象,建立作用域链和确定this的指向。而执行上下文的创建是在函数调用的时候,所以:
this的指向,是在函数被调用的时候确定的,也是说执行上下文被创建的时候确定的。
所以同一个函数,调用方式的不同,就会导致this指向不一样的对象。
var a = 10var obj = {a: 20}function fn() {console.log(this.a)}fn() //10fn.call(obj) // 20
除此之外,this被确定后就无法再次修改,所以,如果我们在fn函数中试图去更改this的指向,就会报错
全局对象中的this
在浏览器中,this就相当于window,node中就是global对象,全局对象中的this也很简单明了
this.a1 = 10var a2 = 20a3 = 30//三种赋值操作,最终对象都是在全局对象中,这里的this也都指向全局对象console.log(a1)console.log(a2)console.log(a3)
函数中的this
我们以及知道稍微一点点的改变,就会让this的指向发生天翻地覆的变化,所以在刷面试题的时候,你有可能见到一下,看上去没什么区别的代码,但是结果却天差地别,下面我们就先来看几个this相关的变态代码
var a = 20function fn() {console.log(this.a)}fn()
var a = 20function fn() {function foo() {console.log(this.a)}foo()}fn()
var a = 20var obj = {a: 10,c: this.a + 20,fn: function() {return this.a}}console.log(obj.c)console.log(obj.fn())
首先,我们先把判断this指向的方法写出来,再根据这个方法来看上面的代码:
在一个函数上下文中,this由调用者提供,由调用函数的放式来决定。如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。如果函数是独立调用,那么该函数内部的this指向undefined。非严格模式下,this指向undefined就会自动指向全局对象。
根据上面这句话,第一个例子就很简单,fn独立调用,指向全局对象,输出20
第二个例子稍微有些迷惑,但其实所有的迷惑都只是迷惑,foo的调用仍然是独立调用,foo中的this仍然是指向全局。
最后一个例子,fn的调用很简单,被obj所拥有,所以fn中的this指向obj对象,而访问c中的this.a只是在一个{}中,并没有独立的作用域,所以这里的this仍然处于全局作用域之中。
这里我们可以对第三个例子进行一点小小的修改:
'use strict'var a = 20function foo() {var a = 1var obj = {a: 10,c: this.a + 20,fn: function() {return this.a}}return obj.c}console.log(foo())console.log(window.foo())
一堆无用的代码,让这段代码变得不忍直视,但是对于this的考察,这确实是一段好代码,无视掉那些没有的迷惑代码,foo()的调用是独立调用,所以this指向undefined,但是这里是严格模式,所以就会报错。下面利用window.foo()this指向了window,所以this.a = 20,也就输出了40.
并没有想象中的那么复杂,看似复杂的地方其实都只是在迷惑你。只需要确定函数的调用是独立调用还是被某一个对象拥有就好。
下面再来几个例子,加深一下是否独立调用的理解。
var a = 20;var foo = {a: 10,getA: function () {return this.a;}}console.log(foo.getA()); // 10var test = foo.getA;console.log(test()); // 20
var a = 20;function getA() {return this.a;}var foo = {a: 10,getA: getA}console.log(foo.getA()); // 10
function foo() {console.log(this.a)}function active(fn) {fn(); // 真实调用者,为独立调用}var a = 20;var obj = {a: 10,getA: foo}active(obj.getA);
使用call,apply显式指定this
JavaScript内部提供一种机制,让我们可以修改this的指向,他们就是使用call和apply。这两个方法功能一样,只是参数不同,第一个参数都是作为this将要指向的对象。后面的参数是给将要执行的函数传递的参数,apply传递的是一个数组,call以一个一个值的形式传递。这里我之前一直分不清,后来想到一个方法,apply是以a开头,所以传递的是一个array。
function fn(num1, num2) {console.log(this.a + num1 + num2)}var obj = {a: 20}fn.call(obj, 10, 20)fn.apply(obj, [10, 20])
根据需要灵活修改this
var foo = {name: 'joker',showName: function () {console.log(this.name);}}var bar = {name: 'rose'}foo.showName.call(bar);
实现继承
// 定义父级的构造函数var Person = function (name, age) {this.name = name;this.age = age;this.gender = ['man', 'woman'];}// 定义子类的构造函数var Student = function (name, age, high) {// use callPerson.call(this, name, age);this.high = high;}Student.prototype.message = function () {console.log('name:' + this.name + ', age:' + this.age + ', high:' + this.high + ', gender:' + this.gender[0] + ';');}new Student('xiaom', 12, '150cm').message();// result// ----------// name:xiaom, age:12, high:150cm, gender:man;
Student中,利用了call,引用了Person中的内容,但是this的指向还是在student中,所以相当于是复制了Person的内容到Student中,这就相当于是实现了继承。
而对于原型上面的方法就更简单了,调用原型上的方法是通过new出来的Student,所以原型方法中的this指向new放回的新对象。
this丢失
var obj = {a: 20,getA: function() {setTimeout(function () {console.log(this.a)}, 1000)}}obj.getA()
上面的代码根据我们之前的经验,getA()被obj拥有,所以输出20.但结果并不是。因为setTimeout匿名函数的存在,匿名函数内部的this指向了全局,这里的a并不是我们想要的20
解决办法有很多,第一个可以想到的就是在getA中定义一个变量self保存this,在匿名函数中通过self来访问,这也是我们之前经常用到的。另外还有一个方法可以实现,那就是bind方法。
bind
var obj = {a: 20,getA: function () {setTimeout(function () {console.log(this.a)}.bind(this), 1000)}}
