判断this绑定的4个规则
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)硬绑定
硬绑定就是我们经常看到的call
、apply
、bind(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中有几个内置函数,例如:filter
、forEach
等都有一个可选参数,在执行 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
共同学习,写下你的评论
评论加载中...
作者其他优质文章