一、this指向问题
1)默认绑定,即作为独立的普通函数调用
此时this指向全局对象window,如果是严格模式下,则指向undefined;
2)隐式绑定,即具有调用上下文(一种场景就是作为对象的属性调用)
隐式绑定会将this绑定到这个上下文对象,如obj.getA();this就指向.之前的函数调用者;对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。如obj1.obj2.foo(),调用时this指向obj2;
隐式绑定常见的问题就是隐式绑定的函数会丢失绑定对象。这点在第二部分会详细介绍。
3)显式绑定,
通过call/apply指定上下文对象。事实上ES5提供了内置方法Function.prototype.bind,也可以达到类似的效果。
4)new构造调用
事实上javascript中并不存在所谓的“构造函数”,只有对函数的“构造调用”。在javascript中,构造函数只是一些使用new操作符时被调用的普通函数,它们不会属于某个类,也不会实例化一个类。当使用new运算符调用函数时,该函数会返回一个对象,构造器中的this就指向返回的这个对象;需要注意的是,如果构造器显式返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,构造器中的this也会指向这个对象,而非我们期望的this;
下面是自己实现的new(没有考虑构造函数待参数的情况,如果需要考虑,加一个参数即可)
var myNew = function(func) { var o = Object.create(func.prototype); var k = func.call(o); if (typeof k === 'object') { return k; } else { return o; } }
5)this绑定的优先级:
a:函数是否在new中调用?如果是的话this绑定的是新创建的对象;
b:函数是否通过call,apply显示绑定?如果是的话,this绑定的是指定的对象;
c:函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是指定的对象;
d:如果都不是得花,使用默认绑定,在非严格模式下,绑定到全局对象window;在严格模式下绑定到undefined.
二、隐式绑定中的this丢失问题
思考下面一个问题,在prototype.js等框架中,做过这样的事情:
var getId=function(id){ return document.getElementById(id); } var oDiv=getId('div1');
思考为什么不用下面更简单的方式:
var getId=document.getElementById; var oDiv=getId('div1');
后者会抛出一个异常,这是因为许多引擎的document.getElementById方法的内部会用到this;这个this被期望指向document,当getElementById被作为方法调用的时候,this指向没有问题;但是当用getId来引用documant.lgetElementById的时候,就成了普通函数的调用,因为getId引用的只是getElementById函数本身,其函数内部的this指向了window,而不是期望的doucment;
可以尝试利用下面的方法进行this修正(显式绑定)
document.getElementById=(function(func){ return function(){ func.apply(document,arguments); } })(document.getElementById); var getId=document.getElementById; var div=getId('div1');
三、显示绑定:call和apply
1.这两个函数可以指定this的指向,apply使用数组或者类数组作为参数,call则参数数量不固定,从这个意义来说,apply比call使用的频率更高;如果传入的首个参数是null,则代表作为普通函数调用,this此时指向window;
2.这两个函数在某些场合下使用的目的不在于指向this,而是用于借用其他对象的方法;比如类数组如arguments添加数据可以借用[].prototype.push.call;转换为真正的数组可以采用[].prototype.slice.call;截取收元素可以借用[].prototype.shift.call;能够借用的关键在于这些函数内部实现原理同样也可适用于类数组的对象;只要满足以下条件:
1)对象本身可以通过数字下标存取属性;
2)对象的length属性可以读写;
jQuery框架中的$对象设计成类数组,其思想大概也是由此而来的吧。
3 值得注意的是,如果把null或者undefined作为this的绑定对象传入call,apply或者bind,这些值在调用的时候回被忽略,实际应用的是默认绑定规则。
这在借用其他对象的方法时,会时常碰到这种用法。
此时可能产生一些副作用,如果某个函数确实应用了this,就有可能修改全局对象,带来不可预料的后果。一种“更安全”的方法时,创建一个DMZ对象来替代null/undefined,在javascript创建一个空对象最简单的方法是object.create(null),object.create(null)和{}很像,但是不会创建prototype这个委托,因此比{}更空。