为了账号安全,请及时绑定邮箱和手机立即绑定

判断this绑定的4个规则

标签:
JavaScript

this关键字是JavaScript中最复杂的机制之一,因为this既不指向函数自身也不指向函数的作用域,而是取决于函数在哪里被调用。

既然this取决于调用位置,那我们就先看看什么是调用栈和调用位置:

function baz() {  // 当前调用栈是: baz
  // 因此, 当前调用位置是全局作用域
  console.log('baz');
  bar();
}function bar() {  // 当前调用栈是baz -> bar
  // 因此,当前调用位置在baz中
  console.log('bar');
  foo();
}function foo() {  // 当前调用栈是bza -> bar -> foo
  // 因此,当前调用位置在bar中
  console.log('foo');
}

baz(); // baz的调用位置

在明确了函数的直接调用位置后,我们就要学习四条判断this绑定对象的规则。

1. 默认绑定

默认绑定:直接使用不带任何修饰的函数引用进行调用函数,则this都指向全局对象(在浏览器中是window,在node中是global),下面来看一个例子:

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

浏览器下: // 2node下:  //undefined

以上foo中this.a就是使用默认绑定,this等于全局对象(window、global),由于var a = 2是在全局作用域中声明,在浏览器中这个声明相当于window.a = 2,所以结果得到了2。但在node中要声明全局对象要写成a=2 或者 global.a = 2,所以以上代码在node下运行是undefined

在严格模式下,this将保持他进入执行上下文时的值,所以下面的this将会默认为undefined:

function foo() {  'use strict';  console.log(this.a)
}
a = 2;
foo() // Cannot read property 'a' of undefined  (this 为 undefined)

在严格模式下,如果 this 没有被执行上下文(execution context)定义,那它将保持为 undefined。

不过这里有一个微妙的细节,只要foo()的执行上下文在非严格模式下时,即使在严格模式下调用,也不影响默认绑定规则。请看下面:

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

a = 2;

(function(){  'use strict';
  foo();   //2})();

以上就是this的默认绑定规则,下面我们来看下一条规则

2. 隐式绑定

什么是式隐式绑定,我们先来看一段代码:

function say() {  console.log(this.name);
}var man = {  name: '内孤',  say: say
}

man.say(); // 内孤

上面就是一个隐式绑定this的例子,通过man来调用say,使用了man的上下文引用了say()。所以在理解this上有句比较简单的话:谁去调用就指向谁,其实默认绑定this也可以这样理解,foo()其实就是window.foo(),所以this指向window

在这我们要注意,对象属性引用链中只有上一层起作用:

function say() {  console.log(this.name);
}var man = {  name: '内孤',  say: say
}var he = {  name: '他是内孤',  say: man
}

he.say.say();   // 内孤

隐式绑定还是比较好理解,不过它会在我们不经意的时候失效,这就是隐式丢失现象。下面让跟着我一起来看看什么情况下会丢失:

function say() {  console.log('我是' + this.color + '猫');
}var whiteCat = {  color: '白',  say: say
}var color = '黑';var cat = whiteCat.say;

cat(); // 我是黑猫

虽然cat是whiteCat.say的一个引用,但是实际上,它引用的是say函数本身,因此此时的cat其实是一个不带任何修饰的函数调用,因此应用了默认绑定。下面再举两个常用并且容易造成隐式丢失的代码:

// 第一种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'// => // 第二种function foo() {  console.log(this.a)
}var obj = {  a: 2,  foo: foo
}var a = 'oops, global';
setTimeout(obj.foo, 100); // 'oops,global'/**  setTimeout的伪代码
 * function setTimeout(fn, delay) {
 *    fn();
 * }
*/

参数传递其实就是一个隐式的赋值,因此我们传入函数时也是会被隐式赋值,所以现象和上面cat的例子是一样的结果。

3. 显式绑定

显式绑定里面有硬绑定API调用的"上下文"控制this�,下面我们来分别看一下:

1)硬绑定

硬绑定就是我们经常看到的callapplybind(es5中)三个方法,还是用一段代码来看一个究竟:

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

bar();  //2setTimeout(bar, 100)  //2global.a = 22;// 硬绑定后就固定,不可修改bar.apply(global)  // 2bar.bind(global)  //2
2)API调用的"上下文"

在JavaScript中有几个内置函数,例如:filterforEach等都有一个可选参数,在执行 callback 时的用于的 this 值。下面以forEach为例子:

var girl = {  name: '小郑'}function say(item) {  console.log(this.name + ' ' + item)
}

[1,2,3,4].forEach(say, girl)//小郑 1//小郑 2//小郑 3//小郑 4

这写函数实际上就是用call、apply实现的显式绑定。

4. new绑定

new应该是比较常见的操作,经常用它new各种实例函数(情人节就new girlfriend())。那我们来看一下在new的时候,会发生一些什么事情:

  • 创建(或者说构造)一个全新的对象

  • 这个新对象会被执行[[Prototype]]连接

  • 这个新对象会绑定到函数调用的this

  • 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

function foo(name) {  this.name = name;
}var bar = new foo('内孤');console.log(bar.name) // 内孤

总结

现在我们已经了解4个判断this绑定的规则,判断一个简单的情况肯定是没有什么问题。但面对复杂的情况,我们必须要了解他们之间的优先级。关于他们之间的优先级会在另外一个篇文章中介绍。
注意: es6中的箭头函数不会使用上面四条绑定规则,箭头函数是没有自己的this,它是通过继承外层函数调用的this绑定


参考资料

《你不知道的JavaScript》上卷



作者:内孤
链接:https://www.jianshu.com/p/46b09e4814fc


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消