论坛首页 Web前端技术论坛

Javascript中同名标识符优先级

浏览 7535 次
该帖已经被评为隐藏帖
作者 正文
   发表时间:2010-11-16   最后修改:2010-12-11


一,局部变量先使用后声明,不影响外部同名变量

var x = 1; // --> 外部变量x
function fn(){
	alert(x);  // --> undefined 局部变量x先使用
	var x = 2; // 后声明且赋值
}
fn();
alert(x); // --> 1

第一点,函数fn内第一句输出x,x是在第二句才定义的。这在js中是允许的,这里的允许是指不会出现语法错误程序可以运行。
但在其它语言如C,Java中却是不允许的。变量必须先声明后使用,如:

public class Test {
	public static void main(String[] args) {
		System.out.println(x); // 先使用
		int x = 10; // 后声明
	}
}

 

Java中编译器会提示错误,程序无法运行。

第二点,函数fn内的局部变量x不会影响到外部的变量x。即fn内alert输出不是1,而是undefined。

 

二,形参优先级高于函数名

function fn(fn){
	alert(fn);
}
fn('hello'); // --> "hello"

 

可以看到函数名和形参同名都是fn,输出的是字符串"hello",却不是函数fn的函数体(fn.toString())。

 

 

三,形参优先级高于arguments

function fn(arguments){
	alert(arguments);
}
fn('hello'); // --> "hello"

 

arguments对象可以直接在函数内使用,是语言本身提供的一个 特殊标识符
这里刚好将形参声明成与其同名。输出可以看到是"hello"而非"[object Object]",即形参arguments覆盖了语言本身提供的真正的arguments。

 

 

四,形参优先级高于只声明却未赋值的局部变量

function fn(a){
	var a;
	alert(a);
}
fn('hello'); // --> "hello"

 

函数fn形参为a,函数内第一句仅声明局部变量a,却并未赋值。从输出结果是"hello"而非undefined可以看出形参a优先级高于仅声明却未赋值的局部变量a。

 

 

五,声明且赋值的局部变量优先级高于形参

function fn(a){
	var a = 1;
	alert(a);
}
fn('hello'); // --> "1"

 

函数fn形参为a,函数内第一句仅声明局部变量a,赋值为1。从输出结果是"1"而非"hello"可以看出声明且赋值的局部变量a优先级高于形参a。

 

 

六,形参赋值给同名局部变量时

function fn(a){
	var a = a;
	alert(a);
}
fn('hello');

 

暂不运行,猜测下结果。如果按照第五点:声明且赋值的局部变量优先级高于形参。那么a将是undefined。但实际上a是"hello",即右a是形参a,左a才是局部变量a。

 

 

 

这里的两个a互不干扰,谁也没覆盖谁。这与刚刚说的赋值的局部变量优先级高于形参又矛盾了。但引擎这样做的确是我们想要的,因为并不希望var a = a后a是undefined。

 

  • 大小: 13.8 KB
   发表时间:2010-11-16  
对你总结及推测并不赞同,你把声明和赋值弄一起了,这样测试出的结果反而偏离了核心。

ECMAScript 5th中,函数执行的处理顺序(简要说明,详细的见帖后的原文)
首先进行执行环境的创建{
  先进行形参声明及赋值
  再进行函数声明及赋值
  如果arguments在当前环境未声明,则声明并初始化。否则不作处理
  最后进行函数体内的变量声明
}
然后才是执行函数体语句
例如代码中的var a = 1,在函数执行前就处理了a的声明,执行时只是赋值a=1而已

再分析你写的示例:
第2节:
function fn(fn){
	alert(fn);
}
fn('hello'); // --> "hello"

首先,这段代码运行前会先处理函数声明,在全局创建了fn这个变量。
然后执行函数体:fn('hello'),这时进入新的函数,先创建执行环境:
声明形参fn,值为'hello'
然后执行函数体:alert(fn)
对于标识符(identifier)的取值,先从当前环境查找,找到了'hello'。
(如果没找到,往上层环境查找,那就是函数定义时的环境了,即全局,它也有fn。参考scope chain)

第4节:
function fn(a){
	var a;
	alert(a);
}
fn('hello'); // --> "hello"

fn调用时创建执行环境:
声明形参a,值为'hello'
处理变量声明,a已经声明,不作处理
(处理函数体内变量声明时,如果已经声明过,不作处理。未声明时才声明并设置为undefined)

第5节:
function fn(a){
	var a = 1;
	alert(a);
}
fn('hello'); // --> "1"

执行环境建立:
(同第4节)
声明形参a,值为'hello'
处理变量声明,a已经声明,不作处理
执行函数体:
a = 1  // 修改变量a的值为1
...
(没什么优先级,a = 1这一句的a,还是参数声明时创建的标识符。同4节,函数体内变量重复声明不作任何处理)

