杰瑞科技汇

JavaScript高级教程,如何突破进阶瓶颈?

JavaScript 高级教程:从核心到精通

引言:什么是“高级”的 JavaScript?

在学习 JavaScript 的过程中,我们通常会经历几个阶段:

JavaScript高级教程,如何突破进阶瓶颈?-图1
(图片来源网络,侵删)
  1. 入门阶段:学习基本语法、变量、数据类型、条件语句、循环。
  2. 进阶阶段:学习函数、对象、数组、DOM 操作、事件处理。
  3. 高级阶段:理解语言背后的工作原理,掌握复杂的编程范式,能够写出高性能、可维护、可扩展的代码。

本教程将带你进入第三阶段,重点围绕以下几个方面展开:

  • 执行上下文与作用域链:理解代码运行的底层环境。
  • 闭包:JavaScript 最强大也最容易被误解的特性。
  • this 关键字:彻底搞清 this 的指向问题。
  • 原型与原型链:理解 JavaScript 的继承机制。
  • 异步编程:从回调到 Promise,再到 async/await
  • ES6+ 新特性:现代 JavaScript 开发的必备知识。
  • 函数式编程思想:提升代码的健壮性和可维护性。
  • 性能优化:写出更快、更高效的代码。

第一部分:JavaScript 核心机制

执行上下文 与作用域链

这是理解 JavaScript 如何执行代码的基石。

执行上下文:可以理解为代码执行时的环境,每当 JavaScript 代码运行时,它都在一个特定的执行上下文中,主要分为三种:

  1. 全局执行上下文:代码首次执行时进入的环境,在浏览器中,它是 window 对象,一个程序中只会有一个全局执行上下文。
  2. 函数执行上下文:当函数被调用时,会为该函数创建一个新的执行上下文,可以有任意多个函数执行上下文。
  3. Eval 执行上下文:在 eval 函数内部执行的代码也有自己的执行上下文(不推荐使用 eval)。

执行上下文的生命周期

JavaScript高级教程,如何突破进阶瓶颈?-图2
(图片来源网络,侵删)
  1. 创建阶段
    • 创建变量对象:扫描函数的参数、函数声明和变量声明,并将它们添加到变量对象中,变量声明会被提升,但赋值不会。
    • 建立 this 指向:确定 this 关键字在当前上下文中的值。
    • 建立作用域链:将当前活动对象的父级作用域链链接起来。
  2. 执行阶段

    变量赋值、函数执行、代码执行。

作用域链:当在函数中查找一个变量时,JavaScript 引擎会沿着当前函数的作用域链向上查找,直到找到该变量或到达全局作用域为止。

function outer() {
  const name = 'Alice';
  function inner() {
    console.log(name); // 会沿着作用域链向上查找 outer 的作用域
  }
  inner();
}
outer(); // 输出 'Alice'

实践:变量提升

console.log(myName); // 输出 undefined,而不是报错
var myName = 'JavaScript';
// 上述代码等同于:
var myName; // 声明被提升
console.log(myName); // myName 是 undefined
myName = 'JavaScript'; // 赋值操作在原地

闭包

闭包是 JavaScript 中一个非常重要且强大的概念,官方定义是:函数和其周围状态(词法环境)的引用捆绑在一起构成闭包。

JavaScript高级教程,如何突破进阶瓶颈?-图3
(图片来源网络,侵删)

闭包就是指一个函数可以访问并记住其词法作用域,即使该函数在其词法作用域之外执行。

闭包的形成条件

  1. 内部函数
  2. 内部函数引用了外部函数的变量

闭包的应用场景

  1. 创建私有变量:模拟面向对象中的私有属性。

    function createCounter() {
      let count = 0; // count 是一个私有变量
      return {
        increment: function() {
          count++;
          console.log(count);
        },
        decrement: function() {
          count--;
          console.log(count);
        }
      };
    }
    const counter = createCounter();
    counter.increment(); // 1
    counter.increment(); // 2
    counter.decrement(); // 1
    // console.log(count); // Uncaught ReferenceError: count is not defined
  2. 函数柯里化:创建已经设置好一个或多个参数的函数。

  3. 防抖 和节流:优化事件处理频率。


this 关键字

this 的值在函数被调用时确定,而不是在函数定义时,理解 this 的指向至关重要。

