js学习笔记:this

this是Javascript语言的一个关键字,它代表函数运行时自动生成的一个内部对象,只能在函数内部使用。

this是在函数运行时绑定的,而不是编写时。它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式

随着函数使用场合的不同,this的值会发生变化。但是有一个总的原则,那就是:
this指的是,调用函数的那个对象!
this指的是,调用函数的那个对象!
this指的是,调用函数的那个对象!

所以首先必须要搞清楚在JS里面有几种函数的调用方式:

  • 普通函数调用
  • 作为对象方法来调用
  • 作为构造函数来调用
  • 使用apply/call方法来调用
  • Function.prototype.bind方法

接下来就按每种情况分别讨论一下this的指向问题:

调用规则

普通函数调用

  var x = 1;
  function test(){
    alert(this.x);
  }
  test(); // 1

这里定义的全局变量x其实是全局对象window的一个属性,因此在调用函数时输出了全局对象的x属性值,即1。

  var x = 1;
  function test(){
    this.x = 0;
  }
  test();
  alert(x); //0

可以证明这里的this指代的就是全局对象window。

当然如果在严格模式中执行,那么这里的this会是undefined。

作为对象方法来调用(隐式绑定)

作为一个对象的方法进行调用时,此时this指代的就是这个对象

    var name="XL";
    var person={
        name:"xl",
        showName:function(){
            console.log(this.name);
        }
    }
    person.showName();  //xl

    var showNameA=person.showName;
    showNameA(); //XL

person.showName(),此时调用函数的很明显是person对象,因此this就是person对象。
showNameA() 则是普通函数调用,this为全局变量window。(虽然函数是person对象定义的,但此处只是普通函数调用)

    var personA={
        name:"xl",
        showName:function(){
            console.log(this.name);
        }
    }
    var personB={
        name:"XL",
        sayName:personA.showName
    }

    personB.sayName();  //XL

虽然showName这个方法是在personA对象中定义的,但却是personB调用的,因此this还是指向personB对象。

即,对象属性引用链中只有最后一个对象会影响this;

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

var obj2 = {
    a:42,
    foo:foo
}

var obj1 = {
    a:2,
    obj2:obj2
}

obj1.obj2.foo();  //42

即最后是obj2调用的函数,因此this指向obj2。

但是在一些调用情况下,this没有绑定到对象上,而是默认绑定到了全局对象或undefined(严格模式)上。这称为绑定丢失

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

var obj = {
    a:2,
    foo:foo
}

var bar = obj.foo;

var a = "oops,global";

bar();  //"oops,global"

虽然bar是obj.foo的一个引用,但是实际上bar只是引用foo函数本身,与直接的函数调用没有任何区别,因此this指向的是全局变量。

另外一种情况发生在传入回调函数时:

function foo(){
    console.log(a);
}

function doFun(fn){
    fn();  
}

var obj = {
    a:2,
    foo:foo
}

var a = "oops,global";

doFun(obj.foo);  //"oops,global"

fn()其实引用的是foo(),因此是普通函数调用,this指向全局变量。

使用js内置的函数也没有区别:

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

var obj = {
    a:2,
    foo:foo
}

var a = "oops,global";

setTimeout(obj.foo,100);  //"oops,global"

作为构造函数调用

所谓构造函数,就是通过这个函数生成一个新对象(object)。这时,this就指这个新对象。

  function test(){
    this.x = 1;
  }
  var o = new test();
  alert(o.x); // 1
    function  Person(name){
        this.name=name;
    }
    var personA=Person("xl");
    console.log(personA.name); // 输出  undefined
    console.log(window.name);//输出  xl

在这个例子中,没有使用new操作符,因此不是作为构造函数而是普通函数调用,因此this指向全局对象window。

call/apply方法的调用(显式绑定)

call()/apply()是函数对象的一个方法,它的作用是改变函数的调用对象,它的第一个参数是一个对象,它们会把这个对象绑定到this,借着在调用函数时指定这个this。
通过这个方法可以直接指定对应函数this的绑定对象,因此为显式绑定。

    var name="XL";
    var Person={
        name:"xl",
        showName:function(){
            console.log(this.name);
        }
    }

    Person.showName.call(); //XL

这里call函数的第一个参数为空,默认指向window对象。

若将代码改为:

var o = {
    name:"this is o"
}

Person.showName.call(o); //this is o

此时调用函数的对象是o,因此this为o。

硬绑定

使用硬绑定可以解决之前说的绑定丢失问题:

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

var obj = {
    a:2
}

var bar = function(){
    foo.call(obj);  
}

bar();  //2
setTimeout(bar,100);  //2

//硬绑定的bar不可能再修改它的this
bar.call(window);  //2

在函数bar内部,我们强制把foo的this绑定到obj。无论之后如何调用函数bar,它总会手动在obj上调用foo。
这种绑定是一种显式的强制绑定,因此为硬绑定。

由于硬绑定是一种非常常用的模式,因此ES5中提供了内置的方法bind()。

