JavaScript 函数

223 阅读8分钟

定义一个函数

具名函数(具有名字的函数)

function函数名(形式参数1,形式参数2){
    语句
    return(返回值)
}

匿名函数(具名函数去掉名字就是匿名函数)

let a = function(x,y){
    return x+y       
}
也叫函数表达式,等号右边是函数表达式,等号左边是声明一个变量a并把它赋值

let a = function(x,y){return x+y}
fn(1,2)
会报错,fn的作用域只在等号右边

箭头函数

let f1 = x =>x*x                  x 输入参数,x*x 输出参数
let f2 =(x,y)=> x+y              括号不能省略
let f3 =(x,y)=>{return x+y}      花括号不能省略,有花括号就必须加return,要不然JS不知道
let f4 =(x,y)=>({name:x, age:y}) 直接返回对象JS会报错,需要加圆括号

构造函数

let f =new Function('x','y','return x+y')
所有的函数都是Function构造出来的,包括 Object Array Function

函数自身和函数调用,fn和fn()的区别

let fn=()=>console.log('hi')
fn              没有效果,因为fn没有执行
fn()            才有结果,有圆括号才是调用

let fn=()=>console.log('hi')
let fn2 = fn    只保留了函数的地址
fn2()
fn保存了匿名函数的地址,这个地址被复制给了fn2
fn2调用了匿名函数
fn和fn2都是匿名函数的引用而已,真正的函数既不是fn也不是fn2

函数的要素

调用时机
作用域
闭包
形式参数
返回值
调用栈
函数提升
arguments(除了箭头函数)
this(除了箭头函数)

调用时机(时机不同,结果不同)

let a =1
function fn(){ console.log(a)}
打印出多少?
不知道,因为还没有调用函数代码

let a =1
function fn(){ console.log(a)}
fn()
打印出多少?
1

let a =1
function fn(){ console.log(a)}
a=2
fn()
打印出多少?
2(调用函数时a=2)

let a =1
function fn(){ console.log(a)}
fn()
a=2
打印出多少?
1(先调用的函数)

let a =1
function fn(){
setTimeout(()=>         
console.log(a)},)
fn()
a=2
打印出多少?
2(fn的执行时机是在a=2之后)
setTimeout过一段时间在执行,先把当前所有代码执行完,才做等一会做的事情

let i=0
for(i=0; i<6; i++){
setTimeout(()=>{
console.log(i)},0)}
打印出多少?
6个6
先执行整个循环,最后i=6,要打印6次,所以是6个6

for(let i=0; i<6; i++){
setTimeout(()=>{
console.log(i)},0)}
打印出多少?
0,1,2,3,4,5
因为JS在forlet一起用时会加东西,每次循环时会多创建一个i

作用域(每个函数都会创建一个作用域)

function fn(){let a = 1}
console.log(a)
a不存在
是不是因为fn没有执行?
就算fn执行了,也访问不到作用域里面的a

let的作用域范围:let往前找第一个花括号,往后找第一个花括号,就算let唯一一个作用域

全局变量VS局部变量

在顶级作用域声明的变量是全局变量
window的属性是全局变量,其它的都是局部变量

函数可以嵌套,作用域也可以嵌套

function f1(){
    let a =1
    function f2(){
        let a = 2
        function f3(){
        console.log(a)
            }
            a = 22
            f3()
            }
        console.log(a)
        a = 100
        f2()
        }
    f1()

作用域的确定跟函数执行无关,这种跟函数执行无关的作用域叫静态作用域,也叫做词法作用域

作用域的规则

如果多个作用域有同名变量a
那么查找a的声明时,就向上取最近的作用域,简称(就近原则)
查找a的过程与函数执行无关,但a的值与函数执行有关

闭包

如果一个函数用到了外部变量,那么这个函数加这个变量,就叫做闭包
左边的a和f3 组成了闭包

function f1(){
    let a =1
    function f2(){
        let a = 2
        function f3(){
        console.log(a)
            }
            a = 22
            f3()
            }
        console.log(a)
        a = 100
        f2()
        }
    f1()

形式参数

形式参数的意思是非实际参数,形式参数可以认为是变量声明,本质是变量声明

function add(x,y){return x+y} 
x,y并不知道是多少,只是代表了这个函数,还没有赋值
其中x和y就是形式参数,因为并不是实际的参数

add(1,2)
调用add时,1和2就是实际参数,会赋值值给x,y
当我们赋值的时候,我们只是把x的地址复制到形式参数
没有什么值和地区的区别,所有东西根据内存图全部复制

function add(){
var x = arguments[0]
var x = arguments[1]
return x+y
}
arguments[]拿到所有参数
等价于上面代码,形式参数可多可少(形式参数只是给参数取名字)

function add(x){
    return x+arguments[1]
    }
add(1,2)=3

function add(x,y,z){
    return x+y}
add(3,4) = 7                          
形式参数声明了可以不使用

返回值(return后面的值是返回值)

每个函数都有返回值

function hi(){ console.log('hi')}
hi()
没写return,所以返回值是undefined

function hi(){ return console.log('hi')}
hi()
返回值为console.log('hi')的值,即undefined

