this 是 JavaScript 中最复杂的机制之一. 它是一个很特别的关键字, 被自动定义在所有函数的作用域中.
一, this 到底指向什么?
this 既不指向函数自身, 也不指向函数的词法作用域, 具体指向什么, 取决于你是怎么调用函数.
直接使用不带任何修饰的函数引用进行调用 (即: 方法名 + 括号), this 指向全局对象(非严格模式) 或者 undefined(严格模式), 这种绑定称为默认绑定
- function foo() {
- console.log(this.a);
- }
- var a = 2;
- foo(); // 2
严格模式:
- function foo() {
- "use strict"
- console.log(this.a);
- }
- var a = 2;
- foo(); // undefiend
如果方法是某个对象的一个属性, 通过该对象调用方法(即调用位置存在上下文对象), 则 this 指向该对象, 这种绑定称为隐式绑定.
- function foo() {
- console.log(this.a);
- }
- var obj = {
- a: 2,
- foo: foo
- }
- obj.foo(); // 2
注意事项
如果多个对象之间形成了引用链, 方法中的 this 指向其最顶层或者最后一层对象.
- function foo() {
- console.log(this.a);
- }
- var obj1 = {
- a: 42,
- foo: foo
- }
- var obj2 = {
- a: 3,
- obj1: obj1
- }
- obj2.obj1.foo() // 42
隐式丢失, 被隐式绑定的函数会丢失绑定对象, 也即会应用默认绑定, 从而把 this 绑定到全局对象或者 undefined 上.
这种情况常出现在回调函数上:
- function foo() {
- console.log(this.a);
- }
- function doFoo(fn) {
- // fn 其实引用的是 foo
- fn();
- }
- var obj = {
- a: 2,
- foo: foo
- }
- var a = "oops, global";
- doFoo(obj.foo) // 输出 "oops, global"
使用 call, apply 方法调该函数时, this 指向 call,apply 方法所传入的对象, 这种方式成为显示绑定
JS 中几乎所有的函数都有 call()和 apply()方法, 这两个方法的第一个参数是一个对象, 他们会把这个对象绑定到 this.
- function foo() {
- console.log(this.a);
- }
- var obj = {
- a: 2,
- }
- foo.call(obj); // 2
显示绑定仍然存在绑定丢失的问题: 例如
- function foo() {
- console.log(this.a);
- }
- function doFoo(fn) {
- fn();
- }
- var obj = {
- a: 2,
- }
- var a = "oops, global";
- doFoo.call(obj, foo); // 输出 oops, global
解决办法:
++ 硬绑定 ++
在 doFoo 函数内, 直接绑定 obj 对象
- function doFoo(fn) {
- fn.call(obj);
- }
硬绑定一种常见的应用常见是创建一个可重复使用的辅助函数
- function foo(something) {
- console.log(this.a, something);
- return this.a + something;
- }
- // 简单的辅助绑定函数
- function bind(fn, obj) {
- return function() {
- return fn.apply(obj, arguments);
- }
- }
- var obj = {a:2};
- var bar = bind(foo, obj);
- var b = bar(3); // 输出 2 3
- console.log(b); // 输出 5
ES5 中提供了内置的硬绑定的方法: Function.prototype.bind, 用法如下:
- function foo(something) {
- console.log(this.a, something);
- return this.a + something;
- }
- var obj = {
- a:2;
- }
- var bar = foo.bind(obj);
- var b = bar(3); // 2 3
- console.log(b); // 5
bind(..) 会返回一个硬编码的新函数, 它会把参数设置为 this 的上下文并调用原始函数.
new 绑定
在 JavaScript 中, 所有的函数都可以用 new 来调用, 这种函数调用被称为构造函数调用. 使用 new 来调用函数时, 会自动执行下面操作:
创建一个全新的对象;
这个新对象会被执行 [[原型] 链接;
这个新对象会绑定到函数调用的 this
如果函数没有返回其他对象, 那么 new 表达式中的函数调用会自动返回这个新对象
示例:
- function foo(a) {
- this.a = a; // 在这里, this 绑定到 bar 对象
- }
- var bar = new foo(2);
- console.log(bar.a); // 2
二, 如果函数调用时, 既满足隐式绑定, 又满足显示绑定或 new 绑定, 该怎么办?
new 绑定优先级> 显示绑定> 隐式绑定> 默认绑定
比较显示绑定和隐式绑定, new 绑定和隐式绑定
- function foo(something) {
- this.a = something;
- }
- var obj1 = {foo: foo};
- var obj2 = {};
- obj1.foo.call(obj2, 3); // foo 函数的调用同时出现了隐式绑定和显示绑定, 则显示绑定优先, this 指向 obj2
- console.log(obj2.a); // obj2.a = 3
- var bar = new obj1.foo(4); // 同时出现了隐式绑定和 new 绑定, this 指向 new 绑定创建的对象 bar, 而不是 obj1
- console.log(bar.a); // 输出 4
比较 new 绑定和显示绑定:
- function foo(something) {
- this.a = something;
- }
- var obj1 = {};
- var bar = foo.bind( obj1 );
- bar( 2 );
- console.log( obj1.a ); // 2
- var baz = new bar(3);
- console.log( obj1.a ); // 2
- console.log( baz.a ); // 3
bar 被硬绑定到 obj1 上, 但是 new bar(3) 并没有像我们预计的那样把 obj1.a
修改为 3. 相反, new 修改了硬绑定 (到 obj1 的) 调用 bar(..) 中的 this
三, 特殊情况
调用 call 或者 apply 时, 参数传入 null 或者 undefined, 此时 this 会采用默认绑定
- function foo() {
- console.log(this.a);
- }
- var a = 2;
- foo.call(null); // 输出 2
函数的间接引用, 此时 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()
箭头函数
前面提到的四种绑定规则对箭头函数不适用, 箭头函数中的 this 是根据外层作用域来决定的.
- function foo() {
- return (a) => {
- console.log(this.a);
- }
- }
- var obj1 = {a:2};
- var obj2 = {a:3};
- var bar = foo.call(obj1); // 箭头函数的外层作用域中的 this 指向 obj1, 所以箭头函数中 this 也指向 obj1, 箭头函数的绑定无法被修改
- bar.call(obj2); // 输出 2,
四, 小结
判断一个运行中函数的 this 绑定, 就需要找到这个函数的直接调用位置, 找到后就可以顺序应用这四条规则判断 this 的绑定对象.
由 new 调用? 绑定到新创建的对象;
由 call 或者 apply 调用? 绑定到指定对象;
由上下文调用? 绑定到上下文对象;
默认: 严格模式下绑定到 undefined, 否则绑定到全局对象;
另外还需要注意上面提到的几种特殊情况.
来源: http://www.bubuko.com/infodetail-2912476.html