函数可以封装一些功能,可以供外部去重复的调用。所以,一般我们把函数叫做具有重复功能的代码块。
JavaScript 基础到高级
Canvas游戏开发
原生JavaScipt案例合集
JavaScript +DOM基础
假设饭店就是一个函数,饭店的功能就是做各种各样的菜,但是具体做什么菜,需要用户来点,用户可以是我们当中的任何一个人,当我们点好菜付完帐,那么饭店就行驶它做饭的功能,开始为我们做指定的可口饭菜,这个我们点餐的过程,其实就是类似于函数调用的过程。最后,饭店做完菜给我们呈上来,这个菜就类似于函数返回的数据
函数使用关键字 function 来进行声明
在声明的函数内部,我们可以书写一些代码,使这个函数具有某一种特定的功能。在函数内部书写的代码我们习惯上称为函数体。
函数声明的语法格式:
function 函数名(形式参数){
函数体
}
思考:求任意两个数的和
函数名的命名规则:同变量命名规则
函数声明后,不会自动执行,需要进行调用
函数调用的语法 函数名()
函数的调用方式
练习:使用函数包装流程语句,也就是将流程语句当作函数体。一个大于0的整数,打印它的所有约数。
分析:一个数它的最小约数是1,最大约数是它本身。如果还有其它约数,肯定在 1 和 它本身之间。
什么是约数?
约数,又称因数。整数a除以整数b(b≠0) 除得的商正好是整数而没有余数,我们就说a能被b整除,或b能整除a。a称为b的倍数,b称为a的约数。
使用函数封装,简单介绍几个好处:
function f(){}
console.log(typeof f);//function
参数可以类似的看作我们之前声明的变量,只不过它的位置在函数后面的小括号中,且不需要再使用var进行声明。函数的参数分为两种:形式参数 和 实际参数。
**形式参数:**简称形参。在函数声明的时候,小括号中给定的参数,此时,不能确定参数的数据类型 和 具体的值。类似于前面讲的去饭店服务员给我们的菜单,可以提供给这么多菜,但是具体做什么菜还需要客户指定。
**实际参数:**简称实参。在函数调用的时候,用来替换形参的具体的值(传递的参数),此时确定了数据类型和值。
类似于前面讲的去饭店点菜,根据服务员提供的菜单,点具体的菜名
**传参:**函数调用,有一个传递参数的过程,这个过程我们叫它传参。
需求:求一个大于0的整数,约数的个数
通过上面的描述和演示,总结出JS的另外一个特点:
JS是一门动态的、弱类型语言。
重载可以看作是函数参数的应用。
函数的重载:在一个程序中,声明多个同名函数,但是其数据类型以及参数的个数并不相同。
JS中不存在函数重载的概念,我们只能模拟。因为一旦在JS中声明多个同名函数,后面的会覆盖前面的。
arguments对象是JS中一个特殊的对象。它是一个类数组,存储的是函数调用时所有传递的实际参数。
arguments对象中内置了一个 length 属性,用来获取函数调用时实际传递的参数的个数 语法 arguments.length
类数组和数组都具有索引,索引从0开始,依次递增,如上图:arguments对象中,有对应的索引,一一对应于传递的参数。通过 arguments[索引] 可以获取执行的对应的参数值:
获取第一个参数 arguments[0]
获取最后一个参数 arguments[arguments.length - 1]
function sum() {
console.log("arguments:",arguments);
console.log("函数调用时传递的第一个参数:",arguments[0]);
console.log("函数调用时传递的参数个数:",arguments.length);
console.log("函数调用时传递的最后一个参数:",arguments[arguments.length - 1]);
}
模拟函数的重载
很多情况下,我们封装函数的目的是为了利用函数的这个功能,得到某些数据,然后对这些数据进行二次操作。
那么,如果想要在函数中返回这些数据,那么需要用到关键字 return。
return 关键字用于在函数中返回数据,并跳出函数。
如果仅仅是为了跳出函数,而不需要返回任何数据,那么直接return即可。
练习: 判断一个大于0的数字是否是质数。
什么是质数?
质数也叫素数,是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
分析: 可以根据约数的个数,当约数的个数是2的时候,那么这个数就是质数。
return作为跳出函数用。一般会在面试中经常被问道,return、break、continue的区别?
**概述:**当声明函数后,在函数的内部(大括号函数体位置)形成自己一个独立的内部区域,这个内部区域就叫做函数作用域。
函数作用域相对于整个脚本区域来讲,只是一个很小的独立区域,这个很小的区域我们通常叫它局部作用域。而整个脚本区域,我们通常叫它全局作用域。
全局作用域:函数外的区域叫做全局作用域;在全局作用域中声明的变量,可以在脚本的任何位置(包裹函数内)都可以对其进行调用,这个变量我们称为全局变量。全局变量的生命周期:从文件被执行时创建(当前就是页面被加载),页面关闭后销毁。
局部作用域:函数内的区域叫做局部作用域;在局部作用域中声明的变量,只能在函数的内部进行访问调用,那么此时我们称其为局部变量。局部变量的生命周期:函数被调用时创建,函数调用完毕销毁。
嵌套函数中,存在多个被声明的同名函数,在调用时,首先查看当前作用域是否有这个变量,有则调用,没有则去它的上一级作用域中查找,如果有则使用,如果没有,继续向上查找,直到找到为止
//声明一个全局变量 num
var num = 1;
function fn() {
//在第一层函数中,声明一个变量 num
var num = 10;
//在第一层函数中,声明一个内部函数 fn2
//创建函数的另外一个方式 将声明的匿名函数赋值给一个变量
var fn2 = function () {
var num = 100;
return num;
}
return fn2;
}
如果在函数的内部,不使用var声明,首先这是不标准的写法,一般不建议,且在严格摸下会报错。其次,如果我们不小心这样用了,那么在函数被调用后,这个变量会提升为全局变量。如果函数没有被调用,则不会创建这个变量。
function f() {
num = 10;
console.log("num:",num);
}
这种情况,很容器造成全局变量的污染,不建议使用。
var i = 2;
console.log('i:',i);//i: 2
function f1() {
var i = 20;
console.log('i:',i);//i: 20
}
f1();
console.log('i:',i);//i: 2
var j = 3;
console.log("j:",j);//j:3
function f2() {
j = 30;
console.log("j:",j);//j:30
}
f2();
console.log("j:",j);//j:30
上面的 j 本身是想作为一个局部变量来处理,但是由于没有使用 var 声明,造成函数调用时,这个本要作为局部变量定义的变量 变成了 全局变量的赋值操作,这样就造成了全局变量的污染,或称为命名冲突。
如果在同一个作用域内,一个变量被多次声明,那么声明只会生效一次,以第一次声明为准,后面的声明不再作数,直接作为变量的赋值操作。
var num = 1;
console.log("num:",num);//num:1
var num = 10;
console.log("num:",num);//num:10
num = 100;
console.log("num:",num);//num:100
在 JS 中,有变量和函数的提升,JS引擎在解析代码时,分成两步:
在JS中,函数的创建方式有三种:
第一种标准的方式 function 函数名(参数列表){函数体}
function f() {
num = 10;
return "num:"+num;
}
第二种函数表达式的方式:将一个匿名函数赋值给一个变量 var f = function(参数列表){函数体}
var f1 = function(){
num = 10;
return "num:"+num;
}
第三种内置对象的方式 var f = new Function(“参数列表”,“函数体”)
var f3 = new Function("num = 10; return 'num:'+num");
var f2 = new Function("a","b","return a+b");
使用标准的方式创建的函数,与其它两种方式在调用时的不同:
如果都是先声明,再去调用,看不出有什么区别:上面已经验证过 f() f1() f3() 结果一致
如果时先调用,再去声明的情况,那么结果就不同了,如下代码
非标准的方式,利用的是一个变量来接收一个函数,那么其本质就是变量。前面讲过变量的提升,相当于将变量的声明提升到当前作用域的最顶部,这时还没有赋值,在调用的时候,变量的值是undefined。所以后面两种创建函数的方式,只能先声明再调用。
**标准的方式:**会将整体作为一个声明部分,再预解析阶段,将其整体提升到当前作用域的最顶部,所以标准的方式创建的函数可以在当前作用域的任何为止被调用。
小问题:如果在同一个文件中,存在同名的变量 和 函数,函数优先于变量提升;而前面讲到,在一个文件中,声明多个变量,只有第一个会生效,而函数也可以看作是变量,不管使用var还是使用function都叫做声明。所以函数优先于变量提升后,其它变量的声明都变成了赋值操作。
假设有一个酒店:酒店中有保安负责安保、前台负责接待、服务生负责提供服务、厨师负责做饭、大堂经理负责协调等等。
这时,如果将安保、前台、服务生、厨师、协调等工作全部交给大堂经理一个人去做,这样效率会很慢,酒店几乎没有多久肯定会倒闭。此时,酒店提供所有的功能都在大堂经理一个人身上,肯定是不行。
这时,酒店考虑到经营问题,又招来了保安、前台等等人,负责其自己相应的领域工作,然后大堂经理只需要调配这些人即可。这样效果就会很高了。
那么,代码中也是一样,尽可能的减少代码的耦合性。我们可以利用函数,将功能封装的尽量单一,在不同的场景下,可以被重复的利用,优化了代码又提高了执行的效果。这些,其实就是模块化的思想。
后面随着课程的深入,我们会接触到 seajs、requirejs、nodejs 这些东西,都是用来进行模块化开发的,不同之处在于其将一个js文件作为一个模块,可以避免全局变量的污染。
这里,我们使用函数来体会模块化编程的思想。
人类从古至今,习惯将事情分工,将一些内容做成一些公共模块,模块可以重复反复使用。
模块化编程:将一些基础的公共的部分单独封装到一个函数内,可以多次被调用。
**案例:**输出100以内的质数,模块化编程。
逆向思维的过程:输出100以内的质数 → 判断是不是质数 → 找约数个数
练习:找100以内的完美数:(一个数的约数除了它本身外其他约数和还等于这个数)。
注意:模块化编程,可以让我们的程序更加优化,各个小模块要尽量功能单一,提高重复使用率。
**练习:**赢数是一种特殊的自然数,除去它本身以外所有的约数和大于其本身。请输出100以内所有的赢数
练习:如果整数 A 的全部约数(包括1,不包括 A 本身)之和等于 B,且 整数 B 的全部约数(包括1,不包括B 本身)之和等于 A,则称整数 A 和 B是一对亲密数。输出1000以内的亲密数。
封装一个函数,实现功能:不借助临时变量,进行两个整数的交换。
提示:运算符、函数有两个参数
封装一个函数,求3个数中的最大值
提示:函数有三个参数、要求至少使用两种方式实现
封装一个函数,实现在页面上输出 100 ~ 1000之间所有的质数,并要求每行显示 6 个数字
封装一个函数,实现 12! - 10! 结果
要求:不能使用递归
数据类型有两大类:基本数据类型 和 引用数据类型
基本数据类型存储在内存的栈中,引用数据类型存储在堆中
代码演示:
var a = 10,
b = 20,
c = 30,
fn = function () {};
console.log("a:",a,"b:",b,"c:",c,"fn:",fn);//a: 10 b: 20 c: 30 fn: ƒ () {}
//修改b的值
b = 200;
console.log("a:",a,"b:",b,"c:",c,"fn:",fn);//a: 10 b: 200 c: 30 fn: ƒ () {}
//声明一个变量d来接收基本数据类型的变量c
var d = c;
console.log("c:",c,"d:",d);//c: 30 d: 30
//改变基本数据类型c的值
c = 300;
console.log("c:",c,"d:",d);//c: 300 d: 30
//总结:基本数据类型变量在进行赋值时,只是将变量保存的值复制了一份给另外一个变量进行赋值。此时变量值得改变不会互相影响。
//声明一个变量,来接收引用数据类型得变量
var fn2 = fn;
console.log("fn:",fn,"fn2:",fn2);//fn: ƒ () {} fn2: ƒ () {}
console.log(fn === fn2);//true 指向同一个内存地址
//改变其中一个引用数据类型的结构
fn.x = 100;
console.log("fn:",fn,"fn2:",fn2);//fn: ƒ () {} fn2: ƒ () {}
console.log(fn === fn2);//true 指向同一个内存地址
console.log("fn.x:",fn.x,"fn2.x:",fn2.x);//fn.x: 100 fn2.x: 100
//总结:引用数据类型变量在进行赋值时,将引用数据类型的变量保存的地址复制了一份给另外一个变量复制。此时两个变量指向同一个地址,其中一个结构发生改变会影响另外一个
递归就是在函数的内部调用函数自己本身;
递归函数多用来解决一些 数字0以上 的数学问题;
递归函数必须给定一个条件,来退出函数,否则会造成内存堆栈溢出的问题。
如:
累加问题 1 + 2 + 3 + … + 98 + 99 + 100
累乘问题 100! = 100 * 99 * 98 * … * 3 * 2 * 1
生兔子的问题 有一对兔子,三个月开始每个月生一对小兔子,这一对小兔子在成长到三个月的时候,每个月也是生一对小兔子,问一年之后,在没有死亡的情况下,总共有多少对兔子?
换算成数学,就是一个斐波那契数列 1 1 2 3 5 8 13 21 34 55 89 144…
// 斐波那契数列 1 1 2 3 5 8 13 21 34 55 89 144...
console.log(feiBo(1));
console.log(feiBo(2));
console.log(feiBo(11));
console.log(feiBo(12));
// 求斐波那契数列中第 N 项的值
function feiBo(n) {
if(n === 1 || n === 2){
return 1;
}
return feiBo(n - 1) + feiBo(n - 2);
}
//feiBo(3) = feiBo(3 - 1) + feiBo(3 - 2) = feiBo(2) + feiBo(1) = 1 + 1 = 2
//feiBo(4) = feiBo(4 - 1) + feiBo(4 - 2) = feiBo(3) + feiBo(2) = feiBo(3 - 1) + feiBo(3 - 2) + 1 = 1 + 1 + 1 = 3
//feiBo(5) = ....
匿名函数:也叫拉姆达函数,说白了就是没有名字的函数
自执行函数:IIFE,也叫立即执行函数。不需要进行调用,随着程序的执行会自动调用。IIFE有两部分组成,函数体和执行部分。
//匿名函数的使用
//1. 声明一个变量,来接收一个匿名函数
var f = function () {
};
//事件驱动 window窗口对象的点击事件
var count = 0;
window.onclick = function () {
console.log("第" + (++count) +"次点击了窗口...")
};
//IIFE自执行函数
;(function (a,b) {
console.log(a + b);
})(10,20);
var result = (function (a,b) {
return a + b;
})(10,20);
console.log("result:",result);
什么是闭包?
闭包就是一个函数可以访问另外一个函数内部的变量。
想要理解闭包,需要先了解 作用域链的问题 和 垃圾回收机制的问题(设置到变量的生命周期)。
在JS中,任何一个函数都可以认为是闭包;常见的情况是,函数中可以访问全局变量,嵌套函数可以访问祖先元素中的变量。
function f(){
var i = 1;
function f2(){
console.log(i);
}
return f2;
}
//f函数中的 变量i 是一个局部变量,局部变量只能在函数的内部被访问到
function f3(){
console.log(i);
}
如果想要在一个函数的外部,访问这个函数内部的变量,那么该怎么操作?
假设,我们将嵌套在内部的函数作为一个联通内外的桥梁,在调用外层函数时,返回这个内部的函数
function f() {
var i = 1;
return function () {
return i++;
}
}
console.log(f);
console.log(f());
console.log(f()());//1
console.log(f()());//1
//也就证明,每一次都是一个全新的环境,得到的值也是一样的 局部变量和函数在外部函数调用完毕后即销毁
//那么,利用对象之间的引用关系,可以保存当时执行的一个环境,进而将局部的变量保存下来
//垃圾回收机制中:对存在引用关系的对象 不会进行回收
var test = f();//此时test就是一个函数
console.log(test)
console.log(test());//1
console.log(test());//2
console.log(test());//3
var test2 = f();//此时又是一个新的闭包函数 当执行f()时,传递给test2的那个桥梁函数会记住当时所执行的上下文环境
console.log(test2);
console.log(test2());//1
console.log(test2());//2
闭包的问题:
闭包不宜使用过多,容易造成内存的泄漏问题;
但是,使用闭包可以避免全局变量的污染,很多高级程序中,都会有闭包的应用。后面在高级课,会利用闭包来私有化对象的属性和方法。
闭包目前可以用来解决:
循环中,有异步语句存在的情况下,无法正确获取索引的问题。
// console.log(1111);
//回调函数:将一个函数作为另外一个函数的参数
//在JS中,有一个内置的延迟器,在指定时间后,去执行相应的操作
// setTimeout(function () {
// console.log(2222);
// })
// console.log(3333);
//循环中,异步语句,获取索引的问题
// for (var i = 0; i < 10; i++) {
// console.log("i:",i);
// }
for (var i = 0; i < 10; i++) {
(function (j) {
setTimeout(function () {
console.log("内j:",j);
})
})(i);
}
console.log("外i:",i);