返回值是你在执行之后才会使用的,如果不执行就不存在返回值,函数执行完了才会返回

只有函数有返回值
1+2 返回值为3,   错的因为没有return
1+2 值为3,       值不是返回值

调用栈

JS引擎在调用一个函数前,需要把函数所在的环境push到一个数组里,这个数组叫调用栈
等函数执行执行完了,就把环境弹(pop)出来,然后return到之前的环境,继续执行后续的代码

JS每次进入一个函数都要记录回到哪,所以要把回到的地址写到栈里(压栈)
等把函数执行完,就弹栈(知道回到哪)

递归函数

阶乘
function f(n){
    return n!== 1? n*f(n-1):1
}

阶加
function f(n){
     return n!== 1? n+f(n-1):1
}

理解递归:n不等于1,就返回 n*(fn - 1),否则返回1
f(4)
=4*f(3)
=4*(3*f(2))
=4*(3*(2*f(1)))
=4*(3*(2*(1)))
=4*(3*(2))
=4*(6)
=24
先递进,在回归

阶乘
n!= 1       n=1
n*(n-1)!    n>=1时

1!=1
2!=2*1=2
3!=3*2!=6
4!=4*3!=24

函数提升

什么是函数提升
function fn(){}
不管你把具名函数声明在哪里,它都会跑到第一行

什么不是函数提升
let fn = function(){}
这是赋值,右边的匿名函数不会提升

arguments和this

每个函数都有arguments和this,除了箭头函数没arguments和this

function fn(){
console.log(arguments)
console.log(this)
}

如何传arguments,调用fn即可传arguments
fn(1,2,3)那么arguments就是[1,2,3]的伪数组

如何传this
目前可用 fn.call(xxx,1,2,3)传this和arguments
而且xxx会被自动转化成对象

fn.call(1,2,3,4)
call会分成2段:1是this,变成对象;2,3,4是arguments,伪数组

个人结论

this是隐藏参数,this是参数,arguments普通参数

规则一:如果不给this任何条件,那么this默认指向window
规则二:如果传给this不是对象,JS会自动帮你封装成对象

function fn(){'use strict'
    consloe.log(this)}
fn.call(1)                     
1就不会变成对象了

this

JS在每个函数里加了this,用this获取那个对象

let person={
    name:'frank'
    sayHi(隐藏的this){
      console.log(`你好,我叫`+this.name)
  }
}
        person.sayHi()
相当于  person.sayHi(person)

然后person被传给this了
person是个地址,这样每个函数都能用一个this获取一个未知对象的引用

person.sayHi()会隐式的把person作为this传给sayHi,(方便shyHi获取person 对应的对象)

JS做了两件事

把this给你
把person给this
就相当于把person给你

this就是最终调用sayHi的对象

我们想让函数获取对象的引用,但是并不想通过变量名做到
JS通过额外的this做到:person.sayHi()会把person自动传给sayHi,sayHi可以通过this引用person

this的两种调用方法

person.sayHi()
会自动把person传到函数里,作为this

person.sayHi.call(person)
需要自己手动把person传到函数里,作为this

person.sayHi.call({name:1})
call后面传什么,this接什么
如果使用call传给this,那么不使用this时,需要找东西占位

function add(x,y){
    return x+y
}
add.call(undefined,1,2) 
add.call('hello',1,2)

array.forEach2.call(array)         用forEach2调用array
array.forEach2()                   用array作为thiis调用forEach2

Array.person.forEach2=function(fn){
    for(let i=0;i<this.length;i++){
        fn(this[i],i,this)
    }
}
遍历当前数组,当前数组是this,this可以引用未来的当前数组

this是什么

由于大家使用forEach2的时候总是会用arr.forEach2
所以arr就被自动传给forEach2了

this不一定是数组
Array.prototype.forEach2.call({0:'a',1:'b'})

this的两种使用方法

隐式传递 
fn(1,2)          等价于 fn.call(undefined,1,2,)  
obj.child.fn(1)  等价于 obj.child.fn.call(obj.child,1)
undefined作为this,占位
obj.child对象里面的一个属性的函数,函数前面这部分(obj.child)当做this 

显示传递
fn.call(undefined,1,2)
fn.apply(undefined,[1,2]) 

绑定this

使用.bind可以让this不被改变
function f1(p1, p2){
  console.log(this, p1, p2)
}
let f2 = f1.bind({name:'frank'})

那么 f2 就是 f1 绑定了 this 的新函数
f2()等价于f1.call({name:'frank'})
f2是f1,this的绑定版本

.bind还可以绑定其它参数

let f3 = f1.bind({name:'frank'},'hi')

        f3()        
等价于  f1.call({name:'frank'}, hi)

箭头函数

没有arguments和this;如果函数里面出现了this,就会当做没有出现;里面的this就是外面的this

console.log(this)                   window
let fn =() =>console.log(this)      声明这个函数fn
fn()                                不管任何时候调用fn,this都是window

fn.call({name:'frank'})             就算加call都没有this,还是window

箭头函数里面的this就是一个普通变量,外面是什么它是什么

详细资料点击:JavaScript 函数