javascript中this指针探讨
javascript是一门类java语言有很多跟java相类似的特点,但也仅是类似而已,真正使用中还是有很大的差别。this指针常常让很多初学者抓狂,本人也曾为此困惑不解,查找过很多资料,今天在这里总结一下,希望能帮助后来者更快驯服这只拦路虎。网上有很多讲解this指针的文章其中不乏精品,以我看来了解this指针关键在于掌握javascript中函数的四种调用模式。那么什么是调用?调用指的是跟在任何产生一个函数值的表达式之后使用"()",obj.f()这种方式成为调用,obj.f这种方式称为访问。
一、方法调用模式:
在该调用模式中函数作为一个对象的方法被调用:obj.fun()。当函数以此种形式被调用时this指针绑定到调用该方法的对象上即obj。
1 var myObj = { 2 value: 0, 3 increase: function(){ 4 this.value++; 5 console.log(this.value); 6 } 7 }; 8 // this绑定到myObj对象上 9 myObj.increase(); // 1 10 11 myObj2 = { 12 value: 10 13 }; 14 myObj2.increase = myObj.increase;// myObj2与myObj的increase指向同一函数引用地址 15 // this绑定到myObj2对象上 16 myObj2.increase(); // 11
所以在将一个html元素的某一事件绑定某一函数时,若函数中使用this指针,则this指针会被绑定到该元素上。
1 var ele1 = document.getElementById('id'); 2 ele1.addEventListener('click', myObj.increase, false); 3 // 该绑定相当于 4 ele1.onclick = myObj.increase; 5 // 事件触发时即相当于调用click方法 6 ele1.click(); // 与上文中myObj2.increase一个道理,若ele1中没有value属性则会报错
二、函数调用模式:
当函数没有当做方法调用即没有被一个对象通过点语法,这时它被当做一个函数来调用。以此模式调用函数时,this被绑定到全局对象。
1 var value = -10; 2 var myObj = { 3 value: 0, 4 increase: function(){ 5 this.value++; 6 console.log(this.value); 7 } 8 }; 9 // this绑定到myObj对象上 10 myObj.increase(); // 1 11 12 var gInc = myObj.increase; 13 gInc(); // -9 this绑定到window对象上
所以在方法中使用内部函数时要特别注意,下例中other函数中的this并未绑定到myObj对象上
var value = -10; var myObj = { value: 0, increase: function(){ this.value++; console.log(this.value); var other = function(){ this.value++; console.log(this.value); }; other();// -9, 这时other中的this绑定到window对象上 } }; // this绑定到myObj对象上 myObj.increase(); // 1 var gInc = myObj.increase; gInc(); // -8 /////////////// //1 //-9 //-8 //-7
幸运的是我们可以使用以下方式来在other中访问myObj对象:
1 var myObj = { 2 value: 0, 3 increase: function(){ 4 this.value++; 5 console.log(this.value); 6 var that = this; 7 var other = function(){ 8 that.value++; 9 console.log(that.value); 10 }; 11 other();// -9, 这时other中的this绑定到window对象上 12 } 13 }; 14 // this绑定到myObj对象上 15 myObj.increase(); // 1 16 ////////////// 17 //1 18 //2
作为浏览器兼容性中很重要的一条:element.attachEvent绑定事件方法,当该事件触发时this指针并未绑定到element对象上,而是绑定到了window对象上,原因在于IE中事件触发时,响应函数是以一个独立函数即函数调用模式来调用的
三、构造器模式调用:
如果在一个函数前面加上new调用则成为构造器模式调用。使用new运算符,会产生一个连接到该函数prototype的新对象,this指针则被绑定到这个新对象上。
1 var P = function(n){ 2 this.name = n; 3 } 4 P.prototype.getName = function(){ 5 console.log(this.name); 6 } 7 var p = new P('woodtree'); // 这时P中的this对象呗绑定p指向的对象引用上 8 p.getName();// woodtree
同时new运算符还会改变函数的返回值。以函数调用模式调用P即P()返回undefined,而通过new运算符则返回一个新对象。new运算符类似于以下Function.prototype.create函数:
1 var P = function(n){ 2 this.name = n; 3 } 4 P.prototype.getName = function(){ 5 console.log(this.name); 6 } 7 var p = new P('woodtree'); 8 p.getName(); 9 10 Object.prototype.create = Object.create || function(proto){ 11 var F = function(){}; 12 F.prototype = proto; 13 return new F(); 14 } 15 16 Function.prototype.create = function(){ 17 var that = object.create(this.prototype); 18 var obj = this.apply(that, arguments); 19 20 return obj || that; 21 } 22 var p2 = P.create('hello new'); 23 p2.getName(); // hello new
四、apply、call模式调用:
javascript中函数对象继承自Object亦可以拥有方法。call跟apply允许我们选择this的绑定对象。这两个方法的区别在于apply第二个参数必须是一个数组或者类数组对象即拥有length属性(arguments、NodeList、HTMLElementCollection等),而call除了第一个参数为要绑定this的对象外,后可跟无数的参数,参数之间用逗号间隔。
1 var P = function(n){ 2 this.name = n; 3 } 4 P.prototype.getNameAndAge = function(age){ 5 console.log(this.name + age); 6 } 7 8 P.prototype.getNameAndAge.call({ 9 name: 'catboat' 10 }, 99); 11 P.prototype.getNameAndAge.apply({ 12 name: 'lanuch' 13 }, [99]);
通过使用apply与call方法我们可以自行实现ES5中的bind函数
1 var P = function(n){ 2 this.name = n; 3 } 4 P.prototype.getNameAndAge = function(age, age2){ 5 console.log(this.name + age + age2); 6 } 7 8 P.prototype.getNameAndAge.call({ 9 name: 'catboat' 10 }, 99); 11 P.prototype.getNameAndAge.apply({ 12 name: 'lanuch' 13 }, [99]); 14 15 Function.prototype.bindContext = function(that){ 16 var _method = this, 17 slice = Array.prototype.slice, 18 args = slice.call(arguments, 1); 19 20 return function(){ 21 _method.apply(that, Array.prototype.concat.apply(args, arguments)); 22 } 23 }; 24 25 var f1 = P.prototype.getNameAndAge.bindContext({ 26 name: 'barque' 27 }, 88); 28 f1(9);
同样我们也可以解决IE中事件触发时,this指针问题(以下代码来自《Javascript框架设计》):
1 var addEvent = document.addEventListener ? function(el, type, fn, capture){ 2 return el.addEventListener(type, fn, capture); 3 } : function(el, type, fn){ 4 el.attachEvent('on' + type, fn.bindContext(el, event)); 5 }