JS中`THIS`相关问题梳理

1,450 阅读5分钟

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

一、定义

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

二、使用情况

  • 1、全局作用域里的thiswindow,全局作用域下相当于是window.fn()执行只是把window.省略了(严格模式下是undefined)。

    console.log(this === window) // true
    
    window.a  = 13;
    console.log(this.a) // 13
    
  • 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

使用方法:

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;//=>对象中原本没有,所以使用后要删掉
```

自己基于原生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'
    
  • 以题为例分析

最终我们可以得出:

  • 一个CALL是让左边函数执行(this是传递的参数)
  • 多个CALL是让最后传参的函数执行(thiswindow/undefined

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]);

3、bind

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

语法:

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

区别:

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

作用:

  • 把函数中的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的同时立即把方法执行)