深入理解JS知识点总结 | 豆包MarsCode AI刷题

39 阅读16分钟

深入理解 JavaScript

1. JavaScript 的基本概念

1.1 诞生与发展
  • 诞生:1995 年,Brendan Eich 开发了 JavaScript,这一语言的诞生融合了多门语言的优势特性,可谓是站在了 “巨人的肩膀” 上。借鉴 C 语言的基本语法使得它对于有 C 语言基础的开发者来说,上手相对容易,代码结构和逻辑表达较为清晰直观。借鉴 Java 语言的数据类型和内存管理,则让 JavaScript 在数据处理和资源管控方面有了较为规范的模式。而将函数提升到 “第一等公民” 的地位,借鉴自 Scheme 语言,这极大地增强了函数在编程中的灵活性与重要性,使得函数可以像普通数据一样被传递、赋值等操作,让代码的复用性和抽象程度都大大提高。还有借鉴 Self 语言使用基于原型(prototype)的继承机制,这种继承方式与传统的基于类的继承有所不同,更加灵活且符合 JavaScript 本身动态、灵活的特点,为面向对象编程提供了一种别样的思路。

  • 发展

    • 1995年:Mocha -> LiveScript -> JavaScript。
    • 1997年6月:第一版 ECMAScript 发布。
    • 1999年12月:第三版 ECMAScript 发布。
    • 2009年12月:第五版 ECMAScript 发布。
    • 2009年:Ryan Dahl 创建了 Node.js。
    • 2010年:Isaac Z. Schlueter 基于 Node.js 写出了 npm。
    • 2015年6月:第六版 ECMAScript 发布。
  • 体会 -在学习这部分发展历程的过程中,我深刻体会到一门语言的发展不是一蹴而就的,它需要不断地根据实际应用场景、开发者的需求以及技术发展趋势去迭代更新。而且每一次重大的版本更新或者新技术的出现,都可能会引领一波新的开发潮流,我们作为开发者需要紧跟这些变化,不断学习新特性,才能更好地利用这门语言创造出优秀的项目。

1.2 浏览器进程
  • GUI 线程:负责渲染页面,它的工作关乎用户看到的页面最终呈现效果,就像是一个幕后的 “画家”,精心绘制着网页的每一个元素,从 HTML 结构到 CSS 样式的展现,任何微小的卡顿或者问题都可能影响用户体验,所以在编写 JavaScript 代码时,要特别注意避免长时间阻塞这个线程,例如避免在主线程中进行复杂的计算或者大量的同步操作,不然页面就可能出现 “假死” 状态,用户只能干着急等待页面响应了。
  • JS 线程:执行 JavaScript 代码,它和 GUI 线程之间的交互十分微妙,因为 JavaScript 是单线程的,很多时候它执行代码的效率和时机就决定了页面的交互流畅度。比如,频繁地操作 DOM 元素,如果处理不当,就容易造成页面重排和重绘,进而影响性能。
  • GPU 进程:处理图形渲染,现在越来越多的页面有复杂的图形效果、动画等,GPU 的作用就凸显出来了,它能够加速图形相关的处理,让页面的视觉效果更加流畅和炫酷。像一些 3D 游戏网页或者带有复杂动画特效的页面,GPU 的性能好坏直接影响着用户看到的画面质量。
  • 事件触发线程:管理事件循环,这可以说是 JavaScript 异步编程的核心 “调度员” 了。它通过事件循环机制,合理地安排各种异步任务的执行顺序,比如定时器任务、网络请求回调等,让代码能够在单线程的限制下,模拟出多线程的并发效果,极大地提高了程序的运行效率和响应能力。
  • 渲染进程:负责页面渲染,和 GUI 线程紧密配合,把各种资源(HTML、CSS、图片等)整合起来,渲染出完整的页面呈现给用户。
  • 定时器触发线程:处理定时器任务,像我们常用的 setTimeout 和 setInterval 函数,就是靠它来管理计时和触发相应回调的,不过要注意定时器的执行时间并不是绝对精准的,因为它受到事件循环机制以及其他任务执行情况的影响。
  • 网络线程:处理网络请求,在如今的 web 应用中,网络请求无处不在,从加载页面的初始资源到后续的异步数据获取等,网络线程的效率和稳定性对于页面的加载速度、数据交互及时性等方面起着关键作用。
  • 插件进程网络进程:处理插件和网络相关任务,插件可以为网页增加各种丰富的功能,但同时也可能带来安全隐患和性能问题,需要谨慎使用;网络进程则保障了各种网络相关操作的顺利进行,它们共同协作,构建出完整的浏览器运行环境。
  • 体会
  • 通过了解这些浏览器进程,我明白了在开发中不能只关注 JavaScript 代码本身的逻辑实现,还要考虑到它在浏览器这个复杂环境中的运行情况,各个进程之间相互影响、相互配合,任何一个环节出现问题都可能导致页面出现异常,所以要从整体上去优化和把控代码的执行效果。
