理解this绑定规则

关于this

this是一个很特别的关键字,被自动定义在所有函数的作用域中。

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

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

默认绑定

默认绑定,在不能应用其它绑定规则时使用的默认规则,通常是独立函数调用

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

在调用foo()时,应用了默认绑定,this指向全局对象(非严格模式下),严格模式下,this指向undefined,undefined上没有this对象,会抛出错误。

上面的代码,如果在浏览器环境中运行,那么结果就是10

使用严格模式:

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

 注意:

  • 开启了严格模式,只是说使得函数内的this指向undefined,它并不会改变全局中this的指向。

  • 另外,它也不会阻止a被绑定到window对象上。

 还有种情况也需要注意:

1. 定义在函数内部的函数,不带任何修饰的函数引用进行调用

function foo () {
  var a = 100;
  console.log(this.a);
  function inner1 () {
    console.log('---inner1 start---');
    console.log(this);
    console.log(this.a);
  }
  var inner2 = function () {
    console.log('---inner2 start---');
    console.log(this);
    console.log(this.a);
  }
  inner1();
  inner2();
}
var a = 10;
foo();

inner1, inner2函数虽然是在foo内部定义的,但是它仍然是一个普通的函数,this仍然指向window。 输出结果为:

输出结果:

 

2. 赋值表达式

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 () {
    console.log(this.a);
}
var obj = {
    a: 100,
    foo: foo
}
var a = 10;
obj.foo();  // 100

函数foo()虽然是定义在window下,但是我在obj对象中引用了它,并将它重新赋值到obj.foo上。且调用它的是obj对象,因此打印出来的this.a应该是obj中的a

当函数引用有上下文对象时, 隐式绑定规则会把函数调用中的this绑定到这个上下文对象。

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

function foo () {
  console.log(this.a);
};
var obj2 = {
  a: 200,
  foo: foo
};
var obj1 = {
  a: 100,
  obj2: obj2
};
var a = 10;
obj1.obj2.foo();  // 200

隐式绑定的隐式丢失问题 

1. 赋值表达式

function foo () {
  console.log(this.a);
}
var obj1 = {
  a: 1,
  foo: foo
}
var a = 'global';
var fn = obj1.foo;  // 函数别名
var obj2 = {
  a: 2,
  foo: obj1.foo
}
obj1.foo();  // 1
fn();    // 'global'    this指的是window
obj2.foo();    // 2    this指得是obj2

obj1.foo是引用属性,赋值给fn的实际上就是foo函数(即:fn指向foo本身)。

那么,实际的调用关系是:通过fn找到foo函数,进行调用。整个调用过程并没有obj的参数,所以是默认绑定,全局属性a。

2. 函数当成参数传递

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

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

var obj = { a: 100, foo: foo };
var a = 10;
bar(obj.foo);

// this -> window 所以this.a是10

 foo, bar函数是在全局作用域下定义的,还是相当于默认调用,所以this指向window

函数传入内置的setTimeout中

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

var obj = { 
    a: 100, 
    foo: function () {
        setTimeout(function() {
            console.log(this.a);
        }, 100) 
    }
};
var a = 10;
var obj2 = {
    a: 20,
    foo: foo
};
obj.foo();    //  this -> window, 输出10

setTimeout(obj2.foo, 200);    //  this -> window, 输出10

setTimeout(function() {
    obj2.foo();
}, 300);

//  this -> obj2, 输出20
  • 第一个输出很好理解,setTimeout的回调函数中,this使用的是默认绑定,非严格模式下,执行的是全局对象
  • 第二个可以理解为将obj2.foo赋值给了一个变量,最后执行了变量,这个时候,foo中的this显然和obj2就没有关系了
  • 第三个,隐式绑定,因此这是this指向的是obj2,跟当前的作用域没有任何关系

显式绑定

相对隐式绑定,this值在调用过程中会动态变化,可是我们就想绑定指定的对象,这时就用到了显示绑定。

显示绑定主要是通过改变对象的prototype关联对象,可以通过call()、apply()方法直接指定this的绑定对象

两者区别: call接收若干个参数,而apply接收的是一个数组

function foo () {
  console.log(this.a);
}
var obj = { a: 100, foo: foo };
var a = 10;
var obj2 = {a: 20, foo: foo};

