JavaScript基础-this

关于this

this关键字是JavaScript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。

为什么要用this

下面我们解释一下为什么要使用this

function identify() {
    return this.name.toUpperCase();
}

function speak() {
    let greeting = "Hello,I'm" + identify.call(this);
    console.log(greeting);
}

var me = {
    name:"Kyle"
};

var you = {
    name:"Reader"
}

console.log(identify.call(me));//KYLE
console.log(identify.call(you));//READER

speak.call(me);//Hello,I'mKYLE
speak.call(you);//Hello,I'mREADER

这段代码可以在不同的上下文对象(me和you)中重复使用函数identify()speak(),不用针对每个对象编写不同版本的函数。

如果不使用this,那就需要给identify()speak()显示传入一个上下文对象。

function identify(context) {
    return context.name.toUpperCase();
}

function speak(context) {
    let greeting = "Hello,I'm"+identify(context);
    console.log(greeting);
}

var me = {
    name:"Kyle"
};

var you = {
    name:"Reader"
}

console.log(identify(me));//KYLE

speak(you);//Hello,I'mREADER

然而,this提供了一种更优雅的方式来隐式"传递"一个对象引用,因此可以将API设计得更加简洁并且易于复用。

随着你的使用模式越来越复杂,显示传递上下文对象会让代码变得越来越混乱,使用this则不会这样。

this到底是什么

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

当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

this全面解析

每个函数的this是在调用时被绑定的,完全取决于函数的调用位置(也就是函数的调用方法)。

调用位置

调用位置就是函数在代码中被调用的位置(而不是声明的位置)(函数的调用方法)。

绑定规则

我们来看看函数的执行过程中调用位置(调用方法)如何决定this的绑定对象。你必须找到调用位置,然后判断需要应用下面四条规则中的哪一条。

默认绑定

首先要介绍的是最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则。
思考一下下面的代码:

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

var a = 2;
foo();//2

值得注意的是用var声明在全局作用域中的变量会作为全局对象(window)的一个属性。

在本例中,函数调用时应用了this的默认绑定,因此this指向全局对象(window)。可以通过分析调用位置来看看foo()是如何调用的。在代码中,foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。

如果使用严格模式(strict mode),则不能将全局对象用于默认绑定,因此this会绑定到undefined

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

var a = 2;
foo();//TypeError:this is undefined

这里有一个微妙但是非常重要的细节,虽然this的绑定规则完全取决于调用位置,但是只有foo()运行在非strict mode下时,默认绑定才能绑定到全局对象;在严格模式下调用foo()则不影响默认绑定:

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

var a = 2;
(function (){
    "use strict";
    foo();//2
})();



通常在代码中不应该混用严格模式和非严格模式。

隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,不过这种说法可能会造成一些误导。

思考下面的代码:

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

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

obj.foo();//2

当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用foo()时ths被绑定到obj,因此this.a和obj.a是一样的。

对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。举例来说:

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

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

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

obj1.obj2.foo();//42

隐式丢失
一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否是严格模式。

思考以下代码:

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

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

var bar = obj.foo;

var a = "ops global";

bar();//"ops,global"

虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

一种更微妙、更常见并且更出乎意料的情况发生在传入回调函数时:

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

function doFun(fn) {
    fn();
}

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

var a = "ops global";

doFun(obj.foo);//ops global

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样。
如果把函数传入语言内置的函数,结果也是一样的,没有区别。就像我们看到的那样,回调函数丢失this绑定是非常常见的。

显示绑定

就像我们刚才看到的那样,在分析隐式绑定时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象上。如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么做那?

可以使用函数的call(…)和apply(…)方法。这两个方法是如何工作的呢?它们的第一个参数是一个对象,是给this准备的,接着在调用函数时将其绑定到this。因为你可以直接指定this的绑定对象,因此我们称之为显式绑定

思考以下代码:

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

var obj = {
    a:2
}

foo.call(obj)//2

通过foo.call(…),我么可以在调用foo时强制把它的this绑定到obj上。

如果你传入了一个原始值来当做this的绑定对象,这个原始值会被转换成它的对象形式。

从this绑定的角度来说,call(…)和apply(…)一样的,它们的区别体现在其他参数上。可惜,显示绑定仍然无法解决我们之前提出的丢失绑定的问题。

new绑定

这个最后一条this的绑定规则,在JavaScript中,构造函数只是一些使用new操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已。

思考下面代码:

function foo(a) {
    this.a = a;
}
var bar = new foo(2);
console.log(bar.a)//2

使用new来调用foo(…)时,我们会构造一个新对象并把它绑定到foo(…)调用中的this上。

判断this

四条规则存在着一定的优先级。可以按照下面的顺序来进行判断:

  1. 函数是否在new中调用(new绑定?)如果是的话this绑定的是新创建的对象。var bar = new foo()
  2. 函数是否通过call、apply(显示绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。var bar = foo.call(obj2)
  3. 函数是否存在某个上下文对象中调用(隐式调用)?如果是的话,this绑定的是那个上下文对象。var bar = obj1.foo()
  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。var bar = foo()。

对于正常的函数调用的话,理解这些知识你就可以明白this的绑定原理了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值