1、this既不指向函数自身,也不指向函数的词法作用域
this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用
如果要从函数对象内部引用自身,只使用this是不够的
解决方法一:
具名函数,在它内部可以使用foo来引用自身
function foo(num){ console.log(‘foo:‘+num); foo.count = 4; } foo.count =0 var i; for(i = 0;i<10;i++){ if(i>5){ foo(i); } }
// foo:6
// foo:7
// foo:8
// foo:9
console.log(foo.count);//4
解决方法二:强制this指向foo函数对象
function foo(num){ console.log(‘foo‘+num); this.count++; } foo.count =0 var i; for(i = 0;i<10;i++){ if(i>5){ // 使用call((..)可以确保this函数指向函数对象foo本身 foo.call(foo,i); } } console.log(foo.count); // 4
this在任何情况下都不指向函数的词法作用域
this的绑定和函数声明的位置没有任何关系,只取决于函数的调用
2、调用位置
最重要的是分析调用栈(就是为了到达当前执行位置所调用的所有函数)我们关心的调用位置就在当前正在执行的函数的前一个调用中
function baz() { console.log(‘baz‘); bar(); } function bar() { console.log(‘bar‘); foo(); } function foo() { debugger; console.log(‘foo‘); } baz();
3、绑定规则
在函数的执行过程中调用位置如何决定this绑定对象
1)默认绑定
独立函数调用,可以把这条规则看作无法应用其他规则时的默认规则
如何知道应用了默认绑定?
函数调用时应用了this的默认绑定,因此this指向全局对象
通过分析调用位置来看看FOO()是如何调用的,foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定。
2)隐式绑定
另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。
隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
function foo() { console.log(this.a); } var obj = { a: 2, foo: foo } obj.foo();
隐式丢失
被隐式绑定的函数会丢失绑定对象,也就是说他会应用默认绑定
从而把this绑定到全局对象或者undefined上
function foo(){ console.log(this.a); } var obj = { a:2, foo: foo }; var bar = obj.foo; var a = ‘oops,global‘; bar();
虽然bar是obj.foo的一个引用,但是实际上他引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰符的函数调用。因此应用了默认绑定
参数传递其实就是一种隐式赋值
3)显式绑定
如果我们不想在对象内部包含函数引用,想在某个对象上强制调用函数
可以使用函数的call()和apply()方法
他们的第一个参数是一个对象,是给this准备的,接着在调用函数时将其绑定到this,因为你可以直接指定this的绑定对象,因此我们称之为显示绑定
function foo() { console.log(this.a); } var obj = { a:2 }; foo.call(obj);//2
硬绑定
function foo(){ console.log(this.a); } var obj = { a:2 }; var bar = function(){ foo.call(obj); }; bar();// 2 setTimeout(bar,100);//2 bar.call(window); //2
我们创建了函数bar(),并在它的内部手动调用了foo.call(obj),因此强制把foo的this绑定到了obj
硬绑定的典型应用场景就是创建一个包裹函数,负责接收参数并返回值。
function foo(something){ console.log(this.a,something); return this.a + something; } var obj = { a:2 }; var bar = function() { return foo.apply(obj,arguments); } var b = bar(3); // 2 3 console.log(b); // 5
4)new绑定
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作
1、创建(或者说构造)一个全新的对象
2、这个新对象会被执行[[Prototype]]连接
3、这个新对象会绑定到函数调用的this
4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
function foo(a){ this.a = a; } var bar = new foo(2); console.log(bar.a);//2
使用new来调用foo(...)时,我们会构造一个新对象,并把它绑定到foo(...)调用中的this上。new是最后一种可以影响函数调用时this绑定行为的方法,我们称为new绑定
5)优先级
默认绑定<隐式绑定<显示绑定<new绑定
4、判断this
现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。如果要判断一个运行中的函数的this绑定,就需要找到这个函数的直接调用位置,找到后
可以按照下面的顺序来判断:
1、函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象
var bar = new foo()
2、函数是否通过call,apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象
var bar = foo.call(obj2)
3、函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象
var bar = obj1.foo()
4、如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined否则绑定到全局对象
var bar = foo()
ES6中的箭头函数并不会使用4条标准的绑定规则,而是根据当前的词法作用域 来决定this,具体来说箭头函数,会继承外层函数调用的this(无论this绑定到什么)这和ES6之前的代码中的self=this机制一样