this就是js里的关键字,有特殊意义,代表函数执行主体。

一、定义

  • 函数执行的主体(不是上下文):意思是谁把函数执行的,那么执行主体就是谁

二、使用情况

  • 1、全局作用域里的thiswindow,全局作用域下相当于是window.fn()执行只是把window.省略了(严格模式下是undefined)。
  1. console.log(this === window) // true
  2. window.a = 13;
  3. console.log(this.a) // 13
  4. 复制代码
  • 2、函数里的this,看执行主体前有没有,如果有,那前面是谁,函数里的this就是谁,如果没有,那函数里的this就是window,严格模式下是undefined
function fn(){
    console.log(this)
}
fn();//window
let obj = {
    fn:function(){
        console.log(this) 
    }
}
obj.fn();//obj
var f = obj.fn;
f();//window
复制代码
  • 3、自执行函数里的thiswindowundefined(严格模式下)
(function(){
    console.log(this);//==>window
})();
~function(){}();//==>window
+function(){}();//==>window
-function(){}();//==>window
!function(){}();//==>window
复制代码
  • 4、回调函数里的this一般情况下是window
let ary = [1,2,3];
ary.forEach(function(item,index){
    console.log(this)
})
//================================//
setTimeout(function(num){
    console.log(num)
    console.log(this)
},1000,1)
//================================//
function fn(m){
    m()
}
fn(function(){
    console.log(this)
})
复制代码
  • 5、箭头函数没有this

但是要是在箭头函数里使用this,他就会往上一级作用域查找,如果上一级作用域也没有,那就继续往上找,直到找到全局的window为止

let obj = {
    num: 2,
    fn: function () {
        // this-->obj
        let m = () => {
            // this-->obj
            console.log(this.num) // obj(向上一级作用域查找)
        }
        m()
    }
}
obj.fn()
复制代码
  • 6、构造函数里的this是当前实例
  • 7、实例原型上的公有方法里的this一般是当前实例(下面改变this的方法中有体现)
  • 8、给元素绑定事件行为,那事件里的this就是当前被绑定的元素本身
btn.onclick = function(){
    console.log(this) // btn
}
复制代码

三、面向对象中有关私有/公有方法中的THIS问题

总结下来this在面向对象中,主要还是看是谁执行的,也就是执行函数点前面是谁

  • 1、方法执行,看前面是否有点,点前面是谁THIS就是谁
  • 2、把方法总的THIS进行替换
  • 3、再基于原型链查找的方法确定结果即可

四、改变this指向:call/apply/bind

每一个函数(普通函数/构造函数/内置类)都是Function这个内置类的实例,所以:函数.__proto__===Function.prototype函数可以直接调取Function原型上的方法

call / apply / bind
  • 原型上提供的三个公有属性方法
  • 每一个函数都可以调用这个方法执行
  • 这些方法都是用来改变函数中的THIS指向的

1、call

语法:

  • 函数.call(context,params1,params2….)

定义:

  • 函数基于原型链找到Function.prototype.call这个方法,并且把它执行,在call方法执行的时候改变里面的this关键字

作用:

  • 让当前函数执行
  • 把函数中的THIS指向改为第一个传递给CALL的实参
  • 把传递给CALL其余的实参,当做参数信息传递给当前函数

