JS函数对象

235 阅读7分钟

在JS中,函数就是特殊的对象,典型的对象有:Object对象、Array数组对象,函数对象。

1. 概念

1.1 什么是函数

  • 是由事件驱动的或者当它被调用时执行的可重复使用的代码块;也可以理解为函数是一组拥有某些特定功能的、能够被重复调用的、闭合代码块。函数的意义在于出现大量程序相同的时候,可以封装为一个function,这样只用调用一次,就能执行很多语句。

1.2 什么是参数

  • 定义在函数内部的语句,都是相同的,但可以通过“参数”这个东西,来让语句有差别。

  • 定义函数的时候,内部语句可能有一些悬而未决的量,就是变量,这些变量,我们要求在定义的时候都罗列在小括号中(形式参数),形参(非实际参数)只用来在函数内部使用,在函数外部形参失效。通常形式参数不用var声明,直接写变量名即可,形参可认为是变量声明。

  • 调用的时候,要把这个变量的真实的值(实际参数),一起写在括号里,这样随着函数的调用,这个值也传给了a

1.3 什么是返回值

函数可以通过参数来接收东西,更可以通过return的语句来返回值,函数执行完了以后才会返回,“吐出”东西。函数已经return后面的语句就不执行。没写return,返回值为undefined

1.4 什么是函数的作用域与变量

  • 作用域(scope)指的是变量存在的范围。Javascript只有两种作用域:
  1. 全局作用域:变量在整个程序中一直存在,所有地方都可以读取。
  2. 函数作用域:变量只在函数内部存在,只在花括号里面生效{},在函数外部变量失效。
  • 和作用域与之对应的,javascript中有两种变量:
  1. 全局变量(global variable):在函数外部声明的变量,它可以在函数内部读取。定义在全局范围内的,没写在任何function里面的,叫做全局变量:主要有两种:

在顶级作用域声明的变量是全局变量;
window的属性是全局变量

let b = 1;        //在顶级作用域声明的变量
window.c = 2;     //window的属性是全局变量,在任意地方都声明有效,能够读取
  1. 局部变量(local variable):在函数内部定义的变量,外部无法读取。在function里面直接用var声明的变量,叫做局部变量,只在function里面有定义,出了function没有定义的;函数的参数,会默认定义为这个函数的局部变量
  • 作用域规则

如果多个作用域有同名变量a:

查找a的声明时,就向上取最近的作用域,简称"就近原则"

查找a的过程与函数执行无关(静态作用域,也叫词法作用域);

但a的值与函数执行有关

  • 函数能封闭住作用域:

一个变量如果定义在了一个function里面,那么这个变量就是一个局部变量,只在这个function里面有定义。出了这个function,就如同没有定义过一样。

1.5 什么是嵌套函数

  • 局部作用域嵌套局部作用域,遵循就近原则
function f1(){
    let a = 1
    function f2(){
    let a = 2
    console.log(a)      //输出结果2
    }
    console.log(a)      //输出结果1
    a = 3
    f2()
}
f1()

1.6 闭包

如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包。

2. 定义一个函数:

浏览器JS引擎构造了Function,然后指定它的构造者是自己,所有函数都是由window.Function构造的

let fn = new Function('x','y','return x+y')

//  x为第一个形参,y为第二个形参,return x+y 为函数体

函数对象自身属性和共用属性

自身属性:

  • 'name'/'length'

共用属性:

  • 'call'/'apply'/'blind'

2.1 具名函数

function 函数名(形式参数1,形式参数2,...){
   函数体语句
   return 返回值
}
  • 定义一个函数,用关键字function来定义,function就是“功能”的意思。表示这里面定义的语句,完成了一些功能。function后面有一个空格,后面就是函数名字,函数的名字也是关键字,命名规范和变量命名是一样的。名字后面有一对儿圆括号,里面放置参数,然后就是花括号,花括号里面是函数的语句,举例如下:
function fn(x,y){
    return x*x+y
}
fn(2,5) // 9

2.2 匿名函数

也就是去掉函数名(fn)的具名函数,但应该给一个变量来容纳它

let a =function(x,y){
    return x+y
}

