一.前言
在JavaScript中this的指向经常困扰着很多人,也折磨着很多人,经常性的会出现下列的问题,想用this的时候发现this指向的对象不是自己想要的,但是显式的更改又更改不掉,问题的本质还是对this的使用方法没有真正的理解,这篇文章我们就一次性的将this的使用方式进行总结,一次性解决this的使用问题。
二.this到底指向什么?
需要注意的是,当我们在进行一个函数调用的时候,JavaScript会默认给this绑定一个值,this的绑定和定义的位置没有关系,this的绑定和调用的方式和调用的位置有关系,this是在运行时被绑定的,当我们了解了这些前置知识后我们就可以开始去探索this的绑定规则了,让我们开始吧!
三.默认绑定
什么是默认绑定哪?默认绑定简单来讲就是独立函数调用,独立函数调用我们可以理解为,函数没有被绑定到其他对象上调用,我们可以通过以下的几个案例来看下什么样的情况下是独立函数调用。
function myFun(){ console.log(this); } myFun() // window
另外当一个函数在另外一个函数中进行调用的时候也是独立函数调用,this依然指向的是全局对象。
function myFun(){ console.log(this); } function myFun2(){ myFun() } myFun2() // window
甚至我们给一个函数传输一个函数,然后在函数体的内部执行这个函数this的指向依然是当前顶级对象。
function myFun(fn){ fn() } let obj = { name:"wang", age:12, fun:function(){ console.log(this); } } myFun(obj.fun) // window
从上述的内容中我们可以得出一个结论,只要是一个函数被独立进行调用,那么这个函数就指向当前作用域的顶级对象也就是window。
四.隐式绑定
除了上述直接对函数进行调用,还有一种方式是通过对象对函数进行调用,也就是在它的调用位置中是通过某个对象发起的函数调用,我们通过如下几个案例来了解下隐式绑定的情况。
let obj = { name:"wang", age:12, fun:function(){ console.log(this); } } obj.fun(); // { name: 'wang', age: 12, fun: [Function: fun] }
我们通过执行的结果可以看出来,通过对象进行调用this指向的是当前的对象,接下来我们来看下连续使用对象进行调用,最终的结果指向的是什么。
function foo () { console.log(this) } var obj1 = { name: "obj1", foo: foo, } var obj2 = { name: "obj2", obj1: obj1, } obj2.obj1.foo() // { name: 'obj1', foo: [Function: foo] }
我们从上述的内容中可以看出来,当进行对象的连续调用的时候,this指向的调用次序最近的那个对象。
💡需要主要注意的是,当我们在通过对象调用某个方式,但是我们将这段代码重新赋值了变量,然后使用这个变量进行调用,这个时候this指向的是window。
function Myfn(){ console.log(this); } let obj = { name:"wang", foo:Myfn } let returnFn = obj.foo; returnFn(); // window
五.显式绑定
我们在上述内容中了解了默认绑定和隐式绑定,但是仅仅这些内容不能完全满足我们的需求,隐式绑定有一个条件,必须在调用的对象内部有一个函数的引用,如果没有这样的引用,在进行调用时,会报找不到该函数错误,间接的将this绑定到了这个对象上,在很多时候我们想要按照自己的编程的想法去更改this的指向,这个时候隐式绑定就满足不了我们的需求了,这个时候我们就需要使用显示绑定来解决这个问题,在JavaScript中,所有的函数都可以调用call,apply,call和apply的使用方法仅仅是参数列表不同(如下),当我们在使用bind的时候bind会返回一个绑定函数,我们需要对返回的函数进行调用,bind的参数传递方式和apply一致。
// call的使用方法 fn.call(this,[argsArray]) // apply的使用方法 fn.apply(this,args1,args2) // bind的使用方法 let resultFn = fn.bind(this,args1,args2) resultFn()
那么既然我们已经了解了显式绑定的使用,那么我们就来具体的通过代码来使用一下call绑定。
function myFn(){ console.log(this); } myFn.call("wangpeng") // [String: 'wangpeng']
然后我们使用apply来进行绑定。
function myFn(){ console.log(this); } myFn.apply("wangpeng") // [String: 'wangpeng']
bind的使用和上述两者稍微有点差别。
function myFn () { console.log(this) } let bindFn = myFn.bind("wangpeng") bindFn() // [String: 'wangpeng']
六.new绑定
在JavaScript中函数是可以作为构造函数进行实例化的,也就是使用new关键字,在使用new绑定之前我们需要先来看下,当我们在使用new来调用函数的时候发生了什么。
创建一个全新的对象。
这个对象会被执行prototype连接。
这个新对象会绑定到函数调用的this上(this绑定在这个步骤完成)
如果函数没有返回返回其他对象,表达式会返回这个新对象。
function myFn (name) { console.log(this) // myFn {} this.name = name // myFn { name: 'wangpeng' } } let p = new myFn('wangpeng') console.log(p);
七.绑定的优先级
在上述内容中我们进行this绑定都是单独进行测试和使用的,但是在实际的开发中有的时候可能是几种绑定规则在一起的,这个时候我们就要考虑,当不同的规则一起使用的时候它们的优先级问题。
默认规则的优先级最低,因为当存在其他规则的时候就会使用其他规则进行绑定。
显示绑定高于隐式绑定。
let obj = { name:"John", foo:function(){ console.log(this); } } obj.foo.call("wangpeng") // [String: 'wangpeng']
new绑定优先级高于隐式绑定。
let obj = { name:"John", foo:function(){ console.log(this); } } new obj.foo() // foo {}
new绑定的优先级高于bind,同时new绑定无法和call,apply一起使用。
function myFn(){ console.log(this); } let resultFn = myFn.bind(); new resultFn() // myFn {}
八.常见内置函数this的指向
虽然我们已经了解了this绑定的所有的规则,但是我们在实际的开发中,往往会调用JS中内置的函数,或者第三方库帮我们调用,在这种情况下,我们其实也不知道this到底被绑定到了什么。
定时器函数
setTimeout(function(){ console.log(this) }) // window
按钮的点击监听
var box = document.querySelector(".box"); box.onclick = function(){ console.log(this === box) } // box
数组内置的forEach方法
var names = ["abc","cba","nba"] var obj = {name:"why"} names.forEach(function(item){ console.log(this) // 三次obj },obj)
💡注意在forEach中如果不使用第二个参数指定指向的话,默认指向window
九.this绑定规则之外
情况一:当我们使用显式绑定的时候传入null或者undefined,那么显式绑定的规则会被忽略,使用默认绑定。
function myFn () { console.log(this) } myFn.call(null) // window myFn.apply(null) // window let resultFn = myFn.bind(null) resultFn() // window
情况二:创建一个函数的间距引用,这种情况下使用默认绑定规则。
function foo () { console.log(this) } var obj1 = { name: "obj1", foo: foo } var obj2 = { name: "obj2" } obj1.foo(); // obj (obj2.foo = obj1.foo)() // window
情况三:箭头函数中没有this,所以箭头函数中的this指向的上层作用域中的this。
let obj = { data: [], getData: function () { setTimeout(() => { console.log(this) }, 1000) } } obj.getData() // obj
十.总结
这篇文章我们学习了JS中this的绑定规则,this的绑定规则大致分为四种,分别是默认绑定,隐式绑定,显式绑定和new绑定,然后我们讲解了在它们在一起使用的时候的优先级,隐式绑定优先于默认绑定,显式绑定优先于隐式绑定,new绑定优先于隐式绑定,new绑定优先于bind绑定,除了这些有明确规则的绑定方式之外,我们还需要在平时开发者中总结常见内置函数的绑定,在文章的最后,我们了解了在规则之外的三种情况,在日常中使用较多的是箭头函数,需要牢记,熟练使用~