this 的绑定规则有以下几种,优先级从高到低:

  1. new 绑定:使用 new 调用函数时,this 指向新创建的对象。

    function Person(name) {
      this.name = name;
    }
    const p = new Person('Tom');
    console.log(p.name); // 'Tom'
  2. 显式绑定:使用 call(), apply(), bind() 方法来指定 this

    function sayHello() {
      console.log(`Hello, ${this.name}`);
    }
    const obj = { name: 'Jerry' };
    sayHello.call(obj); // 'Hello, Jerry'
  3. 隐式绑定:当一个函数作为对象的方法被调用时,this 指向该对象。

    const obj = {
      name: 'Bob',
      sayName: function() {
        console.log(this.name);
      }
    };
    obj.sayName(); // 'Bob'
  4. 默认绑定:当函数在非严格模式下独立调用时,this 指向全局对象(浏览器中是 window),在严格模式下,thisundefined

    function say() {
      console.log(this);
    }
    say(); // 在浏览器中输出 window 对象

箭头函数中的 this: 箭头函数没有自己的 this,它会从其外层(词法)作用域继承 this,这使得箭头函数非常适合用在回调函数中。

const obj = {
  name: 'Arrow',
  sayName: function() {
    // 这里的 this 指向 obj
    setTimeout(() => {
      // 箭头函数的 this 继承自外层 sayName 函数的 this,所以也是 obj
      console.log(this.name); // 'Arrow'
    }, 1000);
  }
};
obj.sayName();

第二部分:面向对象与继承

原型与原型链

JavaScript 没有传统的基于类的继承,而是使用原型继承

原型: 每个 JavaScript 对象(除了 null)在创建时都会关联另一个对象,这个关联的对象就是它的原型,对象可以从原型那里继承属性和方法。

  • __proto__:是每个实例对象上的属性,指向其构造函数的 prototype
  • prototype:是每个构造函数上的属性,指向一个对象,该对象的所有属性和方法都将被构造函数的实例共享。

原型链: 当访问一个对象的属性时,如果该对象自身没有这个属性,JavaScript 引擎会去它的原型对象上查找,如果原型对象上也没有,就会去原型的原型上查找,直到找到 Object.prototype 的原型为 null 为止,这个由原型对象链接起来的链条就是原型链

function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`);
};
const p1 = new Person('Alice');
const p2 = new Person('Bob');
p1.sayHello(); // 'Hello, I'm Alice'
p2.sayHello(); // 'Hello, I'm Bob'
console.log(p1.__proto__ === Person.prototype); // true
console.log(p1.__proto__.constructor === Person); // true
console.log(p1.__proto__.__proto__ === Object.prototype); // true

ES6 Class 语法糖

ES6 引入了 class 关键字,它为创建对象和实现继承提供了一种更清晰、更像传统面向语言的语法,但这只是语法糖,其底层仍然是基于原型的。

// ES6 Class
class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log(`Hello, I'm ${this.name}`);
  }
}
class Student extends Person {
  constructor(name, grade) {
    super(name); // 调用父类的 constructor
    this.grade = grade;
  }
  study() {
    console.log(`${this.name} is studying.`);
  }
}
const s = new Student('Charlie', 10);
s.sayHello(); // 'Hello, I'm Charlie' (继承自 Person)
s.study();    // 'Charlie is studying.'

第三部分:异步编程进阶

从回调到 async/await

异步编程是 JavaScript 的核心,用于处理非阻塞操作(如网络请求、文件读写)。

  1. 回调函数:最原始的方式,容易产生“回调地狱”(Callback Hell)。

    function fetchData(callback) {
      setTimeout(() => {
        callback('Data received');
      }, 1000);
    }
    fetchData(data => {
      console.log(data);
      // 如果还有异步操作,会继续嵌套
    });
  2. Promise:代表一个异步操作的最终完成或失败,它有三种状态:pending, fulfilled, rejected

    function fetchData() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve('Data received via Promise');
        }, 1000);
      });
    }
    fetchData()
      .then(data => console.log(data))
      .catch(error => console.error(error));
  3. async/await:基于 Promise 的语法糖,让异步代码看起来像同步代码,极大地提高了可读性。

    async function getData() {
      try {
        const data = await fetchData(); // await 会暂停函数执行,直到 Promise 完成
        console.log(data);
      } catch (error) {
        console.error(error);
      }
    }
    getData();

第四部分:现代 JavaScript (ES6+) 特性

