函数
JavaScript是一种基于对象的脚本语言,JavaScript 代码复用的单位是函数,但它的函数比结构化程序设计语言的函数功能更丰富。函数在JavaScript中非常强大,它可以独立存在,而且JavaScript 的函数可以作为一个类使用,是定义类的唯一途径(即函数与类定义方法基本相同,只是起名时习惯把函数定义成全小写,把类定义成首字母大写),与此同时,函数本身也是一个对象, 函数本身是Function实例。
函数的定义
JavaScript是弱类型语言,在定义是不需要声明返回值类型,不用声明参数的类型。函数的最大作用是提供代码的复用,将需要重复使用的代码块定义成函数,便可以最大程度减轻代码量。
定义函数有三种方法
- 定义命名函数
function funct ionName (parameter-list)
{
...
}
//例
hello('china') ;
//定义函数hello,该函数需要一个参数
function hello (name){
alert(name + ", 你好");
}
#注 函数可以有返回值,也可以没有返回值,函数的返回值使用return语句返回,在函数的运行过程中,只要遇到return,函数就结束并且返回。
- 定义匿名函数
<script type="text/javascript">
var f = function (name){
document.writeln('匿名函数<br/>');
document.writeln('你好' + name) ;
};
f('china') ;
</script>
这种定义方法在JavaScript中非常常见,不用给函数具体的明,而是将函数定义赋值给一个变量,这样调用该变量即可调用该函数
- 使用Function类匿名函数
JavaScript提供了一个Function 类,该类也可以用于定义函数,Function 类的构造器的参数个数可以不受限制,Function 可以接受一系列的字符串参数, 其中最后-一个字符串参数是函数的执行体,执行体的各语句以分号( ; ) 隔开,而前面的各字符串参数则是函数的参数。
//定义匿名函数,并将函数赋给变量f
var f = new Function ('name',"document.writeln('Function定义的函数<br>');"+"document.writeln('你好'+ name) ;") ;
//通过变量调用匿名函数
f('china');
上面代码使用new Function()语法定义了一个匿名函数,并将该匿名函数赋给f变量,从而允许通过f来访问匿名函数。可以注意到在用这种方式时,Function()构造器的最后一个参数包含了函数的作用,当这个函数作用变得复杂时,这个参数将变得十分臃肿,可读性也会变差,尽量不要使用
函数的递归调用
函数不仅仅可以被变量调用,还可以被自己本身调用,在一些特殊情况下,这种我调我自己的方式可以减少很多运算,如计算阶乘
<script type="text/javascript">
//定义求阶乘的函数
var factorial = function(n){
//只有n的类型是数值,才执行函数
if (typeof(n) == "number"){
//当n等于1时,直接返回1
if(n==1){
return 1;
}
//当n不等于1时,通过递归返回值
else{
return n*factorial(n - 1) ;
}
//当参数不是数值时,直接返回
}
else{
alert("参数类型不对! ");
}
//调用阶乘函数
}
alert(factorial(5)) ;
</script>
运用递归函数时,一定是大问题比较复杂,而小问题类型和大问题相同的情况,并且一定要向一指的方向递归,在上面问题中,5!,10!,n!可能比较难求解,但是1!的值却是确定的,所以要将大问题化小,化到问题为求1!时,问题才能被解决
局部函数
局部函数的概念与局部变量类似,局部变量在函数里用var关键字定义,而局部函数也在函数里定义。函数中定义的函数就叫局部函数
<script type="text/javascript">
//定义全局函数
function a (){
//定义第一个局部函数
function b(){
document.write("局部函数1 <br>");
}
//定义第二个局部函数
function c(){
document.write("局部函数2 <br>");
}
//在函数中调用第一个局部函数
b();
//在函数中调用第二个局部函数
c();
}
//调用全局函数
a();
</script>
注意
定义完局部函数,记得在父级元素里调用一下局部函数。
局部函数只在他的父级函数里有效,出了父级元素就会失效,而且在外部函数里调用局部函数并不能让局部函数获得执行的机会,只有当外部函数被调用时,外部函数里调用的局部函数才会被执行。
函数、方法、对象、变量和类
函数在JavaScript中非常重要,功能也非常强大丰富,在JavaScript定义一个函数后,实际上可以得到五个东西
- 函数:定义的就是函数,这个函数可以调用。
- 对象:定义一个函数时,系统也会创建一一个对象,该对象是Function类的实例。
- 方法:定义一个函数时,该函数通常都会附加给某个对象,作为该对象的方法。
- 变量:在定义函数的同时,也会得到一个变量。
- 类,在定义函数的同时,也得到了一个与函数同名的类。
函数的实例属性和类属性
由于JavaScript中函数不仅仅是一个函数,而且是一个类, 该函数还是此类唯一的构造器,所以只要在调用函数时使用new关键字,就可返回一个Object,这个Object不是函数的返回值,而是函数本身产生的对象。因此在JavaScript中定义的变量不仅有局部变量,还有实例属性和类属性两种。根据函数中声明变量的方式,函数中的变量有3种。
- 局部变量:在函数中以var声明的变量。
- 实例属性:在函数中以this前缀修饰的变量。
- 类属性:在函数中以函数名前缀修饰的变量。
局部变量是只能在函数里访问的变量。实例属性和类属性则是面向对象中的概念:实例属性是属于单个对象的,因此必须通过对象来访问;类属性是属于整个类(也就是函数)本身的,因此必须通过类(也就是函数)来访问。同一个类(也就是函数)只占用一块内存, 因此每个类属性将只占用一块内存:同一个类(也就是函数)每创建一个对象, 系统将会为该对象的实例属性分配一块内存。
<script>
function Person(national,age){
this.age = age;
Person.national = national;
var bb = 0;
}
//创建Person的第一个对象p1
var p1 = new Person('中国',29);
document.writeln("创建第一个Person对象<br/>");
//输出第一个对象p1的年龄和国籍
document.writeln("p1的age属性为" + p1.age + "<br/>");
document.writeln("p1的national属性为" + p1.national + "<br/>");
document.writeln("通过Person访问静态national属性为" + Person.national + "<br/>");
//输出bb属性
document.writeln("p1的bb属性为" + p1.bb + "<br /><br />");
//创建Person的第二个对象p2
var p2 = new Person('美国',32);
document.writeln("创建两个Person对象<br/>");
//再次输出p1的年龄和国籍
document.writeln("p1的age属性为" + p1.age + "<br/>");
document.writeln("p1的national属性为" + p1.national + "<br/>");
//输出p2的年龄和国籍
document.writeln("p2的age属性为" + p2.age + "<br/>");
document.writeln("p2的national属性为" + p2.national + "<br/>");
//通过类名访问类属性名
document.writeln("通过Person访问静态national属性为" + Person.national + "<br/>");
</script>
我们定义了一个Person函数(也是一个Person类),其中age被定义成了实例属性,所以Person类下的每个对象,他们的实例属性age都不同,想要访问age就需要通过每一个对象(如p1.age)来访问,而national是类属性,他是Person类所特有的(类似其他语言中的静态变量),所以想要访问类属性必须通过Person类来访问,而Person类的对象既不能定义类对象national,也不能访问,所以会返回undefined,bb是Person的局部变量,所以除了Person函数之后就无法再访问了,所以也会返回undefined
函数的调用
定义一个函数后,JavaScript提供了三种调用函数的方式
-
直接调用函数
直接调用函数是最常见、最普通的方式。这种方式直接以函数附加的对象作为调用者,在函数后括号内传入参数来调用函数。这种方式是前面最常见的调用方式。 -
以call()方法调用函数
直接调用函数的方式简单、易用,但这种调用方式不够灵活。有些时候调用函数时需要动态地传入一个函数引用,此时为了动态地调用函数,就需要使用call方法来调用函数了。
<script>
var each = function(array,a){
for(var index in array){
a.call(null , index , array[index])
}
}
each([4,20,3] , function(index , ele){
document.write( "第" + index + "个元素是:" + ele + "<br />" );});
</script>
例如需要定义一个形如each(array, a)的函数,这个函数可以自动迭代处理array数组元素,而a函数则负责对数组元素进行处理-一此时需要在each函数中调用a函数,但目前a函数并未确定,因此无法采用直接调用的方式来调用a函数,需要通过call(方法来调用函数。
- 以apply()方法调用函数
apply()方法与call()方法功能基本相似,他们都可以动态的调用函数,他们的区别如下
- 通过call()调用函数时,必须在括号中详细地列出每个参数。
- 通过apply(动态地调用函数时,可以以数组形式一次性传入所有调用参数。
这样在参数比较多的时候,就不需要像call()样把每个参数都写在列表里,可以使用arguments代表apply()的参数数组,而在之后用example(a,b,c,…)方法将参数传入
函数的独立性
前面我们知道,函数的功能非常强大,而他的等级也非常高,函数永远是独立的。
这代表如果在函数里定义了一个局部函数,它虽然在父级函数,通过父级函数调用,但是它本质上并不属于父级函数,所以可以通过window对象直接调用,而此时该函数中的实例this代表的将不再是父级函数,而是整个全局
<script>
function Person(name){
this.name = name;
this.info = function(){
alert("这是一个:" + this.name);
}
}
var p = new Person("实例变量");
p.info();
var name = "全局变量";
p.info.call(window);
</script>
第一个弹框
第二个弹框
可以看到,两次调用info,但是却输出了不同的值。
第一次调用info,调用它的对象是p,所以此时info前的this指的还是Person类,它将输出p对象内的name变量,即“局部变量”,
第二次调用info,是通过call调用的,此时调用info的对象是window,程序在全局也定义了一个变量name,它的值是“全局变量”,所以第二次调用info,输出的就是“全局变量”。
总结
当定义某个类的方法时,该类内的局部函数是独立存在的,他不会作为类或是实例的附庸存在,这些内嵌函数可以被分离出来独立使用,这也包括让局部函数成为另一个对象的局部函数。
箭头函数
箭头函数相当于其他语言的Lambda表达式或必报写法,箭头函数是普通函数的简化写法,它的语法如下
{参数1,参数2,参数3,...} => {...函数语句...}
上面式子等同于
function(参数1,参数2,参数3....){
...函数语句...
}
这是通用写法,而如果函数语句只有一条语句而且是return语句,那么就可以省略return;如果参数只有一个,那么就可以省略圆括号,如下
function(a){
return alert(a);
}
//等同于
(a) => {return alert(a);}
//等同于
a => alert(a);
具体例子
<script type = "text/javascript">
var arr = ["a","b","c","d"];
var newArr1 = arr.map(function(ele){
return ele.length;
});
var newArr2 = arr.map((ele) => {return ele.length});
var newArr3 = arr.map(ele => ele.length);
console.log(newArr1);
arr.forEach(function(ele){
console.log(ele);
});
arr.forEach((ele) => {
console.log(ele);
});
arr.forEach(ele => console.log(ele));
</script>
可以正常输出
注意
- 与普通函数不同的是,箭头函数并不拥有自己的this关键字,对于普通函数而言,如果程序通过new 调用函数创建对象,那么该函数中的this 代表所创建的对象;如果直接调用普通函数,那么该函数中的this代表全局对象( window)。例如,如下代码示范了普通函数中的
<script type = "text/javascript">
var age = 0;
function Person(){
this.age = 0;
setInterval(function gropUp(){
console.log(this === window);
this.age++;
},1000);
}
var p = new Person();
setInterval(function(){
console.log(window.age);
},1000);
</script>
第一次的console中的第一次this指向的是包含它的类,第二次console中的this指向的是全局,箭头函数中的this总是代表包含箭头函数的上下文
- 箭头函数并不绑定arguments,因此不能在箭头函数中通过arguments来访问调用箭头函数的参数。 箭头函数中的arguments总是引用当前上下文的arguments。
<script type = "text/javascript">
var arguments = "lc";
var arr = () => arguments;
console.log(arr());
function foo(){
var f = (i) => 'Hello,' + arguments[0];
return f(2);
}
console.log(foo('teeku','fkit'));
</script>
- 箭头函数不允许在形参列表和箭头之间包含换行;否则会提示语法错误。
- 在函数体比较复杂,或者出现其他运算符的时候,最好将函数体用括号包起来,否则容易出现错误
<script type-"text/javascript">
var f=() => ({name: 'yeeku'}) ;
console.log(f()) ;
//或是
var func;
func = func || (() => "yeeku") ;
</script>
函数的参数处理
大部分时候,函数都需要接受参数传递,JavaScript的参数传递也全部都是采用值传递的方式
基本类型和复合类型的参数处理
对于基本类型参数,JavaScript 采用值传递方式,当通过实参调用函数时,传入函数里的并不是实参本身,而是实参的副本,因此在函数中修改参数值并不会对实参有任何影响
- 当使用基本变量x作为参数调用change()时,x并未真正传入change()函数中,传入的仅仅是x的副本,所以在change()中对参数赋值,并不会影响变量x本身实际的值
- 复合类型的参数处理
<script type = "text/javascript">
function changeAge(person){
person.age = 10;
document.write("函数执行中person的age值为:" + person.age + "<br />");
person = null;
}
var person = {age : 5};
document.write("函数调用之前person的age值为:" + person.age + "<br />");
changeAge(person);
document.write("函数调用之后person的age值为:" + person.age + "<br />");
document.write("person对象为:" + person);
</script>
在上面代码中,使用JSON语法创建了一个person对象,并作为一个复合类型的变量传入changeAge()函数
可以看到,changeAge()函数中的最后一行代码,将person对象赋值为了null,可是出函数后的最后一行代码,验证完person依然是个对象,而且age还是等于10,所以这表明person本身还是并未传入changeAge(),只是将他的副本传入了进去,
复合类型的变量本身并未持有对象本身,函数体内的age和函数体外的age是同一个age,只是他们在不同的地方调用age,person=null相当于毁掉了函数体内调用age的能力,但是并未毁掉age本身,相当于有两把钥匙,第二把钥匙在开完锁(将10赋值给age)后就被毁掉,但是第一把钥匙还在,所以在调用完changeAge()把函数内部的person赋值为null后,函数外第一把钥匙所在的person对象依然还在,age也在并且值为10
空参数
JavaScript允许在定义一个有参数的函数(类)后,调用函数时如果没有传入参数,程序依然不会出现问题,只是函数内的参数已经被定义,但是没有值,所以他的值会变成undefined。
这在其他强类型语言中是不被允许的,也因此JavaScript没有其他语言中的函数重载,若模仿其他语言定义两个同名而不同参的函数,只会发生后定义的把前定义的函数覆盖掉的情况。
函数名就是这个函数的唯一标识,也只能通过这个方式调用函数。
参数类型
JavaScript函数声明的参数列表无须类型声明,这是他作为弱类型语言的一个特征
Java中定义一个简单的类
public void changeAge(Person p){
p.setAge(34);
}
在这里,p属于Person实例,而Person实例具有setAge()方法,所以如果没有定义setAge()方法,在传入时没有传参,或是传入的参数不是Person对象,都会出现编译错误
将该代码简单的转换为JavaScript
function changeAge(p){
p.setAge(34);
}
由于JavaScript是弱类型语言,所以在调用时不必管传入的p是什么,程序不会报错,但是这些类型的setAge()方法,程序强制调用该方法,就会导致出错,这是所有弱类型语言的通病,
为了解决这个问题,弱类型语言提出了“鸭子类型(Duck Type)”的概念
鸭子类型的理论认为,如果弱类型语言的参数需要接受参数,则应先判断参数类型,并且判断参数是否包含了需要访问的属性。方法,只有当这些条件满都满足时,程序才开始真正处理调用参数的属性、方法。
<script>
function changeAge(person){
if (typeof person == 'object'&&typeof person.age == 'number'){ //判断person是否为对象,person下的age是否为数值类型
document.write("函数执行前age的值为" + person.age +"<br />")
person.age = 10;
document.write("函数执行中age的值为" + person.age +"<br />")
}else{ //否则将输出提示,参数类型不符合
document.write("参数类型不符合" + typeof person + "<br />");
}
}
//分别采用不同的方式调用函数
changeAge() ;
changeAge ('xxx') ;
changeAge (true) ;
//采用JSON语法创建第一个对象
p = {abc : 34};
changeAge (p) ;
//采用JSON语法创建第二个对象
person = {age : 25};
changeAge (person) ;
</script>
依靠这种语法,就可以简单的完成如同Java一样的参数校验,必须输入符合要求的参数烈性,变量类型后,才执行函数体里的逻辑操作