⚙️ JavaScript Function 函数
函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。函数也是一个对象,也具有普通对象 的功能(能有属性)。
函数的作用:提高代码的复用。便于阅读。
# 声明函数
使用关键字 function
。
function functionName()
{
// 执行代码
}
// 带有参数的函数:
// 变量和参数必须以一致的顺序出现。第一个变量就是第一个被传递的参数的给定的值,以此类推。🚨
function myFunction(var1,var2)
{
// 代码
}
2
3
4
5
6
7
8
9
10
11
# 局部变量与外部(全局)变量
- 在函数中声明的变量只在该函数内部可见。
- 函数对外部变量拥有全部的访问权限。函数也可以修改外部变量。
- 只有在没有局部变量的情况下才会使用外部变量。如果在函数内部声明了同名变量,那么函数会 遮蔽 外部变量。
全局变量:任何函数之外声明的变量,都被称为 全局 变量。
全局变量在任意函数中都是可见的(除非被局部变量遮蔽)。
减少全局变量的使用是一种很好的做法。现代的代码有很少甚至没有全局变量。大多数变量存在于它们的函数中。但是有时候,全局变量能够用于存储项目级别的数据。
# 函数的参数
# 形参
(形式参数)定义函数时,可以在 ()
中定义一个或多个形参,形参之间使用 ,
隔开。定义形参就相当于在函数内声明了对应的变量但是并不赋值,形参会在调用时才赋值。
# 实参
(实际参数)调用函数时,可以在 ()
传递实参,传递的实参会赋值给对应的形参,调用函数时 JavaScript 解析器不会检查实参的类型和个数,可以传递任意数据类型的值。
如果实参的数量大于形参,多余实参将不会赋值;
如果实参的数量小于形参,则没有对应实参的形参将会赋值
undefined
;(JavaScript 新特性)默认参数:可以使用
=
为函数声明中的参数指定所谓的「默认」(如果对应参数的值未被传递则使用)值:function showMessage(from, text = "no text given") { alert( from + ": " + text ); } showMessage("Ann"); // Ann: no text given
1
2
3
4
5默认值也可以为一个更复杂的表达式,并且只会在缺少参数时才会被计算和分配。
function showMessage(from, text = anotherFunction()) { // anotherFunction() 仅在没有给定 text 时执行 // 其运行结果将成为 text 的值 }
1
2
3
4默认值也可以放在放在函数执行(相较更后期)而不是函数声明时。通过将参数与
undefined
进行比较,来检查该参数是否在函数执行期间被传递进来。(使用||
和??
都可以实现。)
# 返回值
返回值为函数可以将一个值返回到调用代码中作为结果。
return
后边的代码都不会执行,一旦执行到return
语句时,函数将会立刻退出。return
后可以跟任意类型的值,可以是基本数据类型,也可以是一个对象。- 如果
return
后不跟值,或者是不写return
则函数默认返回undefined
。
注意
不要在 return
与返回值之间添加新行。换行后的表达式会被忽略。如果想要将返回的表达式写成跨多行的形式,那么应该在 return
的同一行开始写此表达式。或者至少按照如下的方式放上左括号:
return (
some + long + expression
+ or +
whatever * f(a) + f(b)
)
2
3
4
5
# 函数的命名规范
函数就是行为(action)。所以它们的名字通常是动词。它应该简短且尽可能准确地描述函数的作用。这样读代码的人就能清楚地知道这个函数的功能。
常见的函数前缀: 有了前缀,只需瞥一眼函数名,就可以了解它的功能是什么,返回什么样的值。
"get…"
—— 返回一个值,"calc…"
—— 计算某些内容,"create…"
—— 创建某些内容,"check…"
—— 检查某些内容并返回 boolean 值,等。
提示
函数遵循「一个函数一个行为」的原则:一个函数应该只包含函数名所指定的功能,而不是做更多与函数名无关的功能。
两个独立的行为通常需要两个函数,即使它们通常被一起调用(在这种情况下,我们可以创建第三个函数来调用这两个函数)。
# 函数是注释般的存在
一个单独的函数不仅更容易测试和调试 —— 它的存在本身就是一个很好的注释!
🌰 例子:
function showPrimes(n) {
nextPrime: for (let i = 2; i < n; i++) {
for (let j = 2; j < i; j++) {
if (i % j == 0) continue nextPrime;
}
alert( i ); // 一个素数
}
}
2
3
4
5
6
7
8
9
10
function showPrimes(n) {
for (let i = 2; i < n; i++) {
if (!isPrime(i)) continue;
alert(i); // 一个素数
}
}
function isPrime(n) {
for (let i = 2; i < n; i++) {
if ( n % i == 0) return false;
}
return true;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
即使不打算重用,也可以创建函数。函数可以让代码结构更清晰,可读性更强。
# 函数表达式
function sayHi() {
alert( "Hello" );
}
2
3
上所述的仅仅为函数的声明。函数表达式允许我们在任何表达式的中间创建一个新函数。
例如:
let sayHi = function() {
alert( "Hello" );
};
2
3
function
关键字后面没有函数名。函数表达式允许省略函数名。- 与原来声明函数的含义相同,都是「创建一个函数并将其放入变量
sayHi
中」。
# 函数表达式与函数声明比较
语法:
函数声明:在主代码流中声明为单独的语句的函数。
// 函数声明 function sum(a, b) { return a + b; }
1
2
3
4函数表达式:在一个表达式中或另一个语法结构中创建的函数。下面这个函数是在赋值表达式
=
右侧创建的:// 函数表达式 let sum = function(a, b) { return a + b; };
1
2
3
4
JavaSript 引擎创建函数的时机:
函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用。 意味着只有赋值了才能使用。
** 在函数声明被定义之前,它就可以被调用。** 意味着对于整个脚本,无论放在何时何地,都可以被调用。
这是内部算法的原故。当 JavaScript 准备 运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数。将其视为「初始化阶段」。
在处理完所有函数声明后,代码才被执行。所以运行时能够使用这些函数。
函数声明的另一个特殊功能:「块级作用域」。严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见。
let age = prompt("What is your age?", 18); if (age < 18) { function welcome() { alert("Hello!"); } } else { function welcome() { alert("Greetings!"); } } welcome();
1
2
3
4
5
6
7
8
9
10
11
12
13如果想要在代码块外可见,可以使用函数表达式,先在声明变量,在代码块内声明函数。
let age = prompt("What is your age?", 18); let welcome; if (age < 18) { welcome = function() { alert("Hello!"); }; } else { welcome = function() { alert("Greetings!"); }; } welcome();
1
2
3
4
5
6
7
8
9
10
11
12
13
如何选择函数声明与函数表达式? 函数声明其实能为组织代码提供更多的灵活性,并且能在声明函数之前调用,并且对代码的可读性也更好。但是如果由于「块级作用域」的问题,函数表达式会更适合。
# 调用函数
# 函数是一个值
无论函数是如何创建的,函数都是一个值。
没有添加
()
时:在 JavaScript 中,函数是一个值,所以我们可以把它当成值对待。例如:最后只会为一个字符串值,即函数的源码。function sayHi() { alert( "Hello" ); } alert( sayHi );
1
2
3
4
5
函数的复制:
function sayHi() { // (1) 创建 alert( "Hello" ); } // 当 let sayHi = function() { ... } 时也相同 let func = sayHi; // (2) 复制 func(); // Hello // (3) 运行复制的值(正常运行)! sayHi(); // Hello
1
2
3
4
5
6
7
8注意:
sayHi
后面没有括号。如果有括号,func = sayHi()
会把sayHi()
的调用结果写进func
,而不是sayHi
函数 本身。分号的用法:
function sayHi() { // ... } let sayHi = function() { // ... };
1
2
3
4
5
6
7这里函数表达式是在赋值语句
let sayHi = ...;
中以function(…) {…}
的形式创建的。建议在语句末尾加上分号;
,它不是函数语法的一部分。分号用于更简单的赋值,例如
let sayHi = 5;
,它也用于函数赋值。
# 回调函数
🌰 例子:
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
function showOk() {
alert( "You agreed." );
}
function showCancel() {
alert( "You canceled the execution." );
}
ask("Do you agree?", showOk, showCancel);
2
3
4
5
6
7
8
9
10
11
12
13
14
ask
的两个参数值 showOk
和 showCancel
可以被称为 回调函数 或简称 回调。主要思想是我们传递一个函数,并期望在稍后必要时将其「回调」。
简写为:
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
ask(
"Do you agree?",
function() { alert("You agreed."); },
function() { alert("You canceled the execution."); }
);
2
3
4
5
6
7
8
9
10
没有命名的函数:匿名函数。这样的函数在 ask
外无法访问(因为没有对它们分配变量)。
回调函数正符合 JavaScript 的「一个函数表示一个行为」的原则。我们可以在变量之间传递它们,并在需要时运行。
# 箭头函数
箭头函数是一种简单创建函数的语法:
let func = (arg1, arg2, ..., argN) => expression;
创建了一个函数
func
,它接受参数arg1..argN
,然后使用参数对右侧的expression
求值并返回其结果。相当于:
let func = function(arg1, arg2, ..., argN) { return expression; };
1
2
3
🌰 例子:
let sum = (a, b) => a + b;
如果只有一个参数,还可以省略掉
()
。let double = n => n * 2;
1如果没有参数,则括号是空的,但是括号必须保留。
let sayHi = () => alert("Hello!"); sayHi();
1
2
3箭头函数可看作为函数表达式:
let age = prompt("What is your age?", 18); let welcome = (age < 18) ? () => alert('Hello'!) : () => alert("Greetings!"); welcome();
1
2
3
4
5
6
7多行箭头函数:用花括号括起来之后,需要包含
return
才能返回值(就像常规函数一样)。let sum = (a, b) => { / let result = a + b; return result; };
1
2
3
4
5
# 函数的调用多种方法
- 直接调用
test()
; - 通过对象调用
obj.test
; - 通过
new
调用 test.call/apply(obj)
让test
成为obj
的方法进行调用。
var obj = {}
function test2 (){
this.xxx = 'aaa'
}
// obj.test2()
test2.call(obj) // 相当于obj.test2
// 可以让一个函数称为指定对象的方法进行调用
console.log(obj.xxx) // aaa
2
3
4
5
6
7
8
🌰 网页按钮调用函数功能:
<button onclick="myFunction('Harry Potter','Wizard')">点击这里</button>
<script>
function myFunction(name,job){
alert("Welcome " + name + ", the " + job);
}
</script>
2
3
4
5
6
7
🌰 带有返回值的函数:
function myFunction(a,b)
{
return a * b;
}
document.getElementById("demo").innerHTML=myFunction(4,3);
2
3
4
5
6
🌰 仅希望退出函数,可以使用 return
function myFunction(a,b)
{
if (a>b)
{
return;
}
x=a+b
}
// 如果 a 大于 b,则上面的代码将退出函数,并不会计算 a 和 b 的总和
2
3
4
5
6
7
8
9
🌰 立即执行函数:函数定义完,立刻被调用,往往只会执行一次:
(function(a,b){
console.log("a = "+a);
console.log("b = "+b);
})(123,456);
2
3
4
# 函数的关键字
break
: 退出循环;
continue
:跳过当次循环;
return
:退出函数;
# this
this
是函数的上下问对象,根据函数的调用方式不同会指向不同的对象:
如果单独使用,
this
表示全局对象。在浏览器中,window 就是该全局对象为 [object Window]:
严格模式下,如果单独使用,
this
也是指向全局 (Global) 对象。var x = this;
1在函数中,
this
表示全局对象。在浏览器中,window 就是该全局对象为 [object Window]:
严格模式下函数是没有绑定到 this 上,这时候
this
是undefined
。var name = 'simon'; function shows(){ console.log(this.name); } shows();
1
2
3
4
5
6以方法的形式调用时,
this
表示该方法所属的对象:// 创建一个对象 var person = { firstName: "John", lastName : "Doe", id : 5566, fullName : function() { return this.firstName + " " + this.lastName; } }; // this 表示 person 对象。 // fullName 方法所属的对象就是 person。
1
2
3
4
5
6
7
8
9
10
11
12对象方法中绑定:
var person = { firstName : "John", lastName : "Doe", id : 5566, myFunction : function() { return this; } }; // this 是 person 对象,person 对象是函数的所有者
1
2
3
4
5
6
7
8
9var person = { firstName: "John", lastName : "Doe", id : 5566, fullName : function() { return this.firstName + " " + this.lastName; } }; // this.firstName 表示 this (person) 对象的 firstName 属性
1
2
3
4
5
6
7
8
9
10事件中, this 指向接收事件的 HTML 元素:
<button onclick="this.style.display='none'"> 点我后我就消失了 </button>
1
2
3显式函数绑定:在 JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。
var person1 = { fullName: function() { return this.firstName + " " + this.lastName; } }'' var person2 = { firstName:"John", lastName: "Doe", } person1.fullName.call(person2); // 返回 "John Doe" //使用 person2 作为参数来调用 person1.fullName 方法时, this 将指向 person2, 即便它是 person1 的方法
1
2
3
4
5
6
7
8
9
10
11
call()
: 直接传递函数的实参;
apply()
:通过第一个实参来制定函数中的 this
JavaScript 中 call ()、apply ()、bind () 的用法 | 菜鸟教程 (runoob.com) (opens new window)
aruguments
和 this
类似,都是函数中的隐含的参数。是类数组元素,用来封装函数执行过程中的实参。所以即使不定义实参,也可以通过 arguments
来使用实参。arguments 中有一个属性 callee 表示当前执行的函数对象;
x = findMax(1, 123, 500, 115, 44, 88);
function findMax() {
var i, max = arguments[0];
if(arguments.length < 2) return max;
for (i = 0; i < arguments.length; i++) {
if (arguments[i] > max) {
max = arguments[i];
}
}
return max;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
JavaScript 函数参数 | 菜鸟教程 (runoob.com) (opens new window)
# 构造函数
构造函数是专门用来创建对象的函数,
一个构造函数我们也可以称为一个类;
通过一个构造函数创建的对象,我们称该对象时这个构造函数的实例;
通过同一个构造函数创建的对象,我们称为一类对象;
构造函数如果直接调用,它就是一个普通函数;如果使用
new
来调用,则它就是一个构造函数;
function Person(name , age , gender){
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = function(){
alert(this.name);
};
}
2
3
4
5
6
7
8
构造函数的执行流程:
- 创建一个新的对象,
- 将新的对象作为函数的上下文对象
this
, - 执行函数中的代码,
- 将新建的对象返回,