1.3 单线程与特性
  • 单线程:JavaScript 是单线程语言,一次只能执行一个任务,这既是它的特点也是它的限制。好处是避免了多线程带来的诸如死锁、线程同步等复杂问题,让代码的执行逻辑相对简单清晰;但坏处也很明显,面对一些复杂的、耗时较长的任务时,如果处理不好,就容易造成页面卡顿,影响用户体验。比如进行大量的数据计算或者加载超大文件时,就需要采用异步编程等手段来避免阻塞主线程。
  • 动态、弱类型:JavaScript 是动态类型语言,变量类型在运行时确定,这使得编写代码时非常灵活,不需要像静态类型语言那样提前声明变量的具体类型,代码可以快速地进行迭代和修改。不过,也正因如此,在代码规模较大或者多人协作开发时,容易出现类型相关的错误,而且这类错误往往在运行时才会暴露出来,增加了调试的难度。所以在实际开发中,现在也有很多开发者借助一些工具(如 TypeScript)来提前进行类型检查,弥补这一不足。
  • 副作用、纯函数:函数可以有副作用,也可以是纯函数。纯函数具有确定性、无副作用等优点,它的输出只取决于输入,便于测试、复用和理解,在函数式编程中是非常重要的概念;而有副作用的函数虽然在实际应用中不可避免,比如操作 DOM、修改全局变量等,但过多使用副作用函数会让代码的逻辑变得复杂且难以维护,所以在开发中要合理地平衡纯函数和有副作用函数的使用,尽量让代码的核心逻辑部分由纯函数构成,提高代码的可维护性和可读性。
  • 面向对象、函数式:支持面向对象编程和函数式编程,这两种编程范式各有千秋。面向对象编程便于对现实世界中的事物进行建模,通过类、对象、继承等概念组织代码,让代码结构更加清晰,适合开发大型复杂的项目;函数式编程则强调不可变数据、纯函数等理念,能够写出更加简洁、易于并行处理的代码,在处理数据转换、异步流程等方面有独特的优势。在实际项目中,可以根据具体的业务需求和场景灵活选择或者结合使用这两种编程范式,发挥各自的长处。
  • 解释类语言、JIT:JavaScript 是解释类语言,但现代引擎使用即时编译(JIT)提高性能。刚开始学习的时候觉得它作为解释类语言执行效率可能不高,但了解到 JIT 机制后才明白,浏览器引擎在背后做了很多优化工作,通过在运行时将部分代码编译成机器码,提高了执行速度。这也体现了 JavaScript 不断发展和优化以适应复杂应用场景的特点,不过我们在编写代码时,还是要遵循一些性能优化的原则,比如避免频繁地创建和销毁对象、合理使用变量等,这样能更好地配合引擎的优化机制,提升整体性能。
  • 安全、性能差:JavaScript 设计初衷是安全,但性能相对较差。它在浏览器环境中运行,有严格的安全限制,比如不能直接访问本地文件系统等,这保障了用户在浏览网页时的安全性;然而,早期的 JavaScript 性能确实不尽如人意,不过随着技术的发展,从引擎优化到各种新特性的推出,性能已经有了很大的提升,但在一些对性能要求极高的场景下,仍然需要开发者格外关注性能优化的问题,采用合适的算法、数据结构以及优化技巧来提升代码的运行效率。
  • 原型、继承、封装:使用原型链实现继承,支持封装。原型链继承是 JavaScript 面向对象编程中很独特的部分,它与传统基于类的继承相比,理解起来可能稍微复杂一些,但一旦掌握,就能利用它实现非常灵活的代码复用和对象关系构建。封装则有助于隐藏内部实现细节,对外提供统一的接口,提高代码的模块化程度和可维护性,在开发中合理运用这些特性,可以让代码更加健壮和易于扩展。
