javascript管中窥豹 形参与实参浅析

  引子:

  今天看到别人的一个题目:

  

复制代码 代码如下:

  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

  重点关注后面两个函数(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赋值也是一样。

  上面的删除的部分是昨天的,红字部分是写到一半的时候发现有问题加上去的。今天回过神来,昨天为什么要傻逼的想到快照呢,这个不就是函数开始运行时直接

  判断关联么?于是改了一下表述:

  

复制代码 代码如下:

  在函数开始执行时,设置形参与arguments的关联信息。如果形参与对应的arguments里面能找到对应的项(均为undefined),那么两者关联。后面不论怎么处理,都不会改变整个函数体内的关联信息。

  于是后面的实例说明的说法也要改变:

  回到例子,fn_2函数语法检测通过,从第二步开始执行:

  不带参数的情况

  

复制代码 代码如下:

  fn_2();

  function fn_2(x){//arguments赋值完成,由于没有实参,于是arguments参数列表为空。同时判断关联信息,显然形参有,arguments空,两者相互独立,以后都不会再关联

  var x = 3;//x赋值为3,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。同时形参x有值,两者相关联,永结同心。

  var x = 3;//x赋值为3,x与arguments[0]关联,于是arguments[0]被赋值为3,。

  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动态添加了,老死不相往来,所以依旧失败

  console.log(x,arguments[0]);//3,2

  }

  带参数

  

复制代码 代码如下:

  fn_2(1);

  function fn_2(x){

  arguments[0] = 2;//关联

  console.log(x,arguments[0]);//2,2

  x = 3;//关联

  console.log(x,arguments[0]);//3,3

  }

  由于我们只有一个形参,可能说服力不够,现在增加到两个。

  只有一个实参的情况:

  

复制代码 代码如下:

  fn_2(1);

  function fn_2(x,y){ //arguments赋值完成,arguments[0]=1,arguments[1]=undefined,因此只有x与arguments[0]关联,y与arguments[1]老死不往来

  console.log(x,y,arguments[0],arguments[1]); //1,undefined,1,undefined

  var x = 3; //x赋值为3,x与arguments[0]关联,于是arguments[0]被赋值为3。

  console.log(x,y,arguments[0],arguments[1]); //3,undefined,3,undefined

  var y = 4; //y赋值为3,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,x与arguments[0],y与arguments[1]都相互关联

  console.log(x,y,arguments[0],arguments[1]); //1,6,1,6

  var x = 3; //x赋值为3,x与arguments[0]关联,于是arguments[0]被赋值为3。

  console.log(x,y,arguments[0],arguments[1]); //3,6,3,6

  var y = 4; //y赋值为3,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);

  }

  }

  替换成:

  

复制代码 代码如下:

  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];

  }

  //在运行开始就判断关联信息

  for(var j = 0; j < Math.min(_arguments.length,_args.length); j++){

  _args[j]["link"] = true;

  }

  //下面执行函数体

  eval(fn_body);

  }

  }

  上面按理来说,关联应该是把两者指向同一个对象,可是我只需要分析例子,没打算做得那么精细,所以是在函数体里面用if语句判断的 。

  把例子中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(_args[0]["link"]){ _arguments[0] = _args[0][_args[0]["key"]];}' +

  'console.log(_args[0][_args[0]["key"]],_arguments[0]);'+

  '_arguments[0] = 2;'+

  'if(_args[0]["link"]){ _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(1);

  

  画了一张图来表示实例与改写函数两者的关系,顺便也改了一下:

javascript管中窥豹 形参与实参浅析

回到文章开头的例子:

  

复制代码 代码如下:

  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

  原文来自cnblogs小西山子