目录
函数定义
函数分类
函数参数
传递参数方式
参数异常的情况
参数同名
arguments类数组
arguments方法
定义一个函数
函数的声明提升
函数的重复定义
函数的嵌套定义
函数返回值
为什么需要返回值
怎样使函数返回某个值
使用返回值时注意
函数实例部分
函数的调用
函数的递归调用
函数this
特殊类型的函数
构造函数
作为值的函数
匿名函数
立即执行函数(IIFE)
函数的属性和方法
函数作用域
Javascript预定义函数
函数是一段可以被重复执行或调用的代码块。
当某一段代码使用次数较为频繁时,使用函数将这一段代码简化为一个函数,只需要函数名就可以调用这一整段代码,可以减少程序员工作量,使开发工作更有条理性。
eg:
//关键词function声明函数,类似于var声明变量
//add_two_nums为函数名,可以使用小驼峰命名法和下划线分割单词等命名规范,
//小括号内为函数参数,
//{}大括号内是函数体
//return 指定函数返回值
function add_two_nums(num1,num2){
var result=num1+num2;
return result;
}
函数分为用户自定义和系统自带两类
用户自定义函数:function add(num1,num2){······}
预定义函数:alert(),Math对象的方法,parseInt(),isNaN()······
函数定义时会在函数名后的小括号内书写至少0个参数。要求参数间以逗号分隔,
参数类型不限,命名遵循标识符规则,写在小括号内的参数就是函数的形式参数。形式参数相当于定义在函数内部的临时(局部)变量。函数执行时把实际参数(实参)的值传递给形参。函数在执行完成后如果没有方式访问函数内部的参数,这些参数将会被视为内存垃圾,被垃圾回收机制自动清除。
函数参数分为基础数据类型和复合数据类型(对象,正则表达式,函数等)
基础数据类型的参数传递是直接传值。实际参数的值赋给形式参数。形式参数在函数运行过程中可以修改,对实际参数无影响。
复合数据类型的参数传递是地址的传递,也可以说是引用方式的传递。仍然只有一个对象,在函数中执行的对参数的修改会修改内存中的参数对象,这一点值得注意。
javascript调用函数时不会检查函数参数个数,所以会出现函数实际参数比形式参数多或少的情况。
当调用函数时实际参数少于形式参数时,在实际参数传递值给形式参数时,由于没有实际参数,会被赋值为undefined,最终只有部分参数被赋值,其余参数为undefined。我们使用的一部分对象方法的参数可以省略,就是检测到对应的形式参数传值后为undefined,再根据不同参数的省略情况执行不同代码。这样扩展了函数的功能和实用性。
当调用函数时实际参数多于形式参数时,前面几个参数可以被正常的传值,后面的参数的值会传入到一个arguments的类数组。
大概这类问题只会在面试题中出现,正常工作中不会出现。出现同名参数时函数调用的参数的值是后一个传入的参数值。
function f(a,a){
console.log(a);
}
f(1,2);//这里输出2
//可以理解为后一个a=2,把前一个a=1覆盖了
//实际想要调用这两个参数时建议使用arguments类数组
函数的参数会传值到一个叫做arguments的类数组。这个类数组用于保存函数参数,我们可以使用参数名访问部分变量,也可以使用arguments[0]访问第一个参数,arguments[1]访问第二个参数······
arguments这个类数组在函数执行时产生,不论函数参数个数多少都会保存。值得注意的是这个是类数组,具有length属性,但不具有push等数组方法。
arguments这个类数组具有caller和callee两个方法,这两个方法作用一样,但caller已经在更新中被放弃,callee仍可用。作用是指向函数本身。在函数中调用这个方法可以在函数内部调用函数本身。可以用于函数的递归调用(函数执行中再次调用自身)。
注意:在ES5严格模式下不能使用callee这个方法
函数使用function关键词定义。定义一个函数包括函数名,函数体,函数参数部分。
我们也可以使用Function这个构造函数进行函数的创建。
var add=new Function("num1","num2","return num1+num2");
Function()的可以有任意个参数。
这任意个参数中,最后一个参数是add函数的函数体,前面的参数都是add函数的参数。
这个使用构造函数创建的add函数为
function add(num1,num2){
return num1+num2;
}
一般由于这个方式创建函数比较繁琐 ,平时使用较少。
javascript预编译过程中发生变量声明提升和函数整体提升,函数在执行前会把定义部分提前,我们可以在函数定义前就调用函数,这不会报错。
f();
function f(){
console.log("hello world");
}
函数定义重复时后面定义的函数会覆盖前面定义的同名函数。变量的重复声明不会影响变量的使用,而函数的重复定义会改变函数,函数最最终为后定义的函数。
eg:
console.log(add(2,1));//输出结果为1
function add(num1,num2){
return num1+num2;
}
function add(num1,num2){
return num1-num2;
}
两个函数同名,后一个函数成为最终add表示的函数,实质上进行的是2-1=1的运算。
js中函数可以嵌套定义。一个函数内部可以定义其他函数并加以调用。嵌套定义时函数中声明的变量作用域相互嵌套,使得javascript功能更加强大。常见实例有window.οnlοad=function(){}。这句代码可以使js代码在DOM树建立完成后再执行,避免js代码执行过早,html元素还未出现就执行js代码,导致js获取元素失败。
函数被调用并执行后结束,其中的变量等内容会因无法访问被垃圾回收机制处理。如果函数的作用只是输出固定内容等,这样的函数就不需要返回值。如果函数对数据进行处理后还需要对处理后的数据进行进一步处理,这时就需要把运算结果保留到函数外部并交给其他代码处理。这时我们需要使用函数返回值。
使函数返回某个值需要return关键词后跟需要返回的内容。返回值可以是基本数据类型,字符串,数字,布尔类型,null或undefined,也可以是对象。这个对象包括数组,正则表达式,函数,新建对象等内容。
在return语句执行后,函数的执行就正式结束,不管后面还有没有其他语句。return后不写时返回undefined
function add(num1,num2){
return num1+num2;
}
console.log(add(1,2));//输出3
这里使用返回值得到两者的和。
函数在定义后需要被调用才会执行函数体内部的代码。函数调用需要函数名和小括号()
一般组成为函数名()。函数如果有参数的话需要在小括号内部填写参数,参数间以逗号分隔,填写的参数就是函数的实参(实际参数)。
针对对象的方法,对象的方法也是一个函数,执行该函数需要调用该方法,一般方式为objname.function_name()。与直接调用函数类似。
在执行过程中调用它自身的函数我们称为递归函数,递归调用使得函数体更加精简。我们求阶乘的函数,求斐波那契数列的函数,都可以使用递归函数完成。
eg:斐波那契数列
function fbnq(n){
if(n==1||n==2){
return 1;
}else if(n>=2){
return fbnq(n-1)+fbnq(n-2);
}
}
console.log(fbnq(5));
阶乘函数
function factorial(n){
if(n==1){
return 1;
}
else{
return n*factorial(n-1);
}
}
console.log(factorial(10));
值得注意的是,递归函数需要达到某个程度后停止运行。例如上面的n=1,n=2时, 返回值不再调用函数,而是直接返回某个值。如果函数没有指定到达某种情况停下,递归函数就会不断递归调用,不会停止。
function fun1(){
console.log("这是函数1,直接调用。");
}
var obj={
name:"函数2",
say_name:function(){
console.log("这是调用对象的方法"+this.name);
}
}
var arr1=[function(){
console.log("这是数组中的第一个函数");
},
function(){
console.log("这是数组中的第二个函数");
}];
//函数调用部分
fun1();
obj.say_name();
obj["say_name"]();//这里是另一种访问对象属性或方法的方式
arr1[0]();
arr1[1]();
this不是一个函数,对象,也不是一个变量,而是一个关键词。这个关键词不可赋值,指向调用该方法的对象。
var obj={
name:"函数1",
say_this:function(){
console.log(this);
},
father:{
name:"对象名",
son:function(){
console.log(this);
}
}
}
obj["say_this"]();//输出对象obj
obj.father.son();//输出对象father
函数嵌套层数过多时,为了下一层次的函数可以使用上一层次的调用对象,我们可以使用变量保存this。
构造函数在调用前需要加上关键词new,我们可以看到js内置的构造函数有Object,Number,Function,Array,RegExp,Document等。构造函数执行时会新建一个对象,这个对象继承构造函数的prototype属性,构造函数中this关键词指向这个新建的对象,在构造函数中我们使用this为新建对象赋值。
eg:
function Person(){
this.name="新建对象";
}
var per1=new Person();
console.log(per1);//这里输出的就是新建的对象
//为了使构造函数的功能更加强大我们可以引入参数来扩大函数功能。
function Person2(name,age){
this.name=name;
this.age=age;
return this;
}
per1=new Person2("新建对象",0);
console.log(per1);
javascript中的函数实质上也是对象,可以作为值赋给变量,也可以作为对象的属性。 作为对象的属性时函数就成了对象的方法,作为值赋给某个变量时,我们就可以通过这个变量来调用这个函数。通过赋值给变量再调用函数对于匿名函数来说,可以增加调用函数的方式。
匿名函数:顾名思义,就是没有函数名的函数。这类函数定义时没有声明函数名,再次调用时会有一点不方便,一般可以赋值给变量来调用。
匿名函数优点
当函数只需要执行一次时”不方便调用“就不再是个问题,不需要函数名反而能够节省命名空间。
定义方式1
function(num1,num2)
{
return num1+num2;
};
一般为了便于调用会把这个函数赋值给一个变量
var add=function(num1,num2)
{
return num1+num2;
};
定义方式2:
(function(num1,num2)
{
console.log(num1+num2);
return num1+num2;
})(1,2);
这里匿名函数定义后立即执行,并且之后将不能再被调用。
立即执行函数(Immediately-Invoked Function Expression)在定义后立即执行。
(function f(num){console.log(num);})(1);
(function s(str1){console.log(str1)}("hello world"));
以上定义了两个立即执行函数,这两个立即执行函数是常见的立即执行函数格式。
注意使用()把函数定义部分(函数参数可有可无)括起来。
javascript关于函数function有如下机制:function在一行代码开头时,被认为是语句,否则被认为是表达式。
function f(){//function作为语句
/*code*/
}//
var s=function ss(){/*code*/}//function作为表达式
function作为表达式时,可以直接和()组合调用 。eg:
var f=function(num){alert(num)}(1);
实际上,只要不让function作为一行代码的开头,就可以定义函数再直接调用。
true &&function f(num){console.log(num)}(1);
!function f(num){console.log(num)}(2);
+function f(num){console.log(num)}(3);
-function f(num){console.log(num)}(4);
~function f(num){console.log(num)}(5);
以上内容参考这里 。
函数也是对象的一种,具有多个属性和方法。
name | 返回函数的函数名 |
length | 函数形式参数个数 |
toString() | 返回函数的源码 |
var ff=function f(){
console.log("hello world");
}
console.log(ff.name);//输出为f
var ss=function(){
console.log("???");
}
console.log(ss.name)//匿名函数直接输出变量名
function add(x,y){
return x+y;
}
console.log(add.length);//输出为2
function nums(num1,num2,num3){
return num1+num2+num3;
}
console.log(nums.length);//输出为3
console.log(nums.toString())//输出函数的源码
作用域(scope):是变量作用的范围。ES5中javascript包含全局作用域和函数作用域,ES6中还有块级作用域。
全局作用域:直接在script标签中声明的变量就在全局作用域中。这里要求变量不能在某个函数内部声明。也可以是在函数内部直接赋值,没有使用var关键词声明的变量(暗示全局变量)。全局作用域的变量都是window的属性。var a=10等价于window.a=10。
函数作用域:在函数内部没有使用var关键词声明的变量,作用域就是全局作用域,如果使用var关键词声明,作用域就是函数作用域。
块级作用域:使用let关键词声明变量,声明的变量只能在当前大括号{}中生效,变量声明不再提升,且不可重复声明。这个大括号包括函数,if(){},for(){}。
if(true){
// console.log(age);由于声明不再提升,这里输出会报错。
var name=1;//var声明的变量在全局作用域中
let age=2;//在{}中且使用let声明,在块级作用域中
// let age=3;块级作用域中重复声明一个变量会报错
}
console.log(name);//正常输出1
console.log(age);
//输出age时报错,age这个变量使用let关键词声明,作用域是块级作用域,只在{}内部生效。
//总结如下1. let不能重复声明
//2. let声明的变量不会在预编译阶段声明提升
//3. let声明的变量只会在{}内生效
tips:变量声明的几个关键词
1. var 在函数中使用就是声明函数作用域中的变量,全局中使用是全局作用域中的变量。
2. let 在{}中使用声明块级作用域中的变量,没有声明提升,不可重复声明,不能跨作用域访问。
3.const 声明变量时必须赋值,在后续代码中不可修改变量的值,只在当前{}块中生效,不能跨块访问。
4.同一个变量不能使用多个关键词声明。
javascript具有很多预定义函数。
eval() | 对参数中的js代码求值 |
isFinite() | 有限数字返回真,无限或NaN返回假 |
parseInt() | 对参数字符串求整数,只是返回前几位数字组成的整数 |
parseFloat() | 对参数字符串求浮点数,类似于parseInt() |
笔者也在学习javaScript中,本文为学习内容的整理笔记。如有错误,欢迎留言指正。