掌握以下特性是成为现代 JavaScript 开发者的必备条件。

  • letconst:块级作用域变量声明,解决了 var 的诸多问题。

  • 箭头函数:简化的函数语法,不绑定自己的 thisarguments

  • 模板字符串:使用反引号 ` 创建字符串,方便嵌入变量。

  • 解构赋值:从数组或对象中快速提取值并赋给变量。

    const [a, b] = [1, 2];
    const { name, age } = { name: 'John', age: 30 };
  • 默认参数:为函数参数设置默认值。

  • 剩余参数 和展开运算符 ():用于处理不定数量的参数或展开数组/对象。

  • 模块化:使用 importexport 来组织代码,避免全局污染。

    // math.js
    export const add = (a, b) => a + b;
    export const PI = 3.14159;
    // app.js
    import { add, PI } from './math.js';
    console.log(add(1, 2)); // 3

第五部分:函数式编程思想

函数式编程是一种编程范式,它将计算视为数学函数的求值,并避免使用程序状态以及可变对象。

核心概念

  • 纯函数:对于相同的输入,永远返回相同的输出,并且没有任何可观察的副作用(如修改全局变量、DOM 操作)。

    // 纯函数
    const add = (a, b) => a + b;
    // 非纯函数
    let c = 10;
    const addWithSideEffect = (a, b) => {
      c = a + b; // 修改了外部变量
      return c;
    };
  • 不可变性:不直接修改数据,而是创建数据的副本进行修改。

    const arr = [1, 2, 3];
    // 非不可变
    arr.push(4); // 修改了原数组
    // 不可变
    const newArr = [...arr, 4]; // 创建了一个新数组
  • 高阶函数:函数可以作为参数传递给另一个函数,或者作为函数的返回值。

    • map(), filter(), reduce() 是最常用的高阶函数。

使用 map, filter, reduce

const numbers = [1, 2, 3, 4, 5];
// map: 转换数组
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
// filter: 过滤数组
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]
// reduce: 汇总数组
const sum = numbers.reduce((acc, n) => acc + n, 0); // 15

第六部分:性能优化

  1. 事件委托:利用事件冒泡机制,将事件监听器添加到父元素上,而不是为每个子元素都添加一个监听器,这能显著减少内存使用和提高性能。

    document.getElementById('parent-list').addEventListener('click', function(e) {
      if (e.target && e.target.tagName === 'LI') {
        console.log('List item clicked:', e.target.textContent);
      }
    });
  2. 防抖 和节流:用于限制函数的执行频率,常用于处理 resize, scroll, input 等高频触发的事件。

    • 防抖:在事件被触发后等待一段时间(如 300ms),如果在这段时间内没有再次触发事件,才执行函数,适用于搜索框输入。
    • 节流:每隔固定的时间(如 200ms)执行一次函数,适用于滚动事件。
  3. 避免内存泄漏

    • 移除不再需要的事件监听器。
    • 清理定时器 (clearInterval, clearTimeout)。
    • 避免闭包中不必要的引用。
  4. 使用高效的数据结构:根据场景选择 ArraySet/Map,需要快速查找是否存在某个元素时,Sethas() 方法比 Array.includes() 快得多。


总结与学习路径

阶段 核心主题 学习目标
基础巩固 执行上下文、作用域链、闭包、this 能够分析代码的执行流程,解释变量提升、闭包形成原因,准确判断 this 指向。
面向对象 原型、原型链、ES6 Class 理解 JavaScript 的原型继承机制,并能使用 class 语法优雅地创建对象和实现继承。
异步编程 Promise, async/await 能够熟练使用 Promise 链式调用,并用 async/await 写出清晰、易读的异步代码。
现代特性 ES6+ 常用特性 熟练运用 let/const, 解构, 模块化等特性,编写出符合现代标准的代码。
编程范式 函数式编程思想 理解纯函数、不可变性,并能在日常编码中实践,提升代码质量。
工程实践 性能优化、设计模式 了解常见的性能瓶颈和优化手段,学习并应用经典的设计模式来解决复杂问题。

学习建议

  1. 动手实践:不要只看不练,尝试自己实现一些功能,比如自己的 debounce 函数、一个简单的 EventEmitter
  2. 阅读源码:选择一些优秀的开源库(如 Lodash, Vue, React)的源码,看看大师们是如何组织代码和解决问题的。
  3. 深入理解:遇到问题时,不要满足于“能跑就行”,要深挖其背后的原理,为什么 async/await.then() 更好?
  4. 关注社区:关注 TC39 提案,了解 JavaScript 未来的发展方向。

希望这份教程能为你打开 JavaScript 高级世界的大门,祝你学习愉快!

分享:
扫描分享到社交APP
上一篇
下一篇