bind()方法

    var name="XL";
    function Person(name){
        this.name=name;
        this.sayName=function(){
            setTimeout(function(){
                console.log("my name is "+this.name);
            },50)
        }
    }
    var person=new Person("xl");
    person.sayName();  //my name is XL

这里虽然是作为person对象的方法调用的,但此方法内是一个setTimeout函数。

setTimeout/setInterval/匿名函数执行的时候,this默认指向window对象,除非手动改变this的指向。

因此这里实际上函数执行时this指向的是window对象。

那么如何能绑定person对象呢?答案就是利用bind()函数

    var name="XL";
    function Person(name){
        this.name=name;
        this.sayName=function(){
            setTimeout(function(){
                console.log("my name is "+this.name);
            }.bind(this),50)  //注意这个地方使用的bind()方法,绑定setTimeout里面的匿名函数的this一直指向Person对象
        }
    }
    var person=new Person("xl");
    person.sayName(); //输出 “my name is xl”;

匿名函数使用bind( )后将this绑定为person对象。

优先级

我们已经了解了this绑定的一些规则。但是如果在某个调用位置可以应用多个规则怎么办?这时就必须给这些规则设定优先级。

  • 首先,普通函数调用优先级是最低的
  • 那么,显式绑定与隐式绑定呢?
function foo(){
    console.log(this.a);
}

var obj1 = {
    a:2,
    foo:foo
}

var obj2 = {
    a:3,
    foo:foo
}

obj1.foo();  //2
obj2.foo();  //3

obj1.foo.call(obj2);  //3
obj2.foo.call(obj1);  //2

可以看到显式绑定优先级更高。

  • 那么构造函数调用和隐式绑定呢?
function foo(something){
    this.a = something;
}

var obj1 = {
    foo:foo
};
var obj2 = {};

obj1.foo(2);
console.log(obj1.a);  //2

obj1.foo.call(obj2,3);
console.log(obj2.a);  //3

var bar = new obj1.foo(4);
console.log(obj1.a);  //2
console.log(bar.a);   //4

可以看出构造函数绑定比隐式绑定优先级高。

根据以上优先级和规则可以按照下面的顺序进行判断:

  • 函数是否在new中调用?(调用构造函数)如果是的话this绑定的是新创建的对象。
var bar = new foo();
  • 函数是否通过call/apply(显式绑定)或者bind(硬绑定)进行调用?如果是的话this绑定的是指定的对象。
var bar = foo.call(obj2);
  • 函数是否在某个对象中调用(隐式绑定)?如果是的话this绑定的是该对象。
var bar = obj1.foo();
  • 如果以上都不是的话,函数绑定到全局对象(非严格模式)或undefined(严格模式)。
var bar = foo();

绑定例外

在某些场景下this的绑定行为可能会出乎意料。

被忽略的this

如果把null或者undefined作为this的绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际绑定的会是全局对象。

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

var a = 2;

foo.call(null);  //2

那什么情况下会传入null呢?
一种非常常见的做法是使用apply()来“展开”一个数组,并当做参数传入一个函数。

function foo(a,b){
    console.log(a+","+b)
}

//把数组展开成参数
foo.apply(null,[2,3]);  //2,3

apply方法需要传入一个参数当做this的绑定对象,如果不关心this的话,你仍然需要传入一个占位值,这时null可能是一个不错的选择。

间接引用

另一个需要注意的是,你有可能无意的创建一个函数的间接引用,在这种情况下this会指向全局对象。

间接引用在赋值时最容易发生。

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

var a = 2;
var  o = {
    a:3,
    foo:foo
}
var p = {a:4}

o.foo(); //3
(p.foo = o.foo)();  //2

赋值表达式p.foo = o.foo的返回值是目标函数的引用,因此调用的是foo()而不是p.foo()或o.foo()。

箭头函数

之前介绍的绑定规则中可以包含所有正常的函数,但是ES6中介绍了一种无法使用这些规则的特殊函数类型:箭头函数。

箭头函数并不是使用function关键字定义的,而是使用操作符=>定义的。
箭头函数不遵循上面的this绑定规则,而是根据外层作用域来决定this。
即,箭头函数会继承外层函数调用的this绑定。

function foo(){
    //返回一个箭头函数
    return (a) => {
        //this继承自foo()
        console.log(this.a);
    };
}

var obj1 = {
    a:2
};

var obj2 = {
    a:3
};

var bar = foo.call(obj1);
bar.call(obj2);  //2,不是3!

foo()内部的箭头函数会捕获调用时foo()的this。由于foo()的this绑定的是obj1,因此bar(引用箭头函数)的this也绑定到obj1。箭头函数的绑定无法被修改。

箭头函数最常应用于回调函数中。

function foo(){
    setTimeout(()=>{
        //这里的this继承自foo()
        console.log(this.a);
    },100)
}

var obj = {a:2}

foo.call(obj);  //2
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值