ECMAScript this

默认绑定

function foo() {
  console.log(this.a);
}
var a = 2;
foo(); // 其实就是this.foo()
输出:2。

函数调用时应用了this的默认绑定,因此this.a其实就是全局变量中的a变量。

严格模式下,不使用默认绑定。因此foo方法里的this会绑定到undefined。

function foo() {
  "use strict";
   console.log(this.a);
}
var a = 2;
foo(); // 此时会报错TypeError

隐式绑定

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

var obj = {
  a: 3,
  foo: foo
}
obj.foo(); // 调用方法foo时有上下文对象obj
输出:3。

当方法调用有上下文对象时,隐式绑定规则会将方法中的this绑定到这个上下文对象。因此此时foo方法里的this其实指向obj。this.a即obj.a。

对象属性引用链中只有最后一层会影响调用位置。看下例:

function foo() {
  console.log(this.a);
}
var obj2 = { a: 'obj2', foo: foo};
var obj1 = { a: 'obj1', obj2: obj2};
obj1.obj2.foo(); // 调用方法foo时有上下文对象obj2

 

输出:obj2。

隐式丢失

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

var bar = obj.foo; // 方法别名!

var a = "oops, global"; // 全局对象的属性

bar();


输出:oops, global。

obj.foo指向foo方法体。

bar = obj.foo,则bar和obj.foo的指向相同,同指向foo方法体。

即bar是不带任何修饰的方法调用(无上下文),因此此处应用了默认绑定。

如果直接obj.foo(),则输出:2。因为有上下文obj。

看下面的例子:

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

输出:oops, global

参数传递其实就是一种隐式赋值。

obj.foo指向foo方法体。

doFoo方法体里的fn=obj.foo,其实就和obj.foo的指向相同。

因此fn也指向foo方法体。

调用fn()时,没有上下文,调用位置就是在全局作用域。

在全局作用域下调用方法,this.a获取的也就是全局对象的属性了。

自己声明的方法传入JS内置的方法中,也是一样的结果。

setTimeout(obj.foo,100);

100ms后,也会输出:oops, global。

JS内置的setTimeout方法和下面伪代码类似:

function setTimeout(fn, delay) {
  // 等待delay毫秒
  fn();
}

显式绑定

call(...)和apply(...)

call和apply的第一个参数就是this的绑定对象。

function foo(add) {
  console.log(this.a + add);
}
var obj = {
  a: 2
};
foo.call(obj, 4);

输出:6

foo方法体内的this指向就是obj。4作为参数,隐式赋值给add。
如果传入的第一个参数是原始值(字符串类型、布尔类型或数字类型),这个原始值会被隐式转换为它的对象形式(new String(...)、new Boolean(...)或new Number(...))。这叫做装箱。

硬绑定

API调用的上下文

第三方库、JS语言和宿主环境中许多内置函数都提供了可选参数确保回调函数的this指向。

function foo(el) {
  console.log(el, this.id);
}
var obj = { id: 'awesome'};
[1,2,3].forEach(foo, obj);
输出:1 awesome 2 awesome  3 awesome 

上面这类方法实际上就是通过call和apply实现了显式绑定,将foo的this指向obj。

new绑定

JS的构造方法和其他面向对象语言的构造方法不同,它只是使用new操作符时被调用的方法。

使用new来调用方法,会自动执行下面的操作:

  1. 创建一个全新的对象
  2. 这个新对象会被执行[[原型]]连接
  3. 这个方法里的this会自动指向这个新的对象
  4. 如果方法没有其他返回对象,那么new表达式中的方法调用会自动返回这个新的对象

优先级

方法是否在new中调用(new绑定)?如果是,this指向新创建的对象。

方法是否通过call、apply或硬绑定调用?如果是,this绑定的是指定的对象。

方法是否在某个上下文对象中调用?如果是,this绑定的是那个上下文对象。

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


空绑定

call、apply、bind的第一个参数如果传入的是null或undefined,这些值在调用的时候会被忽略,实际应用的是默认绑定规则。

因此,可以用apply来展开一个数组,并被当做参数传入函数中。可以用bind对参数进行柯里化(预先设置一些参数)。

function foo(a,b,c) {
  console.log(a+b+c);
}
foo.apply(null, [2,3,4]); // 9

var bar = foo.bind(null, 2);
bar(3,4); // 9
但用null可能会产生一些副作用。可能有些函数确实用到了this,那传入null的话,默认绑定规则会把this绑到全局对象。

你可以传入Object.create(null)作为第一个参数。

function foo(a,b,c) {
  console.log(a+b+c);
}
var em = Object.create(null);
foo.apply(em, [2,3,4]); // 9

var bar = foo.bind(em, 2);
bar(3,4); // 9
Object.create(null)和{}不同,因为它不会创建Object.prototype这个委托,因此比{}更空。em instanceof Object 是false。

间接引用

function foo() {console.log(this.a)}
var a = 2;
var o = {a:3,foo:foo};
var p = {a:4};
(p.foo = o.foo)();
输出2。

因为赋值表达式p.foo = o.foo的返回值是目标方法的引用。因此调用位置是foo()而不是p.foo。所以用了默认绑定。

箭头函数

不使用this的四种标准规则,而是根据外层(函数或全局)作用域来决定this。



(参考《你不知道的JavaScript(上)》)



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值