仅做笔记分享,如有错误请高手指出
普通函数的执行方式
函数无非两部分:数据和对数据的操作.
数据又分为外部数据和内部数据
内部数据又分为参数和变量两部分。
在函数每次执行的时候,参数都会被赋予一个新值,而变量则每次都会被设置为一个相同的初始值。
函数的变量和参数保存方式:
对于多个数据来说,最常用保存方式是使用数组保存,这样按序号查找起来就比较方便,一个函数的参数和变量都会集中保存在一个数据或者数组或者跟数组类似的结构(例如栈)中。但是数组本身存在一个缺点,它要求每个元素的长度都相等,这对参数(或变量)来说很难符合要求。但是为了使用数组(或栈)的便携性通常会在数组中保存一个包含地址的数据(除地址外,还可能包含数据类型等其他数据),而不是实际的数据,这样即可以使用数组,又可以保存不同长度的数据。此时,在函数中使用参数(或变量)的时候只需要使用“第几个参数(或变量)”就可以,至于数组中具体一个元素使用多少位,需要根据不同的硬件平台(如,32位还是64位)和具体引擎的开发者来确定。但是,这里还存在一个小问题,对于复杂的数据来说,这样保存无可厚非,而对于直接使用数组元素就可以保存的简单数据(例如整数)来说,在使用这种方式就显得复杂了,而且多一步通过地址查找数据的操作也会影响效率,因此这种情况一般会直接将值保存到数组中,而不是保存地址。
函数在每次执行前都会新建一个参数数组和一个变量数组(也可合并为一个数组,而且通常会使用栈来实现),然后将调用时所传递的参数设置到参数数组中,而变量数组在每次执行前都具有相同的内容,对数据进行操作时只需要使用“第几个参数”或者“第几个变量”即可。
简单的数据(如整数)会直接保存在数组中,而对于复杂的数据,数组中只保存地址,具体的数据保存在堆中。可以简单地将堆理解为一堆草纸,其所保存的数据是所有函数所共享的,不过也并不是每个函数都可以调用堆中所有的数据。因为调用堆中数据的前提是能找到,若找不到也就不可调用。例如,在函数中定义了一个字符串的对象变量s,这时就会将s的内容保存到堆中,然后将堆中所保存到堆中,然后将堆中所保存数据的地址保存到函数的变量数组中,这时对于函数外部来说,虽然可以访问堆中的数据,但是因为没有s的地址,所以也就无法访问s这个字符串变量了。
function paramF(p1){
var msg="hello";
console.log(p1); //设置断点
for(var i in arguments){
console.log(arguments[i]);
}
}
paramF("a","b","c"); //a a b c
函数在执行时会将参数p1和函数中所用到的变量msg、i放到相同的地位,即在函数内部执行的时候就不回区分是参数还是变量。另外,在js的函数中,会自动创建一个名为arguments的内部变量,然后将所有参数的地址保存到其中。arguments类似数组对象,可以通过它来获取函数调用时所传递的参数。
function paramF(p1){
console.log(p1)
for(var i in arguments){
console.log(arguments[i])
}
}
paramF("a") //a a
paramF("a","b","c") //a a b c
paramF方法首先打印了参数p1的值,然后便利打印arguments中所有参数的值。可以看出,参数p1的值和arguments[0]的值是一样的,函数的参数按顺序一次保存在arguments变量中。还可看到,在调用函数传入参数的个数也可以定义时不一样。例如,虽然paramF函数定义时是有一个参数,但是在调用时却可以传递三个参数,当然也可以传递任意个数的参数(不传也行),因此JS中不存在同名函数重载的用法。
函数定义时的参数(通常叫形参)和arguments对象的关系如下:在JS的函数调用前JS引擎会创建一个arguments对象,然后在其中保存调用时的参数(通常叫实参),而形参其实只是一个名字,在实际操作时会将其翻译为arguments对象的一个元素。例如,对于“console.log(p1)”这条语句,在操作时会被翻译为“控制台打印arguments的第一个元素”,即函数的形参只是一个名字,给程序员看的,引擎在实际操作时会自动将其翻译为arguments中的一个元素,可以使用下面的例子来验证。
function paramF(p1){
console.log(argument[0]===p1)
}
paramF("HELLO")
匿名函数
在JS中可以使用匿名函数,原理很简单。在JS中函数其实也是一种对象,在底层只要用一块内存将其保存下来即可。在调用时只需要找到这块内存,然后创建好执行环境(包含参数数组、变量数组等内容)就可以执行了。所以有两个关键方面:
- 将函数对象保存到一块内存中
- 找到这块内存
通常使用通常使用函数名来查找这块内存的地址,不过函数名只是查找这块内存的一个工具,最主要的目的其实是找到这块内存,也就是说,即使没有函数名也可以,只要能找到这块内存就可以。因此JS中可以使用匿名函数。
用法:
先使用function关键字定义一个函数,然后将其使用小括号括起来(这只是语法要求,否则后面执行的语句无法被引擎正确识别),这样九江函数定义好了,引擎会为其分配一块内存来保存。然后直接在后面加个小括号,并将参数放入其中,这样引擎就知道要使用这块内存所保存的函数来执行了。因为对JS来说, 在函数后面加小括号是调用函数的意思, 这是JS的语法规则。这时既有保存函数的内存也有内存地址,这样就可以执行了。
例子:
var log=(function(){
console.log("创建日志函数")
return function(param){
console.log(param)
}
})()
log("hello")
这里也创建了一个自运行的匿名函数,不过其返回值仍然是一个匿名函数,也就是说函数自运行后返回的结果仍然是一个函数。把返回的函数赋值给log变量,就可以使用log变量来调用返回的函数了(注意与前面所介绍的函数表达式创建函数的区别)。这里其实包含两块保存函数的内存,自运行的匿名函数本身有一块内存来保存,当碰到后面表示执行的小括号后就会自动执行,另外还有一块内存来保存所返回的函数,而返回的值其实是这块内存的地址,这样log变量指向了这块保存函数的内存,因此也可以使用log来调用此函数。
虽然JS表面看起来有很多复杂的东西,但只要理解了其本质(特别是内存模型)后就很简单了。