跳转至

函数

信息

前置知识:

变量提升

变量可以在其使用之后声明. 这是因为JS引擎会将所有的声明移至当前作用域的顶部.

Tip
  • 声明和赋值不同
  • 所有的声明, 包括var, let, const, function, class, function*都会被提升
  • var, function, function*在提升的时候会被赋值为undefined或相应的函数体
  • let, const, class在提升的时候不会被赋值. 因此在变量未被赋值之前访问它们会抛出ReferenceError异常

定义方式

函数有两种定义方式, 以函数声明的方式和以函数表达式的形式.

函数声明

例子
function foo() {
    ...
}

函数表达式

例子
const foo = function () {
    ...
}

const foo = () => {
    ...
}

参数

自动捕获

非箭头函数内部有一个arguments对象, 它永远指向当前函数的调用者传入的所有参数.

例子

定义:

function foo(x) {
    for (let i=0; i<arguments.length; i++) {
        console.log('arg ' + i + ' = ' + arguments[i])
    }
}

foo(10, 20, 30)

执行:

$ node test.js
arg 0 = 10
arg 1 = 20
arg 2 = 30
Tip

箭头函数的arguments对象是从外围环境中继承的, 如果在箭头函数中尝试使用arguments, 将会访问到外围函数的arguments对象.

手动捕获

可以通过捕捉额外的参数并作为数组赋值给一个对象.

例子

定义:

function foo(a, b, ...rest_param) {
    console.log('a = ' + a)
    console.log('b = ' + b)
    console.log(rest_param)
}

foo(1, 2, 3, 4, 5, 6, 7)

执行:

$ node test.js
a = 1
b = 2
[ 3, 4, 5, 6, 7 ]

定义:

const foo = (a, b, ...rest_param) => {
    console.log('a = ' + a)
    console.log('b = ' + b)
    console.log(rest_param)
}

foo(1, 2, 3, 4, 5, 6, 7)

执行:

$ node test.js
a = 1
b = 2
[ 3, 4, 5, 6, 7 ]
Tip

若传入的参数连显式定义的参数都没填满, 则捕获的对象是一个空数组.

返回值

Tip

若没有return语句, 函数执行完毕后也会返回结果, 结果为undefined.

作用域

有三种作用域:

  • 块作用域
  • 局部/函数作用域
  • 全局作用域

块作用域

块作用域是ES6引入的. ES6还引入了两种新的关键字: letconst. letconst定义的变量在块外无法访问; var定义的变量在块外可以访问.

例子

定义:

{
    var name1 = "mc" 
    let name2 = "wenzexu"
}

console.log(name1)
console.log(name2)

执行:

$ node test.js
mc
C:\Users\wenzexu\test.js:8
console.log(name2)
            ^

ReferenceError: name2 is not defined
    at Object.<anonymous> (C:\Users\wenzexu\test.js:8:13)
    at Module._compile (node:internal/modules/cjs/loader:1358:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)
    at Module.load (node:internal/modules/cjs/loader:1208:32)
    at Module._load (node:internal/modules/cjs/loader:1024:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12)
    at node:internal/main/run_main_module:28:49

Node.js v20.13.1

局部/函数作用域

在函数内部定义的变量会变成函数的局部变量, let, const, var定义的变量在函数外都无法访问.

Tip
  • JS中函数的局部变量和Python中函数的局部变量不同. JS函数中的局部变量需要使用let, constvar声明, 而Python函数中的局部变量无需使用关键字声明.
  • 若局部变量名与外层变量名产生冲突, 会优先使用局部变量名.
例子

定义:

const foo = () => {
    let name = "wenzexu"
    console.log(name)
}

foo()
console.log(name)

执行:

$ node test.js

wenzexu
C:\Users\wenzexu\test.js:7
console.log(name)
            ^

ReferenceError: name is not defined
    at Object.<anonymous> (C:\Users\wenzexu\test.js:7:13)
    at Module._compile (node:internal/modules/cjs/loader:1358:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)
    at Module.load (node:internal/modules/cjs/loader:1208:32)
    at Module._load (node:internal/modules/cjs/loader:1024:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12)
    at node:internal/main/run_main_module:28:49

Node.js v20.13.1

全局作用域

在所有函数和块外面声明的变量具有全局作用域.