这种声明函数的方式没有函数名,而是用变量a来指代函数。调用函数的时候通过访问变量名来调用函数。声明一个变量a,容纳了这个函数的地址,并且赋值,等号右边的也叫函数表达式

2.3 具名+匿名结合写法:

let a =function fn(x,y){
    return x+y
}
fn(1,2)
Uncaught ReferenceError: fn is not defined
at <anonymous>:1:1

出现报错的原因是上述代码中的fn的作用域只在function fn(x,y){xx+y}这一块地方,其他地方都未定义,需要用的时候只能借助于变量a,如果没有等于号,只剩下function fn(x,y){xx+y},那就是全局作用域。

2.4 箭头函数

这是ES6的一个新语法,一些用法如下:

let f1 = x =>x*x    //箭头左边为输入参数,右边为输出参数
f1(3)               // 9
-----------------------------------------------------
let f2 = (x,y) => x*y    // 注意圆括号不能省
f2(6,8)                  // 48
-----------------------------------------------------
let f3 = (x,y) => {       // 花括号、return都不能省
  console.log('hello')
  return x*y
}                      
f3(6,8)                   // hello  48
------------------------------------------------------
let f4 = x => ({ name:x }) // 注意圆括号
f4('xinhai') // {name: 'xinhai'} 对象    
------------------------------------------------------
let f5 =(x,y)=>({name:x,age:y})
f5('xinhai',18) // {name: 'xinhai', age: 18} 对象

2.5 构造函数

let f = new Function('x','y' 'return x+y')

用的较少,不过用的好处是能让你清楚函数是由谁构造出来的。所有函数都是Function构造出来的,Function是由Function构造出来的

3. 函数自身和函数调用

fn是函数自身,而f()是函数调用。相较于函数声明的多种多样,函数的调用方式就简单很多。只要函数已经被声明,直接写出函数名和函数参数即可调用函数。函数如果不调用,那么里面的语句就一辈子不会执行,不调用就等于白写。

举例1:

let fn =()=>console.log(1)
undefined
fn

上例中,fn将不会有任何结果,这是因为fn并没有调用/执行,而fn()将会打印出1,有()才是调用。

举例2:

let fn = ()=> console.log('hi')
let fn2 = fn
fn2()

fn保存了匿名函数的地址,这个地址被复制给了fn2 fn2()调用了匿名函数 fn 和 fn2 都是匿名函数的引用而已 真正的函数既不是fn也不是fn2

4. 函数的要素

4.1 调用时机

函数的调用时机不同,得到的结果也不同。

  • 例1:
let a = 666
function fn(){
    console.log(a)
}

上面只是创建了函数,并没有调用它,故不会打印出什么来。

  • 例2:
let a = 666
function fn(){
    console.log(a)
}
fn()           // 666

上例已经调用了函数,打印出666

  • 例3:
let a =1
function fn(){
    console.log(a)
}
a = 2
fn()        //2
  • 例4:
let a = 1
function fn(){
    console.log(a)
}
fn()
a = 2     //1,因为调用的时候就是1
  • 例5:
let a = 1
function fn(){
    setTimeout(()=>{
        console.log(a)
    },0)
}
fn()
a=2

上例中是,setTimeout定时器设置的是要把整个代码执行完,马上就做的事,此时a=2,再打印出a

  • 例6:
let i = 0
for(i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}

上例中是,循环先执行完,此时i=6,再去执行定时器,打印出6次,6个6

  • 例7
for(let i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}

答案是0,1,2,3,4,5

上例7其实与例6,并无本质区别,ES6却把此代码的执行结果改为每次循环会多创建一个i,也就是因为for与let一起用的时候会加东西。

4.2 调用栈

JS引擎在调用一个函数前,需要把函数所在的环节push到一个数组里,这个数组叫做调用栈。记录了进入了一个函数,怎么回去,回到哪里的问题。

相关概念:弹栈、压栈、爆栈

4.3 递归函数

阶乘:

function f(n){
    return n!== 1 ? n*f(n-1) : 1
}    //如果n不等于1,则n*f(n-1),否则(n=1),结果就是1