第6节:
function fn(a){
	var a = a;
	alert(a);
}
fn('hello');

执行环境建立:
(同第4,5节)
声明形参a,值为'hello'
处理变量声明,a已经声明,不作处理
执行函数体:
a = a  // 变量a的值不变,还是为hello
...


附1
ECMAScript 5th中的声明绑定过程(Declaration Binding Instantiation)及部分翻译(个人理解,译得不准请见谅):
引用

10.5  Declaration Binding Instantiation
Every execution context has an associated VariableEnvironment. Variables and functions declared in ECMAScript code evaluated in an execution context are added as bindings in that VariableEnvironment’s Environment Record. For function code, parameters are also added as bindings to that Environment Record.
Which Environment Record is used to bind a declaration and its kind depends upon the type of ECMAScript code executed by the execution context, but the remainder of the behaviour is generic. On entering an execution context, bindings are created in the VariableEnvironment as follows using the caller provided code and, if it is function code, argument List args: // 设args为实参数组

1.  Let env be the environment record component of the running execution context’s VariableEnvironment.
2.  If code is eval code, then let configurableBindings be true else let configurableBindings be false.
3.  If code is strict mode code, then let strict be true else let strict be false.
4.  If code is function code, then 
a.  Let func be the function whose [[Call]] internal method initiated execution of code. Let names be the value of func’s [[FormalParameters]] internal property.   // 设func为函数,names为形参数组
b.  Let argCount be the number of elements in args. // 设argCount为实参个数
c.  Let n be the number 0. // 设n为0
d.  For each String argName in names, in list order do // 顺序遍历形参数组,对每个形参名argName作处理
i.  Let n be the current value of n plus 1. // n=n+1 (即n为当前形参序号,从1开始)
ii.  If n is greater than argCount, let v be undefined otherwise let v be the value of the n’th element of args. // 如果n大于形参数目,则设v为undefined。否则v为对应的实参值
iii.  Let argAlreadyDeclared be the result of calling env’s HasBinding concrete method passing argName as the argument. // 设argAlreadyDeclared为[当前环境.标识符是否已声明(argName)]的返回值
iv.  If argAlreadyDeclared is false, call env’s CreateMutableBinding concrete method passing argName as the argument. // 如果为false,即未声明,则创建之
v.  Call env’s SetMutableBinding concrete method passing argName, v, and strict as the arguments. // 设置环境中的argName变量值为v
5.  For each FunctionDeclaration f in code, in source text order do // 对于函数声明,按代码先后作处理(对于函数声明详细信息,见附2)
a.  Let fn be the Identifier in FunctionDeclaration f.
b.  Let fo be the result of instantiating FunctionDeclaration f as described in Clause 13.
c.  Let funcAlreadyDeclared be the result of calling env’s HasBinding concrete method passing fn as the argument.
d.  If funcAlreadyDeclared is false, call env’s CreateMutableBinding concrete method passing fn and configurableBindings as the arguments. // 如果标识符未声明,则声明之
e.  Call env’s SetMutableBinding concrete method passing fn, fo, and strict as the arguments. // 设置值
6.  Let argumentsAlreadyDeclared be the result of calling env’s HasBinding concrete method passing "arguments" as the argument  // 设argumentsAlreadyDeclared为[当前环境.标识符是否已声明("arguments")]的返回值
7.  If code is function code and argumentsAlreadyDeclared is false, then // 如果未声明,则处理
a.  Let argsObj be the result of calling the abstract operation CreateArgumentsObject (10.6) passing  func, names, args, env and strict as arguments.
b.  If strict is true, then 
i.  Call env’s CreateImmutableBinding concrete method passing the String "arguments" as the argument.
ii.  Call env’s InitializeImmutableBinding concrete method passing "arguments" and argsObj as arguments.
c.  Else,
i.  Call env’s CreateMutableBinding concrete method passing the String "arguments" as the argument.
ii.  Call env’s SetMutableBinding concrete method passing "arguments", argsObj, and false as arguments.
8.  For each VariableDeclaration and VariableDeclarationNoIn d in code, in source text order do // 按代码顺序处理变量声明
a.  Let dn be the Identifier in d.
b.  Let varAlreadyDeclared be the result of calling env’s HasBinding concrete method passing dn as the argument.
c.  If varAlreadyDeclared is false, then // 如果未声明
i.  Call env’s CreateMutableBinding concrete method passing dn and configurableBindings as the arguments. // 声明标识
ii.  Call env’s SetMutableBinding concrete method passing dn, undefined, and strict as the arguments. // 设值为undefined


附2
函数声明(FunctionDeclaration)的分辨请见:http://www.iteye.com/topic/802482#1741032
(不知道是谁投的隐藏帖,无语了)
4 请登录后投票
   发表时间:2010-11-16  