Tip
  • 在浏览器环境中, 全局对象是window
  • 在Node.js环境中, 全局对象是global, 为了保证模块之间命名空间隔离, 每个模块中顶层代码中的变量其实是"伪全局变量", 因为它们的外面还包着一层模块包装器, 并非真正的全局变量, 详情见模块.
  • 将变量/方法绑定到全局对象上

    笔记
    • var定义的顶层变量: 可以绑定到全局对象上
    • let定义的顶层变量: 不可以绑定到全局对象上
    • const定义的顶层变量: 不可以绑定到全局对象上
    • 函数声明: 可以绑定到全局对象上
    • var定义的函数表达式: 不可以绑定到全局对象上
    • const定义的函数表达式: 不可以绑定到全局对象上j
    • let定义的函数表达式: 不可以绑定到全局对象上j
    • var定义的顶层变量: 不可以绑定到全局对象上
    • let定义的顶层变量: 不可以绑定到全局对象上
    • const定义的顶层变量: 不可以绑定到全局对象上
    • 函数声明: 不可以绑定到全局对象上
    • var定义的函数表达式: 不可以绑定到全局对象上
    • const定义的函数表达式: 不可以绑定到全局对象上j
    • let定义的函数表达式: 不可以绑定到全局对象上j
注意
  • 在函数内部对未经声明的变量赋值, 该变量会在函数执行之后 变成全局变量.
  • 在块内部对未经声明的变量赋值, 该变量会变成全局变量.
例子

定义:

foo = () => {
    name = "wenzexu"
}

foo()
console.log(name)

执行:

wenzexu

定义:

{
    name = "wenzexu"
}

console.log(name)

执行:

wenzexu

this

在JS函数/方法中, this指向值得探讨:

  • 函数调用模式:
    • 严格模式: undefined
    • 非严格模式: window/global
  • 方法调用模式: 调用方法的对象
  • 构造器调用模式: 新建的对象
  • 箭头函数: 定义时外层this的值
  • call()/apply()/bind()定义: 第一个参数
  • HTML事件: 绑定的元素
Tip
  • 在浏览器环境中, this在顶层代码中指向的是window
  • 在Node.js环境中, this在顶层代码中指向的是module.exports, 因为整一个模块都被包含在模块包装器里面, Node.js在调用这个模块包装器时, 会用类似于call(), apply()函数的机制改变内部this的指向为module.exports(), 详情见模块. 但是this在函数调用模式(非严格模式)指向的是global对象, 这是由JS语言本身决定的.

函数调用模式

严格模式

严格模式下, this禁止指向window/global.

例子

定义:

function foo() {
    'use strict'
    console.log(this)
}

foo()

执行:

$ node test.js
undefined

非严格模式

非严格模式下, this指向window/global.

例子

定义:

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

foo()

执行:

$ node test.js
<ref *1> Object [global] {
    global: [Circular *1],
    clearImmediate: [Function: clearImmediate],
    setImmediate: [Function: setImmediate] {
        [Symbol(nodejs.util.promisify.custom)]: [Getter]
    },
    clearInterval: [Function: clearInterval],
    clearTimeout: [Function: clearTimeout],
    setInterval: [Function: setInterval],
    setTimeout: [Function: setTimeout] {
        [Symbol(nodejs.util.promisify.custom)]: [Getter]
    },
    queueMicrotask: [Function: queueMicrotask],
    structuredClone: [Function: structuredClone],
    atob: [Getter/Setter],
    btoa: [Getter/Setter],
    performance: [Getter/Setter],
    fetch: [Function: value],
    crypto: [Getter]
}

方法调用模式

this指向调用的对象.

例子

定义:

const obj = {
    a: 1,
    fun: function() {
        console.log(this)
    }
}

obj.fun()

执行:

$ node test.js
{ a: 1, fun: [Function: fun] }

定义:

const obj = {
    a: 1,
    b: {
        name: 'b',
        fun: function() {
            console.log(this)
        }
    }
}

obj.b.fun()

执行:

$ node test.js
{ name: 'b', fun: [Function: fun] }

构造器调用模式

this指向新建的对象.

例子

定义:

function Student(name, age, id) {
    this.name = name
    this.age = age
    this.id = id
}

let stu1 = new Student('wenzexu', 19, '001')
console.log(stu1.name, stu1.age, stu1.id)

let stu2 = new Student('ricolxwz', 18, '002')
console.log(stu2.name, stu2.age, stu2.id)

执行:

$ node test.js
wenzexu 19 001
ricolxwz 18 002

箭头函数

参见这里.

自定义this函数

总共有3个函数可以用于自定义this值: call()/apply()/bind().

注意

自定义this函数不要用在箭头函数上, 其第一个参数即this的指定值会被忽略.

call()

