函数在JS中也是一个对象,它具有其它对象的所有功能,函数中可以存储代码,且可以在需要的时候调用这些代码
函数的操作函数的定义
- 函数声明
function 函数名([参数列表]) {// 函数体return 返回值;}
- 函数表达式
const 函数名 = function([参数列表]) {return 返回值;}
- 箭头函数
const 函数名称 = ([参数列表]) => {return 返回值;}const 函数名称 = ([参数列表]) => console.log("箭头函数");
函数的调用
函数名称(参数1,参数2,.....);
函数的类型
function fn(){console.log("我是牛逼")}// 返回functionconsole.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 + blet 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 ==> windowconsole.log(this)}
- 当我们以方法的形式调用的时候,this指向的是调用方法的对象本身
function fn() {// 这里this ==> windowconsole.log(this)}const obj = { name:'jack' }obj.test = fn;// 这里this ==> objconsole.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() }}// objobj.fn()// windowobj.fn2()
高阶函数
根据OCP原则,我们对修改关闭,对扩展开放,有些地方我们需要扩展,我们可以往我们一直使用的函数的参数中传递一个函数,通常回调函数都是匿名函数,而我们将一个函数的参数或返回值时函数,则称为这个函数为高阶函数
将函数作为参数,意味着,我们可以往函数里面动态的传递代码
function filter(arr,fn) { for(let i = 0; i { 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 5const newFn = fn.bind(obj,10,20,5) // this ---> objnewFn()