js中关于this的情况以及call、apply、bind的源码分析

170 阅读3分钟

js中的this的情况

自执行函数/setTimeout/setIntrval

自执行函数/setTimeout/setIntrval中的this在非严格模式下一般指向window,严格模式下指向undefined。

自执行函数

(function(){
    console.log(this);
})()

setTimeout/setInterval

function Fn(){
    setTimeout(function(){
        console.log(this);
    },0)
}
Fn();

事件绑定

给元素的某个事件绑定方法,事件触发,方法执行,此时方法中的this一般指向元素本身.

<div id="box"></div> 
<!-- 使用DOM0级事件绑定 -->
<script>
// 情况1
box.onclick = function(){
    console.log(this);  // box
}

// 情况2
function click2(){
    console.log(this);  // box
}
box.onclick = click;

// 情况3
function click3(){
    console.log(this);      // this=>window;
    return function(){
        console.log(this)  // this=>box;
    }
}
box.onclick = click3();
</script> 
<!-- 使用DOM2级事件绑定 -->
<script>
// 标准浏览器下: 
box.addEventListener('click',function(){
    console.log(this);      // this=> box
})
// 在ie6,7,8下,绑定事件需要attachEvent,使用attachEvent绑定元素,在ie6,7,8下,this不执行当前元素,指向window.
box.attachEvent('click',function(){
    console.log(this);      // this=> window
})
</script>  

普通函数this指向

普通函数执行

  • this是谁和普通函数在哪定义或者在哪执行都没有关系.
  • 普通函数执行,他的this指向谁,取决于方法前面是否有"点".
    • 有"点"的话,点前面是谁,一般就指向谁
    • 没有"点"的话,一般都指向window(严格模式下指向undefined)
  • 自执行函数,一般都指向window.
function fn(){
    console.log(this);
}
let obj = {
    a: 10,
    fn: fn
}
fn();           // this->window
obj.fn();       // this->obj

// 在setTimeout中
setTimeout(obj.fn,1000);        // this->window
    // 这个时候相当于把obj.fn地址值拿过来,并没有执行,this:window
setTimeout(obj.fn(),1000);      // this->obj
    // 这个时候相当于把obj.fn直接执行,方法执行: this: obj

构造函数执行

构造函数中的this一般指向当前类的实例

function Fn(){
    console.log(this);  // fn
}
let fn = new Fn();

箭头函数中的this

  • 箭头函数没有this执行,箭头函数中所用到的this都是指向其上下文
    • 箭头函数用call,apply时,this被忽略掉了,因为箭头函数没有this
  • 箭头函数还没有arguments实参集合和prototype(也就是没有构造器,所以不能被new执行)
let obj = {
    fn: ()=>{
        /*
         * 这种是箭头函数执行,箭头函数没有this,this都是其上下文中的this;
        * */
        // 不管怎么执行: this: window
        console.log(this)
    },
    fn1(){
        /*
         * obj = { fn(){} }  相当于 obj = {fn: function(){}}
         * 省略方法的function
         * */
        console.log(this);
    }
}
obj.fn();       // this->window;
obj.fn1();      // this->obj

call、apply、bind分析

都是为了强制改变函数中的this执行,对箭头函数没有效果

call、apply、bind的区别

let obj = {};
function Fn(){};
// call语法
obj.call([context],[参数1],[参数2],[...]);
// apply语法
obj.apply([context],[[参数1,参数2,...]]);
// bind语法
obj.bind([context],[[参数1,参数2,...]])();

- [context]: 就是改变this的指向,传谁就是谁(特殊: 非严格模式下,传递null/undefined指向的也是window)
- call/apply唯一的区别就是参数不同
    call是一个一个传
    apply是把需要传递的参数以数组的形式传递
  • call/apply都是改变this的同时把函数执行了
  • bind不是立即执行,而是预先改变this,在传递一些内容(柯理化函数)

call/apply/bind源码解析

(function(proto){
    /*
        call
    */ 
    function call(context){
        if(context == undefined) {
            context = window;
        }
        context.fn = this;
        var arr = [];
        for(var i = 1;i<arguments.length;i++){
            arr.push(arguments[i]);
        }
        let result = context.fn(...arr);
        delete context.fn;
        return result;
    }
    /*
        apply
    */
    function apply(context,...args){
        if(context == undefined) {context = window;}
        context.fn = this;
        var result = context.fn(...args);
        delete context.fn;
        return result;
    }
    /*
        bind
    */
    function bind(context){
        if(context == undefined) {context = window;}
        var that = this;
        var outArgs = Array.prototype.slice.call(arguments,1);
        return function anonymous(){
            var innerArgs = Array.prototype.slice.call(arguments,0);
            params = outArgs.concat(innerArgs);
            that.apply(context,params);
        }
    }
    function bind(context,...outArgs){
        var that = this;
        return function anonymous(...innerArgs){
            that.apply(context,[...outArgs,...innerArgs])
        }
    }
    
    proto.bind = bind;
    proto.apply = apply; 
    proto.call = call; 
})(Function.prototype)

阿里面试题

function fn1(){ console.log(this,1) }
function fn2(){ console.log(this,2) }
fn1.call(fn2);
fn1.call.call(fn2);
Function.prototype.call(fn2)
Function.prototype.call.call(fn2)