call()方法调用一个函数, 第一个参数用于指定函数内部this的值, 随后的参数用于传递给原函数.

例子

定义:

function greet(greeting, punctuation) {
    console.log(greeting + ', ' + this.name + punctuation);
}

const person = { name: 'Alice' };

greet.call(person, 'Hello', '!');

执行:

$ node test.js
Hello, Alice!

apply()

apply()方法调用一个函数, 第一个参数用于指定函数内部this的值, 第二个参数是一个数组, 用于传递参数给原函数.

例子

定义:

function greet(greeting, punctuation) {
    console.log(greeting + ', ' + this.name + punctuation);
}

const person = { name: 'Alice' };
param = ['Hello', '!']

greet.apply(person, param);

执行:

$ node test.js
Hello, Alice!

bind()

bind()方法创建一个函数, 第一个函数用于指定函数内部this的值, 随后的参数用于作为新创建函数的预设参数.

例子

定义:

function greet(greeting, punctuation) {
    console.log(greeting + ', ' + this.name + punctuation);
}

const person = { name: 'Alice' };

const boundGreet = greet.bind(person, 'Hello');
boundGreet('!');

执行:

$ node test.js
Hello, Alice!

HTML事件

this指向绑定的元素.

例子

定义:

<script>
    document.querySelector('button').onclick = function fun() {
        console.log(this)
    }
</script>

点击按钮后控制台输出:

<button onclick="fun()">[text]</button>

箭头函数

ES6新增了箭头函数. 箭头函数相当于一个匿名函数, 简化了函数的定义.

笔记
  • 返回值:

    • 只包含一个表达式: 可以省略return{}
    • 包含多条语句: 不可以省略return{}
  • 参数:

    • 只有一个参数: 可以省略()
    • 有多个参数: 不可以省略()
注意

只包含一个表达式的情况下要返回一个对象, 必须修改函数体的{}().

例子
const x => ({ foo: x })

箭头函数的this

箭头函数不会创建自己的this, 它会捕获自己在定义时(注意, 不是调用时)所处的外层环境的this并继承它的值.

例子

定义:

var id = "GLOBAL"
let obj = {
    id: "OBJ",
    a: function() {
        console.log(this.id)
    },
    b: () => {
        console.log(this.id)
    }
}

obj.a()
obj.b()

在浏览器中执行:

OBJ 
GLOBAL

定义:

this.id = "GLOBAL"
let obj = {
    id: "OBJ",
    a: function() {
        console.log(this.id)
    },
    b: () => {
        console.log(this.id)
    }
}

obj.a()
obj.b()

执行:

OBJ 
GLOBAL

Node.js模块中顶层代码中的this指向的是module.exports, 相当于我们往module.exports对象里面写入了一个属性id, 而箭头函数继承的是外层的this, 即module.exports. 详情见模块.

闭包

参见Python的闭包.

装饰器

JS的装饰器和Python的装饰器实现类似, 这里用到了解包...arguments对象.

例子

定义:

var count = 0;
var oldParseInt = parseInt; // 保存原函数

window.parseInt = function () {
    count += 1;
    return oldParseInt(...arguments); // 使用解包语法调用原函数
};

高阶函数

参见Python的高阶函数.

标签函数

标签函数允许我们将模版字符串自动转化为对函数的调用.

例子

定义:

const email = "test@example.com";
const password = 'hello123';

function sql(strings, ...exps) {
    console.log(strings)
    console.log(exps)
    // SQL操作
    return {
        // 数据对象
    };
}

const result = sql`SELECT * FROM users WHERE email=${email} AND password=${password}`;

console.log(JSON.stringify(result));

执行:

$ node test.js
[ 'SELECT * FROM users WHERE email=', ' AND password=', '' ]
[ 'test@example.com', 'hello123' ]
{}

生成器

生成器是ES6引入的. 由function*定义. 和Python的生成器基本相同.

不同的是:

  • 可以定义return作为最终返回值, 如果没有定义, 就是return undefined.
  • 执行next()每次遇到yield [value], 会返回一个对象{value: [value], done: true/false}, 然后暂停. done表示这个生成器是否已经执行结束了, 如果donetrue, 则[value]就是return的返回值.

  1. 函数定义和调用. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1023021087191360 

  2. 变量作用域与解构赋值. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1023021187855808 

  3. 方法. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1023023754768768 

  4. 箭头函数. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1031549578462080 

  5. 闭包. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016 

  6. 高阶函数. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1023021271742944 

  7. 标签函数. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1572792289984547 

  8. Generator. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1023024381818112 

评论