函数概念
什么是函数
使用关键字"function", 定义的一段具有独立作用域,能被反复执行的语句块。
函数有什么作用
函数一般用于描述某种功能,实现某种功能。
函数的声明方式
主要有三种声明方式
1,利用关键字“function”声明
基本格式为:
function(){
}
2,利用函数表达式进行赋值声明
var fn=function(){
}
注意:这样声明的函数,必须先声明后调用,否则会报错
3,利用构造函数“ Function”声明
基本语法格式为:
var fnName = new Function(paramList , 函数体字符串);
var newFn=new Function("a","b",return a*b);
newFn(3.3)
输出:9
注意:
- 这种写法是将参数列表和函数体放置在了一起同样作为了参数。
- 如果只有一个参数,那这个参数就是函数体。(就是花括号里面的代码)
- 构造函数内的参数无论有多少个,始终会将其最后一个参数作为函数体去执行
- 参数和函数体的语句要作为字符串去呈现
提示:重复声明会覆盖
和声明和一个变量一样,在同一个作用域内,若函数重复声明,后声明的函数会覆盖之前声明过的函数,使之前声明过的任何同名函数无效。
函数的返回值
- 每一个函数都会有一个返回值,这个返回值可以通过关键字“return”进行设置
- 若未显示地设置函数的返回值,那函数会默认返回一个undefined值
- 但若手动地设置了函数的返回值(return)后,函数将返回开发者手动设置的那个值
- 在函数中,一旦执行完成“return”语句,那么整个函数就结束了,后续语句将不再执行;
- 就是“return”之后的值只能有一个。如果尝试返回多个值,那得到的结果始终是最后一个值
- 如果真的需要函数返回多个值,那就只有将值组合成一个对象或数组进行返回
function testFn(){
if(typeof testFn=="function"){
console.log("函数数据类型")
}
}
testFn()
输出:函数数据类型
函数的参数
基本概念
- 函数的参数称为形参与实参
形参:定义函数时写的参数是形参(形式上的参数)
实参:调用函数时传递的参数是实参(实际参数)
函数最终的运算结果由实参所解决定
不论形参还是实参,都不需要关键字“var”的显示声明,函数的参数只能在函数内部访问
对位传参法
- 形参与实参是通过位置而不是参数名来匹配对应的
- 形参与实参的个数可以不一致
- 如果一个形参没有接收到实参传递来的值,那么他为undefined
对象传参法
- 当参数非常多时,想要不在通过位置来匹配参数,想让参数具有确定性,那么可以使用对象作为参数,然后就可以通过属性名key来匹配。
function fn1(obj){
return {
name:obj.name,
genger:obj.genger,
age:obj.age
}
}
console.log(fn1({name:"yb",genger:"男",age:"20"}))
参数默认值
可以给参数设置默认值(es5写法两种,es6一种)
1. es5: 三目运算符,逻辑或运算符
三目运算符
function fn1(){
a=(a==undefined?0:a);
b=(b==undefined?1:b);
console.log(a,b)
}
逻辑或运算符
function fn1(){
a=a丨丨"aaa";
b=b丨丨"bbb";
console.log(a,b)
}
fn1(5,6)
输出:5,6
fn1()
输出:aaa bbb
2. es6: 在参数列表里面赋值
function fn1(a=10,b=20){
return a+b
}
fn1()
输出:30
Arguments
arguments代表了一个函数的所有参数,他是一个类似数组的东西,可通过下标取值。在我们不知道具体的参数个数时,他尤为有用。
将arguments转换为一个数组
方式一:Array.prototype.slice.call(arguments)
可通过数组原型上得slice方法截取arguments中所有的内容,然后作为一个数组返回,如下
function newsunm(param){
arguments=Array.prototype.slce.call(arguments);
arguments.push(4);
return arguments
}
newsunm(1,2,3)
输出:[1,2,3,4]
方式二:Array.from(arguments)
function test(a,b,c,d){
console.log(Array.from(arguments))}
test(1,2,3,4)
输出:[1,2,3,4]
5 .函数的作用域
全局作用域 & 函数作用域 & 块级作用域
全局作用域:在当前文件中的所有函数,块中都有效,不管let还是
函数作用域:只在某个函数里面有效,不管是var或者let都可以
块级作用域:只在if判断、for循环等语句里面有效,需要使用let关键字声明
规定了我们定义的变量在某些范围下才能生效的规则
有哪些作用域? 什么是作用域?
全局作用域:在当前文件中的所有函数,块中都有效
var test=666
function fn(){
console.log(test)
}
fn()
输出:666
函数作用域:只在某个函数里面有效,不管是var或者let都可以
function fn(){
var test1="hello"
console.log(test1)
}
fn()
输出:"hello"
块级作用域:只在if判断、for循环等语句里面有效,需要使用let关键字声明
for(var i=0;i<10;i++){
}
i
输出:10
for(let i=0;i<10;i++){
}
i
报错
关于let的一些说明
- let声明的变量具有块级作用域
- let声明的变量不允许重复声明
- let声明的变量不会进行变量提升
注意
在es5里面,变量作用域只分为两种类型,一种是“全局作用域”,一种是“函数作用域”。在es6里面才拥有块级作用域,即if判断、for循环等语句都有自己独立的作用域
不同作用域变量的访问问题
- 访问优先级:里层能访问外层,外层不能访问里层
- 块级能访问局部,局部能访问全局,
- 全局不能访问局部,局部不能访问块级
函数递归
- 函数在执行的时候调用自身,称之为递归。
- 通过递归可以同更少的代码完成很多需要大量代码来实现的功能。
- 递归和循环十分的相似。在使用递归时同样要注意一个问题,就是要防止结束条件的不明确导致出现“死循环”,导致浏览器崩溃。
- 建议使用循环来替代“递归”,防止有些浏览器对递归迭代周期过长而产生的报错。
- 和递归等效的循环
function arr(n){
if (n>1){
return n+arr(n-1)
}
else return n
}
arr(100)
输出:5050
- 自执行匿名函数
- 匿名函数
匿名函数就是没有函数名的函数。
匿名函数的一些使用:
绑定事件:Btn.οnclick=function(){}
排序传参:[].sort(function(){})
声明字面量函数:var fn=funtion(){}
自执行匿名函数
这种函数不需要任何调用,即可立即执行。
它也叫做 立即调用的函数表达式(标准叫法),或者一些其他的不怎么标准但常用的叫法【自调用函数、立即执行函数等】
基本语法
( function(param) {...} ) (param );
//不带参数
(function(){
return "我是自己调用的函数"
})();
输出:"我是自己调用的函数"
//带有参数
(function(a,b){
return a*b
})(3,4);
输出:12
- 回调函数
回调函数的概念
作为参数的函数就是回调函数!
例如有两个函数A,B
如果函数A作为B的一个参数传入B的话,
那么我们就称A是回调函数。
例如:
function a(){
var i=2
function b(){
return "这是函数a的值"+i
}
return b
}
a()()
输出:这是函数a的值2"
- 闭包 【返回一个函数的函数】
- 为什么会产生闭包
由于作用域的原因,我们无法在函数外访问函数里面定义的变量,但有时候我们又会有这样的需求,这个时候我们就需要使用闭包了。
所以:
当我们想要从外部读取函数里面定义的局部变量时,我们可以定义一个闭包实现。
- 什么是闭包?
- 在函数A内部再定义一个子函数a,然后子函数a控制父函数中的变量v1,然后在父函数A中把这个子函数a返回给调用方,这个就叫做闭包。
- 闭包的用途
闭包从编码角度上讲,主要有两种用途
- 可以读取整个父级作用域函数内部的变量,
- 让这些变量的值始终保持在内存中。
-
function tex(){ var j=10 function tex1(){ return "这是j的值:"+j; }function tee2(index){ j = index return j } return { get:tex1, mot:tee2 } } var pow=tex() undefined pow Object {get: function, mot: function} pow.get() "这是j的值:10" pow.mot(100) 100 pow.get() "这是j的值:100"
-
ES5模拟后端继承实现(不继承原型)
前言
在现实生活中,如果你是一个富二代,或者只要你的父母有房有车,那么你就不要这么辛苦的奋斗了,因为大家可能要为之奋斗一生的东西,如房子,车子等只需要从父辈继承即可。
当然,如果你不是,那么恭喜你,也许你会成为富一代。
正所谓穷则变,变则通,贫穷往往能激发一个人最大的潜力。
相信自己,万事皆有可能!
JavaScript中的继承
在现实生活中存在着继承,那么在js中页是也在着继承的。
例如:
对于人而言,可以直接简单的归类为people类,也可以稍微细致点归为男人类,女人类,又或者更多的类。
那么现在如果已经有了人这个类了,这个类具有人的基本属性:姓名,性别,年龄等。
然后,现在你需要在定义一个女人这个类,具有:【姓名,性别,年龄等属性 + 独属于女人类的属性】。
这时对于姓名,性别,年龄等属性你需要在重新定义一次吗?不需要的,我们可以直接继承people类即可,这样既能简化代码,又能提高扩展性,还更清晰明了。
这就是js中的继承这个概念,然后继承可以分为单继承和多继承,分别如下所示。
- 修改this指向:单继承基本实现
这里我们就需要使用call或者apply来实现
function aaa(name,sex,age){
this.name=name;
this.sex=sex;
this.age=age
}
function bbb(name,sex,age,identity){
aaa.call(this,name,sex,age);
this.identity=identity
}
var wu=new bbb("zs", "女", 20,"学生")
undefined
wu
bbb {name: "zs", sex: "女", age: 20, identity: "学生"}
修改多个对象的this指向:多继承的基本实现
如果要继承多个类,我们只需要进行多次call或者apply调用即可
function Person(name){
this.name=name;
}
function man(age){
this.age=age;
}
function identity(name,age,id){
Person.call(this,name);
man.call(this,age);
this.id=id
}
var jj=new identity("zs","20","学生")
undefined
jj
identity {name: "zs", age: "20", id: "学生"}
- 构造函数与es6中的class
前言
- Es6中的class语法就是Es5中构造函数的另一种写法,一种更高级的写法,
- class语法的底层还是es5中的构造函数,只是把构造函数进行了一次封装而已。
- Es6出现的目的为了让我们的让对象原型的写法更加清晰、更像面向对象编程让JavaScript更加的符合通用编程规范,即大部分语言对于类和实例的写法。
Es5中的构造函数写法
function Person(x,y){
this.x=x;
this.y=y;
}
Person.prototype.toString=function(){
return "这是"+this.x+"岁"+"这是"+this.y+"岁"
}
var tex=new Person(22,33)
function (){
return "这是"+this.x+"岁"+"这是"+this.y+"岁"
}
tex.to
undefined
tex.tos
undefined
tex.toString()
"这是22岁这是33岁"
Es6中的class写法
- Es6的class基本写法
基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
class text{
constructor(x,y){
this.x=x;
this.y=y;
}
toString(){
return "这是"+this.x+"岁"+"这是"+this.y+"岁"
}
}
var tex=new text(22,33)
class text{
constructor(x,y){
this.x=x;
this.y=y;
}
toString(){
return "这是"+this.x+"岁"+"这是"+this.y+"岁"
}
}
tex.toString()
"这是22岁这是33岁"
- class注意事项
(1)和let一样,ES6的class不允许重复定义
(2)和let一样,ES6的class不存在变量提升,需要先定义在使用
ES5与Es6的对应关系
- ES5的构造函数Person,对应ES6的Person类的构造方法constructor
- ES5的Person原型上的方法对应Es6的除了constructor以外的其他方法。
es6的继承
继承原理
子类的原型对象的__proto__就是一个父类的实例对象,这样子类实例就能访问父类原型上的方法与属性,父类的原型对象还是Object的一个实例,,所以最终会找到Object的原型对象上去。
如果用es5的语法来实现的话,就可以这样来处理,将父构造函数的实例赋值给子构造函数的原型属性
关于constructor方法
constructor方法是类的构造函数是默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个默认的constructor方法会被添加。所以即使你没有添加构造函数,也是有默认的构造函数的。但是默认的constructor方法只会返回一个空对象