javascript中函数中的this的值取决于使用哪个对象调用这个函数;
function invocation in js is merely sending message between objects.
函数的调用者提供身份,而函数本身的实现提供在这个身份下的行为。函数的执行结果依赖于这个函数的调用者的身份,不同的函数的调用者,函数的输出是不同的。即使是js引擎内部的native方法也是这样。
值得注意的地方是,被调用的函数,有时是在由调用者的引用通过原型链查找所找到的在该对象的constructor.prototype这个对象中;除此以外,系统中还有几个地方有关this的规定,值得留意一下。
一个小例子
var s = Object.prototype.toString;
console.log(s()); //输出[object, window]
console.log(Object.prototype.toString()); //输出[object, Object]
上例的前者调用toString方法是通过全局内的一个变量,该变量作为window的一个属性;而后者是Object.prototype这个对象的一个方法;
弄清由函数被调用时是由哪个对象引用着的
另一个例子:
var b = 10;
var a = {
ab : function(){
console.log(this);b--; //这里只有第一次输出[object, Object],后面9次都是[object, window]
var c = arguments.callee, //注意保留arguments.callee的引用
d = Array.prototype.slice.call(arguments);
b >0 && setTimeout(arguments.callee, 0)
//b > 0 && setTimeout(function(){ c.apply(a, d);}, 0); //如果使用这句则输出10次[object,Object], this指向a
}
}
a.ab();
上例当中的arguments.callee被递归调用时,this为window;利用闭包的特性,可以使得其每次被系统调用时this仍指向a;
常常在程序运行时改变引用指向的对象,当这种情况时,需要理解真正引用着的对象是什么。例如下例。
function foo1(){
//some api functions
this.a = function(){alert(1);};
this.b = function{}{alert(2);};
}
function foo2(){
//some api functions
this.c = function(){alert(3);};
this.d = function(){alert(4);};
}
var a = new foo1();//2
foo1.prototype.extend = function(func){
func.prototpye = a;
a = new func();
return this; //whom does this this refer to?
}
var b = a.extend(foo2);//1
alert(b.c); //what 's the output? can we visit the foo2's api function by using the reference b?
上例中,1处的a引用,指向2处new foo1所创建的那个对象,但extend方法在操作了原型链之后,让a指向了新的对象,通过更新后的a引用,可以通过原型链访问到a,b,c,d4种方法,但是extend方法中的this,却总是指向着这个方法被开始调用时的那个对象,即原来的a所引用着的2处的那个对象,若如上例返回this,则b实际上引用着原来2处创建的对象,alert(b.c)为undefined;
稍作修改可以让extend返回的对象成为继承着a和所传入的function对象的全部接口方法的新对象
foo1.prototype.extend = function(func){
func.prototype = a;
return new func;
弄清对象在被使用时的状态
出于某种原因,在并行下载的2段js当中,共同操作同一个命名空间或者同一个全局对象,其中一个js中的方法需要等待另一个js中对共同全局对象某些操作完成后才予以执行。我们可以通过labjs或者control js所介绍的方法去协调并行下载的js之中的依赖关系的问题,另外有关async属性和script异步下载与执行的问题可以参考这里和这里
我这里只是通过一个简单的wait函数(polling skill),利用浏览器对js代码的调度机制(这个可以阅读john resgn的这篇文章),来不断测试是否执行条件为true来调用目标函数,wait函数还提供了等待失败时对应的调用函数,这里要强调的是我们需要使用正确的引用。
<body>
<script type="text/javascript">
function add_a_script(url,async){
var s = document.createElement("script");
(async === true || async === "true" ) && (s.async = true);
s.src = url;
document.getElementsByTagName("head")[0].appendChild(s);
}
gloabl = {
extend: function(func){
func.prototype = global;
return new func;
}
}
add_a_script("http://some.url/2.js", true);
add_a_script("http://some.url/1.js");
</script>
</body>
1.js和2.js分别如下:
/* -- 1.js -- */ !function(g_var){ g_var = g_var.extend(function(){ this.wait = function(flag, callback, num, callback_final){ flag + "_" + num in this || (this[flag + "_" + num] = num); var c = arguments.callee, d = arguments, that = this; var win_gv = window.global; _flag = win_gv.get_tag(flag); //use window.global rather than 'this' if (!_flag && this[flag + "_" + num] == 0) { typeof callback_final =="function" && callback_final.call(win_gv); return; } _flag ? callback.call(win_gv, _flag):(this[flag + "_" + num] > 0 && this[flag + "_" + num]--, setTimeout(function(){ c.apply(that, Array.prototype.slice.call(d)) }, 0)); } this.get_tag = function(tag){ return this[tag]; } }); window.global = g_var; g_var.wait("get_ready",function(flag){ flag.call(this); }, 10, function(){ alert("execute anyway!") }); }(window.global)
/* -- 2.js -- */
!function(g_var){
alert(2);
g_var = g_var.extend(function(){
this.get_ready = function(){
alert("flag ready!");
}
});
window.global = g_var;
}(window.global)
上例中1.js和2.js的执行匿名函数,通过extend方法,在给global这个对象增加自己的api;当使用例子中的方法用add_a_script函数把1.js, 2.js添加到页面上,在支持async属性的浏览器中,2.js的执行会在1.js之后;wait函数,通过js的调度机制来递归执行自身,等待另一片js的执行给global增加的api方法,特别值得注意的是,通过get_tag方法获取所等待的函数饮用水时,要使用window.global这个变量,而不是this。如前面所说的,函数中this的这个值,总是固执的指向着这个函数的在开始被调用时的那个调用者对象,在经过另外一片(2.js)的执行之后,这个this所引用着的对象已经是global的__proto__隐藏属性所引用的那个prototype对象了;因此,要注意,要想取到被等待的函数引用,应该使用正确的引用 - global;
注意加入脚本的方式和async属性的作用;