使用关键字“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是返回函数的参数,它返回的值是类似一个数组的集合。
将其转换为真数组有两种方式
①: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:"男"}
概念:就是创建一类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)
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,性别:男。ES5与Es6的对应关系
1.ES5的构造函数People对应ES6的People类的构造方法constructor;
2.ES5的People原型上的方法对应Es6的除了constructor以外的其他方法。
通过class实现原型链的继承
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方法,那么最终的构造函数返回的是空对象。
Class的静态方法与静态属性
如果在一个方法前,加上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); 结果显示为:男