1.4 数据类型
  • 基础类型NumberStringBooleanNullUndefinedSymbol(ES6)。这些基础类型各有其用途,Number 类型可以表示各种数值,不过要注意浮点数运算可能存在精度问题,在涉及到精确计算时需要特别小心,比如金额计算等场景,可能需要借助一些第三方库来解决精度误差。String 类型用于处理文本信息,JavaScript 提供了丰富的字符串操作方法,方便对文本进行拼接、截取、替换等操作。Boolean 类型简单明了,常用于条件判断。Null 和 Undefined 都表示值的空缺,但它们有细微的区别,Null 是开发者主动赋值表示空值,而 Undefined 更多表示变量未被初始化等情况,在代码中要注意区分它们的使用场景,避免出现一些意想不到的错误。Symbol 类型是 ES6 引入的,它主要用于创建独一无二的值,在对象属性名等场景下可以避免属性名冲突,为代码的安全性和唯一性提供了保障。
  • 引用类型ObjectArrayFunctionObject 是 JavaScript 中最基础也是最常用的引用类型,几乎所有复杂的数据结构都可以通过对象来表示,它可以灵活地存储各种键值对,方便对数据进行组织和管理。Array 类型用于处理一组有序的数据,数组的各种方法(如 mapfilterreduce 等)在数据处理和转换方面非常强大,掌握这些方法可以让我们更高效地操作数组数据。Function 作为引用类型,不仅可以执行代码逻辑,还可以像其他数据一样被传递、赋值等,这种函数作为 “一等公民” 的特性为 JavaScript 的编程带来了极大的灵活性,比如实现回调函数、函数柯里化等高级编程技巧都离不开函数的这种特殊性质。 在实际开发中,正确地选择和使用数据类型非常重要,不同的数据类型在内存占用、操作效率以及代码可读性等方面都有差异,要根据具体的业务需求和场景合理运用它们,同时也要注意数据类型之间的转换和兼容性问题,避免出现类型错误。
1.5 作用域
  • 变量的可访问性和可见性
    • 静态作用域:通过词法作用域预测代码执行过程中如何查找标识符,这一点在理解代码的执行逻辑和变量查找机制时很关键。它意味着变量的作用域是在代码编写阶段就确定好了的,根据函数声明的位置等语法结构来决定,而不是在运行时动态变化。比如在嵌套函数中,内部函数可以访问外部函数定义的变量,就是基于词法作用域的原理,这种机制方便了代码的模块化和数据的传递,但也需要我们在编写代码时清晰地把握变量的作用范围,避免出现变量名冲突或者意外访问到不该访问的变量的情况。
    • 全局作用域函数作用域块级作用域。全局作用域中的变量在整个代码中都可以访问,不过过多地使用全局变量会导致代码的可维护性变差,容易出现命名冲突等问题,所以要尽量减少全局变量的使用。函数作用域限定在函数内部,使得函数内部的变量和外部隔离开来,有利于代码的封装和复用。而块级作用域(ES6 引入 let 和 const 后)让代码在诸如 if 语句块、for 循环块等地方也能有独立的作用域,进一步细化了变量的作用范围,避免了一些由于变量提升等带来的问题,让代码更加严谨和易于理解。 在开发过程中,合理利用不同的作用域来管理变量是编写高质量代码的关键之一,要根据变量的使用场景和生命周期来决定将其放在哪个作用域中,同时也要注意不同作用域之间变量的访问和修改规则,避免出现逻辑错误。
