2019-12-20:第五章:引用类型(函数)
5.function类型
函数实际上是一个对象。因此函数名的本质是一个指向函数对象的指针。
函数的对象定义法他并不与某个函数绑定,这同时也就解释了将某一个函数名多次定义,js只会调用最后一次定义的内容。
另外,由于我们说函数名实际上是一个指向函数对象的指针,那么这也就意味着同一个函数可以有多个名字。我们可以在定义一个函数后,定义另外一个变量 = 之前的指针,这样同一个函数就有两个名字了。因此,函数名只是指向函数对象的引用,这个定义非常重要。要访问函数指针,只需要访问变量值,而去掉括号即可。
5.1没有重载(深入讲解)
函数名是一个指针,他实际上并不与某个函数绑定,这同时也就解释了将某一个函数名多次定义,js只会调用最后一次定义的内容。也就是没有重载的原因。
调用的是最后一次定义的a5.2函数声明和函数表达式
在运行js代码时,解析器和率先读取到函数的声明,并且使其在任何代码被执行前设置为可访问状态。而真正的函数表达式,则必须等到解析器执行到了调用它的具体代码时,才会被解释和执行。
函数的声明会被升格到代码顶部,而具体的定义只有在被调用的是皇后才会被解释和执行为了验证这个结论,我们可以使用两种函数定义的方式,检查函数声明的升格情况:
赋值型的函数定义报错了这是因为赋值的函数定义并不是函数声明,不会在代码被解释前执行函数的升格操作。因此在运行到console.log(sum2(a,b))时,sum2并没有被定义,它也根本没有指向文中定义的函数对象,所以报错sum2 is not a function也不足为奇了。
5.3作为值的函数
因为我们可以将函数名看做是指向函数对象的引用类型变量,因此,可以将函数作为一个参数传递给另外一个函数,就是理所当然的了。
这里sum1是作为内部的参数变量而使用的。如果不将sum1写入函数中,根据前面我们讲过的标识符解析流程,他仍然可以在外层环境中找到该标识符。除此之外,在一个函数中返回另外一个函数也是非常好的写法:举个例子,在一个函数的连环定义中,内容其实有三个参数:propertyName,object1,object2:
函数中返回一个函数我们可以在外面的函数中传入一个propertyName的值,随后这个函数会返回一个函数的定义:
function(object1,object2){ do some things}
此时这个被返回的函数就可以在别的函数中作为一个参数被使用过了:最简单的使用例子就是我们定义比较函数:
一个非常好的模块化编程的例子5.4函数内部的属性
函数内部有两个特殊属性:arguments和this。
① arguments我们在前文多次提到过,他是一个类数组的保存参数的对象,虽然arguments的主要用途是保存参数对象,但这个对象其实还有一个叫做callee的属性,这个属性是一个引用类型的指针,它指向拥有arguments对象的函数。
而我们可以使用它,在函数发生递归时,一旦想要改变函数名,那么递归就会失效,但是使用callee替代原来的函数名指向函数就不会有什么问题:
可以发现使用callee达到了相同的效果② 函数内部地另外一个特殊对象是this。this是在js编程中经常搞不清楚的一点。但是书里有一句清晰的定义,即永远记住一句话:this指向的是函数据以引用的环境对象。或者说,this的值就是据以执行的环境对象。
换句话说,函数定义在哪个类里,它所指向的就是哪个类。
第二次第三次结果不同,因为执行的环境对象不同对于第一个this.color,因为其定义在全局类里,环境对象为window,因此color为全局的red。
对于第二个this.color,因为其是从ob1.f()发起的,环境对象为ob1,因此this指向ob1,因此color为类内的blue。
对于第三个this.color,虽然是在函数内定义,但是因为会发生函数定义升格,因此其环境是全局类,环境对象为winodw,因此color为全局的red。
但是在这里同时要注意,函数的名字仅仅是一个包含指针的变量。因此,即便在不同的环境里,打印color得到不同的值,但其实全局的fun1与ob1.f两个指针同样指向一个函数。二者做逻辑判断也会返回true。
③ 还有一个函数属性叫做caller,它返回调用当前函数的函数的引用。定义读起来很绕,但是看个例子就明白了:
打印出的是fun1()的定义5.5函数的属性与方法
前文我们提到过,函数其实也是对象,因此函数也有属性和方法。每个函数都有两个属性:length和protype,以及三个方法:apply和call,和bind。
length为这个函数在定义时写入的参数个数。
prototype为真正保存引用类型所有实例方法的地方,它是函数的一个子对象,在第六章会详细介绍这个属性,在这先按下不表。另外,prototype属性是不可以枚举的,因此for-in无法发现该属性。
展示对象自带的方法apply和call的用途都是在特定的作用域中调用函数。实际功能就是设置函数体内this的值。
apply接受两个参数:1是特定的作用域,另外一个是参数数组。而call与apply唯一的不同仅仅是call是填参数数组,而apply是把参数全部一个一个列进去。
首先运行fun2时,this指向window,因此this.c = 10,fun1的结果也就是13。
当将d.f也指向函数fun2时,this指向了d,因此this.c = 20,fun1的结果也就变成了23。
apply和call可以有效的指定函数在何种作用域运行,我们可以首先定义类,再将不同的类作为环境传入函数中去,这时函数中的this.prototype的值就会变成不同环境域下的值。
而bind与前二者不同,bind是根据已有的函数复制一个新的函数,而这个新的函数的作用域和this指向的对象由开发人员自己决定。一般使用可以分为三个步骤:
可以发现,新的函数的作用域已经被更换为了o,因此即便是在全局话你就能够下跑这个函数,仍然是采用o作为执行环境对象。另外其实每个函数也都有toLocaleString,toString,valueOf三个方法,但是因为不同的浏览器返回的值不同,但基本上都是函数实现的内容,这些信息在调试代码时是比较有用的。在这就不多赘述了。
上一张图吧还是