前端必刷系列之红宝书——第 10 章

"红宝书" 通常指的是《JavaScript 高级程序设计》,这是一本由 Nicholas C. Zakas(尼古拉斯·扎卡斯)编写的 JavaScript 书籍,是一本广受欢迎的经典之作。这本书是一部翔实的工具书,满满的都是 JavaScript 知识和实用技术。

不管你有没有刷过红宝书,如果现在还没掌握好,那就一起来刷红宝书吧,go!go!go!

系列文章:

第一部分:基本知识(重点、反复阅读)

  1. 前端必刷系列之红宝书------第 1、2 章
  2. 前端必刷系列之红宝书------第 3 章
  3. 前端必刷系列之红宝书------第 4、5 章
  4. 前端必刷系列之红宝书------第 6 章

第二部分:进阶内容(重点、反复阅读)

  1. 前端必刷系列之红宝书------第 7 章
  2. 前端必刷系列之红宝书------第 8 章
  3. 前端必刷系列之红宝书------第 9 章
  4. 前端必刷系列之红宝书------第 10 章

第 10 章 函数

基本概念

  • 函数定义: 使用关键字(例如 function)定义函数。
  • 参数: 函数可以接受输入参数,这些参数是在调用函数时传递给函数的值。
  • 返回值: 函数可以产生输出,即返回值,通过 return 语句定义。

函数的类型

在不同的上下文中,函数可以分为几种不同的类型,其中包括但不限于:

  • 全局函数(Global Functions): 在整个程序中都可访问的函数。
  • 局部函数(Local Functions): 在其他函数内部定义的函数,只能在包含它的函数内部访问。
  • 匿名函数(Anonymous Functions): 没有名称的函数,通常作为参数传递给其他函数,或者作为立即执行的函数表达式(IIFE)使用。
  • 箭头函数(Arrow Functions): ES6 引入的一种简写语法,具有更短的语法和改变 this 行为的特点。
  • 高阶函数(Higher-Order Functions): 接受函数作为参数或返回一个函数的函数,常用于函数式编程。
  • 构造函数(Constructor Functions): 可以通过 new 关键字调用的函数,用于创建对象的实例。

函数的调用模式

函数的调用方式有四种主要模式:

  • 函数调用模式(Function Invocation Pattern): 直接调用函数,例如 func()
  • 方法调用模式(Method Invocation Pattern): 通过对象调用的函数,例如 object.method()
  • 构造函数调用模式(Constructor Invocation Pattern): 使用 new 关键字调用的函数,创建对象实例。
  • 间接调用模式(Indirect Invocation Pattern): 使用 callapply 方法调用函数,可以改变函数的上下文。

箭头函数

箭头函数没有自己的 this,它继承自外围作用域(即定义时所在的作用域)的 this。这与传统的函数(函数表达式或函数声明)不同,它们的 this 值会在运行时动态确定。

  • 不适用于构造函数:箭头函数不能被用作构造函数,不能使用 new 关键字调用。
  • 没有 arguments 对象:箭头函数没有自己的 arguments 对象,它继承自外围作用域的 arguments
  • 不能用作 generator 函数:箭头函数不能使用 yield 关键字创建迭代器。
  • 没有 thisargumentssupernew.target 绑定。
  • 不能通过 call()apply()bind() 方法修改 this
  • 没有原型 prototype 属性。

函数声明和函数表达式

函数声明是一种定义函数的方式,使用 function 关键字后跟函数名。函数声明提升至作用域的顶部,因此可以在声明之前调用函数。

js 复制代码
function greet() {
  console.log('Hello, World!');
}

greet(); // 调用函数

函数表达式是将函数赋值给变量的一种方式,函数表达式的函数名是可选的。这种方式的函数通常是匿名函数,但也可以有名字。

函数表达式的变量不会提升,只有在赋值语句执行时才会创建。

js 复制代码
// 匿名函数表达式
const greet = function() {
  console.log('Hello, World!');
};

// 有名字的函数表达式
const sayHello = function greet() {
  console.log('Hello, World!');
};

函数声明 vs 函数表达式:

  • 提升(Hoisting): 函数声明会被提升到当前作用域的顶部,可以在声明之前调用。而函数表达式只有在赋值语句执行时才会创建,不会提升。
  • 调用时间: 函数声明可以在定义之前和之后调用,而函数表达式只能在定义之后调用。
  • 命名: 函数声明必须有名称,而函数表达式可以是匿名的,也可以有名字。
  • 赋值给变量: 函数声明不能被赋值给变量,而函数表达式通常是赋值给变量的。

在函数表达式中,可以为函数指定一个名字,这被称为命名函数表达式。命名函数表达式的主要用途是在函数内部引用自身,方便递归调用。

js 复制代码
const factorial = function self(n) {
  if (n === 0 || n === 1) {
    return 1;
  } else {
    return n * self(n - 1);
  }
};

console.log(factorial(5)); // 输出: 120

IIFE 是一种立即调用的匿名函数表达式,通常用于创建私有作用域,防止变量污染全局作用域。

