一、什么是this
this
的值:当前执行代码的环境对象,this
的指向不取决于它在什么位置创建,完全取决于函数在什么地方被调用,this
不能在执行期间被赋值,并且在每次函数被调用时this的值也可能会不同。
二、this
的值
在全局环境(任何函数调用的外部)中,this
的值都是全局对象(浏览器中是window
对象,node中是global
对象)
在函数内部环境,this
的值取决于函数被调用的方式
三、this
指向规则
简单总结一个原理:this 永远指向最后调用它的那个对象
- 函数绑定
直接调用函数的时候this
指向的是全局对象
var name = 'Window.name'function foo() { console.log('this',this.name)}foo() // Window.name
注意:如果使用的是严格模式的话,全局对象是undefined
var name = 'Window.name'function foo() { 'use strict' console.log('this',this.name)}foo() // Uncaught TypeError: Cannot read property 'name' of undefined
- 隐式绑定(方法调用)
当函数作为对象的属性被调用的时候就属于隐式绑定,这个时候,this指向的是这个函数的对象
var obj = { num:0, add:function() { console.log('this',this) // this {num: 0, add: ƒ} this.num +=1 }}obj.add()console.log('obj',obj) // obj {num: 1, add: ƒ}
在看下面一个栗子:
var name = 'ahwgs'var obj = { name:'aaa', foo:function() { console.log('name',this.name) }}var res = obj.foores() // ahwgs
申明一个obj
对象,虽然将obj.foo
的引用赋值给res
,但实际上此时是res()
是不带修饰的函数调用(属于第一种函数绑定的情况),所以此时打印的值是ahwgs
下面我们做一点改动:
var name = 'ahwgs'var obj = { name:'aaa', foo:function() { console.log('name',this.name) }}obj.foo() // name aaa
这就是正常的隐式调用,这时候的this
为obj
本身
在看一个栗子:
var name = 'ahwgs'function doFoo(fn) { console.log('fn',fn) fn() //f(){console.log('name',this.name)} fn=obj.foo}var obj = { name:'aaa', foo:function() { console.log('name',this.name) }}doFoo(obj.foo) // name ahwgs
这时候,obj.foo
作为参数传递给了doFoo
,实际上调用还是doFoo
,这时候this
指向的是全局对象,所以打印的是ahwgs
最后一个栗子:
var name = 'ahwgs'function foo() { var name = 'aaa' fn() function fn() { console.log('name',this.name) }}foo() // ahwgs
借用开头说的一句话,this指向的是这个函数所属的对象所以,fn
指向的是全局对象
- 显式绑定
使用call/apply/bind
方法进行显式绑定
var name = 'ahwgs'function foo() { console.log('this',this) console.log('name',this.name)}var obj = { name:'obj'}foo.apply(obj) // name objfoo.call(obj)// name objfoo.bind(obj)()// name obj
这时候的this
指向都被显式绑定至obj
,此后,无论如何调用函数,总会将obj绑定到foo中的this上。
new
绑定
通过new
关键字调用的函数,属于new
绑定模式。这时this
关键字指向这个新创建的对象。
function User(name,age) { this.name = name this.age = age this.getInfo = function() { console.log('info',this.name + '--->'+this.age) }}var user = new User('ahwgs',20)console.log(user) // User {name: "ahwgs", age: 20, getInfo: ƒ}console.log(user.getInfo()) // info ahwgs--->20
- 总结:
- 函数是否是new绑定?如果是,则this指向新创建的对象
- 函数是否通过call/apply/bind显式绑定或硬绑定?如果是,则this指向指定的对象;
- 函数是否在某个上下文对象中隐式调用?如果是,this绑定的是那个上下文对象;
- 上述全不是,则使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局window对象。
- 记住:this 永远指向最后调用它的那个对象
四、如何改变this
指向
- 使用ES6箭头函数
箭头函数的 this 始终指向函数定义时的 this,而非执行时,箭头函数中没有 this
绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this
绑定的是最近一层非箭头函数的 this
,否则,this 为 undefined
。
看一个栗子:
var name = 'ahwgs'var obj = { name:'aaa', foo1:function() { console.log('this.name',this.name) }, foo2:function() { setTimeout(()=>{ this.foo1() },100) }}obj.foo2() // aaa
由于定时器中使用的是箭头函数的形式,上一级没有使用箭头函数,所以this
绑定的是最近一层非箭头函数的this
,在这里,即obj
- 在函数内部
const self = this
var name = 'ahwgs'var obj = { name:'aaa', foo1:function() { console.log('this.name',this.name) }, foo2:function() { var self = this setTimeout(function() { self.foo1() }) }}obj.foo2() // aaa
定义一个变量,将当前的this
指向改变至self
中,这样调用foo1
的时候this
指向就是obj
这个对象
- 使用
apply、call、bind
先看一下这三个函数的使用方法:
apply
function.apply(obj, [param1,params2,...])// obj:要绑定的this// 第二个参数:类数组或数组,作为function的参数传入// 立即执行
call
function.call(obj, param1, param2, ...)// obj:要绑定的this// 第二个参数:函数运行的参数,用逗号隔开// 立即执行
bind
function.bind(obj, param1, param2, ...)// obj:要绑定的this// 第二个参数:函数运行的参数,用逗号隔开// 返回一个函数
下面使用这三种方法修改this
指向
- 使用
apply
var name = 'ahwgs'var obj = { name:'aaa', foo1:function() { console.log('this.name',this.name) }, foo2:function() { setTimeout(function() { this.foo1() }.apply(obj),100) }}obj.foo2() // aaa
- 使用
call
var name = 'ahwgs'var obj = { name:'aaa', foo1:function() { console.log('this.name',this.name) }, foo2:function() { setTimeout(function() { this.foo1() }.call(obj),100) }}obj.foo2() // aaa
- 使用
bind
var name = 'ahwgs'var obj = { name:'aaa', foo1:function() { console.log('this.name',this.name) }, foo2:function() { setTimeout(function() { this.foo1() }.bind(obj)(),100) }}obj.foo2() // aaa
注意:bind
与其他两种方法不同,因为他返回的是一个函数,所以需要我们()
去调用它。
- 使用
null/undefined
作为bind/call/apply
的参数
var name = 'ahwgs'var obj = { name:'aaa', foo1:function() { console.log('this.name',this.name) }, foo2:function() { setTimeout(function() { this.foo1() }.call(null),100) } }obj.foo2() //Uncaught TypeError: this.foo1 is not a function
如果使用null/undefined
作为参数的话,被调用的时候会被忽略,所以调用obj.foo2()
的时候this
指向的是全局对象,而全局对象中却没有foo2
这个函数,所以报错。
new
实例化新对象
可看上述new
绑定实例
总结
- js中的
this
指的是允许的上下文环境,与后端语言不同 this
不是一成一变的,会随着环境而变化- 严格模式与非严格模式下的
this
也不一样 - 可以使用多种方式修改
this
的指向 - 本文首发于:你不知道的this关键字