看过js的this很多次,每次都是当时理解,过不了多久就会忘掉,所以不如整理出来,温故而知新。
首先对于this的使用应该要明白,每个函数的this是在调用时被绑定的,因为只有在函数被调用的时候每个函数才会接收两个附加的参数:this 和 arguments,因此this的值完全取决于函数的调用位置。而我们要关心的调用位置就是在当前正在执行的函数的前一个调用中(注意这里是前一个调用中)。
在知道了调用位置后,就可以依次使用下面的四条规则来判断this的绑定对象。
- 1.new 绑定 由new绑定?绑定到新创建的对象。
- 2.显示绑定 由call 或者apply(或者bind)调用?绑定到制定的对象。
- 3.隐式绑定 由上下文对象调用?绑定到那个上下文对象。
- 4.默认规则 在严格模式下绑定到undefined,否则绑定到全局对象。
正常情况下这四条就够用了,下面倒序依次讲解这四个规则。然后再说明一些特殊情况。
默认规则
在无法应用其他规则的时候就使用这条规则。首先是独立函数调用。
1 2 3 4 5 6
| function foo(){ var a = 3; console.log(this.a); } var a = 2 ; foo();
|
在这里就是应用了默认绑定,因为foo函数的调用位置是在全局对象中,根据this的值依据的是调用位置来看,此时this的值必定指向全局对象。
但是对于默认规则是和当前模式有关系的。
1 2 3 4 5 6 7
| function foo(){ 'use strict'; var a = 3; console.log(this.a); } var a = 2; foo();
|
隐式绑定
这种情况需要考虑的是 调用位置是否有上下文对象,或者该函数是否属于某个对象。
1 2 3 4 5 6 7 8 9 10 11 12 13
| function foo(){ var a = 3; console.log(this.a) } var obj2 ={ a:2, foo:foo } var obj1 = { a:1, obj2:obj2 } obj1.obj2.foo();
|
在这里隐式绑定规则会把函数调用中的this绑定到这个上下文对象。并且根据this值的判定是根据调用位置的基本规则,也能轻松理解。
隐式绑定经常会发生的问题是 隐式丢失,即不会应用隐式绑定规则而是使用默认绑定规则或者undefined。
1 2 3 4 5 6 7 8 9 10
| function foo(){ console.log(this.a) } var obj={ a:2, foo:foo } var bar = obj.foo; var a = "xx"; bar();
|
虽然bar是obj.foo的一个引用,但是实际上,它引用的是 foo函数本身,所以自然而然的使用的就是默认规则。
1 2 3 4 5 6 7 8 9 10 11 12
| function foo(){ console.log(this.a); } function doFoo(fn){ fn(); } var obj={ a:2, foo:foo } var a = "XX"; doFoo(obj.foo);
|
参数传递是一种隐式传递,同样对于类似的setTimeout(fun,delay)这样的函数也会产生这样的情况。
1 2 3 4 5 6 7 8 9
| var obj = { id:"aa", foo:function say(){ console.log(this.id) } } var id ="xx"; obj.foo(); setTimeout(obj.foo,100);
|
解决办法一 : that = this;
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| var obj = { count :0, foo: function say(){ var that = this; if(that.count<1){ setTimeout(function(){ that.count++; console.log(that.count) },100) } } } obj.foo();
|
解决方法二:硬绑定
1 2 3 4 5 6 7 8 9 10 11 12
| var obj = { count :0, foo: function say(){ if(this.count<1){ setTimeout(function(){ this.count++; console.log(this.count) }.bind(this),100) } } } obj.foo();
|
解决办法三:=>
显示绑定
在隐式绑定中,函数必须明确的指出属于或者包含于哪个对象,若是不想明确的指出包含于哪个函数就进行函数调用则可以使用显示绑定。即使用call(…)和 apply(…)方法。
这两个方法的第一个参数是一个对象,在调用函数时将其绑定到this。因为这种方式是直接指定this的值,所以称之为显示绑定。
1 2 3 4 5 6 7
| function foo(){ console.log(this.a) } var obj ={ a:2 } foo.call(obj);
|
通过foo.call(…)可以在调用foo时强制把它的this绑定到obj上。
前面说call(…)函数的第一个参数是一个对象。倘若传入的是一个原始值(如数字,字符串),这个值会被转换成它的对象形式(即new Number(),new String(),new Boolean()),这就是“装箱”。
显示绑定也会有绑定丢失的情况。
1 2 3 4 5 6 7 8 9 10 11 12
| function foo(){ console.log(this.a); } var obj = { a:2 } var bar = function(){ foo.call(obj); } bar(); setTimeout(bar,100); bar.call(window);
|
硬绑定的典型应用场景是创建一个包裹函数,负责接收参数并返回值。
1 2 3 4 5 6 7 8 9 10 11 12
| function foo(sth){ console.log(this.a,sth); return this.a + sth; } var obj ={ a:2 } var bar = function (){ return foo.apply(obj,arguments); } var b = bar(3); console.log(b);
|
另一种使用方法是创建一个可以重复使用的辅助函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function foo(sth){ console.log(this.a,sth); return this.a + sth; } var obj ={ a:2 } function bind (fn,obj){ return function(){ return fn.apply(obj,arguments); } } var bar =bind(foo,obj); var b = bar(3); console.log(b);
|
硬绑定很有用,所以ES5中包含bind方法。
1 2 3 4 5 6 7 8 9 10
| function foo(sth){ console.log(this.a,sth); return this.a + sth; } var obj ={ a:2 } var bar = foo.bind(obj); var b = bar(3); console.log(b);
|
- API中的 this
1 2 3 4 5 6 7
| function foo(e){ console.log(e,this.id); } var obj = { id:"aa" } [1,2,3].forEach(foo,obj);
|
对于forEach(func,thisArg)中的 thisArc 为“value to use as this when executing func”
new 绑定
在js中构造函数只是普通的函数,只是使用new操作符进行调用的函数。当用new进行调用函数时,会自动执行
- 1.创建一个全新的对象
- 2.这个新对象会被执行[[Prototye]]连接。
- 3.这个新对象会绑定到函数调用的this。
- 4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
1 2 3 4 5
| function foo(a){ this.a =a; } var bar = new foo(2); console.log(bar.a);
|
以上就是正常情况this赋值的四条规则。