前言:在整个计算机编程中,函数的地位可谓十分重要,处理逻辑我们离不开写函数,这一章我们系统的学习下函数的相关知识。
目录
函数的声明:
// function a() { // console.log('这是一个函数'); // } 建议使用这种定义匿名函数,这样windows就调用不到这个函数 let a = function () { console.log('这是一个函数'); } console.log(a());
在对象里面定义函数:函数可以属性简写
let obj = { name: undefined, //简写形式 setName(name) { this.name = name }, getName() { return this.name } } console.log(obj.setName('xiaoyu'), obj.getName());
函数提升:
一般来说,具名函数存在作用域提升,而匿名函数不存在函数提升,函数其实是对象类型,所以存在引用类型赋值。
函数的参数:
默认参数:
函数的形参可以赋值默认的值,如果没有传实参,则使用默认的值,可以替代 ||或语句
function a(b, c = 3) { return b + c } console.log(a(1));
参数整合展开:
当传很多实参时,接受的形参不想写那么多,可以用...args 代表传过来的整个实参列表,即为收集再释放
function a(...args) { //将所有的实参都赋值给args数组,然后展开 console.log(args); //[1, 2, 3, 45, 6] console.log(...args); //1 2 3 45 6 } console.log(a(1, 2, 3, 45, 6));
函数可以作为参数传入:
箭头函数:
箭头函数是函数声明的简写形式,在使用递归调用、构造函数、事件处理器时不建议使用箭头函数。
- 无参数时,()=> { }
- 有一个参数时,省略括号
- 当只有一个表达式时,取消花括号和return
函数的递归:
递归的意思是:函数内部调用自己的方式
- 主要用于数量不确定的循环操作
- 要有退出时机否则会陷入死循环
递归的使用总结:一定要有结束条件,return出来,每次调用自己的时候,传过去的参数都是做逻辑操作 (意思是传的参数都不一样)
简单的递归实例:
function factorial(num = 3) { return num == 1 ? num : num * factorial(--num); } console.log(factorial()); 累加: function sum(...num) { return num.length == 0 ? 0 : num.pop() + sum(...num); } console.log(sum(1, 2, 3, 4, 5, 7, 9)); //31 操作数组对象的属性 let lessons = [{ title: "媒体查询响应式布局", click: 89 }, { title: "FLEX 弹性盒模型", click: 45 }, { title: "GRID 栅格系统", click: 19 }, { title: "盒子模型详解", click: 29 } ]; function change(lessons, num, i = 0) { if (i == lessons.length) { return lessons; } lessons[i].click += num; return change(lessons, num, ++i); } console.table(change(lessons, 100));
函数的回调:
别人使用的函数叫回调函数
闭包:声明在一个函数体内的函数为闭包,声明位置在体内
为什么要闭包:
1:js有垃圾回收机制,每次调用完一个函数后,函数的声明周期就会结束,里面的东西都会销毁,为了延长函数的声明周期,以及避免回收机制,使得父级函数的变量得以缓存起来。
function a() { let value = 2 return function () { return value += 1 } } let c = a() c() //3 c() //4 console.log(c()) //5
2:为了让外部访问到内部函数的变量,避免全局变量的污染
函数体内return 出另一个函数,返回出的这个函数可以读取外层函数作用域里面的变量。
1:函数里面嵌套函数,能读到上层函数的变量值
2:这样一直拿到父辈的值会造成内存泄漏,解决办法是 执行完后将父辈变量置为null
let arr = [3, 2, 4, 1, 5, 6]; function between(a, b) { return function(v) { return v >= a && v <= b; }; } console.log(arr.filter(between(3, 5)));
闭包的问题
1:内存泄露——解决将上级作用域变量制空
2:this的指向问题——使用箭头函数
this:
this的指向问题:
- 在对象的属性方法上,this指向该对象
- 在箭头函数身上,this指向函数上下文,会去找父级的this指向
- 在普通函数(具名函数和赋值匿名函数)身上,this指向windows对象
- 点击oclick等监听事件方法指向dom节点本身,也可以通过event拿到节点本身
解决this指向问题:
在使用回调function时,将之前对象的this存起来赋值给一个变量
使用箭头函数,就可以往上级去找this
改变this的指向:
通过 call 和 apply 和 bind 改变
三种方法的区别:
- 都接受参数,第一个参数均为传进去的对象,即要改变函数体内this指向的对象
- call 方法 第二个传的值为 参数 ,多个参数用 逗号 分割 ,
- apply 方法,第二个传的值为 参数 。把 参数 用 方括号 包起来
- bind 方法, 第二个传的值为 参数 。 他和上面方法有个最大的区别是 他返回的是一个新函数,需要使用()去调用,还有它有两次传参的机会,一次在bind方法里,一次在()调用里 ,如果都传参,优先传入bind方法里参数值
call 方法
let obj = {
a: 1,
show(...args) {
console.log(this.a, ...args);
}
}
obj.show.call({
a: 3
}, 4, 5) //结果: 3 4 5
apply 方法
let obj = {
a: 1,
show(...args) {
console.log(this.a, ...args);
}
}
obj.show.apply({
a: 3
}, [4, 5]) //结果是 3 4 5
bind 方法
let obj = {
a: 1,
show(...args) {
console.log(this.a, ...args);
}
}
obj.show.bind({
a: 3
}, 4, 5)() //结果是 3 4 5
或者
obj.show.bind({
a: 3
})(4, 5) //结果是 3 4 5
或者
obj.show.bind({
a: 3
}, 7, 8)(4, 5) //结果 3 7 8 4 5
普通函数和构造函数的区别:
普通函数包含块级作用域,含有自己的变量,在调用函数时,可以涉及到传参,构造函数可以通过new出来一个对象,然后构造函数里面包含该对象的属性和属性方法,通过this去调用。在没有构造新的对象时,this指向空对象
作用域:
函数作用域:
全局作用域只有一个,每个函数又都有作用域(环境)。
- 编译器运行时会将变量定义在所在作用域
- 只有在全局作用域或一个函数作用域下定义的函数,才会去找父辈寻找变量,其余的只能通过传参
- 使用变量时会从当前作用域开始向上查找变量!
let c = 1 function a() { console.log(c); //c在当前函数作用域内未定义,所以去外层找 } (a())
- 但是对于对象的属性方法,通过this调用函数时,只能靠传参,传递变量参数,对于属性方法里面定义函数,函数可以找父辈要变量,不过这个情况少见,vue中一般都是属性方法里通过this调用其他属性方法,那样就只涉及到传参,因为调用的函数方法不是在该作用域内定义的。面向对象编程尽可能少使用面向过程。
let obj = { a: 1, show() { let c = 2 this.get(c) }, get(c) { console.log(c); //2 } } obj.show() 情况少见 let obj = { a: 1, show() { let c = 2 function s() { console.log(c); } s() }, } obj.show()
- 对于函数作用域里面定义的函数,如果里面的函数没有该变量,就会选择去父辈作用域寻找
function a() { let s = 2 function b() { console.log(s); } b() } a() //2
- var是有函数作用域的,但没有块级作用域
函数在执行后,在其作用域里的变量会销毁,函数每次在调用时,重新创建新的作用域 ,在调用完该函数后,该函数销毁,重新调用,则生成新的函数地址空间,
如果想延申函数的生命周期,则使用闭包,返回函数体内的函数,将变量缓存起来
或者构造函数构造新的对象,对象调用属性方法时也会把构造函数的环境变量缓存起来
块级作用域:
在括号内则为一个块级作用域,仅适用于let const 。 var暂不适用,所以对于那种for循环有个方括号的块级作用域,var同样会不支持,他会跳出块级作用域的范围
两个块级作用于互不干扰 { let a = 1 } { let a = 2 } { var a = 1 } { let a = 2 } console.log(a); //打印 a 为 1
如何实现var的伪块级作用域:我们知道var他是支持函数作用域的,所以我们用一个立即执行函数把var定义的变量包起来,将var定义的变量通过实参传给这个函数
for (var i = 0; i < 3; i++) { (function (a) { console.log(a); // 0 1 2 })(i) } 如果不做伪块级作用域,var不支持块级作用域,他会跳出来进入全局作用域,此时的i会是3 for (var i = 0; i < 3; i++) {} console.log(i); //3