作用 & 意义
this关键字具有一种指向作用,类似于指针的作用,可以为你的变量或方法提供一个指向。
a.如果是一般函数,this指向全局对象window;
b.在严格模式下"use strict",为undefined.
c.对象的方法里调用,this指向调用该方法的对象.
d.构造函数里的this,指向创建出来的实例.
多数情况下,this 指向调用它所在方法的那个对象。
当调用方法没有明确对象时,this 就指向全局对象。在浏览器中,指向 window;在 Node 中,指向 Global。(严格模式下,指向 undefined)。
this 的指向是在调用时决定的,而不是在书写时决定的。这点和闭包恰恰相反。
不管方法被书写在哪个位置,它的 this 只会跟着它的调用方走。
特殊情境下的 this 指向
在三种特殊情境下,this 会 100% 指向 window:
1、立即执行函数(IIFE)
2、setTimeout 中传入的函数
3、setInterval 中传入的函数
“危险” 的严格模式
1、普通函数中的 this 在严格模式下的表现
所谓 “普通函数” ,这里我们是相对于箭头函数来说的。在非严格模式下,直接调用普通函数时,正如我们开篇所说,函数中的 this 默认指向全局变量(window 或 global),而在严格模式下,this 将保持它被指定的那个对象的值,所以,如果没有指定对象,this 就是 undefined 。
2、全局代码中的 this 在严格模式下的表现
像这样处于全局代码中的 this, 不管它是否处于严格模式下,它的 this 都指向 Window(这点要特别注意,区分度非常高,很多同学面试的时候会误以为这里也是 undefined )。
隐式丢失
隐式丢失就是指隐式绑定的函数丢失绑定对象,从而默认绑定到全局或者undefined(取决于是否使用严格模式)。
1、为函数调用创建别名
2、传入回调函数
3、传入语言内置的函数
箭头函数
箭头函数中的 this 比较特别,它和严格模式、非严格模式啥的都没关系。它和闭包很相似,都是认“死理”—— 认“词法作用域”的家伙。所以说箭头函数中的 this,和你如何调用它无关,由你书写它的位置决定(和普通函数的 this 规则恰恰相反~)。
因为箭头函数的 this 指向是静态的,“一次便是一生”。
改变this指向
改变 this 的指向,我们主要有两条路:
1、通过改变书写代码的方式做到(比如上一节提到的箭头函数)。
当我们将普通函数改写为箭头函数时,箭头函数的 this 会在书写阶段(即声明位置)就绑定到它父作用域的 this 上。无论后续我们如何调用它,都无法再为它指定目标对象。
2、显式地调用一些方法来帮忙。
改变 this 指向,我们常用的是 call、 apply 和 bind 方法。
call、apply 和 bind,都是用来改变函数的 this 指向的。
call、apply 和 bind 之间的区别比较大,前者在改变 this 指向的同时,也会把目标函数给执行掉;后者则只负责改造 this,不作任何执行操作。
call 和 apply 之间的区别,则体现在对入参的要求上。前者只需要将目标函数的入参逐个传入即可,后者则希望入参以数组形式被传入。
call方法的模拟
Function.prototype.myCall = function(context, ...args) {
// step1: 把函数挂到目标对象上(这里的 this 就是我们要改造的的那个函数)
context.func = this
// step2: 执行函数,利用扩展运算符将数组展开
context.func(...args)
// step3: 删除 step1 中挂到目标对象上的函数,把目标对象”完璧归赵”
delete context.func
}
apply方法的模拟
Function.prototype.myApply = function (context, arr) {
context.func = this
context.func(...arr)
delete context.func
}
bind方法的模拟
Function.prototype.bind1 = function () {
//将参数拆解为数组
const args = Array.prototype.slice.call(arguments);
//获取 this (数组第一项)
const t = args.shift();
//fn1.bind(...) 中的 fn1
const self = this;
//返回一个函数
return function () {
return self.apply(t, args);
}
}
this的四种绑定规则
1、默认绑定
对于默认绑定来说,决定this绑定对象的是函数体是否处于严格模式,严格指向undefined,非严格指向全局对象。
2、隐式绑定
函数在调用位置,是否有上下文对象,如果有,那么this就会隐式绑定到这个对象上。
隐式绑定丢失的问题:实际上就是函数调用时,并没有上下文对象,只是对函数的引用,所以会导致隐式绑定丢失。
3、显式绑定
如果单纯使用隐式绑定肯定没有办法得到期望的绑定,幸好我们还可以在某个对象上强制调用函数,从而将this绑定在这个对象上。
规则:我们可以通过apply、call、bind将函数中的this绑定到指定对象上。
4、new绑定
在js中,实际上并不存在所谓的‘构造函数‘,只有对于函数的‘构造调用‘。
new的时候会做哪些事情:
1.创建一个全新的对象。
2.这个新对象会被执行 [[Prototype]] 连接。
3.这个新对象会绑定到函数调用的this。
4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
规则:使用构造调用的时候,this会自动绑定在new期间创建的对象上。
this四种绑定规则的优先级
显式绑定和new绑定无法直接比较(会报错),默认绑定是不应用其他规则之后的兜底绑定所以优先级最低,最后的结果是:
显式绑定 > 隐式绑定 > 默认绑定
new绑定 > 隐式绑定 > 默认绑定
箭头函数的this指向不会使用上述的四条规则。
箭头函数的this规则:
1.箭头函数中的this继承于它外面第一个不是箭头函数的函数的this指向。
2.箭头函数的 this 一旦绑定了上下文,就不会被任何代码改变。