this的原理

一、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方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加,这是它们的区别

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值