注意:

  • 如果执行CALL一个实参都没有传递,非严格模式下是让函数中的THIS指向WINDOW,严格模式下指向的是UNDEFINED

  • fn.call(null);

    • //=>this:window
    • 严格下是null(第一个参数传递的是null/undefined/不传,非严格模式下this指向window,严格模式下传递的是谁this就是谁,不传thisundefined

使用方法:

5.JS中`THIS`相关问题梳理 - 图1

function fn(){}
fn.call(); //=>fn函数基于原型链找到Function.prototype上的call方法,并且让其执行(执行的是call方法:方法中的this是fn)
fn.call.call(); //=>fn.call就是Function.prototype上的call方法,也是一个函数,只要是函数就能用原型上的方法,所以可以继续调用call来执行
Function.prototype.call = function $1(){
//...
}
fn.call => $1
fn.call() => $1()  this:fn
fn.call.call() => $1.call() => 继续让call执行,this:$1
实例.方法():都是找到原型上的内置方法,让内置方法先执行(只不过执行的时候做了一些事情会对实例产生改变,而这也是这些内置方法的作用),内置方法中的THIS一般都是当前操作的实例
复制代码

call简单实现原理

```javascript
//=>我们的需求是想让FN执行的时候,方法中的THIS指向OBJ
obj.fn(); //=>Uncaught TypeError: obj.fn is not a function  
//因为此时obj中并没有fn这个属性
-------解决办法---------
obj.fn = fn;
obj.fn(); //=>this:obj  //=>'OBJ'
delete obj.fn;//=>对象中原本没有,所以使用后要删掉

复制代码


<a name="b2c8c983"></a>
### 自己基于原生JS简单的实现内置的call方法

- 详细梳理:

~ function () { /*

    * myCall:改变函数中的THIS指向 
    *   @params
    *      context 可以不传递,传递必须是引用类型值(因为后面要给它加$fn的属性)  
    */
    function myCall(context) {
        //this:sum 也就是当前要操作的这个函数实例
        context = context || window;
        let args = [], //=>除第一个参数外剩余传递的信息值
            result;
        for (let i = 1; i < arguments.length; i++) {
            args.push(arguments[i]);
        }
        context.$fn = this;
        result = context.$fn(...args); //=>args=[10,20] ...是ES6中的展开运算符,把数组中的每一项分别的展开传递给函数 //=>context.$fn(10,20)
        delete context.$fn;
        return result;
    }
    /* 扩展到内置类的原型上 */
    Function.prototype.myCall = myCall;
}();

复制代码


- 可简写为

function myCall(context,…arg){ //context—>obj this—>fn context = context || window; // 如果你不传参、或者传null、传undefined,那context的值都是window let res = null; // 创建一个变量,准备接收函数执行结果 context.fn = this; // 把fn增加到obj里 res = context.fn(…arg); // 让fn指向 delete context.fn; // 把fn从obj里删除 return res; // 把this的返回值return出去 } Function.prototype.myCall = myCall; console.log(fn.myCall(obj,1,2)) // ‘ss’ 复制代码


- 以题为例分析

![](https://cdn.nlark.com/yuque/0/2020/webp/1307905/1598074406026-d9baa8f6-3819-41fc-bcb4-a5c4bd67e153.webp#alt=image)

> 最终我们可以得出:

> - 一个`CALL`是让左边函数执行(`this`是传递的参数)
> - 多个`CALL`是让最后传参的函数执行(`this`是`window`/`undefined`)



<a name="b913c65b"></a>
## 2、apply

> 和`call`方法一样,都是把函数执行,并且改变里面的`this`关键字的,唯一的区别就是传递给函数参数的方式不同

> - `call`是一个个传参
> - `apply`是按照数组传参

let obj={name:’OBJ’}; let fn=function(n,m){ console.log(this.name); } //=>让fn方法执行,让方法中的this变为obj,并且传递10/20 fn.call(obj,10,20); fn.apply(obj,[10,20]); 复制代码


<a name="5425fee1"></a>
## 3、bind

> 和`call`/`apply`一样,也是用来改变函数中的`this`关键字的,只不过基于`bind`改变`this`,当前方法并没有被执行,类似于预先改变`this`


<a name="5da210a2-1"></a>
### 语法:

- 函数.bind(context,params1,....)

<a name="9cdb3b71"></a>
### 区别:

- `bind`是预处理`this`,他并不会让函数执行
- `bind`方法的返回值是一个改变`this`之后的新函数

<a name="8076e5d8-1"></a>
### 作用:

- 把函数中的`THIS`指向通过预处理的方式改为第一个传递给`BIND`的实参
- 一般使用在绑定点击事件,不让函数立即执行时

let obj={name:’OBJ’}; function fn(){ console.log(this.name); } document.body.onclick=fn; //=>当事件触发,fn中的this:BODY //=>点击BODY,让FN中的THIS指向OBJ //document.body.onclick=fn.call(obj); //=>基于call/apply这样处理,不是把fn绑定给事件,而是把fn执行后的结果绑定给事件 document.body.onclick=function(){ //this:BODY fn.call(obj); } document.body.onclick=fn.bind(obj); 复制代码 ```

注意:

  • IE6~8中不支持bind方法
  • 预先做啥事情的思想被称为“柯理化函数”

优点:

  • bind的好处是:通过bind方法只是预先把fn中的this修改为obj,此时fn并没有执行呢,当点击事件触发才会执行fncall/apply都是改变this的同时立即把方法执行)

5.JS中`THIS`相关问题梳理 - 图2