JavaScript(七):函数

177 阅读5分钟

函数也是一个特殊的对象

定义一个函数

// 具名函数
function 函数名(arg1, arg2, ...){
    语句
    return 返回值
}

// 匿名函数——把上面的具名函数的函数名去掉
let a = function 可有可无的函数名(x, y){ return x+y }

匿名函数也称作函数表达式。如果以上面的方式明明一个函数,则是把函数的内存地址赋值给变量a,同时该函数作用域只局限在该表达式中,如果不通过a调用,单独调用该函数则会报错。如果去掉let a =,则匿名函数作用域为全局作用域。这是比较特殊的一点。

// 箭头函数
let f1 = x = > x*x;
let f2 = (x, y) => x+y;
let f3 = (x, y) => {return x+y}  // 有return关键字时花括号不能省
let f4 = (x, y) => ({name: x, age: y})  //直接返回对象会出错,需要加个圆括号

// 用构造函数(基本没人用)
let f = new Function('x', 'y', 'return x+y')

所有函数都是Function构造出来的,包括Object、Array、Function也是

One step further

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

上述代码:

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

函数的要素

setTimeout()函数的作用通俗地理解为:先把手头的事情忙完,再赶紧执行我。

全局变量&局部变量

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

函数可嵌套,作用域也一样

function f1(){
    let a = 1;
    
    function f2(){
        let a = 2;
        console.log(a);
    }
    console.log(a);
    a = 3;
    f2();
}
f1()    // 先输出1,在输出2

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

  • 那么查找a的声明时,就向上取最近的作用域,简称[就近原则]
  • 查找a的过程与函数执行无关,但a的值与函数执行有关

闭包

定义:如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包
闭包先讲定义,之后的笔记再讲用途
举例:

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
}
add(1, 2)

其中x和y是形参,因为并不是实际的参数(形参可认为是声明变量,实际上是一种语法糖)。当调用add方法时,1和2就是实际参数,简称实参。
形参可多可少,查看下面的例子

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

function add(x, y, z){
    return x+y
}

返回值

每个函数都有返回值,函数执行完了才会返回,只有函数有返回值。

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

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

调用栈

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

arguments

arguments是函数最重要的两要素之一。
this合起来说。

this

this是函数最重要的两要素之二,也是JS三座大山的第二座。

function fn(){ console.log(arguments) }
fn(1, 'b')

查看fn(1, 'b')返回的__proto__属性,也就是原型,可知arguments是一个包含所有实际参数的伪数组,因为构造函数是Object

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

如果函数不给任何条件,则this默认指向window
this就是提供一种调用自身对象的方法,因为在没有实例化对象之前,我们不知道实例的名称,所以采用这么一个方法,方便写代码。这样讲的话,其实可以把this理解为一种隐藏的形参。
多的不想讲了。。。这个我会。看一个图吧,可以知道python传递对象的方法有多土了。
bbf5b31239256f37e895f698b9ab190.png

let person = {
    name: 'frank',
    sayHi(){ console.log(this.name); }
}
person.sayHi()    // 传this的小白写法
person.sayHi.call(person)    // 传this的js大师写法
person.sayHi.call({name: 'jack'})    // 对象里的函数和对象本身其实没有什么关系,只是恰好组合在了一起。

如何传this? 可以用fn.call(this, *arguments)thisarguments。需要注意的是:this实参不是对象时,会自动地被封装成相应的对象

Array.prototype.forEach2 = function(){
    console.log(this);
}
let array = [1, 2, 3];
array.forEach2.call(array);    // [1, 2, 3]
array.forEach2();    // [1, 2, 3]

// 实现Array原型的forEach方法
Array.prototype.forEach2 = function(fn){
    for(let i = 0; i < this.length; i++){
        fn(this[i], i);
    }
}

使用这个大师调法时传递的this,可以是任意的值,最终是一个对象;使用小白写法,实参默认就是this。所以可以理解:小白写法是隐式传递默认的this,大师写法是显式传递可以为任意的this。所以下面的代码可以运行

array.forEach2.call(array, (item)=>console.log(item))

下面的代码可以帮助理解

// this的两种使用方法
// 隐式传递
fn(1, 2)    // 等价于fn.call(undefined, 1, 2)
obj.child.fn(1)    // 等价于obj.child.fn.call(obj.child, 1)

// 显式传递
fn.call(undefined, 1, 2)
fn.apply(undefined, [1, 2])    // 调用apply方法时,参数为数组形式

绑定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'}) 

多选题再加深理解:

1649945575(1).png

箭头函数

箭头函数没有argumentsthis
箭头函数里面的this就是外面的this

console.log(this)    //就是默认的window
let fn = () => console.log(this)    
fn()    // window

如果你修改this的指向,那么调用fn函数打印出来的就不再是window了。

let fn = () => console.log(arguments)
fn(1, 2, 3)    // 会报错,因为箭头函数没有arguments