你们讨论的太复杂了......
0 请登录后投票
   发表时间:2010-11-17  
2楼正解。楼主加油啊
0 请登录后投票
   发表时间:2010-11-17   最后修改:2010-11-17
支持clue的观点,在同一scope下的变量多次声明是合法的且也不会引起错误。(It is legal and harmless to declare a variable more than once with the var statement.)

 fn();  

 function fn(){  
     var a = 1;  
     var a;
     alert(a);
 }


PS:自从“写了10年Javascript未必全了解的连续赋值运算”那贴里看了clue回复,发现以前好多自己测试出来的猜测结论,都在读了ECMAScript后清楚了。

clue 写道

函数声明(FunctionDeclaration)的分辨请见:http://www.iteye.com/topic/802482#1741032
(不知道是谁投的隐藏帖,无语了)

这贴的亮点其实就是clue的回复,投隐藏确实有点可惜。


0 请登录后投票
   发表时间:2010-11-17  
变量一定要同名吗,同名有什么必要性,有啥好处!!!
0 请登录后投票
   发表时间:2010-11-18   最后修改:2010-11-18
赞楼主。楼主把各种情况下同名标识符优先级一一列出,说明楼主很有心,关注细节。解疑了困惑我很久的问题。但结尾应该把所有讨论情况总结下。应该是这样的:

局部变量(vars) > 形式参数(formal parameter) > arguments
                                            > 函数名

clue 的回复我觉得是从更深层次(引擎内核)去证明楼主所说的优先级次序。并不矛盾。即楼主摆出了事实,clue 试图去说明事实的深层原因。

此外clue对ECMAScript的理解太认同,比如忽视了最基本的JS中“解释期”,“运行期”。将两者混为谈。
显然他没有理解执行上下文栈(execution context stack),执行上下文(execution context),变量对象(variable object),激活对象(activation object)等。
而是引用一大段ECMAScript 10.5节 去表达自己对ECMAScript的学习和理解。

另外几个回复只能说没认真看帖,看见某“高人”引用ECMAScript就觉得很牛。

总之,帖子是好贴。要细读,测试,体会。
0 请登录后投票
   发表时间:2010-11-18  
pfans 写道

此外clue对ECMAScript的理解太认同,比如忽视了最基本的JS中“解释期”,“运行期”。将两者混为谈。
显然他没有理解执行上下文栈(execution context stack),执行上下文(execution context),变量对象(variable object),激活对象(activation object)等。
而是引用一大段ECMAScript 10.5节 去表达自己对ECMAScript的学习和理解。

不知道你是怎么得出这个结论的,倒像是你没有仔细看
在我的描述中
scope chain = execution context stack
执行环境 = execution context

然后,一个执行环境都必有一个关联的变量对象,一般就把它整合进执行环境一起说了。
激活对象是ECMAScript中的抽象概念,也只是描述当前唯一的处于运行状态的执行环境而已,第5版中甚至把这玩艺给拿掉了。

最后,不知道你有什么高见,难不成你可以整理一份比ECMAScript文档所描述得更精简清晰的文档,来解释所有这些JS解释执行库的行为?

在标准化的潮流下,不认同ECMAScript,你想去认同什么?

P.S.
我的回帖没有任何针对楼主的意思,事实上楼主所发的前几个帖都成功引发我的兴趣,从而导致我去翻ECMAScript文档,试图去弄明白。
我的回帖也是弄懂文档,完全理解以上所有现象后整理出来的,可以看作一个学习笔记
没有楼主之前所发的一系列帖子,也不会有现在对ECMAScript文档的理解。
0 请登录后投票
   发表时间:2010-11-18  
pfans 写道
赞楼主。楼主把各种情况下同名标识符优先级一一列出,说明楼主很有心,关注细节。解疑了困惑我很久的问题。但结尾应该把所有讨论情况总结下。应该是这样的:

局部变量(vars) > 形式参数(formal parameter) > arguments
                                            > 函数名

发完牢骚,再来讨论你的结论。
老实说,其实不用扯什么解释期,执行期,你的结论本身就没分清声明和执行。
在执行前,参数声明最前,函数声明如果有冲突就会覆盖
arguments及局部变量如果有冲突就跳过

要说优先,只有初始值可以讨论讨论(声明完后就是变量,可以随赋值修改)
函数声明最优先(会覆盖参数,不会被其它人覆盖),其次参数声明(后面没人会覆盖它的值),然后arguments(局部变量只声明,不覆盖),最后是局部变量(初始值undefined)。
0 请登录后投票
   发表时间:2010-11-18  
clue 真乃神人也
说实话我完全没读过ECMA Script,看来有必要读读了
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics