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()
/xin-xi-ji-zhu-fu-wu/javascriptfu-xi-03-han-shu-8090.html