1.6 变量提升
  • var:有变量提升,存在函数作用域。这是早期 JavaScript 中定义变量的方式,变量提升可能会导致一些意想不到的情况,比如在函数内部使用 var 声明的变量,即使在声明之前使用也不会报错,而是会返回 undefined,这种隐式的行为容易让代码的逻辑变得模糊,尤其是在代码规模较大或者多人协作时,可能会造成调试的困难,所以现在更推荐使用 let 和 const 来声明变量。
  • letconst:没有变量提升,存在块级作用域。let 和 const 的出现解决了 var 的很多问题,块级作用域让变量的作用范围更加明确,避免了变量在不该出现的地方被访问到,而且不存在变量提升的特性也让代码的执行顺序更加符合开发者的直观预期,代码的可读性和可维护性都得到了提高。不过要注意,const 声明的是常量,一旦赋值后就不能再修改,这在定义一些不希望被改变的值(如配置项等)时非常有用,但如果试图修改 const 声明的变量就会报错,需要开发者格外留意。
  • 函数声明:可以先调用再定义,这也是 JavaScript 中比较特殊的一个语法现象,函数声明会被提升到作用域的顶部,所以可以在函数定义之前就调用它。但同样,这种隐式的行为可能会让代码的阅读者感到困惑,不太容易一眼看出函数的实际定义位置和逻辑关系,在代码规范和可读性方面存在一定的不足,现在也更提倡将函数声明放在合适的位置,遵循代码的逻辑顺序。
  • 函数表达式:赋值给变量的函数无法提前调用,因为函数表达式本质上是将一个函数赋值给一个变量,变量的赋值操作是按照代码的执行顺序来的,只有在赋值完成后才能通过变量来调用函数,这和函数声明有明显的区别,在实际开发中要根据具体需求选择合适的方式来定义和使用函数,确保代码的正确性和可读性。

2. JavaScript 的执行机制

2.1 代码优化
  • 源代码 -> 词法分析 -> 语法分析 -> AST -> 字节码 -> 编译执行 -> 机器码
2.2 执行上下文
  • 全局执行上下文:代码开始执行时创建,存在于整个生命周期。
  • 函数执行上下文:函数调用时创建,函数执行结束后从调用栈弹出。
  • Eval 执行上下文:执行 eval 函数时创建。
2.3 执行上下文的创建
  • 绑定 this:在创建执行上下文时绑定。
  • 创建词法环境:存储变量和函数声明。
  • 创建变量环境:存储变量(var)和函数声明。
  • Outer:指向外部词法环境的指针。
2.4 调用栈
  • 调用栈:用于管理函数调用和执行上下文的栈结构。
2.5 变量环境与词法环境
  • 词法环境:基于 ECMAScript 代码的词法嵌套结构来定义标识符和具体变量和函数的关联。
  • 变量环境:存储函数声明和变量(let 和 const)绑定。
  • Outer:指向外部词法环境的指针。

3. JavaScript 的进阶知识点

3.1 闭包
  • 闭包:函数可以访问其词法作用域外的变量。
    function showName() {
        const company = "Bytedance";
        const dep = "边缘云";
        const name = "zhangqi";
        console.log('company', company);
        return function() {
            console.log(dep);
            return name;
        };
    }
    const getName = showName();
    console.log(getName());
    
3.2 this 关键字
  • 普通函数:this 指向全局对象(浏览器中为 window)。
  • 对象方法:this 指向调用该方法的对象。
  • 构造函数:this 指向新创建的对象。
3.3 垃圾回收
  • 新生代:存储新创建的对象,空间较小(1M-8M)。
  • 老生代:存储存活时间较长的对象。
  • 垃圾回收机制
    • 垃圾标记:标记不再使用的对象。
    • 对象复制:将存活对象复制到空闲区域。
    • 区域反转:交换对象区域和空闲区域。

4. 总结

JavaScript 是一门动态、弱类型的单线程语言,具有丰富的数据类型和灵活的编程范式。通过理解 JavaScript 的基本概念、执行机制和进阶知识点,可以更好地掌握这门语言,并在实际项目中高效地应用它。