js 复制代码
(function() {
  // 这里是一个私有作用域
  const localVar = 'IIFE Example';
  console.log(localVar);
})();
// localVar 在此处不可访问

callapplybind

callapplybind 都是 JavaScript 中用于改变函数执行上下文(this 值)的方法,它们在用法和效果上有一些区别。

js 复制代码
function greet(name) {
  console.log(`Hello, ${name}! My age is ${this.age}.`);
}

const person = { age: 25 };

greet.call(person, 'John');
// 输出: Hello, John! My age is 25.

apply 方法与 call 类似,也是在函数执行时立即调用。区别在于它接收一个数组或类数组对象作为参数,其中数组的每个元素都会作为参数传递给函数。

js 复制代码
function greet(name, city) {
  console.log(`Hello, ${name}! I live in ${city}. My age is ${this.age}.`);
}

const person = { age: 25 };

greet.apply(person, ['John', 'New York']);
// 输出: Hello, John! I live in New York. My age is 25.

bind 方法也是用于改变函数执行上下文,但它不会立即调用函数,而是返回一个新的函数。新函数在调用时将具有绑定的 this 值和可选的参数。

js 复制代码
function greet(name) {
  console.log(`Hello, ${name}! My age is ${this.age}.`);
}

const person = { age: 25 };
const boundGreet = greet.bind(person, 'John');

boundGreet();
// 输出: Hello, John! My age is 25.

尾调用优化

尾调用优化(Tail Call Optimization,TCO)是一种编译器优化技术,用于优化尾递归函数的性能

尾调用是指函数的最后一个操作是调用另一个函数

尾调用优化的目标是减少调用栈的大小,提高程序的性能和内存利用率。

js 复制代码
// 尾递归函数,计算阶乘
function factorial(n, result = 1) {
  if (n <= 1) {
    return result;
  }
  return factorial(n - 1, n * result);
}

console.log(factorial(5)); // 输出: 120

ES6 尾调用优化的关键:如果函数的逻辑允许基于尾调用将其销毁,则引擎就会那么做。

尾调用优化的条件:

尾调用优化的条件就是确定外部栈帧真的没有必要存在了

  • 代码在严格模式下执行。
  • 外部函数的返回值是对尾调用函数的调用。
  • 尾调用函数返回后不需要执行额外的逻辑。
  • 尾调用函数不是引用外部函数作用域中自由变量的闭包。
js 复制代码
"use strict"

// 基础框架
function fib(n){
    return fibImp1(0, 1, n)
}

// 执行递归
function fibImp1(a, b, n){
    if(n===0){
        return a
    }
    return fibImp1(b, a+b, n-1)
}

闭包

闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的

js 复制代码
function outerFunction() {
  let outerVariable = 'I am from outer function';

  function innerFunction() {
    console.log(outerVariable);
  }

  return innerFunction;
}

const closure = outerFunction();
closure(); // 输出: I am from outer function
  • 访问外部变量: 闭包可以访问包含它的外部函数的变量,即使外部函数已经执行完毕。
  • 保持状态: 由于闭包内部可以访问外部变量,可以用闭包来保持一些状态信息,形成类似私有变量的效果。
  • 数据隐藏和封装: 通过闭包可以创建私有变量,实现数据的隐藏和封装。
  • 实现模块化: 使用闭包可以创建模块,将相关的功能和变量封装在一起。
  • 异步编程: 在回调函数和事件处理函数中经常使用闭包,以便访问外部的变量。
  • 内存管理: 闭包可能导致内存泄漏,因为它会保持对外部变量的引用,使得这些变量不能被垃圾回收。
  • 性能: 过度使用闭包可能导致性能问题,因为每个闭包都会创建一个新的作用域链。

this 对象

如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后就可以按照顺序应用下边的规则来判断 this 的绑定对象。

  1. 箭头函数会继承外层函数调用的 this 绑定。
  2. new 调用?绑定到新创建的对象。
  3. call 或 apply 或 bind 调用?绑定到指定的对象。
  4. 上下文对象调用?绑定到那个上下文对象。
  5. 默认:严格模式下帮点个到 undefined,否则绑定到全局对象

未完待续...

参考资料

《JavaScript 高级程序设计》(第 4 版)

相关推荐
神之王楠12 分钟前
如何通过js加载css和html
javascript·css·html
余生H17 分钟前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍20 分钟前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai24 分钟前
网站开发的发展(后端路由/前后端分离/前端路由)
前端
流烟默36 分钟前
Vue中watch监听属性的一些应用总结
前端·javascript·vue.js·watch
2401_857297911 小时前
招联金融2025校招内推
java·前端·算法·金融·求职招聘
茶卡盐佑星_1 小时前
meta标签作用/SEO优化
前端·javascript·html
与衫1 小时前
掌握嵌套子查询:复杂 SQL 中 * 列的准确表列关系
android·javascript·sql
Ink1 小时前
从底层看 path.resolve 实现
前端·node.js
金灰1 小时前
HTML5--裸体回顾
java·开发语言·前端·javascript·html·html5