函数
变量提升¶
变量可以在其使用之后声明. 这是因为JS引擎会将所有的声明移至当前作用域的顶部.
Tip
- 声明和赋值不同
- 所有的声明, 包括
var
,let
,const
,function
,class
,function*
都会被提升 var
,function
,function*
在提升的时候会被赋值为undefined
或相应的函数体let
,const
,class
在提升的时候不会被赋值. 因此在变量未被赋值之前访问它们会抛出ReferenceError
异常
定义方式¶
函数有两种定义方式, 以函数声明的方式和以函数表达式的形式.
函数声明¶
函数表达式¶
参数¶
自动捕获¶
非箭头函数内部有一个arguments
对象, 它永远指向当前函数的调用者传入的所有参数.
例子
定义:
function foo(x) {
for (let i=0; i<arguments.length; i++) {
console.log('arg ' + i + ' = ' + arguments[i])
}
}
foo(10, 20, 30)
执行:
Tip
箭头函数的arguments
对象是从外围环境中继承的, 如果在箭头函数中尝试使用arguments
, 将会访问到外围函数的arguments
对象.
手动捕获¶
可以通过捕捉额外的参数并作为数组赋值给一个对象.
例子
Tip
若传入的参数连显式定义的参数都没填满, 则捕获的对象是一个空数组.
返回值¶
Tip
若没有return
语句, 函数执行完毕后也会返回结果, 结果为undefined
.
作用域¶
有三种作用域:
- 块作用域
- 局部/函数作用域
- 全局作用域
块作用域¶
块作用域是ES6引入的. ES6还引入了两种新的关键字: let
和const
. let
和const
定义的变量在块外无法访问; var
定义的变量在块外可以访问.
例子
定义:
执行:
$ 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
,const
或var
声明, 而Python函数中的局部变量无需使用关键字声明. - 若局部变量名与外层变量名产生冲突, 会优先使用局部变量名.
例子
定义:
执行:
$ 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
定义的函数表达式: 不可以绑定到全局对象上jlet
定义的函数表达式: 不可以绑定到全局对象上j
var
定义的顶层变量: 不可以绑定到全局对象上let
定义的顶层变量: 不可以绑定到全局对象上const
定义的顶层变量: 不可以绑定到全局对象上- 函数声明: 不可以绑定到全局对象上
var
定义的函数表达式: 不可以绑定到全局对象上const
定义的函数表达式: 不可以绑定到全局对象上jlet
定义的函数表达式: 不可以绑定到全局对象上j
注意
- 在函数内部对未经声明的变量赋值, 该变量会在函数执行之后 变成全局变量.
- 在块内部对未经声明的变量赋值, 该变量会变成全局变量.
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
.
非严格模式¶
非严格模式下, this
指向window
/global
.
例子
定义:
执行:
$ 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
指向调用的对象.
例子
构造器调用模式¶
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)
执行:
箭头函数¶
参见这里.
自定义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', '!');
执行:
apply()
¶
apply()
方法调用一个函数, 第一个参数用于指定函数内部this
的值, 第二个参数是一个数组, 用于传递参数给原函数.
例子
定义:
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'Alice' };
param = ['Hello', '!']
greet.apply(person, param);
执行:
bind()
¶
bind()
方法创建一个函数, 第一个函数用于指定函数内部this
的值, 随后的参数用于作为新创建函数的预设参数.
例子
定义:
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'Alice' };
const boundGreet = greet.bind(person, 'Hello');
boundGreet('!');
执行:
HTML事件¶
this
指向绑定的元素.
例子
定义:
点击按钮后控制台输出:
箭头函数¶
ES6新增了箭头函数. 箭头函数相当于一个匿名函数, 简化了函数的定义.
笔记
-
返回值:
- 只包含一个表达式: 可以省略
return
和{}
- 包含多条语句: 不可以省略
return
和{}
- 只包含一个表达式: 可以省略
-
参数:
- 只有一个参数: 可以省略
()
- 有多个参数: 不可以省略
()
- 只有一个参数: 可以省略
箭头函数的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()
在浏览器中执行:
定义:
this.id = "GLOBAL"
let obj = {
id: "OBJ",
a: function() {
console.log(this.id)
},
b: () => {
console.log(this.id)
}
}
obj.a()
obj.b()
执行:
Node.js模块中顶层代码中的this
指向的是module.exports
, 相当于我们往module.exports
对象里面写入了一个属性id
, 而箭头函数继承的是外层的this
, 即module.exports
. 详情见模块.
闭包¶
参见Python的闭包.
装饰器¶
JS的装饰器和Python的装饰器实现类似, 这里用到了解包...
和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));
执行:
生成器¶
生成器是ES6引入的. 由function*
定义. 和Python的生成器基本相同.
不同的是:
- 可以定义
return
作为最终返回值, 如果没有定义, 就是return undefined
. - 执行
next()
每次遇到yield [value]
, 会返回一个对象{value: [value], done: true/false}
, 然后暂停.done
表示这个生成器是否已经执行结束了, 如果done
为true
, 则[value]
就是return
的返回值.
-
函数定义和调用. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1023021087191360 ↩
-
变量作用域与解构赋值. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1023021187855808 ↩
-
方法. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1023023754768768 ↩
-
箭头函数. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1031549578462080 ↩
-
闭包. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1023021250770016 ↩
-
高阶函数. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1023021271742944 ↩
-
标签函数. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1572792289984547 ↩
-
Generator. (n.d.). Retrieved June 21, 2024, from https://www.liaoxuefeng.com/wiki/1022910821149312/1023024381818112 ↩