JavaScript中的this关键字

this是js的一个关键字,也是js种最复杂的机制之一,它被自动定义在所有函数的作用域中。
this的优势在哪里,为什么要用this?先看一个实例:
```
function identify() {
    return this.name.toUpperCase();
}

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

var me = {
    name: "Tom"
};

var you = {
    name: "Jerry"
};

identify.call(me); // TOM
identify.call(you); // JERRY

speak.call(me); // Hello, I'm TOM
speak.call(you); // Hello, I'm JERRY
```

### 1、this误解
以上代码可以在不同的上下文(me和you)中重复使用函数identify()和speak()。
如果不是用this关键字,就需要给identify()和speak()显示传递一个上下文对象,例如如下代码:
```
function identify(context) {
    return context.name.toUpperCase();
}

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

identify(you); // JERRY
speak(me); // Hello, I'm TOM
```

this关键字提供了一种优雅的方式来隐式传递一个对象引用,可以将API设计的更简洁。
人们很容易把this理解为指向函数自身的引用,但是实际上它比这个要复杂的多。
在看一段代码:
```
function bar(num) {
    console.log("bar: " + num);
    this.count++; // 记录bar函数调用次数
}

bar.count = 0;

for(var i=0; i<10; i++) {
    if (i > 5) {
        bar(i);
    }
}

console.log(bar.count); // 0
```

bar函数在for循环中调用了4次,但是bar.count仍然是0,what's the fuck?
很明显,this不是指向函数自身的引用。
还有人认为this指向函数的作用域,这种理解在某些情况下可以成功,但是在类似以下这种情况程序会抛错的:
```
function foo() {
    var a = 2;
    bar();
}

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

foo();  // undefined
```
这段代码试图使用this连通foo()和bar()的词法作用域,从而让bar()可以访问foo()的变量a,看来这样也是失败的
那么this到底指向哪里?它在函数中代表的又是什么呢?

### 2、绑定规则
this指向的东西是在运行时绑定的,它的绑定只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录或者称为上下文。
它会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是它其中一个属性,会在函数执行的过程中用到。
不同的绑定规则,this在函数调用时指向的内容会不同,而且也会根据函数调用位置的不同而不同,
绑定规则共有四条规则

- 默认规则
此规则是在无法应用其他规则是的默认规则,默认绑定this会指向全局对象,以下是实例:
```
function foo() {
    console.log(this.a);
}

var a = 2;

foo();  // 2
```
通过在全局作用域定义的变量是一个全局对象的一个同名属性,对于foo()函数的独立调用会应用this的默认绑定,
也就是this指向了全局对象,所以foo()调用会打印2。
如果使用strict模式,将无法使用默认绑定,this会绑定到undefined

- 隐式绑定
本条规则需要考虑的是函数调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。
实例代码:
```
function foo() {
    console.log(this.a);
}

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

abj.foo();  // 2
```
foo函数作为obj对象的属性被包含,所以通过obj.foo()的方式调用foo()函数时,会使用obj上下文,
因此this被绑定到obj对象,所以this.a和obj.a是一样的。
对于多层包含关系,this会被绑定到离调用函数最近的对象上下文中。例如:
```
function foo() {
    console.log(this.a);
}

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

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

obj1.obj2.foo(); // 42
```

再看一个隐式绑定的函数丢失绑定对象的实例
```
function foo() {
    console.log(this.a);
}

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

var bar = obj.foo;
var a = 3;
obj.foo();  // 2
bar();  // 3
```
bar和obj.foo指向相同的引用,但是bar()函数的调用位置是在全局上下文环境中,
所以使用了默认的绑定规则。

- 显式绑定
如果不想通过对象内部函数引用,想在某个对象上强制调用函数,该怎么办?
可以通过使用函数的call()或apply()方法。它们的第一个参数是一个对象,它们会把该对象绑定到this,因此可以直接指定this的绑定的对象,该方式称为显示绑定。例如:
```
function foo() {
    console.log(this.a);
}

var obj = {
    a: 2
}

foo.call(obj);  // 2
```
1. 硬绑定
硬绑定可以解决隐式绑定中,绑定丢失的问题。
例如:
```
function foo() {
    console.log(this.a);
}

var obj = {
    a: 2
}

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

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

bar.call(window);   // 2 硬绑定的bar不可能在修改它的this
```
因为已经通过foo.call(obj),将foo的this强制绑定到obj。无论如何调用bar()函数,foo()函数的this都会绑定到obj。这是显式的强制绑定,被称为硬绑定。
ES5中提供了内置方法Function.prototype.bind实现硬绑定。bind()方法会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。用法如下:
```
function foo(args) {
    console.log(this.a, args);
    return this.a + args;
}

var obj = {
    a: 2
}

var bar = foo.bind(obj);
var b = bar(3); // 2 3
console.log(b); // 5
```

- new绑定
在传统面向对象语言中,构造函数是类中的一些特殊方法,使用new初始化类时会自动调用类中的构造函数。
js中也有new操作符,使用方法看起来也和面向对象语言相同,其实两者是完全不同的。
在js中,构造函数只是一些使用new操作符时被调用的函数。它们不属于任何类,也不会实例化一个类。实际上,它们只是被new操作符调用的普通函数。
包括内置函数在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。
使用new调用函数时,会自动执行以下操作:
1、创建一个全新对象
2、对性对象执行原型连接
3、新对象会绑定到函数调用的this。
4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
思考以下代码:
```
funtion foo(a) {
    this.a = a;
}

var bar = new foo(2);
bar();  // 2
```
使用new来调用foo(...)时,会构造一个新对象并把它绑定到foo(...)调用中的this上。这种绑定称为new绑定。

如何判断this应用了那种绑定规则呢?可以按照下面的顺序进行判断:
1. 如果函数在new中调用(new绑定),this绑定的是新创建的对象。
```
var bar = new foo();
```
2. 如果通过call、apply(显示绑定)或硬绑定,this绑定的是传入的对象。
```
var bar = foo.apply(obj);
```
3. 如果函数在某个上下文对象中调用(隐式绑定),this绑定的就是那个上下文对象。
```
var bar = obj2.foo();
```
4. 如果都不是,使用默认绑定。如果在strict模式下,this绑定到undefined;否则绑定到全局对象。

但是,还是有一些以上四种绑定规则之外的例外:
1. 如果把null或undefined作为this的绑定对象传入call、apply或者bind,这些值会被忽略,实际应用默认绑定规则。
```
function foo() {
    console.log(this.a);
}

var a = 2;
foo.call(null); // 2
```
2. js还提供了一种软绑定softBind(...),和bind(...)使用方式相同。它会对指定的函数进行封装,首先会检查调用时的this,如果this绑定到全局对象或者undefined,那就把传入的对象绑定到this,否则不会修改this。

### 3、this词法
ES6提供了一种无法使用以上规则的特殊函数类型:箭头函数。
箭头函数使用操作符=>定义函数,而不是function关键字。它是根据外层作用域来决定this。例如:
```
function foo() {
    return (a) => {
        console.log(this.a) // this继承自foo()的this
    }
}

var obj1 = {
    a: 2
}

var obj2 = {
    a: 3
}

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

this是js中比较复杂的机制,使用中需要仔细,使用以上四种绑定规则,就可以解决开发中大部分this相关的问题。
最重要的还是用理解this在js中的含义。
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值