this 详解

187 阅读5分钟

在js对象中,this在函数外部的上下文环境中,指向全局对象。this在函数内部时,指向取决于函数最终运行的方式。

函数外部

this在函数外部的上下文环境中,指向全局对象。

1. 全局环境

在全局运行的上下文中(在任何函数体外部),this 指代全局对象,无论是否在严格模式下。

var name = '小王';
console.log(this.name === window.name); // true

2. 函数外部的上下文环境

this在函数外部的上下文环境中,指向全局window。

var age = 17;
var obj = {
    objage: this.age//this => window
}
obj.objAge; //17

尽管在另一个对象字面量里面,还是指向全局。

var age = 17;
var obj = {
    age: 19,
    objn:{
        objage: this.age//this => window
    }
}
obj.objn.objAge; //17

函数内部

this在函数内部时,指向取决于函数最终运行的方式。

1.直接调用

函数在 window环境下被调用,此时函数内的 this 指向 window。

function foo(){
    console.log(this);//this => window
}
foo();//window

2.对象中的函数

1. 对象调用函数

对象中的函数被对象 obj 调用,此时函数内部的 this 指向对象 obj ,this.name = obj.name,而 this.age 不存在,输出undefined。

var name = '小王';
var age = 17;
var obj = {
    name: '小张',
    objage: this.age,//this => window
    foo: function(){
        console.log(this.name + "年龄" + this.age);//this => obj
    }
}
obj.objAge; //17
obj.foo(); //小张年龄undefined

2. 对象调用就近原则

obj.objn.foo(); 可以看成是 foo() 前最靠近它的对象也就是 objn 调用它,所以函数内部 this 指向 objn。

var name = '小王';
var age = 17;
var obj = {
    name: '小张',
    age: 19,
    objn:{
        name: '小李',
        foo: function(){
            console.log(this.name + "年龄" + this.age);//this => objn
        }
    }
}
obj.objn.foo(); //小李年龄undefined

3. 对象外部调用函数

其实这样调用对象内部的函数就相当于把 obj.foo 这个函数赋值给了变量 a ,这时 foo 的 this 指向谁就得看 a() 怎么运行了,a() 在window 环境下运行,就相当于直接调用函数的那种情况,this 指向 window 。

var name = '小王';
var age = 17;
var obj = {
   name: '小张',
   foo: function(){
       console.log(this.name + "年龄" + this.age);
   }
}
var a = obj.foo;
a(); //小王年龄17 ===> 此时foo 函数内部的 this 指向 window

       ||
       
var a = obj.foo; == var a = function(){console.log(this.name + "年龄" + this.age);}

手动指定this

call、apply、 bind 用来手动的指定函数最终执行时的 this 的指向。

call、apply:当一个函数的函数体中使用了 this 关键字时,通过所有函数都从 Function 对象的原型中继承的 call() 方法和 apply() 方法调用时,它的值可以绑定到一个指定的对象上。

bind:调用 fn.bind(someObject) 方法会创建并返回一个与 fn 具有相同函数体和作用域的函数,但是在这个新函数中,this 将永久地被绑定到了 bind 的第一个参数,无论这个函数是如何被调用的。

1. call、apply、 bind对比

除了 bind 方法后面多了个 () 外 ,结果返回都一致。由此得出结论,bind 返回的是一个新的函数,你必须调用它才会被执行。

var name = '小王';
var age = 17;
var obj = {
    name: '小张',
    objage: this.age,
    foo: function(){
        console.log(this.name + "年龄" + this.age);
    }
}
var objn = {
    name: '小李',
    age: 22
}
obj.foo.call(objn);     // 小李年龄22
obj.foo.apply(objn);    // 小李年龄22
obj.foo.bind(objn)();      // 小李年龄22

bind 返回的新函数的 this 永远的绑定到了 bind 的第一个参数上,无论这个函数是如何被调用的。

function foo() {
    return this.name;
}

var foon = foo.bind({name: '小张'});
foon(); // 小张

var obj = {
    name: '小王',
    foon: foon
};

obj.foon(); // 小张

尽管在此使用 bind 改变也没用!!(call、apply 也是不能改变的)

function foo() {
    return this.name;
}

var foon = foo.bind({name: '小张'});
var foox = foon.bind(obj)
var obj = {
    name: '小王',
    foox: foox
};

obj.foox(); // 小张

2. 对比call、apply、bind 传参情况下

call 和 bind 的参数都是一个一个传的。而 apply 则是全部放在一个数组里面传过去。

var name = '小王';
var age = 17;
var obj = {
    name: '小张',
    objage: this.age,
    foo: function(a, b){
        console.log(this.name + "年龄" + this.age , "来自" + a + "去往" + b);
    }
}
var objn = {
    name: '小李',
    age: 22
}
obj.foo.call(objn, "北京", "上海");     // 小李年龄22来自北京去往上海
obj.foo.apply(objn, ["北京", "上海"]);    // 小李年龄22来自北京去往上海
obj.foo.bind(objn, "北京", "上海")();      // 小李年龄22来自北京去往上海
obj.foo.bind(objn, ["北京", "上海"])();      // 小李年龄22来自北京,上海去往undefined

其他

下面这个代码会报错,因为函数在定时器中运行,就相当于普通函数,这是 this 指向 window 。

var obj = {
    name: '小张',
    foo1: function(){
        console.log(this.name);
    },
    foo2: function(){
        setTimeout(function() {
            console.log(this);//window
            this.foo1();//this => window
        },1000);
    }
}

foo2 作为对象的方法,被对象调用时 this 指向对象,使用 apply(this)\call(this) 也就是手动的指向对象 obj 。解决定时器丢失 this 问题还可以在定时器和 foo2 之间定义一个 var that = this 保存一下 this 的引用,这里其实是闭包, that 不会被注销掉,会一直保存。除此之外,还可以在定时器中使用箭头函数,箭头函数会丢失 this ,所以箭头函数的 this 指向由外层决定,也就是指向外层。

var obj = {
    name: '小张',
    foo1: function(){
        console.log(this.name);
    },
    foo2: function(){
        console.log(this);//obj
        setTimeout(function() {
            console.log(this);//obj
            this.foo1();//this => obj
        }.apply(this),1000);
    }
}

总结

this 在函数外部(全局环境)中,指向 window 。

this 在函数内部环境中,不是由定义函数决定的,而是取决于最终函数被运行的方式。

call、apply、 bind 可以手动指定 this 。bind 返回新函数,this 将永久地被绑定到了 bind 的第一个参数,无论这个函数是如何被调用的。而 call 和 bind 的参数直接传,apply 需要打包成一个数组。

定时器内部运行函数相当于运行普通函数,this 指向 window,解决方案可使用 call、apply 或闭包或者箭头函数。