乐趣区

关于前端基础:JavaScript之this

this 是在运行时绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数申明的地位没有任何关系,只取决于函数的调用形式。
当一个函数被调用时,会创立一个流动记录(有时也称为执行上下文)。这个记录会蕴含函数在哪里被调用(调用栈)。函数的调用形式、传入的参数等信息。this 就是这个记录的一个属性,会在函数执行的过程中用到。

默认绑定

独立函数调用,无奈利用其它规定时的默认规定。

如果应用严格模式,则不能将全局对象用于默认绑定,因而 this 会绑定到 undefined。

隐式绑定

调用地位是否有上下文对象,或者说是否被某个对象领有或者蕴含。如下所示,调用地位会应用 obj 上下文援用函数,因而能够说函数被调用时 obj 对象“领有”或者“蕴含”它。

function foo() {console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
obj.foo(); // 2

这种状况同样实用于 foo 存在于 obj 的原型链上,如下所示:

function foo() {console.log(this.a);
}
var fo = {foo: foo};
// 创立一个对象 obj,使之原型指向 fo
var obj = Object.create(fo);
obj.a = 2;
obj.foo(); // 2

对象属性援用链中只有上一层或者最初一层在调用地位中起作用,如下

function foo() {console.log(this.a);
}
var obj2 = {
    a: 42,
    foo: foo
};
var obj1 = {
    a: 2,
    obj2: obj2
};
obj1.obj2.foo(); // 42

隐式失落

一个最常见的 this 绑定问题就是被隐式绑定的函数会失落绑定对象,也就是说它会利用默认绑定。

function foo() {console.log(this.a);
}
var obj = {
    a: 42,
    foo: foo
};
var bar = obj.foo;
var a = "oops";
bar(); // oops

尽管 bar 是 obj.foo 的一个援用,但实际上,它援用的是 foo 函数自身,因而此时的 bar()其实是一个不带任何润饰的函数调用,因而利用了默认绑定。

一种更出其不意的状况产生在传入回调函数时:

function foo() {console.log(this.a);
}
function doFoo(fn) {fn();
}
var obj = {
    a: 2,
    foo: foo
};
var a = "oops";
doFoo(obj.foo); // oops

参数传递其实是一种隐式传递,因而咱们传入函数时也会被隐式赋值,所以后果和上个例子一样。

显示绑定

隐式绑定时,必须在一个对象外部蕴含一个指向函数的属性,并通过这个属性间接援用函数,从而把 this 间接绑定到这个对象上。如果咱们不想在对象外部蕴含函数援用,而想在某个对象上强制调用函数,该怎么办呢?答案就是 call 或 apply!

这两个办法第一个参数是一个对象,是给 this 筹备的,接着在调用函数时将其绑定到 this。因为你能够间接指定 this 的绑定对象,因而称之为显示绑定。

调用 call 或 apply 时,如果你传入了一个原始值(字符串、布尔、数字)来当做 this 的绑定对象,这个原始值会转化为它的对象模式。这通常被称为“装箱”。

能够重复使用的辅助函数

function foo(something) {return this.a + somthing;}
// 简略的辅助绑定函数
function bind(fn, obj) {return function() {return fn.apply(obj, arguments);
    };
}
var obj = {a: 2};
var bar = bind(foo, obj);
bar(3); // 5

因为这是一种十分罕用的模式,因而在 Es5 中提供了内置的办法Function.prototype.bind

第三方库的许多函数,以及 JavaScript 语言中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”,其作用和 bind 一样,确保你的回调函数能够应用指定的 this。

function foo(num) {console.log(num, this.a);
}
var obj = {a: 2};
[1,2,3].forEach(foo, obj);

这些函数实际上就是通过 call 或者 apply 实现了显示绑定,这样你能够少些一些代码。

new 绑定

应用 new 来调用函数,会执行上面的操作:

  1. 创立一个全新的对象。
  2. 这个新对象会被执行 [[Prototype]] 连贯。
  3. 这个新对象会被绑定到函数调用的 this。
  4. 如果函数没有返回其它对象。那么 new 表达式中的函数调用会主动返回这个对象。

思考上面代码:

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

应用 new 来调用 foo 时,会创立一个新对象,并绑定到 foo()调用中的 this 上。

优先级

  1. new 绑定
  2. 显示绑定
  3. 隐式绑定
  4. 默认绑定

箭头函数

箭头函数不应用 this 的四种规范规定,而是依据外层作用域 (词法作用域) 来决定 this。

function foo() {return (a) => {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,箭头函数的绑定无奈被批改。(new 也不行!)

退出移动版