Js中函数的执行方式
函数无非两部分:数据和对数据的操作 。
数据又分为外部数据和内部数据,对于外部数据可以详看作用域链 。
内部数据又分为参数和变量两部分 。 在函数每次执行的时候参数都会被赋予一个新值,而变量则每次都会被设置为一个相同的初始值。
函数的变量和参数是怎么保存
函数的变量和参数是怎么保存的呢?对于多个数据来说,最常用也是最简单的保存方式就是使用数组保存,这样按序号查找起来就非常方便了。而且,一般来说,一个函数的参数和变量都会集中保存在一个数组或者跟数组类似的结构(例如栈)中。但是,数组本身存在一个非常致命的缺点,它要求每个元素的长度都相等,这对于参数(或变量)来说是很难符合要求的。但是,为了使用数组(或栈)的便捷性通常会在数组中保存一个包含地址的数据(除地址外,还可能包含数据类型等其他数据),而不是实际的数据,这样既可以使用数组,又可以保存不同长度的数据 。 此时,在函数中使用参数(或变量)的时候只需要使用“第几个参数(或变量)”就可以了,至于数组中具体一个元素使用多少位,则需要根据不同的硬件平台(例如,是 32 位还是 64 位)和具体引擎的开发者来确定。但是,这里还存在一个小问题,对于复杂的数据来说,这样保存无可厚非,而对于直接使用数组元素就可以保存的简单数据(例如整数)来说,再使用这种方式就显得复杂了,而且多一步通过地址查找数据的操作也会影响效率,因此这种情况一 般会直接将值保存到数组中,而不是保存地址。
函数在每次执行之前都会新建一个参数数组和一个变量数纽(当然也可以合并为一 个数组,而且通常会使用我来实现),然后将调用时所传递的参数设直到参数数组中,而 变量数组在每次执行前都具有相同的内容,对数据进行操作时只需要使用“第几个参数” 或者“第几个变量”即可 。
简单的数据(例如整数)会直接保存在数组中,而对于复杂的数据,数组中只保存 地址,具体的数据保存在堆中 。 可以简单地将堆理解为一堆草纸,其所保存的数据是所 有函数所共享的,不过也并不是每个函数都可以调用堆中所有的数据 。 因为调用堆中数 据的前提是能找到,如果找不到当然也就调用不了 。 例如,在函数中定义了一个字符串的对象变量 s ,这时就会将 s 的内容保存到堆中,然后将堆中所保存数据的地址保存到 函数的变量数组中,这时对于函数外部来说,虽然可以访问堆中的数据,但是因为没有 s 的地址,所以也就无法访问 s 这个字符串变量了 。
下面来看一个例子,首先对下面的代码设直断点,
1 | function paramF(pl) { |
- debug图片
从图右侧可以看出,函数在执行时会将参数 pl 和函数中所用到的变量 msg 、 i 放到相同的地位,即在函数内部执行的时候就不会区分是参数还是变量 。 另外,在 JS 的函数中,会自动创建一个名为arguments的内部变量,然后将所有参数的地址保存到其中。arguments 类似数组对象,可以通过它来获取函数调用时所传递的参数。接着来看 下面的例子。
1 | function paramF(pl) { |
paramF 方法首先打印了参数 pl 的佳,然后遍历打印 arguments 中所有参数的值。可以看出,参数 pl 的值和 arguments[0] 的值是一样的,函数的参数按顺序依次保存在 arguments 变量中。还可以看到,在调用函数时传入参数的个数也可以和定义时不一样。例如,虽然 paramF 函数定义时只有一个参数,但是在调用时却可以传递三个参数,当然也可以传递任意个数的参数,甚至不传递参数,因此 JS 中不存在同名函数重载的用法。
函 数 定义时的 参数( 通常叫 形参) 和 arguments 对象的关系如下:在 JS 的函数调用 前 JS 引 擎 会创建一个 a 电uments 对象,然后在其中保存调用时的参数(通常叫实参),而 形 参 其实只是一个名字,在实际操作时会将其翻译为 arguments 对象的一个元素 。 例如, 对于“ console.log(p 1 ) ;” 这条语句,在操作时会被翻译为“控制台打印 arguments 的第一个元素”,即函数的形 参 只是一个名字,是给程序员看的,引擎在实际操作时会自动将其 翻译为 ar 阴 men ts 中的一个元素,可以使用下面的例子来验证 。
1 | function paramF (pl) { |
当然,这里给大家介绍的只是一种实现方案,还有其他方案 。 例如,可以直接把参数 对象放入我中,不同 参数 可以使用偏移量来表示,不过原理都是一样的 。