var fn = obj2.foo;

foo();        // 10
foo.call(obj);    // this指向obj,  输出100

fn.apply(obj2);    // this指向obj2,  输出20

fn.call(undefined);    // this指向window,  输出10
    
obj2.foo.call(obj);  // this指向obj,  输出100

bind硬绑定,与apply,call区别:

  • 使用.call()或者.apply()的函数是会直接执行的
  • bind()是创建一个新的函数,需要手动调用才会执行

function foo () {
  console.log(this.a);
}
var obj = { a: 100, foo: foo };
var a = 10;
var obj2 = {a: 20, foo: foo};
var obj3 = {a: 30, foo: foo};

var test = function (fn) {
    fn();
}

var bar = function (fn) {
    fn.call(this);
}

test.call(obj, foo);    // this指向window, 输出10;
bar.call(obj2, foo);    // this硬绑定为obj2, 输出20


obj1.foo.bind(obj2)();    // this硬绑定为obj2, 输出20
obj1.foo.bind(obj2).call(obj3);    // this硬绑定为obj2, 输出20


总结:

  • 使用.call()或者.apply()的函数是会直接执行的
  • bind()是创建一个新的函数,需要手动调用才会执行
  • 如果call、apply、bind接收到的第一个参数是空或者null、undefined的话,则会忽略这个参数

new 绑定

js中的new操作符,和其他语言中(如JAVA)的new机制是不一样的。js中,它就是一个普通函数调用,只是被new修饰了而已。

使用new来调用函数,会自动执行如下操作:

  1. 创建一个空对象,构造函数中的this指向这个空对象
  2. 这个新对象被执行 [[原型]] 连接
  3. 执行构造函数方法,属性和方法被添加到this引用的对象中
  4. 如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
function createObj() {
	// 先创建一个空对象
	var obj = new Object();
	// 获得构造函数
	var constructorFn = [].shift.call(arguments); // 利用数组的shift方法
	//链接到构造函数的原型
	obj.__proto__ = constructorFn.prototype;  // obj可以访问到构造函数原型中的属性
	var result = constructorFn.apply(obj, arguments); // 改变this指向为新创建的obj, 这样可以访问到构造函数中的属性
	return typeof result == "object" && result != null ? result: obj;
}

function foo (a) {
    this.a = a;
    return 1;
}

function bar (a) {
    this.a = a;
    return {};
}

var a = 10;
var f1 = new foo(100);
var b1 = new bar(2);
console.log(f1.a);    // 100
console.log(b1.a);    // undefined

箭头函数

箭头函数里面的this是由外层作用域来决定的(箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined。),且指向函数定义时的this而非执行时

箭头函数在使用时,需要注意以下几点:

1. 函数体内的this对象,继承的是外层代码块的this。

2. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

4. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

5. 箭头函数没有自己的this,所以不能用call()、apply()、bind()这些方法去改变this的指向.

var a = 10;
var obj = {
    a: 100,
    foo1: () => {
        console.log(this.a);
    },
    foo2: function() {
        console.log(this.a);
        return () => {
            console.log(this.a);
        }
    }
};
obj.foo1();        // 10
obj.foo2()();      // 100 100

解析:

  • 对于obj.foo1()函数的调用,它的外层作用域是window,对象obj当然不属于作用域了(我们知道作用域只有全局作用域window和局部作用域函数)
  • obj.foo2()(),首先会执行obj.foo2(),这不是个箭头函数,所以它里面的this是调用它的obj对象,因此打印出obj.a为100,而返回的匿名函数是一个箭头函数,它的this由外层作用域决定,那也就是函数foo2咯,那也就是它的this会和foo2函数里的this一样,就也打印出了obj.a

优先级

判断this

1.  是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象;需要注意的是构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象

2. 是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是 指定的对象;需要注意的是在显示绑定中,对于null和undefined的绑定将不会生效;实际上会进行默认绑定,导致函数中可能会使用到全局变量

3. 是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象;

4. 都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象;

5. 如果是箭头函数,箭头函数的this是定义时上层作用域中的this由外层作用域决定

参考资料:

《你不知道的JavaScript》

阮一峰--ES6文档-箭头函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值