JavaScript中的函数与类

本文深入探讨JavaScript中的函数和类。介绍了函数的定义、递归调用、局部函数、函数与类的区别、实例属性和类属性、调用方式、独立性以及箭头函数的使用。还讨论了函数参数处理,包括基本类型、复合类型、空参数和参数类型的灵活性。通过实例解析了JavaScript的函数特性,强调了弱类型语言中参数校验的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数

JavaScript是一种基于对象的脚本语言,JavaScript 代码复用的单位是函数,但它的函数比结构化程序设计语言的函数功能更丰富。函数在JavaScript中非常强大,它可以独立存在,而且JavaScript 的函数可以作为一个类使用,是定义类的唯一途径(即函数与类定义方法基本相同,只是起名时习惯把函数定义成全小写,把类定义成首字母大写),与此同时,函数本身也是一个对象, 函数本身是Function实例。

函数的定义

JavaScript是弱类型语言,在定义是不需要声明返回值类型,不用声明参数的类型。函数的最大作用是提供代码的复用,将需要重复使用的代码块定义成函数,便可以最大程度减轻代码量。

定义函数有三种方法

  1. 定义命名函数
function funct ionName (parameter-list)
{
	...
}
//例
hello('china') ;
//定义函数hello,该函数需要一个参数
function hello (name){
	alert(name + ", 你好");
}

#注 函数可以有返回值,也可以没有返回值,函数的返回值使用return语句返回,在函数的运行过程中,只要遇到return,函数就结束并且返回。

  1. 定义匿名函数
<script type="text/javascript">
	var f = function (name){
		document.writeln('匿名函数<br/>');
		document.writeln('你好' + name) ;
	};
	f('china') ;
</script>

在这里插入图片描述
这种定义方法在JavaScript中非常常见,不用给函数具体的明,而是将函数定义赋值给一个变量,这样调用该变量即可调用该函数

  1. 使用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提供了三种调用函数的方式

  1. 直接调用函数
    直接调用函数是最常见、最普通的方式。这种方式直接以函数附加的对象作为调用者,在函数后括号内传入参数来调用函数。这种方式是前面最常见的调用方式。

  2. 以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(方法来调用函数。

  1. 以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一样的参数校验,必须输入符合要求的参数烈性,变量类型后,才执行函数体里的逻辑操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值