一.函数的介绍
函数:就是将一些功能或语句进行封装,在需要的时候,通过调用的形式,执行这些语句。
- 函数也是一个对象。
- 使用typeof( )函数检查一个函数对象时,会返回function
- 函数的使用包含两个步骤:
- 定义函数 —— 封装 独立的功能
- 调用函数 —— 享受 封装 的成果
1.2 定义函数
- 定义函数的过程是对某些功能的封装过程
- 在之后的开发中, 我们会根据自己的需求定义很多自己的函数
1.3 调用函数
- 调用函数是让已存在的函数为我们所用
- 这些函数可以是刚刚自己封装好的某个功能函数
- 当然, 我们也可以去使用JavaScript或者其他三方库定义好的函数.(拿来主义)
1.4 函数的作用
- ==将大量重复的语句写在函数里,以后使用这些语句的时候,可以直接调用函数,避免重复劳动。==
- ==简化编程,让编程模块化。==
1.5 函数的例子
计算1+2+3+4+5+6+7......+100?
var sum=0; function sumN(){ for(var i=1;i<=100;i++){ sum+=i; } return sum; } console.log(sumN(100));//5050
二.函数的定义和调用
2.1 函数的定义方式
2.1.1 使用==函数声明==来调用一个函数。
-
语法:
function 函数名([形参1,形参2,....]){//形参是可选的,备注:语法中的中括号表示“可选” 语句; }
-
特点
function:是一个关键字,中文是“函数”,"功能".
函数名字:命名规定和变量的规定一样。==只能是字母、数字、下划线、$,不能数字开头==
参数:可选。
大括号里面,是这个函数的语句。
方法定义是,形参的类型不用写,返回值类型也不写。
方法是一个对象,如果定义名称相同的方法,会覆盖
在JS中,方法的调用只与方法的名称有关,和参数列表无关
在方法声明中有一个隐藏的内置对象(数组),arguments,封装所有的实际参数
2.2.2 使用==函数表达式==来创建一个函数.
- 之前定义的函数被称之为函数的声明,除了声明之外,还有其他函数形式:
- 函数表达式:==匿名函数表达式和命名函数表达式==
- 函数的name属性:所有的函数都有一个name属性用于获取函数的名称
- *函数声明方式的name属性:函数名
- 命名函数表达式的name属性:函数名
- 匿名函数表达式的name属性:变量名
语法:
var 函数名=function([形参1,形参2,....]){//形参是可选的
语句;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>函数表达式写法</title>
</head>
<body>
</body>
<script>
/*
一.函数表达式写法
1.命名函数表达式
2.匿名函数表达式
二:函数的属性:name
3.1 函数声明方式的name属性:函数名
3.2 命名函数表达式的name属性:函数名
3.3 匿名函数表达式的name属性:变量名
*/
//函数声明的方式
function func() {
console.log("我是函数声明的方式");
}
func();
//1.命名函数表达式
var hasName = function test() {
console.log("我是一个有名的函数表达式");
}
hasName();
//2.匿名函数表达式
var noName = function () {
console.log("我是一个匿名的函数表达式");
}
noName();
//3.函数的属性:name
console.log(func.name); //func
console.log(hasName.name); //test
console.log(noName.name); //noName
</script>
</html>
2.2.3 立即执行函数
(function([形参1,形参2,....]){//形参是可选的
语句;
} )()
- 类比函数表达式:==( 这个括号里面就相当于一个匿名函数)(此括号里就是传递形参)==
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>函数声明和调用</title>
<script>
/*
函数声明和调用
1.函数声明的方式
1.1 使用函数声明来创建一个函数
语法:
function 函数名(形参1,形参2,....){//形参是可选的
语句;
}
1.2 函数表达式
1.3 立即执行函数
*/
function func1(){
console.log("我是一个函数声明方式的函数");
};
var func2=function(){
console.log("我是一个函数表达式形式的函数");
};
(function(){//注意函数结束需要加上分号,否则会导致立即执行函数出错
console.log("我是一个立即执行函数");
})();
func1();
func2();
</script>
</head>
<body>
</body>
</html>
2.2 函数形参和实参
function add(a,b){//a,b就是形式参数
return a+b;
}
//下面的add里面的为实参
console.log(add(10,20));//30
console.log(add("10","20"));//1020
console.log(add("hello","World"));//helloWorld
console.log(add(1,2,3,4,5));//3,只有两个参数,后面多余的参数无效
2.2.1 形参
- 可以在函数的()中来指定一个或多个形参
- 多个形参之间使用逗号隔开,声明形参就相当于在函数的内部声明了对应的变量,但是并不赋值
2.2.2 实参
- 在调用函数时,可以在()中指定实参
- 实参将会赋值给函数中对应的形参
2.2.2.1 实参的类型
- 函数的实参可以是任意的数据类型
- 用函数时解析器不会检查实参的类型,所以需要注意,是否有可能需要对参数进行类型的额检查
2.2.2.2 实参的数量
注意:JS里不会对函数实际传入的参数进行检测。可以传值,也可以不传值,也可以任意多个值
调用函数时,解析器也不会检查实参的数量:
==多余实参不会被赋值==
==如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined.==
-
例如:
2.2.3 arguments
可以通过arguments获取所有实际传递的参数
案例:
var total=0;
var sum=function(){
console.log(arguments);
for(var i=0;i<arguments.length;i++){
total+=arguments[i];
}
return total;
}
var result=sum(1,2,3,4,5,6);//21
console.log(result);
三.函数返回值
function sum(a,b){
return a+b;
}
sum(10,20);
return的作用是结束方法.
注意:
return后的值将会作为函数的执行结果,可以定义一个变量,来接收该结果
在函数中的return后的语句都不会执行(函数在执行完return语句之后停止并立即退出)
如果return语句后不跟任何值,就相当于返回一个undefined
如果函数中不写return,则也会返回undefined
-
返回值可以是任意的数据类型,可以是对象,也可以是函数。
//函数的返回值可以是函数 function sum(a,b){ var add=function(){ return a+b; } return add;//返回的整个add函数 return add();//30,这是函数的调用 } console.log(sum(10,20));
四.==函数名、函数体和函数加载问题(重要)==
-
函数名==整个函数
//函数名其实就是函数本省 var add=function(a,b){ return a+b; } console.log(add)==console.log(function(a,b){ return a+b; });
我们知道,当我们在调用一个函数时,通常使用函数()这种格式;但此时,我们是使用函数这种格式,它的作用相当于整个函数。
函数的加载问题:JS加载函数时,只加载函数名,所以如果像使用内部的成员变量,需要调用函数。
五. ==fn( )和fn的区别【重要】==
- fn( ):调用函数,相当于获取了函数的返回值。
- fn:函数对象,相当于直接获取了函数对象
六. 立即执行函数
-
什么是立即执行函数?
- 专业名字:Immediately-Invoked Function Expression(IIFE 立即调用函数表达式)
-
表达的含义是一个函数定义完后被立即执行
第一部分是定义了一个匿名函数,这个函数有自己独立的执行上下文环境。
-
第二部分是后面的(),表示这个函数被执行了
-
这个东西有什么用?
- ==会创建一个独立的执行上下文环境,可以避免外界访问或修改内部的变量,也避免了对内部变量的修改==
6.1 立即执行函数其他写法
-
立即执行函数必须是一个表达式,不能是函数声明:
- 下面的这种写法会报错,因为是一个函数声明,不是一个函数表达式
- 当圆括号出现在匿名函数的末尾想要调用函数时,它会默认将函数当成是函数声明。
-
当圆括号包裹函数时,它会默认将函数作为表达式去解析,而不是函数声明。
-
下面是一个函数表达式,所以可以执行
-
声明式函数立即执行函数的两种写法
(function () { console.log("我是声明式函数"); })()
(function () { console.log("我是声明式函数"); }());
<script>
/*
立即执行函数:函数定义完后被立即执行
特点:执行完后会立即销毁
*/
//1.声明式函数
/*
function func() {
console.log("我是声明式函数");
} //(),后面会报错,因为这里不是表达式
*/
//1.1 改进一:对函数加上();声明式函数立即执行函数调用
/*
(function func() {
console.log("我是声明式函数");
})()
func(); //会报错,func is not defined,所以不要命名函数表达式]
*/
//1.2 改进二:将函数和执行的()一起加上()
(function () {
console.log("我是声明式函数");
}());
(function () {
console.log("我是声明式函数");
})()
//2.匿名函数表达式
var test = function () { //变量test = undefined;因为函数默认返回undefined
console.log("我是匿名函数表达式");
return undefined;
}(); //匿名函数表达式立即执行函数调用
console.log(test);//undefined
</script>
立即执行函数如下:
//立即执行函数
var result=(function(a,b){
return a+b;
})(10,20);
console.log(result);
七. 方法
函数也可以成为对象的属性,如果一个函数作为一个对象的属性保存,那么我们成为这个函数是这个对象的方法。调用这个函数就说调用对象的方法(method).相比于方法,它只是名称上的区别,并没有其他的区别。
函数举例:
//调用函数
fn();
方法举例:
//调用方法
obj.fn();
我们可以这样说,如果直接使用fn(),那就说明是函数调用。如果是发现XX.fn()这种方式,那就说明是方法调用
八.this关键字
解析器在调用函数每次都会向函数内部传递一个硬汉年的参数,这个隐含的参数就是this,this指向的是一个对象,这个对象我们称之为函数执行的上下文对象。
==根据函数的调用方式不同,this会指向不同的对象:==
1.以函数的形式调用时,this永远是window。比如fun( );相当于Window.fun( );
2.以方法的形式调用时,this是调用方法的那个对象
3.以构造函数的形式调用时,this是新创建的那个对象
-
4.使用call和apply调用时,this是指定的那个对象
举例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script>
/*
this关键字
- 1.以函数的形式调用时,this永远是window。比如fun( );相当于Window.fun( );
- 2.以方法的形式调用时,this是调用方法的那个对象
- 3.以构造函数的形式调用时,this是新创建的那个对象
- 4.使用call和apply调用时,this是指定的那个对象
*/
var Student={
name:"jason",
age:20,
like:function(){
console.log(this);//this就是Student对象
console.log(this.name+"喜欢钓鱼");
}
}
var fn=Student.like;
var fn1=function(){
console.log(this);//this就是Student对象
console.log(this.name+"喜欢钓鱼");
}
console.log("-------------------------------");
console.log(fn==fn1);//false,内幕才能地址不一样
console.log(fn==Student.like);//true
console.log(fn());
console.log(Student.like());//this->Student
Student.like();//this->window
</script>
<body>
</body>
</html>
8.1 改变this的指向
格式:
修改this的指针:
1.函数名.call(对象);
2.函数名.apply(对象);
3.bind();
改变this指向有三种方式:apply,call,bind
apply和call的不同点在于传值不同:apply传值使用的是数组,call是直接写就可以
/* 修改this的指针: 1.函数名.call(对象); 2.函数名.apply(对象); 3.bind(); */ var Student={ name:"jason", like:function(){ console.log(this); console.log(this.name+"钓鱼"); } } //Student.like();//this指向Student对象 var fn=Student.like; fn();//this指向window console.log(window.fn===fn);//true //改变this的指针 var teacher={ name:"老王", } //call; //fn.call(teacher); fn.apply(teacher)
==apply,call,bind的使用案例:==
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
/*
修改this指针的异同点
1.apply
2.call
3.bind:直接在对象上绑定,改变this指向
*/
var Student={
name:"小王",
like:function(){
console.log(this.name+"钓鱼");
for(var i=0;i<arguments.length;i++){
console.log(arguments[i]);
}
}.bind(Teacher),
}
var Teacher={
name:"老王",
}
//apply和call的不同:调用方式不一样,传值方式也不一样
//Student.like.call(Teacher,"唱","跳","rap");
//Student.like.apply(Teacher,["唱","跳","rap"]);
Student.like("唱","跳","rap");
</script>
</head>
<body>
</body>
</html>
九.arguments
在调用函数时,浏览器每次都会传递两个隐含的参数:
- 1.函数的上下文对象 this
- 2.封装实参的对象 arguments
例如:
var abc=function(){
console.log(arguments);
console.log(typeof arguments);
}
abc();
arguments是一个类数组对象,它可以通过索引来操作数据,也可以获取长度。
arguments代表的是实参。在函数调用时,我们所传递的实参都会在arguments中保存,它只能在函数中使用
4.1 返回函数实参的个数:arguments.length
arguments.length可以用来获取实参的长度。
4.2 返回正在执行的函数:arguments.callee
arguments里面有一个属性叫callee,这个属性对应的函数对象,就是当前正在指向的函数对象。
在使用函数递归调用时,推荐使用arguments.callee代替函数名本身。
4.3 arguments可以修改元素
之所以说arguments是伪数组,是因为:arguments可以修改元素,但不能改变元素数组的长短。
举例:
/*
函数的arguments对象
*/
var abc=function(a,b,c,d){
console.log(arguments);
console.log(typeof arguments);//Object
//长度
console.log(arguments.length);//4
console.log(arguments.callee);//
//修改元素
if(arguments[0]){
arguments[0]=100;
}
console.log(arguments);
console.log(a);//100,同时也修改了实参
}
abc(1,2,3,4);
3.1 Function:函数(方法)对象
//当形式参数与实际参数长度不一致时
function fun4(a,b,c){
//document.write(a+b+c);
document.write(c);//当实际参数长度小于形式参数长时,形式参数默认为undefined类型
}
//fun4(1,2);
function fun5(a,b){
document.write(arguments[2]);//当实际参数长度大于形式参数时,在方法声明里内置对象(数组),arguments,封装所有实际参数
}
//fun5(1,2,3);
```
//例:求不定长度的和
function sum(){
var total=0;//注意这里一定后面赋值0要加上
for (var i = 0; i <arguments.length ; i++) {
total += arguments[i];
}
return total;
}
document.write(sum(1,2,3,4,5));
```
十.函数练习题
10.1 练习一:实现一个加法计算器
function addCalculate() {
var total = 0;
for (var i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(addCalculate(10, 30, 60));
10.2 练习二:定义一个函数,传入宽高,计算矩形区域的面积
<script>
/**
* 练习二:定义一个函数,传入宽高,计算矩形区域的面积
*/
var square = function (width, height) {
return width * height;
}
var s = square(30, 10);
console.log(s);
</script>
10.3 练习三:定义一个函数,传入半径,计算圆形的面积
<script>
/**
* 练习三:定义一个函数,传入半径,计算圆形的面积
*/
var circleSquare = function (radius) {
return radius * radius * Math.PI
}
console.log(circleSquare(1));
</script>
10.4 练习四:定义一个函数,传入n(n为正整数),计算1~n数字的和
<script>
/*
练习四:定义一个函数,传入n(n为正整数),计算1~n数字的和
*/
function getNumberSum(num) {
var total = 0;
for (var i = 1; i <= num; i++) {
total += i;
}
return total;
}
console.log(getNumberSum(10)); //55
</script>
10.5 练习五:定义一个函数,传入一个数组,对数组进行翻转
<script>
/*
练习五:定义一个函数,传入一个数组,对数组进行翻转
*/
var reverseArray = function (arr) {
var temp;
for (let i = 0; i < arr.length / 2; i++) {
temp = arr[i];
arr[i] = arr[arr.length - 1 - i];
arr[arr.length - 1 - i] = temp;
}
return arr;
}
var arr = [10, 1, 3, 50, 6];
arr = reverseArray(arr);
console.log(arr); //[6,50,3,1,10]
</script>
10.6 练习六:定义一个函数,传入一个数字数组,对数组中的数字进行排序
<script>
/*
练习六:定义一个函数,传入一个数字数组,对数组中的数字进行排序
*/
var arraySort = function (arr) {
var temp;
//冒泡排序,从小到大排列
for (let j = 0; j < arr.length - 1; j++) {
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] >= arr[i + 1]) {
temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
}
}
}
return arr;
}
var arr = new Array(20, 5, 30, 1, 6);
arr = arraySort(arr);
console.log(arr); //[1,5,6,20,30]
</script>
10.7 练习七:定义一个函数,传入一个数字,求对应的菲波那切数列
<script>
/*
练习七:定义一个函数,传入一个数字,求对应的菲波那切数列
斐波那契数列指的是这样一个数列:
0,1,1,2,3,5,8,13,21.......
这个数列从第3项开始,每一项都等于前两项之和。
*/
//var temp = 0;
var arr = [];
var fArr = function (n) {
for (let i = 0; i < n; i++) {
if (i == 0) {
//temp = 0;
arr.push(0);
} else if (i == 1) {
//temp = 1;
arr.push(1);
} else {
if (i <= n) {
// temp += 0 + 1;
//arr.push(temp);
arr[i] = arr[i - 2] + arr[i - 1]; //这个表达式就已经有将数值推进到数组里面的功能,不需要下面特意push()方法,如果有会重复
//arr.push(arr[i]);
} else {
break;
}
}
}
return arr;
}
console.log(`传入${10},求对应的菲波那切数列`, fArr(10));
</script>
<script>
function f(n) {
var sum = 1;
for (let i = 0; i < n; i++) {
if (i == 0) {
var num1 = 0;
} else if (i == 1) {
var num2 = 1;
} else {
sum = num1 + num2;
num1 = num2;
num2 = sum;
}
}
return sum
}
console.log(f(6));//5
十一.递归
<script>
/*
递归调用
1.1 一个函数可以调用另一个函数
1.2 一个函数调用自己就是递归
1.3 在开发中尽量避免使用递归:
1.3.1 递归如果没有写好结束条件,意味着无线循环调用
1.3.2 递归调用非常占用栈空间内存
*/
//1.1 一个函数可以调用另一个函数
function test01() {
console.log("test01函数");
test02();
}
test01();
function test02() {
console.log("test02函数");
}
//1.2 一个函数调用自己就是递归
function test() {
//console.log('test函数');没有结束条件会造成内存耗尽
test();
}
test();
</script>
<title>斐波那契数列递归调用</title>
<script>
function f(n) {
if (n === 1) {
return 0
} else if (n === 2) {
return 1;
} else {
return f(n - 1) + f(n - 2);
}
}
console.log(f(6)); //5
</script>
十二.函数的调用栈
十三.全局变量和局部变量
- 在JavaScript(ES5之前)中没有块级作用域的概念,但是函数可以定义自己的作用域。
- 什么是全局变量和局部变量?
- ==定义在函数内部的变量,被称之为局部变量。==
- ==定义在script标签中的变量,被称之为全局变量。==
- 在函数中,访问变量的顺序是什么呢?
- ==优先访问自己函数中的变量,没有找到时,在全局中访问。==
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>变量作用域</title>
</head>
<body>
</body>
<script>
/*
1.变量作用域:变量能在哪个范围内被使用,这个范围就是变量作用域
2.在JavaScript(ES5之前)中没有块级作用域的概念,
3.但是函数可以定义自己的作用域。
4.什么是全局变量和局部变量?
定义在函数内部的变量,被称之为局部变量。
定义在script标签中的变量,被称之为全局变量。
5.在全局中默认是不能访问局部变量的
5.在函数中,访问变量的顺序是什么呢?
优先访问自己函数中的变量,没有找到时,在全局中访问
*/
//1.块没有自己的作用域
var age = 18; {
var name = "jason";
}
console.log(name, age);
//2.只有函数才有作用域
//优先访问自己函数中的变量,没有找到时,在全局中访问
function getName() {
var name = "tom";
var age = 20;
console.log(age); //20
}
</script>
</html>
十四.值传递和引用传递
1.在 JavaScript 中数据类型可以分为两类:
- 原始数据类型值 primitive type,比如Undefined,Null,Boolean,Number,String。
- 引用类型值,也就是对象类型 Object type,比如Object,Array,Function,Date等。
2.声明变量时不同的内存分配
- 原始值:存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。这是因为这些原始类型占据的空间是固定的,所以可将他们存储在较小的内存区域 – 栈中。这样存储便于迅速查寻变量的值。
- 引用值:存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存地址。这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。
14.1 按值传递
//1.值传递
function test(n) {
n = "kobe";
}
var name = 'why';
test(name);
console.log(name); //why
14.2 按引用传递
//2.引用传递
function func(arr) {
arr[0] = "jason";
}
var array = [1, 2, 3];
func(array)
console.log(array); //['jason',2,3]