概述:
使用关键字“function”定义的一段具有独立作用域,能被反复执行的语句块。
函数中可以封装一些功能(代码),在需要时可以执行功能(代码)。
函数的声明:
1.利用关键字function声明
基本格式为:
function fn(){
}
2.利用函数表达式进行赋值声明
基本格式为:
var fnc=function () {
}
这种方式必须先声明后调用,否则会报错。
3.利用构造函数Function声明
基本格式为:
var fnc=new Function("","")
注意:
- 这种写法是将参数列表和函数体放置在了一起同样作为了参数。
- 如果只有一个参数,那这个参数就是函数体。(就是花括号里面的代码)
- 构造函数内的参数无论有多少个,始终会将其最后一个参数作为函数体去执行
- 参数和函数体的语句要作为字符串去呈现
如果函数声明重复了,则只会调用最后声明的函数
函数的返回值
每个函数都会指定返回一个值,如果你未设置返回值,则返回undefined。
返回值关键字return,return后的值将作为函数的执行结果返回。
注意:return只会返回一个值,如果想返回多个值就必须将多个值放到一个数组或者对象返回;return后的语句将不会被执行;
function fn(n){
return {"n的平方":n**2,"n的立方":n**3}
}
function fn(n){
return [n**2,n**3]
}
拓展:break可以退出当前的循环,continue用于跳过当前循环,return可以结束整个函数。
函数的参数
1.函数的参数分为形参和实参;
function fn(n){
return [n**2,n**3]
}
fn(3)
这里的n就是形参,fn(3)里面的3就是实参
函数的参数是不需要专门声明的;
2.对位传参法
function fn(name,sex,age){
return console.log("我是"+name+",性别:"+sex+",今年:"+age+"岁。")
}
fn("张三","男",20)
这里运行的结果是:
我是张三,性别:男,今年20岁。
对位传参法有一个弊端,假如我传入的参数没有按照顺序传入,则会出现下面的情况:
function fn(name,sex,age){
return console.log("我是"+name+",性别:"+sex+",今年:"+age+"岁。")
}
fn("男","张三",20)
这里运行的结果就是:
我是男,性别:张三,今年20岁。
所以这里就需要用下面的方法传参
3.对象传参法
对象传参法传入的参数是一个对象,例如:
function fn(obj){
return console.log("我是"+obj.name+",性别:"+obj.sex+",今年:"+obj.age+"岁。")
}
fn({sex:"男",name:"张三",age;20})
这里运行的结果是:
我是张三,性别:男,今年20岁。
注意:我们可以给形参一个默认值,当执行这个函数如果没有给实参时就会显示默认值,例如(这里以对象传参作为例子,这种给默认值的方法是ES6以后的版本才能执行的,还有给默认值的方法是用三元表达式或者或运算“||”):
function fn(obj={name:"李四",sex:"男",age:23}){
return console.log("我是"+obj.name+",性别:"+obj.sex+",今年:"+obj.age+"岁。")
}
fn()
这里运行的结果是:
我是李四,性别:男,今年23岁。
Arguments
arguments是返回函数的参数,它返回的值是类似一个数组的集合。
将其转换为真数组有两种方式
①:Array.from(aeguments);②Array.portotype.slice.call(aeguments);
例如:
function fn(){
arguments=Array.portotype.slice.call(arguments)
console.log(arguments)
}
fn(1,2,3,4,5)
这里的运行结果是:
[1,2,3,4,5]
function fn(){
arguments=Array.from(arguments)
console.log(arguments)
}
fn(1,2,3,4,5)
这里的运行结果是:
[1,2,3,4,5]
函数的作用域
块级作用域:只在if判断、for循环等语句里面有效,需要使用let关键字声明
函数作用域:只在某个函数里面有效,不管是var或者let都可以
全局作用域:在当前文件中的所有函数,块中都有效,不管let还是
- 访问优先级:里层能访问外层,外层不能访问里层
- 块级能访问局部,局部能访问全局,
- 全局不能访问局部,局部不能访问块级
- 访问变量时,先在当前作用域查找,然后在去上层作用域查找
关于声明提升
变量提升只是提升的变量名,而函数则是整体提升;
函数递归
定义:在执行函数的时候自己调用自己;达到条件后结束。
例如:
用递归函数打印出n到m的数字之和(n<m);
function sum(n,m){
if(m>n){return m+sum(n,m-1);}
else { return m; }
}
sum(1,100);
这里显示的结果就是5050;
注意:递归函数必须有结束条件,不然就会跟死循环出现一样的效果;
自执行匿名函数
不需要调用,自己执行,例如:
(function(n){
return n
})(2);
这里它会自己执行并返回结果为2;
自执行匿名函数的可以防止变量名的冲突;
回调函数
作为参数的函数就被称为回调函数;
例如:
function fn(){
return 30;
}
function num(a,b){
if(a=="ok"){
console.log(b());
}
else{
console.log(b);
}
}
num("ok",fn);
这里控制台就会打印出30这个结果。这时这里的函数fn()就被称作回调函数。
注意:回调函数是在另一个函数里面进行调用并执行的,所以实际参数的也是由另一个函数负责传递的,我们把回调函数作为参数传入是只给函数名即可,不应该给实际参数。
函数的闭包
在函数中,需要在外部读取函数里面定义的局部变量时,我们可以定义一个闭包实现。
闭包的定义:在一个函数A中定义另一个函数B,在函数B中控制定义在函数A的变量,在函数A中返回函数B,再将这个函数B返回给调用方,这就叫做闭包。
例如:
function fn(){
var a=0;
function fn1(){
return a;
}
return fn1;
}
fn()();
这时将会返回a的值0;
另外可以给a赋值,比如:
function fn(){
var a=0;
function fn1(num){
a= num==undefined? a:num;
return a;
}
return fn1;
}
var cf=fn();
cf(50) ----->这里的结果就是50;这时我们再调用cf(); ----->这时就会返回50,而不是0;
当我们想再次获得a的值时,就可以直接调用cf(),这就实现了在外部获取的里面的值;
注意:闭包函数很消耗内存,所以在退出函数之前,将不使用的局部变量全部删除。
如将当前变量的值设置为“null”,将变量的引用解除,当垃圾回收启动时,会自动对这些值为“null”的变量回收
闭包从编码角度上讲,主要有两种用途
- 可以读取整个父级作用域函数内部的变量,
- 让这些变量的值始终保持在内存中。
构造函数
用function来创造一类,并且函数名首字母大写,调用构造函数时,必须用关键字new来创建一个实例。例如:
function People(name,age,sex){
this.name=name,
this.age=age,
this.sex=sex
}
var zs=new People("张三",18,"男"}
这里就会打印出:People(name:"张三",age:18,sex:"男"}
ES5模拟后端继承实现(不继承原型)
概念:就是创建一类A后,在另一类B中继承A的属性就是单继承;多继承是再一类C中既继承A类又继承B类,例如:
单继承:
function People(name,age){
this.name=name;
this.age=age
}
function Women(name,age,sex){
People.call(this,name,age)
this.sex=sex
}
var dc=new Women("貂蝉",20,"女")
console.log (dc)
这里就会打印出:Women{name:"貂蝉",age:20,sex:"女"}
多继承:
function Man(sex){
this.sex=sex
}
function Studentman(name,age,sex,job){
People.call(this,name,age)
Man.call(this,sex)
this.job=job
}
var zs=new Studentman("张三",15,"男","学生")
console.log (zs)
这里就会打印出:Studentman{name:"张三",age:15,sex:"男","学生"}
注意:继承需要修改this的指向(call,apply,bind)
构造函数与es6中的class
Es5中创建函数的方式与Es6中class
ES5中的构造函数:
function People(name,age,sex){
this.name=name,
this.age=age,
this.sex=sex
}
People.prototype.sayhi=function (){
console.log("姓名:"+name+",年龄:"+age+",性别:"+sex)
}
var zs=new People("张三",20,"男")
zs.sayhi();
这里显示为:
姓名:张三,年龄:20,性别:男。
ES6中class写法:
class People1{
constructor(name,age,sex){
this.name=name,
this.age=age,
this.sex=sex
}
sayhi=function(){
console.log("姓名:"+name+",年龄:"+age+",性别:"+sex)
}
}
var zs=new People1("张三",20,"男")
zs.sayhi();
这里显示结果也为:
姓名:张三,年龄:20,性别:男。
1.ES5的构造函数People对应ES6的People类的构造方法constructor;
2.ES5的People原型上的方法对应Es6的除了constructor以外的其他方法。
ES5中的写法:
function Fn1(){
this.fn1="fn1"
}
function Fn2(){
this.fn2="fn2"
}
function Fn3(){
this.fn3="fn3"
}
Fn3.prototype.__proto__=new Fn2();
Fn2.prototype.__proto__=new Fn1();
var fn4=new Fn3
console.log(fn4.fn1)
这里显示为:fn1;
ES6中class写法;
class Fn5{
constructor(){
this.fn5="fn5";
}
}
class Fn6 extends Fn5{
constructor(){
super();
this.fn6="fn6";
}
}
class Fn7 extends Fn6{
constructor(){
super();
this.fn7="fn7";
}
}
var fn8=new Fn7
console.log(fn8.fn5)
这里显示为:fn5;
ES5中的继承原理:
子类的原型对象的__proto__就是一个父类的实例对象,这样子类实例就能访问父类原型上的方法与属性,父类的原型对象还是Object的一个实例,,所以最终会找到Object的原型对象上去。
ES6中class写法必须有constructor方法,如果你没定义constructor方法,那么最终的构造函数返回的是空对象。
如果在一个方法前,加上static关键字,就表示该方法是在构造函数本身上定义的方法,不会被实例继承,而是直接通过类来调用,这就称为“静态方法”;而静态属性就跟ES5中一样的格式:构造函数名.属性名=XXXX;例如:
静态方法:
class People{
constructor(name){
this.name=name
}
static sayhi=function(){
console.log("我的名字:")
}
}
这里的sayhi只有被构造函数本身调用,不会被实例继承。
People.sayhi();
结果显示为:我的名字:
静态属性:
var p1=new People
p1.sex="男";
console.log(p1.sex);
结果显示为:男