文章目录
5.1函数语法
函数语法
重复代码让程序难以维护,哪怕是一个很小的改动
函数主要用于减少重复代码
创建函数(定义/声明函数)
function 函数名()//函数名要语义化
{
//函数体:函数的内容
}
函数体的代码不会直接运行,必须要手动调用函数,才能运行其中的代码.
调用函数
运行函数体
函数名();
函数提升
通过字面量声明的函数,会提升到脚本块(一个脚本块就是一个script标签)的顶部(就像变量声明提升一样)
通过字面量声明的函数,会成为全局对象的属性.(成为window对象的属性)
其他特点
通过typeof 函数名,得到的类型是"function"
函数内部声明的变量:
- 如果不使用var声明,和全局变量一致,表示给全局对象(window对象)添加属性
- 如果使用var声明, 变量提升到所在函数的顶部,函数外部不可以使用该变量
函数中声明的变量仅能够在函数内部使用,在外部无法访问
参数
参数表示函数运行的未知条件,需要调用者告知的数字;
//参数的有效范围:在函数体中, 参数不需要声明
function 函数名(参数1, 参数2, ...){
}
函数名(参数);
形参是指函数声明时()里的参数—形式参数,实参是指函数调用时()里的参数—实际参数
如果实参没有传递,对应的形参值为undefined
如果实参数量多于形参,多于的实参没有用处,因为形参与实参是一一对应的;
//删除数组末尾指定数量的数据
function deleteArray(arr, number)
{
splice(-number, number);
}
var nums = [1, 2, 3, 4, 5];
deleteArray(nums, 3);//nums传给arr的是地址,数组为引用类型
console.log(nums);
返回值
函数运行后,得到的结果,调用函数时,调用表达式的值就是函数的值
return 会直接结束整个函数的运行
return 后面不跟任何数据, 返回undefined
如果函数中没有书写return, 则该函数默认返回undefined
//对一个数组求和
function sumArray(arr)
{
var sum = 0;
for(var i = 0;i < arr.length;i++)
{
sum += arr[i];
}
return sum;
}
var num1 = [234, 242, 34];
var num2 = {23, 19, 101};
console.log(sumArray(num1) + sumArray(num2));
//判断一个数是不是素数
function isPrime(n)
{
for(var i = 0; i < n-1;i++)
{
if(n % i === 0||n < 2)
return false;
else
return true;
}
}
文档注释
在js中文档注释可以注释变量,也可以注释函数
文档注释的好处:当我们想调用的时候,写上函数名,就可以知道函数的作用:在vs code里面自动显示
/**
* 两个数求和
*@param {*} a 第一个数--对a的描述,下面同理
*@param {*} b 第二个数
*@param {*} s 两数之和
*/
// {}里的*是指后面参数的类型,上述代码不会运行,只起到提示的作用
function sum (a, b)
{
s = a + b;
return s;//求和结果
}
sum
作业:
通用函数编写
新建一个js文件,编写一下函数
- 写一个函数,该函数用于判断某个数是不是奇数
函数名参考: isOdd
- 写一个函数,该函数用于判断某个数是不是素数
函数名参考: isPrime
- 写一个函数,该函数用于对数组求和
函数名参考: sumofArray
- 写一个函数,该函数用于得到数组中的最大值
函数名参考: maxofArray
- 写一个函数,该函数用于得到数组中的最小值
函数名参考:minofArray
函数使用
- 利用上面的某些函数,实现哥德巴赫猜想
任一大于2的偶数都可写成两个质数纸盒,比如: 8 = 3 + 5
让用户输入一个大于2的整数,输出其等于哪两个素数相加
- 让用户输入一个年份,输出该年每个月的天数
作用域与闭包
作用域
作用域表示一个代码区域,也表示一个运行环境
js中,有两种作用域
- 全局作用域
直接在脚本中书写的代码
在全局作用域中声明的变量,会被提升到脚本的顶部,并且会成为全局对象的属性
- 函数作用域
函数中的代码
在函数作用域中声明的变量,会被提升到函数的顶部,并且不会成为全局对象的属性,
因此,函数中声明的变量不会导致全局对象的污染
尽量把功能分装在函数中
函数的声明会污染全局变量,但是,当函数成为一个表达式时,它既不会提升,也不会污染全局对象。
将函数变成表达式的方法之一:将函数用小括号括起来;-----然而这样一来,函数无法通过名称调用;
如果书写一个函数表达式,然后将其立即调用,该函数称之为立即执行函数IIFE(imdiately Inovked Function Expression)
由于大部分情况下,函数表达式的函数名没有实际意义,因此,可以省略函数名;
没有名字的函数,叫做匿名函数,只有函数表达式才能没有名字;
作用域中可以使用的变量
全局作用域只能使用全局作用域中声明的变量(包括函数)
函数作用域不仅能使用自身作用域中声明的变量(包括函数),还能使用外部作用域中声明的变量(包括函数);
有的时候,某个函数比较复杂,在编写的过程,可能需要另外一些函数来辅助它完成一些功能,而这些函数仅仅会被该函数使用,不会在其他位置使用,则可以将这些函数中声明到该函数的内部;
函数内部声明的变量和外部冲突时,使用内部的;
闭包
闭包(closure),是一种现象,内部函数被保存到外部,外部函数可以访问该函数的作用域.
(后面会深入讲解)
函数表达式和this
函数表达式
JS中,函数也是一个数据,语法上,函数可以用于任何需要数据的地方
JS中,函数和其他数据是一样进行操作;—可以将函数放在变量里.
var a = function()
{
console.log("andsjf");
}
a();
//这种写法没有函数提升 ,所以只能在赋值之后使用
函数是一个引用类型,将其保存在变量中,即将其地址保存在变量中,这块地址指向存放函数内容的那块内存中
和普通函数声明的区别:赋值给变量的函数没有函数提升,并且不会附着在window对象上面;
函数可以作为函数的参数传递
function test(callback)
{
console.log("test运行");
callback();
}
var func = function()
{
console.log("abc");
}
test(func);//将func这个函数作为实参传递,把这种函数作为参数传递叫做回调函数
//也可以这样写
// test(function()
// {
// console.log("abc");
// });
this关键字(易考面试题)
this无法赋值
-
在全局作用域中,this关键字固定指向全局对象
-
在函数作用域中,取决于函数是如何被调用的
- 函数直接调用,this指向全局对象
- 通过一个对象调用,格式为
对象.属性()
或对象["属性"]()
,this指向对象
5.6构造函数
构造函数
用于创建对象的函数
用函数创建对象,可以减少繁琐的对象创建流程;
- 函数返回一个对象
function createUser(){
return {
name: "xxx",
age :12,
//...
function sayHello()
{
console.log(`我叫${this.xxx},今年${this.age}岁`);
}
};
}
var u1 = createUser1;
u1();
对象中的属性如果是一个函数,也可以将该属性称之为方法;
- 使用构造函数:专门用于创造对象的函数
new 函数名(参数);
如果使用上面的格式创建对象,则该函数叫做构造函数
-
函数名使用大驼峰命名法
-
构造函数内部,会自动创建一个新的对象,this指向新创建的对象,并且自动返回新对象
-
构造函数中如果出现返回值,如果返回的是原始类型,则直接忽略;如果返回的是引用类型,则使用返回的结果;
-
所有的对象,最终都是通过构造函数创建的;
new.target
该表达式在函数中使用返回的是当前的构造函数,但是,如果该函数不是通过new调用的,则返回undefined
通过判断某个函数是否通过new调用的;
作业
英雄打怪兽的小游戏
英雄和怪兽都具有生命值、攻击力、防御力、暴击几率(暴击时伤害翻倍)
攻击伤害:攻击力 - 防御力
攻击最少为1
创建一个英雄和一个怪兽,让他们互相攻击,直到一方死亡,输出整个攻击过程
5.7 函数的本质
函数的本质
函数的本质就是对象
某些教程中,将构造函数称之为构造器
所有的对象都是通过关键字new出来的,new 构造函数()
函数的本质就是对象,而所有的对象都是通过关键字new出来的,因此,函数也都是通过new Function创建的
在浏览器引擎启动的时候,就已经帮我们写好放到了内存,可以直接使用,所以Function不是new出来的
Funcion ---> 产生函数对象(构造函数) ---> 产生普通对象
//function sum(a, b)
//{
// return a + b;
//}
var sum = new Function("a", "b", "return a + b");
//Function是所有函数的构造函数,系统帮我们已经写好了
//通过new Function构造出sum函数
console.log(typeof sum);//function
console.log(sum(3, 5));//8
由于函数本身就是对象,因此函数中可以拥有各种属性;
包装类
js为了增强原始类型的功能,为boolean, string,number分别创建了一个构造函数
- Boolean
- String
- Number
如果语法上将原始类型当作对象使用时(一般是在使用属性时),js会自动在该位置利用对应的构造函数,创建对象来访问原始类型的属性;
类:在js中,可以认为类就是构造函数
成员属性(方法)/实例属性(方法): 表示该属性是通过构造函数创建的对象调用的.
静态属性(方法)/类属性(方法): 表示该属性是通过构造函数本身调用的.
5.8递归
递归
函数直接或间接调用自身
避免无限递归,无限递归会导致执行栈溢出。(要给出口)
对比死循环
- 死循环不会报错,也不会导致栈溢出
- 无限递归会导致栈溢出(会报错)
//求斐波那契数列第n位的值
function f(n)
{
if(n == 1||n == 2)
{
return 1;
}
return f(n - 1) + f(n - 2);
}
console.log(f(7));//13
//求n的阶乘
function f(n)
{
if(n = 1)
{
return 1;
}
return n * f(n - 1);
}
console.log(f(5));//120
function hannuo(no1, no2, no3, n)
{
if(n === 1)
{
console.log(`${no1}->${no3}`);
} else
{
hannuo(no1, no3, no2, n - 1);
console.log(`${no1}->${no3}`);
hannuo(no2, no1, no3, n - 1);
}
}
hannuo('A', 'B', 'C', 2);
执行栈(call stack)
无论任何代码的执行,都必须有一个执行环境,执行环境为代码的执行提供支持
执行环境和执行栈都是一块内存
执行环境是放到执行栈中的;
每个函数的调用(哪怕是函数调用自身),都需要创建一个函数的执行环境,函数调用结束,执行环境销毁。
执行栈有相对固定的大小,如果执行环境太多,执行栈无法容纳,会报错
尾递归
如果一个函数最后一条语句是调用函数,并且调用函数不是表达式的一部分(也就是说最后一条语句只有纯粹的调用函数),则该语句称为尾调用,如果尾调用是调用自身函数,则称为尾递归。
某些语言或执行环境会对尾调用进行优化,它们会立即销毁当前函数,避免执行栈空间被占用。
在浏览器执行环境中,尾调用没有优化。但在nodejs环境中有优化。