普通视图

发现新文章,点击刷新页面。
今天 — 2025年11月30日技术

JavaScript新手必看系列之预编译

作者 甜味弥漫
2025年11月30日 18:14
前言

预编译是JavaScript的核心概念,也是新手向中级进阶的必经之路。理解它,意味着你能:

  • 彻底搞懂变量提升
  • 理解函数声明的“特权”
  • 避免常见的作用域陷阱

本文用最简单的语言和示例,带你快速掌握全局预编译与函数预编译的完整过程。

What's that?

简单说,JS在执行代码前会进行“准备工作” —— 预编译。在这个阶段JS的V8引擎会进行变量声明,函数声明的提升

预编译的两种场景

1. 全局作用域下的预编译

分三步:

1.创建 GO(Global Object)

  • 在浏览器中就是 window 对象

  • 在 Node.js 中就是 global 对象

2.找到变量声明,作为作为GO属性名,值为undefined

  1. 找到函数声明,作为GO属性名,值为函数体

举个简单的例子:

console.log(a) // undefined
var a = 1
console.log(a) // 1

发什么了什么?

  1. 创建 GO ={}
  2. 找到变量声明 var a ,GO = { a : undefined }
  3. 找函数声明,没有函数声明就不找

执行过程如下:

//预编译后就相当于:
var a = undefined
console.log(a) //输出 undefined
  a = 1
console.log(a) //输出 1

2.函数作用域下的预编译

函数较为复杂,四步:

  1. 创建AO(Activation Object)

  2. 找形参和变量声明,作为AO属性名,值为 undefined

  3. 将实参赋给形参

  4. 找函数声明,作为AO 属性名,值为函数体

举例:

function fn(a) {
  console.log(a);
  var a = 123
  console.log(a);
  function a() {}
  var b = function() {}
  console.log(b);
  function c() {}
  var c = a
  console.log(c);
}
fn(1)

