【JavaScript】this/call/apply/bind深度理解

375 阅读5分钟

一、this

1、什么是this

JS中的this表示了一个对象的引用,那么这个对象到底指向了谁呢?首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象

2、详细了解this

例1:

function a(){
    var name = "liang";
    console.log(this.name); //undefined
    console.log(this); //window
}
a();

这里为什么this打印出来是window呢?其实函数a在执行的时候是全局作用域的window调用的,等于是window.a(),遵循“this的最终指向的是那个调用它的对象”这个规则,可以看下面代码:

function a(){
    var name = "liang";
    console.log(this.name); //undefined
    console.log(this);  //window
}
window.a()

例2:

var obj = {
    name:"liang",
    sayName:function(){
        console.log(this.name)  //liang
    }
}
obj.sayName()

上述代码就很明显的展示了“this的最终指向的是那个调用它的对象”这个规则,这里的sayName这个函数是对象obj调用他的,那么函数内部的this自然指向的是obj这个对象,obj对象中有name属性,打印的就是name属性的值

把上面的代码再改一下,

var obj = {
    name:"liang",
    sayName:function(){
        console.log(this.name)  //liang
    }
}
window.obj.sayName()

结果还是一样,这里的this为什么不是指向window,因为sayName方法最终还是由obj这个对象调用的,只是obj对象是挂载在window下的一个属性,但并不是window调用了sayName方法

同样的,下面的代码依然证明了“this的最终指向的是那个调用它的对象”这个规则:

var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //12
        }
    }
}
o.b.fn();
var o = {
    a:10,
    b:{
        // a:12,
        fn:function(){
            console.log(this.a); //undefined
        }
    }
}
o.b.fn();

上述两段代码中,最终调用fn函数的都只是b这个对象,所以this都指向的是b,第一段代码中b对象有a属性,因此打印的是b中的a属性值。而第二段代码中将b对象中的a注释掉了,那么this指向的b对象中找不到a属性了,自然打印的是undefined

例3:

看这段代码

var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;
j();

这里为什么this指向的是window呢?其实,var j = o.b.fn这一步操作是将fn这个函数赋值给了j,而j是挂载在window下的变量,那么j变量保存的函数执行的时候this指向的就是window

例4:

function Fn(){
    this.user = "liang";
}
var a = new Fn();
console.log(a.user); //liang

这里对象a可以点出函数Fn里面的user是因为new关键字可以改变this的指向,将这个this指向对象a,我们这里用变量a创建了一个Fn的实例(相当于复制了一份Fn到对象a里面),此时仅仅只是创建,并没有执行,而调用这个函数Fn的是对象a,那么this指向的自然是对象a,那么为什么对象a中会有user,因为你已经复制了一份Fn函数到对象a中,用了new关键字就等同于复制了一份

当this遇到return时:

function Fn()  
{  
    this.user = 'liang';  
    return {};  
}
var a = new Fn();  
console.log(a.user); //undefined
function Fn()  
{  
    this.user = 'liang';  
    return function(){};
}
var a = new Fn();  
console.log(a.user); //undefined
function Fn()  
{  
    this.user = 'liang';  
    return 1;
}
var a = new Fn();  
console.log(a.user); //liang
function Fn()  
{  
    this.user = 'liang';  
    return undefined;
}
var a = new Fn();  
console.log(a.user); //liang
function Fn()  
{  
    this.user = 'liang';  
    return null;
}
var a = new Fn();  
console.log(a.user); //liang

经上述几个return,总结得出:如果返回值是一个除null以外的对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例

二、call/apply/bind的理解

1.这三个东西是干什么用的,为什么要使用他们?

碰到以下案例:

var a = {
    user:"liang",
    fn:function(){
        console.log(this.user);
    }
}
var b = a.fn;
b(); //undefined

我想要的效果是b执行后打印出user,却得到结果是undefined,原因是为什么上面已经解释过了,那么如何能达到我要的效果呢? 这时候就需要用到call/apply/bind来改变this的指向了

2、call

var a = {
    user:"liang",
    fn:function(){
        console.log(this.user); //liang
    }
}
var b = a.fn;
b.call(a);

通过call方法,给第一个参数添加要把b添加到哪个环境中,简单来说,this就会指向那个对象

call方法除了第一个参数以外还可以添加多个参数,如下:

var a = {
    user:"liang",
    fn:function(num1,num2){
        console.log(this.user); //liang
        console.log(num1 + num2); //3
    }
}
var b = a.fn;
b.call(a,1,2);

3.apply

var a = {
    user:"liang",
    fn:function(){
        console.log(this.user); //liang
    }
}
var b = a.fn;
b.apply(a);

apply方法和call方法有些相似,它也可以改变this的指向,同样apply也可以有多个参数,但是不同的是,第二个参数必须是一个数组或者类数组,如下:

var a = {
    user:"liang",
    fn:function(num1,num2){
        console.log(this.user); //liang
        console.log(num1 + num2); //11
    }
}
var b = a.fn;
b.apply(a,[10,1]);

4.bind

bind方法和call、apply方法有些不同,但是它们都可以用来改变this的指向,bind改变完this指向之后是不会立马执行的

var a = {
    user:"liang",
    fn:function(){
        console.log(this.user);
    }
}
var b = a.fn;
b.bind(a);

上述代码最终并没有打印出liang,因为b改变完this指向之后并未执行

var a = {
    user:"liang",
    fn:function(){
        console.log(this.user); //liang
    }
}
var b = a.fn;
var c = b.bind(a);
console.log(c); //function() { [native code] }
c()

实际上bind方法返回的是一个修改过后的函数,我们手动执行之后就会成功打印出liang

var a = {
    user:"liang",
    fn:function(e,d,f){
        console.log(this.user); //liang
        console.log(e,d,f); //10 1 2
    }
}
var b = a.fn;
var c = b.bind(a,10);
c(1,2);

同样bind也可以有多个参数,并且参数可以执行的时候再次添加,但是要注意的是,参数是按照形参的顺序进行的

以上就是对call/apply/bind作用和使用的理解