this
在手写Promise的时候被this折腾的不清,网上this又一堆资料看了也还是不会,于是决定认真的看一下你不知道的JavaScript
。用的例子基本都是原文偷的。
首先this只与调用时候的上下文环境有关,与声明的上下文环境无关。
1.了解调用位置
就是函数被调用的位置
1 | function baz() { |
在图片里也可以看到,调用到foo函数的时候baz -> bar -> foo
2.绑定规则
1)默认绑定
1 | var a = 2; |
这里的this指向window。
在严格模式下
1 | var a = 2; |
2)隐式绑定
1 | function foo() { |
当 foo() 被调用时,它的落脚点确实指向 obj 对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。
隐式丢失
1 | var a = 'global'; |
test1和obj.test指向相同。都是指向test函数,因为test1()不带任何修饰的函数调用,所以使用默认绑定
的方式。
关于setTimeout这种里面this指向的问题
1 | function foo() { |
看一下setTImeout的伪代码
1 | function setTimeout(fn,delay) { |
3)显式绑定
显式绑定,其实我觉得就是强制把this绑定在某个对象上。
可以通过使用apply
和call
:这两个方法的区别就是接受参数,第一个是上下文context,apply第二个参数是个数组,call是将参数都放在后面。
看🌰,猫吃鱼,狗吃肉
1 | var cat = { |
在调用 cat.eat 的时候将this强制绑定到了dog上。
硬绑定:显式绑定的变种
看🌰
1 | function foo() { |
其实就是在使用一个函数包裹foo.call(obj),之后怎么调用bar,都会将foo的this绑定在obj上。
硬绑定的应用,es5提供了内置方法Function.prototype.bind
1 | function foo(something) { |
实现一个bind方法
1 | function bind(fn,ctx){ |
4)new绑定
1 | function foo(a) { |
3.优先级
以上我们知道了4种绑定方式:默认绑定,显式绑定,隐式绑定,new绑定。现在对比他们的优先级,默认绑定优先级最低(忽略)。
我们需要找到调用的位置,并且应用哪种绑定方式。
1)显式绑定
和隐式绑定
对比
1 | function test(){ |
优先级:显式绑定 > 隐式绑定
2)隐式绑定
和new绑定
对比
1 | function test(a){ |
优先级:new绑定 > 隐式绑定
3)显式绑定
与new绑定
1 | function foo(something) { |
优先级:new绑定 > 显式绑定
4.判断this
判断this 现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的
顺序来进行判断:
函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
1
var bar = new foo()
函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是 指定的对象。
1
var bar = foo.call(obj2)
函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上 下文对象。
1
var bar = obj1.foo()
如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到 全局对象。
1
var bar = foo()
5.绑定例外
1)apply(null)
使用null的时候,一般是为了展开数组
1 | var arr = [1,2,3]; |
2)间接引用
1 | function foo() { |
赋值表达式p.foo = o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo() 或者 o.foo()。根据我们之前说过的,这里会应用默认绑定。