干了什么?

  1. 创建AO = {

2.找形参和变量声明,作为AO属性名,值为 undefined

AO = { a : undefined
b : undefined
c : undefined }

3.将实参赋给形参

AO = { a : 1 b : undefined
c : undefined }

4.找函数声明,作为AO 属性名,值为函数体

AO = { a : function a() {}

b : undefined
c : function c() {} }

函数体内执行过程:

// 预编译后的状态:
var c
var b
var a
console.log(a); // function a() {}
 a = 123
console.log(a); // 123
b = function() {}
console.log(b);  //function b() {}
c = a
console.log(c); //123

掌握了预编译,你就真正理解了 JavaScript 的执行机制,这对后续学习闭包、作用域链等概念至关重要!

希望这篇文章能帮助你彻底理解 JavaScript 预编译,如果有任何疑问,欢迎在评论区讨论!

el-button源码解读3——:class="buttonKls"与颜色系统的关系

作者 Joie
2025年11月30日 17:44

说明 :class="buttonKls" 与颜色系统的关系:

1. buttonKls 的作用

buttonKls 是一个计算属性,生成按钮需要的所有 CSS 类名:

const buttonKls = computed(() => [
  // el-button
  ns.b(),
  ns.m(_type.value),
  ns.m(_size.value),
  ns.is('disabled', _disabled.value),
  ns.is('loading', props.loading),
  ns.is('plain', _plain.value),
  ns.is('round', _round.value),
  ns.is('circle', props.circle),
  ns.is('text', _text.value),
  ns.is('link', props.link),
  ns.is('has-bg', props.bg),
])

假设 type="primary"buttonKls 可能包含:

['el-button', 'el-button--primary', 'el-button--small', 'is-loading', ...]

2. 颜色系统的触发机制

颜色系统通过 CSS 类选择器触发。当 buttonKls 包含 'el-button--primary' 时,会匹配到对应的 CSS 规则。

3. 完整的关系链

用户使用: <el-button type="primary">
    ↓
props.type = 'primary'_type.value = 'primary'buttonKls = ['el-button', 'el-button--primary', ...]
    ↓
:class="buttonKls"  →  class="el-button el-button--primary"
    ↓
CSS 匹配: .el-button--primary { ... }
    ↓
button-variant('primary') 生成 CSS 变量
    ↓
应用颜色样式

4. 在 CSS 中的对应关系

button.scss 中的定义:

  @each $type in (primary, success, warning, danger, info) {
    @include m($type) {
      @include button-variant($type);
    }
  }

$type = 'primary' 时:

  • @include m('primary') → 生成 .el-button--primary 选择器
  • @include button-variant('primary') → 生成颜色相关的 CSS 变量

5. 实际渲染过程

步骤 1:Vue 组件生成类名

<el-button type="primary">按钮</el-button>
// buttonKls 计算后
['el-button', 'el-button--primary']
<!-- 最终渲染 -->
<button class="el-button el-button--primary">
  按钮
</button>

步骤 2:CSS 匹配类名

浏览器看到 class="el-button--primary",匹配到:

// button.scss 中生成的
.el-button--primary {
  // button-variant('primary') 生成的 CSS 变量
  --el-button-bg-color: var(--el-color-primary);
  --el-button-border-color: var(--el-color-primary);
  --el-button-text-color: var(--el-color-white);
  --el-button-hover-bg-color: var(--el-color-primary-light-3);
  --el-button-active-bg-color: var(--el-color-primary-dark-2);
}

步骤 3:应用颜色

基础样式使用这些 CSS 变量:

.el-button {
  background-color: var(--el-button-bg-color);
  border-color: var(--el-button-border-color);
  color: var(--el-button-text-color);
}

因为 .el-button--primary 定义了这些变量,所以按钮会显示 primary 的颜色。

6. 关键理解

:class="buttonKls" 是连接 Vue 组件和 CSS 颜色系统的桥梁:

Vue 组件层面          CSS 样式层面
─────────────────────────────────
buttonKls            .el-button--primary
  ↓                       ↓
'el-button--primary'  →  匹配 CSS 选择器
  ↓                       ↓
应用类名              应用颜色样式

7. 不同类型的关系

用户传入 buttonKls 包含 CSS 匹配 颜色应用
type="primary" 'el-button--primary' .el-button--primary 蓝色 (#409eff)
type="success" 'el-button--success' .el-button--success 绿色 (#67c23a)
type="warning" 'el-button--warning' .el-button--warning 橙色 (#e6a23c)
type="danger" 'el-button--danger' .el-button--danger 红色 (#f56c6c)

8. 完整示例

<el-button type="primary" size="small" :loading="true">
  提交
</el-button>

生成的类名:

buttonKls = [
  'el-button',           // 基础类
  'el-button--primary',  // 类型类(触发颜色系统)
  'el-button--small',    // 尺寸类
  'is-loading'           // 状态类
]

最终 HTML:

<button class="el-button el-button--primary el-button--small is-loading">
  提交
</button>

CSS 匹配:

.el-button--primary {  // ← 这个类触发了颜色系统
  --el-button-bg-color: var(--el-color-primary);
  // ...
}

9. 总结

  • :class="buttonKls" 生成类名(如 el-button--primary
  • CSS 通过类选择器匹配这些类名
  • 匹配到的规则通过 button-variant 生成颜色变量
  • 基础样式使用这些变量,最终显示对应颜色

核心关系buttonKls 中的类型类名(如 el-button--primary)是触发颜色系统的开关,CSS 通过这个类名应用对应的颜色样式。

搞懂作用域链与闭包:JS底层逻辑变简单

作者 闲云ing
2025年11月30日 17:39

JS底层小揭秘:作用域链与闭包,代码+图解一看就懂

在 JavaScript 的学习过程中,理解其底层运行机制是进阶的关键,而作用域链和闭包更是其中的核心概念。,很多人只停留在“会用”,没搞懂底层逻辑。本文结合代码+调用栈图解,从V8引擎的运行机制出发,拆解这两个概念的本质,帮你从底层视角搞懂 JS 的执行规则。

一、先搭好JS底层的基础框架

JS代码能运行,依赖V8引擎的三个核心模块:

  1. 调用栈:分编译阶段(处理变量/函数提升)和执行阶段(创建执行上下文并压入栈,执行完弹出);
  2. 执行上下文:全局执行上下文(始终在栈底)+ 函数执行上下文(函数调用时创建);
  3. 作用域:定义变量的查找范围和生命周期,包含let/const块级作用域(依托栈结构的词法环境),以及var变量提升特性。

二、作用域链:静态的变量查找路径

作用域链(词法作用域链)的核心是:它由函数声明的位置决定,编译阶段就固定了,和调用顺序无关

案例1:为什么bar里的myName取全局值?

对应代码与图示:

function bar(){
  console.log(myName); // 输出“极客时间”
}
function foo() {
  var myName = '极客邦'
  bar() // 在foo内部调用bar
}
var myName = '极客时间'
foo();

1.jpg

2.jpg

运行逻辑拆解:
  1. 编译阶段:barfoo被声明在全局作用域,因此它们的作用域链默认“自身→全局”;

  2. 执行阶段:

    1. foo调用时,创建foo执行上下文(变量环境包含myName="极客邦")并压入栈;
    2. foo内部调用bar,创建bar执行上下文并压入栈;
    3. bar中查找myName:自身变量环境无→通过outer指向的全局执行上下文查找→取全局的"极客时间"

案例2:块级作用域下的变量查找

对应代码与图示:

function bar () {
  var myName = '极客世界';
  let test1 = 100;
  if (1) {
    let myName = "Chrome 浏览器";
    console.log(test); 
  }
}
function foo() {
  var myName = '极客邦';
  let test = 2;
  {
    let test = 3;
    bar();
  }
}
var myName = '极客时间';
let myAge = 10;
let test = 1;
foo();

3.jpg

查找过程:
  1. bar内部if块的console.log(test),先查自身块级词法环境(只有myName="Chrome 浏览器")→ 没找到;
  2. bar函数的词法环境(有test1=100)→ 没找到;
  3. bar的变量环境(有myName="极客世界")→ 没找到;
  4. 查全局执行上下文的词法环境(有test=1)→ 但由于bar的作用域链是“自身块级→bar函数→全局”,运行中会输出1

三、闭包:函数“背着”变量的专属背包

闭包是词法作用域的延伸——外部函数执行后,其内部变量被嵌套函数引用,因此不会被垃圾回收,形成一个“变量背包”供嵌套函数使用

案例:闭包的形成与运行

对应代码与图示:

function foo() {
  var myName = '极客时间'
  let test1 = 1
  const test2 = 2
  var innerBar = {
    getName: function() {
      console.log(test1) // 输出1
      return myName
    },
    setName: function(newName) {
      myName = newName // 修改foo内部的myName
    }
  }
  return innerBar
}

var bar = foo() // foo执行上下文出栈
bar.setName("极客邦")
console.log(bar.getName()); // 输出1 + “极客邦”

5.jpg

6.jpg

闭包运行流程:
  1. foo执行时:创建执行上下文,变量环境存储myName="极客时间",词法环境存储test1=1test2=2,并压入栈;

  2. foo返回innerBar后:foo执行上下文出栈,但getName/setName引用了myNametest1,这两个变量被保留在内存中(形成闭包,即“专属背包”);

  3. 调用bar.setName/getName时

    1. setName执行时,通过闭包找到myName并修改为"极客邦"
    2. getName执行时,通过闭包找到test1并输出1,同时返回修改后的myName

四、核心总结

  1. 作用域链是静态的:由函数声明位置决定,编译阶段固定,和调用顺序无关;
  2. 闭包是词法作用域的延伸:嵌套函数引用外部函数变量,导致外部函数变量不被回收,形成“变量背包”;
  3. 底层逻辑的关键:理解调用栈、执行上下文、作用域的关系,是搞懂JS变量查找和内存管理的基础。

JavaScript 的底层运行机制中,词法作用域是基础,它决定了作用域链的静态查找规则,而闭包则是词法作用域的延伸,通过保留自由变量实现了函数对外部作用域的持久访问。理解这些概念,不仅能帮助我们写出更符合 JS 运行逻辑的代码,还能解决实际开发中变量作用域、内存泄漏等常见问题。掌握作用域链与闭包,是深入理解 JavaScript 语言特性的重要一步。

❌
❌