标签:http io ar os 使用 java sp strong 文件
转自:http://www.tootei.net/archives/471
Javascript 中函数的内涵非常丰富,在讨论底层原理之前,先总结一下之前提到过的有关函数的概念。
在 Javascript 中,函数是内部实现了 [[Call]]
方法的可执行对象,它继承了 Object 对象的内置属性 。
函数的属性 Function.prototype
,用于在构造对象的时候,设置对象的原型属性(Object.__proto__
)。
函数的属性 Function.caller
代表调用它的函数。如果是从全局域中调用函数,它的 Function.caller
属性为 null
。
函数的属性 Function.length
代表函数定义时的形参个数。
函数的方法 apply
、call
,执行函数,并使用指定的对象作为函数体中 this
指代的对象。
这两个方法的具体区别和应用请看下面的 函数的动态调用 。
从对象的角度考虑,函数作为数据,可以进行声明和赋值,可以作为参数传递、作为执行结果返回。
创建函数的几种方式,请查看 创建函数。
function
声明的方式创建函数,而 “函数创建” 还包括其他的定义函数的方式。以下说到 “函数声明” 的时候,不包括 “函数表达式(Function Expression)”。一旦使用用 “函数声明” 的方式创建了函数,就可以在其定义域内的任何地方调用它。
需要一提的是,浏览器加载代码需要一定的时间,Javascript 以 <script>
块为单位、从上到下一块一块地加载和执行代码。同样是上面的例子,如果放在不同的 <script>
块内,就会报错:
如果有两个文件 a.js
和 b.js
,b.js
中调用了 a.js
中定义的全局函数 f
,a.js
必须在 b.js
之前引入的 Html 中,否则就会报错:
可以看出,形参和实参的数目可以不同:
arguments
对象、用数字下标的方式访问所有实参。arguments
对象不是数组,对它不能调用数组的一些方法( join
、sort
等 )。
arguments
对象形式如下所示:
arguments
对象的类型为 Arguments
:
Arguments.length
是其内包含实参的个数。Arguments.callee
是当前函数的引用。对于没有名称的匿名函数,可以使用 Arguments.callee
实现递归调用:
一个使用 Argument 对象模拟函数重载的例子:
前面介绍函数对象的方法时,已经介绍过这一概念 Function.caller
由于 Function.caller
不属于 ECMAScript 标准的一部分,尽管有浏览器实现了它,还是尽量避免使用这个属性。
不同的使用情境,this 指代的对象不同。底层的原理,请看下面的 This,这里总结一下一般情况:
函数作为对象的方法
一般来说,函数所在的对象就是它的所有者:
如果没有使用对象运算符 .
或 [ ]
来调用对象方法,其中的 this 不指代其所在对象:
函数作为构造函数
构造函数的所有者,就是构造函数所创建的对象。
全局范围内定义的变量和函数
在浏览器客户端,全局的变量和函数的所有者是 window。
如果没有明确的所有者,那它就是指向 window
Function.apply
和 Function.call
,使用指定的对象作为函数体中 this 指代的对象,立即执行函数。Function.call
和 Function.apply
的参数是可选的。
如果有参数的话,第 1 个参数代表了函数运行时 this 所指代的对象。
call
来说,后面的 0 个或多个参数依次作为参数传给函数;apply
来说,第 2 个参数必须是数组,其元素依次作为参数传给函数。eval
代码。Javascript 中的每一句代码,都是在与其相关的执行上下文(Execution context,简称 EC )中进行解析和求值的。全局的执行上下文只有一个。程序一开始的时候,先进入这个上下文中,然后再解析和运行代码。
函数和 eval
的执行上下文可以有多个:每次调用函数或 eval
,都会产生一个新的上下文。
一个执行上下文可以激活另一个执行上下文,比如在函数中调用函数。这在逻辑上,构成了 执行上下文栈。
一个上下文,如果激活了其他的上下文,就叫做 caller
;被激活的上下文,叫做 callee
。callee
被激活时,caller
挂起自身的上下文,并将控制传递给 callee
。callee
则被置于上下文栈的顶端,成为活动的执行上下文(Active Execution Context)。callee
执行完成后,再将控制返回 caller
。caller
继续解析和执行接下来的代码,直到结束。
由于程序一开始就进入了全局上下文,所以执行上下文栈的最下方是全局执行上下文(Global EC
)。
如果全局上下文中激活了一个函数的上下文 EC1
,下图展示了 EC1
进栈和出栈过程中执行上下文栈的变化:
执行上下文可以看做是普通的对象,它拥有一些必要的属性,以跟踪相关代码的执行。下图展示了执行上下文的结构:
除了 Variable object
、Scope chain
、thisValue
这三个必要的属性外,执行上下文可能包含额外的状态,这依赖于它的实现。
注意:函数表达式(和函数声明相对)不包含在变量对象(Variable object)之中。
变量对象(Variable object)是一个抽象的概念。不同的上下文类型中,代表它的对象是不同的。比如在全局上下文中,变量对象(Variable object)就是全局对象本身(window
),所以才能以 window
属性的方式访问全局域中的变量声明和函数声明:
需要注意的是,变量对象(Variable object)作为执行上下文的属性,存在于函数调用之后、代码运行之前。Javascript 扫描函数的代码(不包括嵌套函数),将用 var
声明的变量和用 function
声明的函数登记在变量对象(Variable object)中。其中:
var
声明只登记标识符,值为 undefined
;var
声明 和 function
声明,取后者的绑定结果。上面的代码中,全局变量对象(Global VO)应该是这个样子:
从变量对象的角度来看,局部变量都是 VO 的属性。所以函数内重复的 var
声明是无效的,只有第一个 var
声明起作用。而重复的函数声明,会被最后一个声明覆盖。
上面的例子中,重复声明变量 x
是无效的,它并没有变成 undefined
;重复声明函数 y
,导致任意位置的函数引用都是相同的 —— 因为 var
声明 和 function
声明在函数体未运行前就已经确定了。
需要注意的是,如果函数表达式右边是一个命名函数,它的名字同样不会登记在 VO 中。所以外部不能访问这个名字,只能在函数表达式内部引用。
活动对象(Activation object)被形参、arguments 对象、var 声明和 function 声明填充。其中:
undefined
;arguments
引用是一个 "类数组" 的对象,包含所有实参值;上面说到变量对象(Variable object),强调它是一个抽象的概念。在全局上执行下文(Global Execution Context)中,变量对象(Variable object)就是全局对象 window
。
函数的活动对象(AO)如下图所示:
再次强调,函数表达式 baz
不包含在变量对象中。
一般情况下,作用域链就是所有父变量对象,加上当前函数自身的变量对象。但有些情况下,代码在运行中会将一些对象加到作用域链的前端,比如 with
谓词,catch
从句。
函数的内部属性 [[Scope]]
中,存储了所有的父变量对象。执行上下文中的 Scope Chain
相当于:
作用域链的最后,是全局对象 window
。
一个标识符在上下文中,可能代表一个变量的名称、一个函数声明、一个形参等。如果函数代码中出现了一个不在当前变量对象中的标识符,就称之为自由变量。查询自由变量,就用到了作用域链。
与 对象的原型链 一样,如果变量没有在自身的作用域中找到,就顺着作用域链(Scope chain)查询父变量对象(Variable object)。
理解作用域链有几点要注意:
1. 函数一创建,Javascript 就将其父作用域赋给其内部属性 [[Scope]]
。 这里的 “创建” ,指的是运行时的创建,而不是书面意义上的声明。对于函数表达式(Function Expression)来说,其创建要等到它所在的语句被执行后才能完成;对于函数声明(Function Declaration),所在作用域生成变量对象后,其内所有的函数声明都完成了创建;
2. Javascript 是单线程的,任意时刻只能做一件事情。不管代码处于多深的位置,总要从全局域开始一层一层地执行。函数的内部属性 [[Scope]] 在这过程中,被传递、保存,构成执行上下文对象中作用域链;
3. 作用域链是变量对象的列表,对象是引用类型。函数的内部属性 [[Scope]]
保存是变量对象的引用。
4. [[Scope]] 属性在初始化后,不会变动,直到函数销毁,。
上面的例子中,返回的函数 bar
,通过其内部属性 [[Scope]]
保存了外部函数 foo
的作用域。
foo
的作用域由变量对象 fooAO
和全局对象 window
组成。fooAO
中包含变量 x ,window 也包括变量 x 。但是 fooAO
处于作用域链前端,所以 console.log(x) ;
输出了 10
而不是 20
。
上面的例子中,函数 foo
在声明的时候,它的内部属性 [[Scope]]
被初始化为全局作用域 window 。其内部遇到自由变量时,只能去全局对象 window 中寻找。这种情况下,看似有标识符冲突,其实没有:如果全局域中没有定义 x ,会直接报错 "x is not defined" 的错误。
上面的例子中,返回对象的 foo
方法和 bar
方法各自保存了父作用域(父作用域中包含 [ bazAO , global object ]
),所以可以继续访问到自由变量 x
。
注意:this
是执行上下文的属性,而不是变量对象的属性。
与变量相比,this 的值不参与标识符的裁决过程、不会进行作用域链的查找,而是直接从执行上下文中取值。This 的值在进入上下文的时候就确定了,并且不再改变。
在全局上下文中,this
的值就是全局对象(也是全局的变量对象)。
在函数体中,this
的值与调用方式相关:
这其中的规律或者原理是什么呢?为了理解 this
的判定过程,需要认识一个内部类型: "引用"( Reference type )。
Javascript 中有值类型和引用类型。其中值类型存储的就是它的值;而引用类型存储的只是对象的地址。
将引用类型的数据赋给一个变量,会产生一个引用,并将它赋给变量。
引用,涉及到名称的绑定,只可能发生在两种情况下:
所以,要将纯粹的 "值" 与 "名称" 区别来看。
其中,a
、b
、c
、x
属于名称,0
、function(){}
、2
、{ x : 2 }
属于值。
从执行上下文的角度来看,Javascript 代码中每一个名称都和对象关联着:要么属于全局变量对象(Global Object),要么属于函数的活动对象(Active Object),要么属于某一个常量对象( { x : 1}
),要么属于内置对象或自定义对象。
用伪码来表示引用,形式如下:
其中,base
就是名称所在的对象,referencedName
就是名称的字符串。
标识符可能是变量名、函数名、参数名、意外的全局对象属性名 [1],比如:
Javascript 内部操作的中间结果如下:
为了获取到引用的值,有一个 GetValue
方法,伪码表示如下:
对象的内部方法 [[Get]]
返回对象属性的真实值,这个过程会查询对象的原型链。
标识符是对象属性访问器,比如:
表示成内部的引用类型,伪码如下:
那么,"引用" 类型和函数上下文中的 this 有什么关联呢?
caller
提供,并取决于当前的调用表达式(即如何书写函数的调用)。
( )
的左边是一个引用,this
的值就是其 base
对象。( )
的左边不是一个引用,this
的值 null。base
对象,this
的值 null。window
)。再来看上面的例子:
this
值为刚创建的对象。ECMA-262-3 in detail. Chapter 3. This.
标签:http io ar os 使用 java sp strong 文件
原文地址:http://www.cnblogs.com/kangzhibao/p/4069712.html