函数的定义:函数是JavaScript的基础模块单元,包含一组语句,用于代码复用、信息隐蔽和组合调用。java
函数的建立:在javaScript语言中,能够说函数是其最重要也最成功的设计。咱们能够经过三种方式来建立函数。编程
① 函数声明
② 字面量方式建立
③ 使用Function构造函数建立
json
代码示例数组
1 //01 函数声明 2 //函数名称为:f1,a和b为该函数的形式参数(形参) 3 function f1(a,b) { 4 return a + b; 5 } 6 //02 字面量建立函数 7 //使用字面量建立匿名函数并赋值给f2,能够经过f2来调用,a和b为该函数的形式参数(形参) 8 var f2 = function (a,b) { 9 return a + b; 10 }; 11 //03 构造函数建立 12 //f3函数为Function这个构造函数的实例化对象,若是不传递参数,那么建立出来的函数没有任何用处。 13 //在建立实例对象的时候咱们能够经过参数列表的方式来指定f3的结构。 14 //构造函数的参数中最后一个参数为函数体的内容,其他均为函数的形参。 15 var f3 = new Function("a","b","return a + b"); 16 //函数的调用 17 console.log(f1(1,2)); //3 18 console.log(f2(1,2)); //3 19 console.log(f3(1,2)); //3
函数的结构
函数的通常表现形式为:app
1 //函数声明 2 function fn(n1,n2) { 3 //函数体的内容 4 return n1 + n2; //返回值 5 }
一般,函数包括四个部分:
(1)保留字,function。
(2)函数名,这里为fn。
(3)圆括号以及包围在圆括号中的一组参数。
(4)包括在花括号中的一组语句。编程语言
❐ 函数名能够被省略(称为匿名函数),函数名可用于函数调用或者是递归调用,另外函数名能够被调试器和开发工具识别。函数
❐ 函数声明时的参数为形参,能够有多个,多个参数之间使用逗号进行分隔。
形参将在函数调用的时候被定义为函数中的局部变量,[注意]形参并不会像普通变量同样被初始化为undefined,它们的值根据函数调用时传入的实际参数值设置。
另外,函数调用的时候并不会对实参的类型进行检查。工具❐ 函数体是一组语句,它们在函数
被调用
的时候执行。函数执行完毕后,会返回一个值。post
函数的调用:函数声明后能够经过()运算符来进行调用,JavaScript语言中,只有函数能够被调用。当函数被调用的时候,若是存在参数传递,那么会把实参的值传递给形参,并按照从上到下的顺序逐条执行函数体内部的代码。开发工具
JavaScript中的函数本质上就是对象。
在使用typeof 关键字对数据进行类型检查的时候,获得的结果可能会让咱们产生错觉。
1 var o = {}; 2 var f = function () {}; 3 console.log(typeof o); //object 4 console.log(typeof f); //function
实际上,函数和对象没有质的区别,函数是特殊的对象。
函数的特殊性
① 函数能够被()运算符调用[最重要]。
② 函数能够建立独立的做用域空间。
③ 函数拥有标配的prototype属性。
由于函数自己就是对象,因此在代码中函数能够像对象同样被使用,凡是对象能够出现的地方函数均可以出现。
❐ 函数能够拥有属性和方法。
❐ 函数能够保存在变量、对象和数组中。
❐ 函数能够做为其它函数的参数(称为函数回调)。
❐ 函数能够做为函数的返回值进行返回。
函数和对象的原型链结构
咱们能够经过下面列出的简单示例代码来分析对象的原型链结构。
1 //字面量方式建立普通的对象 2 var o = {name:"文顶顶",age:"18"}; 3 //关于普通对象的结构研究 4 console.log("① 打印o对象\n",o); 5 console.log("② 打印o.__proto__\n",o.__proto__); 6 console.log("③ 打印o.__proto__ === Object.prototype\n",o.__proto__ === Object.prototype) 7 console.log("④ 打印o.constructor\n",o.constructor); 8 console.log("⑤ 打印o.constructor === Object\n",o.constructor === Object);
经过对该代码的运行和打印分析,能够获得下面的图示。
咱们也可使用一样的方式来分析函数对象的原型链结构。
1 //使用构造函数Function 来建立函数(对象) 2 var f = new Function("a","b","return a + b"); 3 //调用函数,证实该函数是合法可用的 4 console.log(f(2, 3)); //获得打印结果5 5 //关于函数对象的结构研究 6 console.log("① 打印函数对象\n",f); 7 console.log("② 打印f.__proto__\n",f.__proto__); 8 console.log("③ 打印f.__proto__===Function.prototype\n",f.__proto__===Function.prototype); 9 console.log("④ 打印f.constructor\n",f.constructor); 10 console.log("⑤ 打印f.constructor === Function\n",f.constructor === Function); 11 //注意 12 console.log(f.hasOwnProperty("constructor")); //检查constructor是否为函数f的实例成员(false) 13 console.log(f.__proto__.hasOwnProperty("constructor")); //true
顺便贴出研究Function原型结构的代码
1 //说明:下面三行代码代表Function的原型对象指向一个空函数 2 console.log(Function.prototype); //ƒ () { [native code] } 是一个空函数 3 console.log(typeof Function.prototype); //function 4 console.log(Object.prototype.toString.call(Function.prototype)); //[object Function] 5 //检查Function.prototype的原型链结构 6 //Function.prototype是一个空函数,是一个对象,而对象均由构造函数实例化产生 7 //检查Function.prototype的构造函数以及原型对象 8 console.log(Function.prototype.constructor === Function); 9 //注意:按照通常逻辑实例对象的__proto__(原型对象)应该指向建立该实例对象的构造函数的原型对象 10 //即此处应该表现为Function.prototype.__proto__--->Function.prototype.constructor.prototype 11 //彷佛能够得出推论:Function.prototype.__proto__ === Function.prototype == 空函数 但这是错误的 12 console.log(Function.prototype.__proto__ === Object.prototype);
经过对函数对象原型结构的代码探索,能够获得下图的原型链结构图(注
:原型链并不完整)
函数的其它隐藏细节
① 函数天生的prototype属性
每一个函数对象在建立的时候会随配一个prototype属性,即每一个函数在建立以后就天生拥有一个与之相关联的原型对象,这个关联的原型对象中拥有一个constructor属性,该属性指向这个函数。
简单描述下就是:
function f(){ //......} //声明函数 //函数声明被建立后,默认拥有prototype属性--->原型对象(空对象)
f.constructor相似的代码
,其实这里使用的constructor属性是从原型链中获取的,实际上是f构造函数关联原型对象上面的属性,即Function.prototype.constructor。
备注:在ECMAScript标准中函数建立相关章节有这样一句话:NOTE A prototype property is automatically created for every function, to allow for the possibility that the function will be used as a constructor.解释了给新建立函数添加prototype属性的意义在于便于该函数做为构造函数使用。
② 函数何以可以被调用
咱们已经理解了函数自己就是对象,但又区别于普通对象,最大的区别在于函数能够被调用,()被称为调用运算符。
❗️ 函数能够被调用的缘由在于JavaScript建立一个函数对象时,会给该对象设置一个“调用”属性。当JavaScript调用一个函数时,能够理解为调用该函数的“调用”属性。
函数名后面跟上()代表这是一个函数调用。调用运算符:
是跟在任何产生一个函数值的表达式以后的一对圆括号。圆括号内能够包含N(N>=0)个用逗号分隔开的表达式,每一个表达式产生一个参数值。每一个参数值被赋予函数声明时定义的形式参数名。
函数的调用
JavaScript中有四种调用函数的模式
① 对象方法调用模式
② 普通函数调用模式
③ 构造函数调用模式
③ 上下文的调用模式
除了声明函数时定义的形参外,每一个函数还接收两个附加的参数,分别是this和arguments。其中arguments用于存储函数调用时接收到的实际参数,this的值则取决于函数的调用模式,下面分别讲解。
普通函数调用模式
当函数并不做为其余对象的属性,直接使用调用运算符来调用时,咱们认为它使用普通函数调用模式
。
1 <script> 2 //01 声明函数fn 3 function fn() { 4 console.log(this); 5 } 6 //02 以普通函数调用模式来调用fn函数 7 fn(); //this被绑定到全局对象window 8 </script>
备注:在咱们看来上面的调用方式很是简单清楚,并且this的指向也没有任何问题。但JSON的做者Douglas Crockford指出这是JavaScript语言设计上的一个错误。由于把this直接绑定给全局变量的方式没有考虑到函数做为内部函数(在其它函数内部声明的函数)使用过程当中须要共享外部对象访问权的问题。
他指出正确的语言设计应该是,当内部函数被调用时,函数内的this应该和外部函数的this保持一致,即这个this应该被绑定到外部函数的this变量。
无疑,这值得思考和讨论。
对象方法调用模式
对象是键值对的集合,对象能够拥有属性和方法。
当函数被保存为对象的属性时,咱们称之为方法。
对象的方法须要经过对象.方法()
或者是对象[方法]()
的方式进行调用。
以对象方法的模式来对函数进行调用,函数内部的this被绑定给该对象。
1 //01 字面量的方式建立对象 2 //02 o对象中拥有name属性和showName方法 3 var o = { 4 name:"文顶顶", 5 showName:function () { 6 console.log(this); 7 console.log(this.name); //文顶顶 8 }}; 9 //03 以对象方法调用模式来调用showName函数 10 o.showName(); //this被绑定到o对象
❗️ this到对象的绑定发生在方法调用的时候。
构造函数调用模式
构造函数:
若是一个函数建立出来以后,咱们老是但愿使用new 前缀来调用它,那这种类型的函数就被称为构造函数。构造函数和普通函数本质上没有任何区别,开发者老是约定以函数名首字母大写的方式来人为进行区分。
若是以构造函数的方式来调用函数,那么在调用时,默认会建立一个链接到该构造函数原型对象上面的新对象,同时让this绑定到该新对象上。
1 //01 声明构造函数Person 2 function Person() { 3 console.log(this); 4 } 5 //02 以构造函数的方式来调用Person 6 new Person(); //this被绑定到Person的实例化对象
❗️ 构造函数调用方式也会改变函数中return语句的行为,若是显示的return语句后面跟着的不是对象类型的数据,那么默认返回this绑定的新对象。
上下文的调用模式
上下文的调用模式,即便用apply或则call方法来调用函数。
由于JavaScrip是一门函数式的面向对象编程语言,全部JavaScript中的函数本质上是对象,也所以函数也能够拥有方法。使用上下文模式对函数进行调用的时候,函数内部的this根据参数传递的状况进行绑定。
1 //声明函数f 2 function f(a,b) { 3 console.log(a, b, a+b); 4 console.log(this); //使用上下文模式调用时,this被绑定给o对象 5 console.log(this.name); //wendingding 6 } 7 //字面量的方式建立对象 8 var o = {name:"wendingding"}; 9 //使用apply和call方法来调用函数 10 f.apply(o, [1,2]); 11 f.call(o,3,4); 12 console.log(f.hasOwnProperty("apply")); //false 13 console.log(Function.prototype.hasOwnProperty("apply"));//true
❐ apply和call方法调用函数,函数内部的this绑定给第一个参数。
❐ apply和call方法定义于Function的原型对象上,因此全部的函数均可访问。
❐ apply和call方法做用基本相同,参数传递的形式有所差异。
函数调用时,会完成实际参数对形式参数的赋值工做。
当实际参数的个数和形式参数的个数不匹配时,并不会致使运行错误。
若是实际参数的数量过多,那么超出的那些参数会被忽略。
若是实际参数的数量不足,那么缺失的那些参数会被设置为undefined。
JavaScript在进行函数调用时不会对参数进行任何的类型检查。
在函数的内部,咱们老是能够得到一个免费配送的arguments参数。
arguments用于接收函数调用时传入的实际参数,它被设计成一个相似于数组的结构,拥有length属性,但由于它不是一个真正的数组因此不能使用任何数组对应的方法。
arguments参数的存在,使得咱们能够编写一些无须指定形参个数的函数。
下面提供一份示例代码用于对传入的全部参数进行累加计算。
1 <script> 2 function sum() { 3 var sum = 0; 4 var count = arguments.length; 5 for (var i = 0; i < count; i++) { 6 sum += arguments[i]; 7 } 8 return sum; 9 } 10 console.log(sum(1, 2, 3, 4, 5, 6, 7, 8, 89)); //125 11 </script>
函数的调用:调用一个函数会暂停当前代码的执行,把控制权和参数传递给正被调用的函数。当一个函数被调用的时候,它会先根据实际参数来对函数的形式参数进行初始化,而后从函数体中的第一个语句开始执行并遇到关闭函数体的 } 时结束。而后把控制权交还给调用该函数的上下文。
函数的返回值:函数体中return语句能够用来让函数提早返回。当retun语句被执行时,函数会当即返回而再也不执行余下的语句,return语句后面跟上返回的具体数据,能够是任意类型(包括函数)。
❐ 函数老是会有一个返回值,若是没有使用return语句指定,那么将老是返回
undefined
。❐ 函数的返回值还和它的调用方式有关系,若是使用new也就是也构造函数的方式来调用,若函数体中没有经过return语句显示的返回一个对象类型的数据,则
默认返回this(新建立的实例对象)
。❗️ JavaScript不容许在return关键字和表达式之间换行。