一、this是什么?
理解this之前, 先纠正一个观点,this 既不指向函数自身,也不指函数的词法作用域。如果仅通过this的英文解释,太容易产生误导了。它实际是在函数被调用时才发生的绑定,也就是说this具体指向什么,取决于你是怎么调用的函数。
二、this的四种绑定规则
this的四种绑定规则是默认绑定、隐式绑定、显示绑定、new 绑定。优先级从低到高~
1、默认绑定
默认绑定是没有其他绑定规则存在时的默认规则。是最常用的绑定规则。
默认绑定规则:this绑定给Window。
function foo() {
console.log( this.a )
}
var a = 2
foo() //打印的是什么?
foo()打印的结果是2。
因为foo()是直接调用的(独立函数调用),没有应用其他的绑定规则,这里进行了默认绑定,将全局对象Window绑定this上。所以this.a就解析成了全局变量中的a,即2。
(在严格模式下,默认绑定规则会把this绑定在undefined上)
注:无论函数是在哪个作用域中被调用,只要是独立调用则就会按默认绑定规则被绑定到全局对象或者undefined上
2、隐式绑定
2.1 隐式绑定
除了直接对函数进行调用外,有些情况是,函数的调用是在某个对象上触发的,即调用位置上存在上下文对象。
隐式绑定的规则:this绑定给离函数最近的对象;判断调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。
function foo() {
console.log( this.a )
}
var a = 2
var obj = {
a: 3,
foo: foo
}
obj.foo() // 打印的是什么?
obj.foo()打印的结果是3
此时,是调用obj对象中的foo方法,this绑定的是obj,则this.a指向obj.a=3。
获取obj.foo属性 -> 根据引用关系找到foo函数,执行调用
所以这里对foo的调用存在上下文对象obj,this进行了隐式绑定,即this绑定到了obj上,所以this.a被解析成了obj.a,即3。
由于隐式绑定是将this绑定给离函数最近的对象,则多层对象嵌套的时候,this指向的对象就显而易见了。看看多层调用链下的this↓
2.2 多层调用链
当多个对象存在嵌套关系,对象属性引用链中只有最顶层或者说最后一层会影响调用位置。
function foo() {
console.log( this.a )
}
var a = 2
var obj1 = {
a: 4,
foo: foo
}
var obj2 = {
a: 3,
obj1: obj1
}
obj2.obj1.foo() //打印的是什么?
打印的是4。
看下调用过程:obj2.obj1找到obj1对象→obj1.foo()找到foo函数,执行调用
此时存在多层调用链,存在两个对象。原则是获取最后一层调用的上下文对象,即obj1,obj1.a=4。
但是有两种情况隐式绑定会丢失↓
2.3 隐式丢失(函数别名)
在调用的时候,将对象的方法赋值给另一个变量。则不会触发隐式绑定。看个代码:
function foo() {
console.log( this.a )
}
var a = 2
var obj = {
a: 3,
foo: foo
}
var bar = obj.foo
bar() //打印的是什么?
bar()打印的结果是2。
调用bar()并没有触发隐式绑定,使用的是默认绑定。实际上调用关系是:通过bar找到foo函数,进行调用。bar指向foo函数,而bar()绑定的是全局对象Window,则this.a=2。、
等价于:
var a = 2
var bar = function foo(){
console.log( this.a );
}
bar()
2.4 隐式丢失(回调函数)
function foo() {
console.log( this.a )
}
var a = 2
var obj = {
a: 3,
foo: foo
}
setTimeout( obj.foo, 100 ) // 打印的是什么?
打印的结果是2。
这里,虽然传参是obj.foo,因为是引用关系,所以传参实际上是foo函数本身。此时this指向的仍然是全局对象Window。
等价于
var a = 0
function foo() {
console.log( this.a )
}
setTimeout( foo, 100 )
总结:如何判断是否是隐式丢失?其实很简单,隐式绑定是对象的函数被执行的时候,this绑定的是离得最近的对象;如果此时的对象绑定的函数并没有被执行,而只是为了传递对象中的函数,此时的this绑定并不能使隐式绑定,this绑定需要根据上下文判断。
3、显示绑定
通过call()、apply()、bind()方法把对象绑定到this上,叫做显式绑定。这种操作也叫改变this的指向,改变this的指向具体参照第五节↓
4、new绑定
new是JS中的一个操作符,用于实例化一个对象。this会绑定在new出来的对象上。
使用new来调用函数时,会构造一个新对象并把函数中this绑定到该对象上。称为new绑定。
function Student (name, age) {
this.name = name
this.age = age
console.log(this) // this → stu1
}
// 实例化
let stu1 = new Student(‘张三’, ‘李四’)
三、this的指向总结
this到底指向谁,是由程序在执行的过程中决定的。以下是关于this指向情况的基本整理。
1.普通函数调用时,this指向window
2.构造函数调用时,this指向当前所创建的对象
3.对象的方法调用时,this指向方法所属的对象
4.事件绑定的方法,this指向事件源
5.定时器函数,this指向window
四、绑定例外
ES6中的箭头函数,它的this绑定取决于外层(函数或全局)作用域。对比一下普通函数和箭头函数:
普通函数:
function foo(){
console.log( this.a )
}
var a = 2
var obj = {
a: 3,
foo: foo
}
obj.foo() //3
箭头函数:
var foo = () => {
console.log( this.a )
}
var a = 2
var obj = {
a: 3,
foo: foo
}
obj.foo() //2
foo.call(obj) //2 ,箭头函数中显示绑定不会生效
五、如何改变this的指向?
改变this的方式:函数有四种方法可以改变内部的this:call、apply、bind
1.call方法
函数名.apply(需要借用的对象, 参数,参数);
应用:①借用构造函数
②借用其他对象中的方法
【特殊情况】若调用借用方式时,传入的是调用者是null,函数内部中this指向的是window。
let a = {
user:"a",
fn:function(){
console.log(this)
}
}
let b = a.fn
b.call(a) // this->a
b() //若不用call,则b()执行后this指的是Window对象
把b添加到第一个参数的环境中,简单来说,this就会指向那个对象。
call方法除了第一个参数以外还可以添加多个参数,如下:
let a = {
user:"a",
fn:function(e,ee){
console.log(this) //this->a
console.log(e+ee) //3
}
}
let b = a.fn
b.call(a,1,2)
2.apply方法
函数名.apply(需要调用的对象,数组);
应用:和call方法一样,唯一不同的是,函数或方法被借用时,apply以数组的方式存放实参。
【特殊情况】若调用借用方式时,传入的是调用者是null,函数内部中this指向的是window。
let a = {
user:"a",
fn:function(e,ee){
console.log(this) //this->a
console.log(e+ee) //11
}
}
let b = a.fn
b.apply(a,[10,1])
【call和apply的第一个参数写的是null,那么this指向的是window对象】
let a = {
user:"a",
fn:function(){
console.log(this)
}
}
let b = a.fn
b.apply(null) // this->Window
3.bind方法
let 变量 = 函数名.bind(想要调用的对象,参数,参数,参数)
应用:和call/apply使用方式一样,不一样的是,使用bind时,借用的函数不会被立即执行,而是返回一个新的函数,若要执行,需要自己调用。
let a = {
user:"a",
fn:function(){
console.log(this)
}
}
let b = a.fn
let c = b.bind(a)
c() //若要执行,需要自己调用 this->a
同样bind也可以有多个参数,并且参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的。
let a = {
user:"a",
fn:function(e,d,f){
console.log(this) // this->a
console.log(e,d,f) //10 1 2
}
}
let b = a.fn
let c = b.bind(a,10)
c(1,2)
总结: call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加,这是它们的区别