this的绑定规则

42 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

大家好!我是前端爬楼工程师🚹,一个野生程序员。好奇新技术,仰慕牛大佬。如果喜欢我的文章,可以关注➕点赞,为我注入能量,与我一同成长吧~

前篇文章说明了,js有编译和运行两个步骤, 作用域的创建是在编译时期完成的,this则是在函数调用时绑定的。

在函数调用的时候,此时创建了活动记录(又名为上下文)。记录里包含了函数在哪调用(调用栈),参数,调用方式等信息,this就是其中的一个。

那么我们将函数调用的位置作为切入点,判断this是如何绑定的。

判断规则之前需要了解两个东西: 函数调用栈和调用位置;

调用位置:很简单 ,如下代码所示,各个函数调用位置

调用栈:就是为了到达调用位置所调用的所有函数(通过谷歌调试工具就能很清晰观察到调用栈)

// 以谷歌调试工具为例
function baz(){
	// 调用栈是 :baz -> anonymous
  // baz调用位置是全局作用域
	console.log('baz') 
	bar() // <=调用位置 说明到达bar的执行位置,调用了baz函数 所以的调用栈是 baz -> anonymous
}
function bar(){
	console.log('bar')
  // 调用栈是 :bar -> baz -> anonymous
	// bar的调用位置在baz中
	foo() // <=调用位置
}
function foo(){
	// 调用栈是 :foo -> bar -> baz -> anonymous
  // foo的调用位置在bar中
	console.log('foo')
}
// 调用栈:anonymous 说明:这里到baz调用位置没有其他函数,所以是匿名栈
baz()// <=调用位置
this绑定的规则有四种:

默认绑定: 后三种规则都不适用的情况下,使用这个规则。这个规则下的this指向的是全局对象(严格模式下指向undefined)。

隐式绑定: 看调用位置是不是有上下文。这个规则下的this指向上下文对象。(如果丢失了绑定对象,则应用默认绑定规则)

function foo(){
	console.log(this.a)
}

var obj = {
	a:2,
  foo:foo	
}
obj.foo() // <= 调用位置  使用了obj来引用函数,符合隐式绑定规则, 打印2

// 另外一种情况

function foo(){
	console.log(this.a)
}

var obj = {
	a:2,
  foo:foo	
}
var a = 'global a !'
var bar = obj.foo
bar() // <=  调用位置  此时的bar没有修饰,所以应用默认绑定规则 ,打印 global a !  

// 回调函数的情况

function foo(){
	console.log(this.a)
}

function doFoo(fn){
	fn()
}

var obj = {
	a:2,
  foo:foo	
}
var a = 'global a !'
doFoo(obj.foo) // 这里我们认为obj.foo赋值给了doFoo的参数fn,和上一种情况一样,应用默认绑定规则,,打印 global a !  

// 使用内置函数调用
setTimeout(obj.foo, 100) //也是global a !

显示绑定:使用 call, apply 来解决

function foo(){
	console.log(this.a)
}
var obj = {
	a:1,
  foo:foo
}
foo.call(obj) // 1  
foo.call('2')// 如果传入的参数是原始类型,不是obj,这个原始类型会帮你转换成对象形式(也叫装箱),string => new String
  • 硬绑定

    在隐式绑定中会存在丢失对象的情况,可以做如下改造

    function foo(){
    	console.log(this.a)
    }
    var obj = {
    	a:1
    	foo:foo
    }
    var bar = function (){
    	return foo.call(obj)
    }
    bar()
    
    

    其实es5 提供了bind方法

  • js的api调用的上下文

function foo(){
	console.log(this.id)
}

var obj = {id: 'id'};
[1,2,3].forEach(foo, obj) // id id id

new绑定: new在js里,new调用的就是普通函数;this 指向的是函数返回的对象

function foo(a){
	this.a = a 
}
var bar = new foo(2) //用new 调用构造函数foo 把bar绑定到this上
console.log(bar.a) // 2

了解的这些规则后,我们就可以判断this

首先看new ,然后看显式绑定,然后是隐式绑定,最后就是默认绑定了;

例外:

  • 被忽略的this
function foo(){
	console.log(this.a)
}
var a = 2
foo.call(null) // 2