难缠的this
在深入了解JavaScript中this关键字之前,有必要先退一步,看一下为什么this关键字很重要。this允许复用函数时使用不同的上下文。换句话说,this关键字允许在调用函数或方法时决定哪个对象才是焦点。之后讨论的所有东西都是基于这个理念。我们希望能够在不同的上下文或在不同的对象中复用函数或方法。
我们关注的第一件事是如何判断this关键字的引用。当你试图回答这个问题时,需要问自己的第一个也是最重要的问题是“这个函数在哪里被调用”。判断this引用什么的唯一方法就是看使用this关键字的这个方法在哪里被调用。
一 隐式绑定
请记住,判断this指向要看使用this关键字的这个方法在哪里被调用。假如有下面一个对象
const obj = {title: 'test',fn(): {console.dir(this.title)} } 复制代码如果想调用fn方法,就要
obj.fn() 复制代码这就把我们带到隐式绑定规则的主要关键点。为了判断 this 关键字的引用,函数被调用时先看一看点号左侧。如果有“点”就查看点左侧的对象,这个对象就是 this 的引用。 在上面的例子中,obj 在“点号左侧”意味着 this 引用了 obj 对象。所以就好像 在 fn 方法的内部 JavaScript 解释器把 this 变成了 obj。 再看如下例子
const obj = {title: 'test',fn() {console.dir(this.title)},sub: {title: 'test2',fn() {console.dir(this.title)}} } obj.fn() // test obj.sub.fn() // test2 复制代码每当判断 this 的引用时,都需要查看调用过程,并确认“点的左侧”是什么。第一个调用,obj 在点左侧意味着 this 将引用 obj。第二次调用中,sub 在点的左侧意味着 this 引用 sub。 所以大多数情况下,检查使用this方法的点的左侧是什么。如果没有点呢,继续往下看。
二 显示绑定
如果fn只是一个独立的函数,如下
fn() {console.dir(this.title) } const obj = {title: 'test' } 复制代码为了判断this的引用必须先查看这个函数的调用位置,那怎样才能让fn调用的时候将this指向this?我们并不能再像上面那样简单的使用obj.fn(),因为obj并没有fn方法。但是在js中,每个函数都有一个方法call,正好解决这个问题。
"call" 是每个函数都有的方法,它允许在调用函数时为函数指定上下文
所以,可以用下面的方式调用fn方法
fn.call(obj) 复制代码call是每个函数都有的属性,并且传递给它的第一个参数会作为函数调用时的上下文。也就是说,this会指向传递给call的第一个参数
这就是显示绑定的基础,因为我们明确的使用call指定了this的引用。 现在将fn改动一下
fn(name1, name2){console.dir(`${this.title} is ${name1} and ${name2}`) } 复制代码此时使用call方法就要如下
fn.call(obj, '张三', '李四') 复制代码使用call方法需要将参数一个一个的传递进去,参数过多,就会越麻烦。此时apply方法就可以解决。
apply和call本质相同,但不是一个个传递参数,可以用数组传参且apply会在函数中为你自动展开。
const arr = ['张三', '李四'] fn.apply(obj, arr) 复制代码除了call和apply可以显示绑定this外,还有bind方法也可以
bind和call调用方式完全相同,不同的是bind不会立即调用函数,而是返回一个能以后调用的新函数
const newFn = fn.bind(obj, '张三', '李四') newFn() 复制代码传入的不是对象: 如果传入了一个原始值(字符串,布尔类型,数字类型),来当做this的绑定对象,这个原始值转换成它的对象形式。 如果把null或者undefined作为this的绑定对象传入call/apply/bind,这些值会在调用时被忽略,实际应用的是默认绑定规则
三 new 绑定
function fn(a) {this.a = a } const bar = new fn(2) console.log(bar.a) // 2 复制代码用new操作符创建对象时会发生如下步骤:
所以,new fn(2)就相当于
const newObj = new Object() newObj.a = a 复制代码还有一种特殊情况,就是当构造函数通过 "return" 返回的是一个对象的时候,此次运算的最终结果返回的就是这个对象,而不是新创建的对象,因此 this 在这种情况下并没有什么用。注意,返回函数也是返回新的对象,函数对象
function fn(a) {this.a = areturn {} } const bar = new fn(2) console.log(bar.a) // undefined 复制代码四 window 绑定
fn() {console.dir(this.title) } const obj = {title: 'test' } fn() // undefined 复制代码当点的左侧没有任何东西,也没有通过call、apply、bind方法调用,也没有new关键字,this就会指向了window。
window.title = '全局' fn() {console.dir(this.title) } fn() // 全局 复制代码在ES5添加的严格模式中,this不会指向window对象,而是保持为undefined
五 四种绑定的优先规则
显式绑定 > 隐式绑定 > 默认绑定 new绑定 > 隐式绑定 > 默认绑定
六 丢失的this
在某些情况下会丢失 this 的指向,此时,我们就需要借助 call、apply 和 bind 来改变 this 的指向问题。 示例一,当fn方法作为obj对象的属性调用时,this指向obj对象,当另外一个变量引用fn方法时,因为它作为普通函数调用,所以this指向window对象
const obj = {title: 'test',fn: function() {console.dir(this.title)} } obj.fn() // test const getTitle = obj.fn getTitle() // undefined 复制代码这种方式实际就是函数调用时,并没有上下文对象,只是对函数的引用,同样的问题,还发生在传入回调函数中
test(obj.fn) // 传入函数的引用,调用时也是没有上下文 复制代码示例二,即使在函数内部定义的函数,如果它作为普通对象调用,this同样指向window对象
const obj = {title: 'test',name: '张三',fn: function() {console.dir(this.title)function getName(){console.dir(this.name)}getName()} } obj.fn() // test// undefined 复制代码七 练习
var xuuu = 123 function test() {var xuuu = 456this.aa = 6666 return function() {console.log(xuuu)console.log(this.aa)console.log(this.xuuu) }var sdf=new test() sdf() // 456, undefined, 123 test()() // 456,6666,123 复制代码结论:第一种情况的中this.aa指向sdf,return中this指向全局对象window;第二种情况中两次this都指向window
八 箭头函数的this
在以往的函数中,this 有各种各样的指向(隐式绑定,显示绑定,new 绑定, window 绑定......),虽然灵活方便,但由于不能在定义函数时而直到实际调用时才能知道 this 指向,很容易给开发者带来诸多困扰。比如下面的代码
function User() {this.name = 'John'setTimeout(function greet() {console.log(`Hello, my name is ${this.name}`) // Hello, my name is console.log(this) // window}, 1000) }const user = new User() 复制代码如果想把greet里面的this指向user对象该怎么办呢? 1 使用闭包
function User(){const self = thisthis.name = 'John'setTimeout(function greet() {console.log(`Hello, my name is ${self.name}`) // Hello, my name is Johnconsole.log(self) // User {name: "John"}}, 1000) }const user = new User() 复制代码** 2 使用显示绑定bind **
function User() {this.name = 'John';setTimeout(function greet() {console.log(`Hello, my name is ${this.name}`); // Hello, my name is Johnconsole.log(this); // User {name: "John"}}.bind(this)(), 1000); }const user = new User(); 复制代码** 利用 setTimeout 的可以传更多参数的特性 **
function User() {this.name = 'John';setTimeout(function greet(self) {console.log(`Hello, my name is ${self.name}`); // Hello, my name isconsole.log(self); // window}, 1000, this); }const user = new User(); 复制代码** 箭头函数如何解决 **
function User() {this.name = 'John';setTimeout(() => {console.log(`Hello, my name is ${this.name}`); // Hello, my name is Johnconsole.log(this); // User {name: "John"}}, 1000); }const user = new User(); 复制代码**箭头函数在自己的作用域内不绑定 this,即没有自己的 this,**如果要使用 this ,就会指向定义时所在的作用域的 this 值。在上面的代码中即指向 User 函数的 this,而 User 函数通过 new 绑定,所以 this 实际指向 user 对象
function foo() {return () => {console.log(this.a);}; } let obj1 = {a: 2 }; let obj2 = {a: 22 }; let bar = foo.call(obj1); // foo this指向obj1 bar.call(obj2); // 输出2 这里执行箭头函数 并试图绑定this指向到obj2 但并不成功 复制代码结论: 1、箭头函数没有自己的this,它的this继承于它外面第一个不是箭头函数的函数的this指向。 2、箭头函数的 this 一旦绑定了上下文,就不会被任何代码改变 3、箭头函数使用call、apply、bind时,会自动忽略掉第一个参数 4、严格模式并不影响箭头函数自己的this
九 最后的图片
总结
- 上一篇: Python面向对象2-类和构造方法
- 下一篇: 爬虫基本原理讲解