Javascript二(函数详解)
一.函数
Javascript是一门基于对象的脚本语言,代码复用的单位是函数,但它的函数比结构化程序设计语言的函数功能更丰富。JavaScript语言中的函数是“一等公民”,它可以独立存在;而且JavaScript的函数完全可以作为一个类来使用(而且它还是该类唯一的构造器);与此同时,函数本身也是一个对象,函数本身是function
实例。
函数的最大作用是提供代码复用,将需要重复使用的代码块定义成函数,提供更好的代码复用。函数可以有返回值,可以没有返回值
1.定义函数的三种方式
a)定义命名函数,
语法格式如下:
function functionName(param1,param1,...){
staments;
}
b)定义匿名函数
语法格式如下:
function(parameter list){
staments
};
与命名函数的区别是没有函数名,函数后面有个分号。
当通过这种语法格式定义了函数之后,实际上就定义了一个函数对象(即function实例),接下来可以将这个对象赋给另外一个变量。例如下面代码:
<script type='text/javascript'> var f = function(name) { document.writeln('匿名函数<br/>'); document.writeln('你好'+name); } f('yukey'); </script>
使用匿名函数提供更好的可读性。
c)使用function类匿名函数
JavaScript提供了一个function类,该类也可以用于定义函数,Function类的构造器的参数个数可以不受限制,function可以接受一系列的字符串参数,其中
最后一个字符串参数是函数的执行体,执行体的各语句以分号(;)隔开,而前面的各字符串参数则是函数的参数,看下面定义函数的方式:
<script type='text/javascript'> var f = new Function('name',"document.writeln('Function定义的函数<br/>');" +"document.writeln('你好'+name);"); f('yukey'); </script>
2.局部函数
局部函数在函数里定义,看下面代码
<script type="text/javascript"> function outer(){ function inner1(){ document.write("局部函数11111<br/>"); } function inner2(){ document.write("局部函数22222<br/>"); } inner1(); inner2(); document.write("结束测试局部函数<br/>"); } outer(); document.write("调用outer之后..."); </script>
在外部函数里调用局部函数并不能让局部函数获得执行的机会。只有当外部函数被调用时,外部函数里调用的局部函数才获得执行的机会。
3.数,方法,对象,变量和类 函数是JavaScript的“一等公民”,函数是JavaScript变成里非常重要的一个概念,当使用JavaScript定义了一个函数之后,实际上可以得到如下四项。
函数:就像Java的方法一样,这个函数可以被调。
对象:定义一个函数时,系统也会创建一个对象,该对象时Function类的实例
方法:定义一个函数时,该函数通常会附加给某个对象,作为该对象的方法
变量:在定义一个函数的同时,也会得到一个变量
类:在定义函数的同时,也得到一个与函数同名的类
定义函数之后,有如下两种方式调用函数
直接调用函数:直接调用函数总是返回该函数体内最后一条return语句的返回值;如果该函数体内不包含return语句,则直接调用函数没有返回值。
使用new关键字直接调用函数:通过这种方式调用总有返回值,返回值就是一个Javascript对象。
<script type="text/javascript"> var test = function(name) { return "你好,"+name; } var rval = test('Sherman'); var obj = new test("Sherman"); alert( rval+"\n"+obj); </script>
可以看出,第一种方式直接调用函数,返回的是return语句返回值,第二种使用new关键字调用给函数,也就是将函数当成类来使用,得到的是一个对象。
下面程序定义了一个person函数,也就定义了一个person类,该person函数也会作为Person类唯一的一个构造器,定义了person函数时希望为该函数定义
了一个方法。
<script type="text/javascript"> function Person(name ,age){ this.name = name; this.age = age; this.info=function(){ document.writeln("我的名字是"+this.name+'<br/>'); document.writeln("我的年纪是"+this.age+'<br/>'); } } var p = new Person('Sherman',24); p.info(); </script>
被this关键字修饰的的变量不再是局部变量,它是该函数的实例属性。
JavaScript定义的函数可以“附加”到某个对象上,作为该对象的方法。实际上如果没有明确指定将函数“附加”到哪个对象上,该函数默认“附加”到window
对象上,作为window对象的方法。
例如如下代码:
<script type="text/javascript"> function hello(name) { document.write(name+",您好<br/>") } window.hello("孙大圣"); p = { //定义一个函数,该函数属于p对象 walk:function(){ for(let i = 0 ; i < 2 ; i++){ document.write("慢慢地走...<br/>"); } } }; p.walk(); </script>
4.函数的实例属性和类属性
由于JavaScript函数不仅仅是一个函数,而且是一个类,该函数还是此类唯一的构造器,只要在调用函数时使用new关键字,就可返回一个object,这个object
不是函数的返回值,而是函数本身产生的对象。因此JavaScript中定义的变脸不仅有局部变量,还有实例属性和类属性两种。根据函数中声明变量的方式,
中的变量有三种
a)局部变量:在函数中以var声明的变量
b)实例属性:在函数中以this前缀修饰的变量
c)类属性:在函数中以函数名前缀修饰的变量
局量只能在函数里访问的变量。实例属性和类属性是面向对象的概念:实例属性是属于单个对象的,因此必须通过对象来访问,类属性是属于整个类本身
(也就是函数)的,因此必须通过类来访问。
同一个类只占用一块内存,因此每个类属性只占用一块内存;同一个类每创建一个对象,系统将为该对象的实例属性分配一块内存。
<script type="text/javascript"> function Person(national,age){ this.age = age; Person.national = national; var bb = 0; } var p1 = new Person('中国',29); with(document){ writeln("创建第一个Person对象"); writeln("p1的age属性为:"+p1.age+"<br/>"); writeln("p1的national属性为:"+p1.national+"<br/>"); writeln("通过Person访问静态national属性为:"+Person.national+"<br/>"); writeln("p1的bb属性为"+p1.bb+"<br/><hr/>"); } var p2 = new Person('美国',32); with(document){ writeln("创建两个Person对象中后<br/>"); writeln("p1的age属性为:"+p1.age+"<br/>"); writeln("p1的national属性为:"+p1.national+"<br/>"); writeln("p2的age属性为:"+p2.age+"<br/>"); writeln("p2的national属性为:"+p2.national+"<br/>"); writeln("通过Person访问静态national属性为:"+Person.national+"<br/>"); } </script>
浏览器输出:
创建第一个Person对象 p1的age属性为:29
p1的national属性为:undefined
通过Person访问静态national属性为:中国
p1的bb属性为undefined
创建两个Person对象中后
p1的age属性为:29
p1的national属性为:undefined
p2的age属性为:32
p2的national属性为:undefined
通过Person访问静态national属性为:美国
值得指出的是,JavaSript和java不一样,它是一种动态语言,它允许随时为对象增加属性和方法,当直接为对象的某个属性赋值时,即可视为给对象增加属性
5.调用函数的3种方式
5.1 直接调用函数
如下代码:
//调用window对象的alert方法 window.alert(); //调用p对象的walk方法 p.walk();
当程序使用window对象调用方法时,window调用者可以省略
5.2 以call方式调用函数
直接调用函数的方式简单易用,但这种调用方式不够灵活,有时候调用函数时需要动态的传入一个函数引用,此时为了动态地调用函数,就需要call方法。call调用函数
语法格式为
函数引用.call(调用者,参数1,参数2,...)
下面通过call方法调用each函数:
<script type="text/javascript"> var each = function(array,fn){ for(var index in array){ fn.call(null,index,array[index]); } } each([4,20,3],function(index,ele){ document.writeln("第"+index+"个元素是:"+ele+"<br/>"); }); </script>
浏览器输出:
第0个元素是:4
第1个元素是:20
第2个元素是:3
5.3 以apply()方法调用函数
apply()方法和call()方法比较类似,都可以动态的调用函数,他们的区别是:
a)通过call()方法调用函数时,必须在括号中列出每个参数
b)通过apply()动态地调用函数时,需要以数组形式一次性传入所有调用函数
以下代码示范了call()和apply()的关系
<script type="text/javascript"> var myfun = function(a,b){ alert('a的值是'+a+'\nb的值是'+b); } //以call()方式动态的调用函数 myfun.call(window,5,20); //以apply()方式动态的调用函数 myfun.apply(window,[3,12]); var example = function(num1,num2){ //直接用arguments代表调用example函数时传入的所有函数 myfun.apply(this,arguments); } example(20,40); </script>
由此可见,apply()和call()对应关系如下:
函数引用.call(调用者,参数1,参数2,...); = 函数引用.apply(调用者,[参数1,参数2,...]);
6.函数独立性
虽然定义函数时可以将函数定义成某个类的方法,或定义成某个对象的方法。但JavaScript的函数是“一等公民”,他永远是独立的,函数永远不会从属于其他类,对象。
下面代码示范了函数的独立性:
<script type="text/javascript"> function Person(name){ this.name = name; this.info = function (){ alert("我的name是:"+this.name); } } var p = new Person("Sherman"); //调用p对象的info方法 p.info(); var name = "测试名称"; //以window对象作为调用者来调用p对象的info方法 p.info.call(window); </script>
当使用匿名内嵌函数定义某个类的方法是时,该内嵌函数一样是独立存在的,该函数也不是作为该类实例的附庸存在,这些内嵌函数也可以被分离出来独立使用,成为另一个对象的函数。如下代码再次证明函数的独立性:
<script type="text/javascript"> function Dog(name,age,bark) { this.name = name; this.age = age; this.bark = bark; //使用内嵌函数为Dog实例定义方法 this.info = function(){ return this.name+"的年龄为:"+this.age+",它的叫声为:"+this.bark; } } var dog = new Dog("旺财",3,"汪汪,汪汪..."); function Cat(name,age){ this.name = name; this.age = age; } //将dog实例的info方法分离出来,在通过call方法调用info方法 //此时cat为调用者 var cat = new Cat("Kitty",2) alert(dog.info.call(cat)); </script>
7.函数提升
JavaScript允许先调用函数,然后再在后面定义函数,这就是典型的函数提升:JavaScript会将全局函数提升到根元素<script.../>元素的顶部定义
例如如下代码:
<script type="text/javascript"> console.log(add(2,5)); function add(a,b){ console.log("执行add函数") return a+b; } </script>
和下面代码效果是一样的:
<script type="text/javascript"> function add(a,b){ console.log("执行add函数") return a+b; } console.log(add(2,5)); </script>
效果如图:
如果使用程序先定义匿名函数,然后将匿名函数赋值给变量,在这种方式下依然会发生函数提升,但此时只提升被赋值的变量,函数定义本省不提升。例如
<script type="text/javascript"> console.log(add(2,5)); var add = function(){ console.log("执行add函数"); return a+b; } </script>
效果如图:
局部函数会被提升到所在函数的顶部,如
<script type="text/javascript"> function test() { function add(a, b) { console.log("执行add函数") return a + b; } console.log(add(2, 5)); } test(); </script>
JavaScript编程时应尽量避免变量名和函数名同名。否则会发生覆盖的情形:分两种情况
a)定义变量时只用var定义变量,不分配初始值,此时函数的优先值更高,函数会覆盖变量。
b)定义变量时为变量值指定了初始值,此时变量的优先值更高,变量会覆盖函数
测试代码如下:
<script type="text/javascript"> function a(){} var a; console.log(a); var b; function b(){} console.log(b); var c = 1; function c(){}; console.log(c); function d(){} var d = 1; console.log(d); </script>
效果如下:
8.箭头函数
箭头函数相当于其他语言的Lambda表达式或闭包语法,箭头函数是普通函数的简化写法。语法格式如下:
(param1,param2,param3,...) => {staments}
相当于定义了如下函数:
function(param1,param2,param3,...){}
如果箭头函数的执行体只有一条return语句,则允许省略函数执行体的花括号和return关键字。
如果箭头函数的形参只有一个参数,则允许省略形参列表的圆括号。
如果箭头函数没有形参,则圆括号不可以省略。
(param1,param2,param3,...) => expression //等同于(param1,param2,param3,...) =>{return expression} singleParam => {staments} //等同于(singleParam) => {staments}
下面代码示范了箭头函数代替传统函数:
<script type="text/javascript"> var arr = ["yuekey","fkit","leegang","sczit"]; 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(newArr3); 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)。例如,如下代码示范了this关键字的功能。
<script type="text/javascript"> function Person(){ this.age = 0;//Person()作为构造器使用时,this代表构造器创建的对象 setInterval(function growUp(){ console.log (this=== window);//对于普通函数来说,this代表全局对象window,总是返回true this.age++; },1000); } var p = new Person(); setInterval(function(){ console.log(p.age);//此处访问p对象的age,总是0 },1000); </script>
箭头函数中的this总是代表包含箭头函数的上下文,例如:
<script type="text/javascript"> function Person(){ this.age = 0; setInterval(() => { console.log(this === window); this.age++;//this总是代表包含箭头函数的上下文 },1000); } var p = new Person(); setInterval(() => console.log(p.age),1000);//此处访问的是p对象的age,总是不断加1 </script>
如果在全局范围内定义箭头函数,那么箭头函数的上下文就是window本身,this代表全局对象window对象。
<script type="text/javascript"> var f = () => {return this;}; console.log(f() === window);//输出true </script>
箭头函数并不绑定arguments,因此不能在箭头函数中通过arguments来访问调用箭头函数的参数,箭头函数的arguments总是引用当前上下文的arguments。例如
<script type="text/javascript"> var arguments = "Sherman"; var arr = () => arguments; console.log(arr()); function foo(){ var f = (i) => 'Hello,'+arguments[1]; return f(2); } console.log(foo("Sherman","Leegang"));//箭头函数中的arguments引用当前上下文的arguments,此时代表调用foo函数的参数 </script>
9.函数的参数处理
大部分时候,函数都需要接受参数传递。与Java完全类似,JavaScript的参数传递也全部采用值传递方式。
9.1基本类型和复合类型的参数传递
对于基本类型参数,JavaScript采用值传递方式,当通过实参调用函数时,传入函数里的并不是实参本身,而是实参的副本,因此在函数中修改参数值并不会对实参
有任何影响:
<script type="text/javascript"> function change(arg1) { arg1 = 10; document.writeln("函数执行中arg1的值为:" + arg1 + "<br/>"); } var x = 5; document.writeln("函数调用前x的值为"+x+"<br/>"); change(x); document.writeln("函数调用之后的x值为"+x+"<br/>"); </script>
对于复合类型的参数,实际上采用的依然是值传递方式,只是很容易混淆。看如下程序
<script type="text/javascript"> function changeAge(person) { person.age = 10; document.writeln("函数执行中age的值为:" + person.age + "<br/>"); person = null; } var person = {age:5}; document.writeln("函数调用之前age的值为"+person.age+"<br/>"); changeAge(person); document.writeln("函数调用之后的age值为"+person.age+"<br/>"); document.writeln("person对象为"+person); </script>
9.2空参数
在JavaScript中,在函数声明时包含了参数,但调用时没有传入实参,这种情况是允许的,JavaScript会自动将参数值设置为undefined值,对于JavaScript来说,函数名就是函数的唯一标识。
如果先后定义两个同名,形参列表不同的函数,这不是函数重载,这种情况后面定义的函数会覆盖前面的函数。
9.3参数类型
JavaScript是弱类型语言,参数列表无需声明参数类型。
“鸭子类型”的理论认为,弱类型语言的函数需要接收参数时,则应先判断参数类型,判断参数是否包含了需要访问的属性、方法。当条件都满足时 ,程序才会真正
执行。看如下代码
<script type="text/javascript"> function changeAge(person) { if (typeof person == 'object' && typeof person.age == 'number') { document.writeln("函数调用之前age的值为" + person.age + "<br/>"); person.age = 10; document.writeln("函数执行中age的值为:" + person.age + "<br/>"); } else { document.writeln("参数类型不符合" + typeof person + "<br/>") } } changeAge(); changeAge("Sherman"); changeAge(true); p = {abc:34};//json格式创建第一个对象 changeAge(p); person = {age:25};//json格式创建第二个对象 changeAge(person); </script>