引子:
今天看到別人的一個題目:
複製代碼 代碼如下: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);
畫了一張圖來表示執行個體與改寫函數兩者的關係,順便也改了一下:
回到文章開頭的例子: 複製代碼 代碼如下: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小西山子