引子:
今天看到别人的一个题目:
function fn(x){ x = 10; arguments[0] = 20; console.log(x,arguments[0]) } fn()
感觉自己对这也是一知半解,自己也可以试一下,于是就特地分析一下。
本想从语言的角度来分析,无奈功力不够,只能粗浅的尝试一下,于是称之管中窥豹,还望大牛指正。
每一本js入门书籍都会提到,JS的函数内部有一个Arguments的对象arguments,用来函数调用的时候实际传入函数的参数,fn.length保存形参的长度。
这些对分析来说略有用处,可是我想得到更多形参的信息,不知道有谁有比较好的办法,我暂时无解。
于是只能模拟了。
先不理会模拟,从实际问题出发:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> </head> <body> <script type="text/javascript"> //形参中含有隐形的声明var x = undefined function fn(x){ console.log(x,arguments[0]); arguments[0] = 2; console.log(x,arguments[0]); } console.log('fn():'); fn(); //undefined , undefined //undefined , 2 console.log('fn(1):'); fn(1); //1,1 //2,2
function fn_1(x){ var x; console.log(x,arguments[0]); arguments[0] = 2; console.log(x,arguments[0]); } console.log('fn_1():'); fn_1(); //undefined , undefined //undefined , 2 console.log('fn_1(1):'); fn_1(1); //1 , 1 //2 , 2
function fn_2(x){ var x = 3; console.log(x,arguments[0]); arguments[0] = 2; console.log(x,arguments[0]); } console.log('fn_2():'); fn_2(); //3 , undefined //3 , 2 console.log('fn_2(1):'); fn_2(1); //3 , 3 //2 , 2 </script> </body> </html>
重点关注后面两个函数(fn_1,fn_2)的执行,在这里,我们直接重新声明了形参对应的x,看到网上有的人说这是声明的一个局部变量x。
也是,不过这个局部变量不是一般的局部变量,x直接关联对应的arguments,上面的实例中就是x关联arguments[0];
所以我猜测这个赋值的流程应该是
1、函数定义的时候,声明了形参,如果函数体内有相同名称的局部变量,则忽略此声明。同时函数体内同时会有一个对象arguments;
(乱入一句:个人以为arguments当初不定义成数组的一个考虑是否是因为在函数定义内无法确定实际参数的个数[运行时动态确定],那么要么这个数组无限大,要么数组一取值就越界)。
回到正题:
对于fn_2,初始化形参相当于var x;(此时x没有赋值,默认为undefined,赋值是在语句执行的时候赋值的)
所以如果可以这么写的话,fn_2就应该是这样:
function fn_2(var x){ x = 3; console.log(x,arguments[0]); arguments[0] = 2; console.log(x,arguments[0]); }
2、函数语法检测通过,执行的时候,函数内部的arguments对象一开始就得到赋值,赋值完毕后,函数体内的语句开始执行。
下面的一段表述是我自己想的,不知道正确不正确(特别是关联的说法):
一旦发现形参(对应的变量)被赋值,那么会去寻找arguments对应的项,如果发现了arguments对应的项,那么设置形参与arguments对应项的关联。如果没有发现arguments里面对应的项(undefined),那么形参和arguments还是保持独立。这里寻找的是arguments在函数运行开始的一个快照。反过来arguments赋值也是一样。
回到例子,fn_2函数语法检测通过,从第二步开始执行:
不带参数的情况 fn_2(); function fn_2(x){//arguments赋值完成,由于没有实参,于是arguments参数列表为空。 var x = 3;//x赋值为3,寻找arguments[0]对应的项,找不到,x与arguments[0]相互独立,arguments[0]还是为undefined console.log(x,arguments[0]);//打印x=3,arguments[0]为undefined arguments[0] = 2;//arguments被赋值,x与arguments[0]相互独立。因此x=3不改变 console.log(x,arguments[0]);//打印x = 3,arguments[0]=2 }
带参数的情况
fn_2(1); function fn_2(x){//arguments赋值完成,arguments[0]=1 var x = 3;//x赋值为3,寻找arguments[0]对应的项,找到了,于是arguments[0]被赋值为3,并且x与arguments[0]关联。 console.log(x,arguments[0]);//打印x=3,arguments[0] = 3 arguments[0] = 2;//arguments[0]被赋值2,由于x与arguments[0]已经关联到一起,于是x同时改变 console.log(x,arguments[0]);//打印x = 2,arguments[0]=2 }
反过来应该也是一样的:
不带参数 fn_2(); function fn_2(x){ arguments[0] = 2;//找不到对应的x(undefined),相互独立 console.log(x,arguments[0]);//undefined,2 x = 3;//找不到对应的arguments[0],相互独立,快照。虽然arguments动态添加了,但是快照里面没有,所以依旧失败 console.log(x,arguments[0]);//3,2 }
带参数 fn_2(1); function fn_2(x){ arguments[0] = 2;//找到相应的x,关联 console.log(x,arguments[0]);//2,2 x = 3;//找到相应的arguments[0],关联 console.log(x,arguments[0]);//3,3 }
由于我们只有一个形参,可能说服力不够,现在增加到两个。
只有一个实参的情况:
fn_2(1); function fn_2(x,y){ //arguments赋值完成,arguments[0]=1,arguments[1]=undefined console.log(x,y,arguments[0],arguments[1]); //1,undefined,1,undefined var x = 3; //x赋值为3,寻找arguments[0]对应的项,找到了,并且x与arguments[0]关联,于是arguments[0]被赋值为3。 console.log(x,y,arguments[0],arguments[1]); //3,undefined,3,undefined var y = 4; //y赋值为3,寻找arguments[1]对应的项,找不到,y与arguments[1]相互独立,arguments[1]还是为undefined console.log(x,y,arguments[0],arguments[1]); //3,4,3,undefined arguments[0] = 2; //arguments[0]被赋值2,由于x与arguments[0]已经关联到一起,于是x同时改变 console.log(x,y,arguments[0],arguments[1]); //2,4,2,undefined arguments[1] = 5; //arguments[1]被赋值5,y与arguments[1]相互独立,于是y还是保持为4 console.log(x,y,arguments[0],arguments[1]); //x=2,y=4,arguments[0]=2,arguments[1]=5 }
有两个实参的情况:
fn_3(1,6); function fn_3(x,y){ //arguments赋值完成,arguments[0]=1,arguments[1]=6 console.log(x,y,arguments[0],arguments[1]); //1,6,1,6 var x = 3; //x赋值为3,寻找arguments[0]对应的项,找到了,x与arguments[0]关联,于是arguments[0]被赋值为3。 console.log(x,y,arguments[0],arguments[1]); //3,6,3,6 var y = 4; //y赋值为3,寻找arguments[1]对应的项,找到了,y与arguments[1]关联,于是arguments[1]被赋值为4。 console.log(x,y,arguments[0],arguments[1]); //3,4,3,4 arguments[0] = 2; //arguments[0]被赋值2,由于x与arguments[0]已经关联到一起,于是x同时改变 console.log(x,y,arguments[0],arguments[1]); //2,4,2,4 arguments[1] = 5; //arguments[1]被赋值5,由于y与arguments[1]已经关联到一起,于是y同时改变 console.log(x,y,arguments[0],arguments[1]); //x=2,y=5,arguments[0]=2,arguments[1]=5 }
以上全部是推测,因为实际中没有办法形参的信息,所以我按照推测写了一个小测试:
function _Function(){//获得的形参列表为数组:_args var _args = []; for(var i = 0; i < arguments.length - 1; i++){ var obj = {}; obj['key'] = arguments[i]; obj[arguments[i]] = undefined; _args.push(obj); } //this._argu = _args; var fn_body = arguments[arguments.length - 1]; //下面的方法获取实参_arguments,这里_arguments实现为一个数组,而非arguments对象 this.exec = function(){ //函数运行时,实参_arguments被赋值 var _arguments = []; for(var i = 0; i < arguments.length; i++){ _arguments[i] = arguments[i]; } //下面执行函数体 eval(fn_body); } }
把例子中fn_2换成对应的形式就是:
// function fn_2(x){ // var x = 3; // console.log(x,arguments[0]); // arguments[0] = 2; // console.log(x,arguments[0]); // } // fn_2(1) //在fn_2body中,用_args[i]["link"] = true;来表示形参与实参相关联 var fn_2body = ''+ '_args[0][_args[0]["key"]] = 3;'+ 'if(_arguments[0]){ _arguments[0] = _args[0][_args[0]["key"]]; _args[0]["link"] = true; }' + //形参变量赋值,关联arguments并同时arguments赋值 'else{}' + 'console.log(_args[0][_args[0]["key"]],_arguments[0]);'+ '_arguments[0] = 2;'+ 'if(_args[0]["link"] == true){ _args[0][_args[0]["key"]] = _arguments[0]}' + //检查关联,给形参变量赋值 'console.log(_args[0][_args[0]["key"]],_arguments[0]);'; var fn_2 = new _Function('x',fn_2body); fn_2.exec();
画了一张图来表示实例与改写函数两者的关系:

回到文章开头的例子:
function fn(x){ x = 10; arguments[0] = 20; console.log(x,arguments[0]) } fn()
显然,两者相互独立:
x = 10,arguments[0] = 20;
推测一下:
function fn(x){ x = 10; arguments[0] = 20; console.log(x,arguments[0]) } fn(1)
应该都是输出20,20
function fn(x){ arguments[0] = 20; console.log(x,arguments[0]) } fn(1)
应该也都是输出20,20
function fn(x){ arguments[0] = 20; console.log(x,arguments[0]) } fn()
应该是undefined和20