JavaScript复习——03 函数
函数在JS中也是一个对象,它具有其它对象的所有功能,函数中可以存储代码,且可以在需要的时候调用这些代码
函数的操作
函数的定义
- 函数声明
function 函数名([参数列表]) {
// 函数体
return 返回值;
}
- 函数表达式
const 函数名 = function([参数列表]) {
return 返回值;
}
- 箭头函数
const 函数名称 = ([参数列表]) => {
return 返回值;
}
const 函数名称 = ([参数列表]) => console.log("箭头函数");
函数的调用
函数名称(参数1,参数2,.....);
函数的类型
function fn(){
console.log("我是牛逼")
}
// 返回function
console.log(typeof fn)
函数的参数
参数
:
- 如果实参和形参相同,那么对应的实参会赋值给形参
- 如果实参多于形参,则多余的实参不会使用
- 如果形参多于实参,则多余的形参为undefined
注意
:JS不检查的参数的类型,任何类型都可以作为参数传递
箭头函数的参数
- 我们用箭头函数作为参数的时候,只有一个参数的时候,可以省略()
- 定义参数时,我们可以指定默认值
- 箭头函数没有arguments
- 箭头函数的 this 不能修改
const fn = (a,b) => {
console.log('a=',a);
console.log('b=',b);
}
// 当我们箭头函数,只有一个参数时,可以省略()
const fn2 = a => {
console.log('a =',a);
}
// 定义参数的时候,我们可以指定默认值
const fn3 = (a = 10,b = 20,c = 0) => {
console.log('a = ',a);
console.log('b = ',b);
console.log('c = ',c);
}
对象作为参数
注意
:
- 我们传递参数的时候,我们传递的是变量中的值,而不是变量本身
- 函数每次调用,都会重新创建一个新的对象
function fn(a) {
a.name = '🐖'
// 打印:a={name:'🐖'}
console.log('a =',a);
}
let obj = {name:'孙悟空'};
fn(obj);
// 打印:{name:'🐖'}
console.log(obj)
两次都是打印唐僧、孙悟空
如果第二次打印 孙悟空、孙悟空,就说明我们函数的调用只会创建一个对象,但是实际不是
function fn2(a = {name:'唐僧'}) {
console.log(a);
a.name = '孙悟空'
console.log(a)
}
// print 唐僧 孙悟空
fn2()
// print 唐僧 孙悟空
fn2()
函数作为参数
在JS中,函数也是一个对象,函数作为对象传递,类似于Spring中的AOP思想,主要是增强代码的扩展性
function fn(a) {
console.log('a =',a)
}
function fn2() {
console.log('我是一个函数')
}
fn(fn2)
fn(()=>{
console.log('我是箭头函数')
})
函数的返回值
- 任何值都可以是函数的返回值,包括对象、函数之类的
- 如果return之后不跟任何值,返回值就是undefined
箭头函数的返回值
注意
:
- 如果我们直接在箭头之后设置对象字面量设置返回值,对象字面量必须使用
()
包起来
const obj = () => ({name:'孙悟空'})
console.log(obj())
const sum = (a,b) => a + b
let result = sum(123,456)
console.log(result)
作用域
作用域指定是我们变量的可见区域,也就是我们变量在哪里可以被访问
作用域有两种
- 全局作用域
- 生命周期:在网页开启时创建,在网页关闭时销毁
- 所有直接编写在 script 标签中代码都位于全局作用域中
- 全局作用域的变量,是全局变量,可以在任意位置访问
- 局部作用域
- 块作用域
- 块作用域是一种局部作用域
- 块作用域在代码执行的时候创建,在代码执行结束的时候销毁
- 在块作用域中声明的变量是局部变量,只能在块内部访问
- 函数作用域
- 函数作用域也是一种局部作用域
- 函数作用域在我们函数调用时产生,调用结束后销毁
- 函数每次调用都会产生一个新的作用域
- 在函数内部定义的变量是局部变量,只能在函数内部访问
- var 虽然没有块作用域,但是有函数作用域
- 块作用域
windows对象
在我们的浏览器中,浏览器为我们提供了一个window对象,可以直接访问
- window对象代表我们的浏览器的窗口,通过对象可以对浏览器的窗口进行各种操作
- 除此之外window对象还负责存储JS中内置对象和浏览器的宿主对象(浏览器作为编译器提供的对象)
- window对象的属性可以通过window对象访问,也可以直接访问
- 向window对象中添加的变量,会自动变成全局变量
alert('哈哈')
window.console.log('哈哈')
- 在全局中使用var声明的变量,会自动变成window对象的属性保存
- 使用function声明的函数,都会作为window对象的方法保存
- 使用 let 声明的变量不会存在window对象中,而是其它地方
- 在我们访问变量的时候,会先访问 let 声明的变量,没有再去找window的属性
- let会存在我们的Script这个对象上,它与Globe(Window的“代理”对象)同级
注意
:
- 我们在局部作用域中,既没有使用 var 来声明变量,也没有使用 let 声明变量,它会自动变成window对象的属性
提升
变量的提升:使用 var 声明的变量,它会在我们所有代码执行前被声明(没有啥子意义)
函数的提升:使用函数声明来创建函数的时候,会在其它的代码执行前被创建,所以我们可以在函数声明前调用函数(也就是函数的创建会被提升到最前面,但是你使用变量形式来声明函数,就不能在函数声明之前使用函数)
let 声明的变量也会被提升,但是在赋值之前,浏览器禁止我们访问
// 这里会输出 undefined 因为只是被声明,而赋值则是在被执行的时候才会赋值
console.log(a)
var a;
a = 10;
// 这里函数会提升
fn()
function fn() {
alert("我是function")
}
// 这里就不行
// 因为这里使用 var 定义只会提升fn2变量的声明,但是他没有赋值
fn2()
var fn2 = function() {
console.log("我是function2")
}
// let 声明的变量也会被提升,但是在赋值之前,浏览器禁止我们访问
fn3()
let fn3 = function() {
console.log("我是function3")
}
立即执行函数
我们应该减少在全局作用域中编写代码,我们代码要尽量编写到局部作用域中,这样代码就不会相互干扰
匿名立即执行函数创建(IIFE)
-
立即执行函数只会调用一次,它时一个匿名函数
-
我们可以利用这个IIFE来解决变量冲突问题
-
由于JS解析器解析的时候,我们如果有多个立即执行函数,会解析成 (XXX)(XXX)
- 这样会报错,因为解析器解析后面加 () 会默认为函数,但是,实际不是函数
- 解决这个问题我们需要在两个立即执行函数末尾加上分号
;
(function() {
var a = 10;
console.log(a)
})();
(function() {
var a = 11;
console.log(a)
})();
this
函数在执行时,JS解析器每次都会传递一个隐含的参数,这个参数就是this,this会指向一个对象
this指向的对象会根据函数调用的方式不同而不同
- 以函数形式调用时,this指向的是window
function fn() {
// 这里this ==> window
console.log(this)
}
- 当我们以方法的形式调用的时候,this指向的是调用方法的对象本身
function fn() {
// 这里this ==> window
console.log(this)
}
const obj = { name:'jack' }
obj.test = fn;
// 这里this ==> obj
console.log(obj.fn())
- 箭头函数,this是由外层来决定,外层的 this 是什么就是什么
- 它的this与调用方式无关
function fn() {
// window
console.log('fn --->',this)
}
const fn2 = () => {
// window
consloe.log('fn2 --->',this)
}
const obj = {
name:'jack',
// 这里属性值和属性名一样可省略属性名
fn:fn,
fn2:fn2,
sayHello() {
console.log('syHello -->',this)
function t() {
console.log("t-->",this)
}
/*
这里是以函数的形式调用,所以它的 this 是 window
*/
t()
const t2 = () => {
console.log("t2-->",this)
}
/*
这里是以箭头函数来调用,它的this由外层来决定,外层的this是obj
所以this就是obj
*/
t2()
}
}
// obj
obj.fn()
// window
obj.fn2()
高阶函数
根据OCP原则,我们对修改关闭,对扩展开放,有些地方我们需要扩展,我们可以往我们一直使用的函数的参数中传递一个函数,通常回调函数都是匿名函数,而我们将一个函数的参数或返回值时函数,则称为这个函数为高阶函数
将函数作为参数,意味着,我们可以往函数里面动态的传递代码
function filter(arr,fn) {
for(let i = 0; i < arr.length; i++) {
if(fn()) {
return arr[i];
}
}
}
// 使用高阶函数
const arr = [1,2,5,3,4,3,5];
const arr1 = [{name:'孙悟空',name:'白菜'}]
// 这样就可以动态的改变条件
filter(arr,(a)=>{ return a>5 })
filter(arr,a => a.name === '孙悟空')
/*
希望在使用someFn()函数的时候,可以记录一条日志
在不修改原函数的基础上,为其增加记录日志的功能
可以通过告诫函数,来动态的生成一个函数
*/
function someFn() {
return 'hello';
}
function outer(cb) {
return () => {
// 这里不仅仅可以写这个东西
console.log('someFn执行')
return cb()
}
}
let result = outer(someFn)
console.log(result())
闭包
我们现在希望创建一个函数,第一次调用时,打印1,第二次调用,打印2
我们要完成上面这种操作,就需要定义全局变量,但是我们定义全局变量,当我们协同工作时,变量很可能被别人修改,风险是比较大的
而为了解决上面这种变量不安全的问题,我们就需要将变量放在一个局部作用域中,而将变量放在函数作用域这种局部作用域就称之为闭包
闭包:
闭包就是能访问外部函数作用域中变量的函数
什么时候使用:
当我们需要隐藏一些不希望被别人访问的内容,就可以使用闭包
// 我们可以将不希望别人访问的变量放在这样的函数中
function outer() {
let num = 0
return () => {
num++
console.log(num)
}
}
const newFn = outer()
newFn()
闭包的要求:
- 函数的嵌套
- 内部函数要引用外部函数中的变量
- 内部函数作为返回值返回
闭包的原理
我们函数的外层作用域,在函数创建的时候就已经确定了(语法作用域),与调用的位置无关
闭包利用的就是 词法作用域
闭包的注意事项
注意
:
-
闭包主要用来隐藏一些希望不被外部访问的变量,这就意味着闭包要占用一些内存空间
-
相较于类来说,闭包比较浪费空间(类可以使用原型,而闭包不可以使用原型)
-
需要执行比较少的时候用闭包,需要执行次数比较多的时候使用类
-
闭包的生命周期:闭包在外部函数调用时产生,
外部函数
每次调用就会产生一个新的闭包 -
闭包在内部函数丢失时销毁(内部函数被CG回收了,闭包就会消失)
function outer() {
let someValue = 'someValue'
return function() {
console.log(someValue)
}
}
function outer() {
let someValue = 1
return function () {
console.log(someValue++)
}
}
let result = outer()
// 这里每次调用都要用一块新空间来给内部的匿名函数使用
result()
result()
可变参数
arguments
除了this以外,我们函数中还隐藏了一个参数arguments,arguments时一个类数组对象
- arguments用来存储我们的实参,无论我们是否定义了形参
- arguments可以通过索引来获取元素,也可以通过for循环遍历,但是它不是数组对象,不能用数组的方法
通过arguments 我们可以不受参数的数量限制,但是它也缺点
- 它不知道参数的数量
- 它不能调用数组的方法
function fn() {
let result = 0
for(const arg of arguments) {
result += arg
}
return result
}
可变参数
特点;
- 可变参数可以接收任意数量的参数,并且把它存到一个数组中
- 可变参数的名字我们可以自己指定
- 可变参数可以使用数组的方法
- 可变参数可以配合其它的参数一起使用
- 可变参数一定要定义到形参列表的最后
function fn(...args) {
return args.reduce((a,b) => a+b,0)
}
call 和 apply
根据我们函数调用的不同,我们的this也不同
- call()和apply()的第一个参数可以指定函数的this
- bind()可以为我们的新函数绑定this,绑定之后无法修改
函数的调用,除了使用函数名()调用,还可以使用call和apply方法
call和apply除了可以调用函数,还可以指定函数中的this
注意
:
- call()和apply()的第一个参数可以指定函数的this
- 通过call()第一参数之后参数会作为函数的参数传递过去
- 通过apply()要向函数传递参数,需要传递数组
let obj = {name:'孙悟空'}
函数.call(obj,函数的参数1,函数的参数2……) // this ---> obj
函数.apply(函数的this,[函数的参数1,函数的参数2……]) // this ---> window
bind()
bind()是函数的方法,用来创建一个新的函数
作用
:
- bind()可以为我们的新函数绑定this
- bind()可以我们的新函数绑定参数(也就是将我们的新函数的参数固定不变)
function fn() {
console.log('fn执行了')
}
let obj = {name:'孙悟空'}
// 函数的前三个参数固定为 10 20 5
const newFn = fn.bind(obj,10,20,5) // this ---> obj
newFn()