目录

⚙️ JavaScript Function 函数

函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。函数也是一个对象,也具有普通对象 的功能(能有属性)。

函数的作用:提高代码的复用。便于阅读。

# 声明函数

使用关键字 function

function functionName()
{
  // 执行代码
}

// 带有参数的函数:
// 变量和参数必须以一致的顺序出现。第一个变量就是第一个被传递的参数的给定的值,以此类推。🚨
function myFunction(var1,var2)
{
	// 代码
}
1
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)
  )
1
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 ); // 一个素数
  }
}
1
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

即使不打算重用,也可以创建函数。函数可以让代码结构更清晰,可读性更强。

# 函数表达式

function sayHi() {
  alert( "Hello" );
}
1
2
3

上所述的仅仅为函数的声明。函数表达式允许我们在任何表达式的中间创建一个新函数。

例如:

let sayHi = function() {
  alert( "Hello" );
};
1
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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

ask 的两个参数值 showOkshowCancel 可以被称为 回调函数 或简称 回调。主要思想是我们传递一个函数,并期望在稍后必要时将其「回调」。

简写为:

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."); }
);
1
2
3
4
5
6
7
8
9
10

没有命名的函数:匿名函数。这样的函数在 ask 外无法访问(因为没有对它们分配变量)。

回调函数正符合 JavaScript 的「一个函数表示一个行为」的原则。我们可以在变量之间传递它们,并在需要时运行。

# 箭头函数

箭头函数是一种简单创建函数的语法:

let func = (arg1, arg2, ..., argN) => expression;
1

创建了一个函数 func ,它接受参数 arg1..argN ,然后使用参数对右侧的 expression 求值并返回其结果。

相当于:

let func = function(arg1, arg2, ..., argN) {
  return expression;
};
1
2
3

🌰 例子:

let sum = (a, b) => a + b;
1
  • 如果只有一个参数,还可以省略掉 ()

    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
1
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>
1
2
3
4
5
6
7

🌰 带有返回值的函数

function myFunction(a,b)
{
    return a * b;
}
 
document.getElementById("demo").innerHTML=myFunction(4,3);
1
2
3
4
5
6

🌰 仅希望退出函数,可以使用 return

function myFunction(a,b)
{
    if (a>b)
    {
        return;
    }
    x=a+b
}
// 如果 a 大于 b,则上面的代码将退出函数,并不会计算 a 和 b 的总和 
1
2
3
4
5
6
7
8
9

🌰 立即执行函数:函数定义完,立刻被调用,往往只会执行一次:

(function(a,b){  
    console.log("a = "+a);  
    console.log("b = "+b);  
})(123,456); 
1
2
3
4

# 函数的关键字

break : 退出循环;

continue :跳过当次循环;

return :退出函数;

# this

this 是函数的上下问对象,根据函数的调用方式不同会指向不同的对象:

  • 如果单独使用, this 表示全局对象。

    在浏览器中,window 就是该全局对象为 [object Window]:

    严格模式下,如果单独使用, this 也是指向全局 (Global) 对象。

    var x = this;
    
    1
  • 在函数中, this 表示全局对象。

    在浏览器中,window 就是该全局对象为 [object Window]:

    严格模式下函数是没有绑定到 this 上,这时候 thisundefined

    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
    9
    var 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)

arugumentsthis 类似,都是函数中的隐含的参数。是类数组元素,用来封装函数执行过程中的实参。所以即使不定义实参,也可以通过 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;
}
1
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);  
    };  
}  
1
2
3
4
5
6
7
8

构造函数的执行流程

  • 创建一个新的对象,
  • 将新的对象作为函数的上下文对象 this
  • 执行函数中的代码,
  • 将新建的对象返回,
📢 上次更新: 2022/09/02, 10:18:16