从HTML敲击乐了解开发流程

158 阅读8分钟

模块化职责分离:前端工程化的基础

  • 专业性:CSS 专注于视觉表现(布局、颜色、字体等),JS 专注于行为交互(事件响应、数据处理等),HTML 专注于内容结构,三者各司其职,符合 “单一职责原则”,避免样式与逻辑混杂导致的代码混乱。

  • 可维护性:当需求变更时(如调整按钮样式或修改点击逻辑),开发者可直接定位到对应的 CSS 或 JS 文件修改,无需在混合代码中查找,降低维护成本。

  • 可扩展性:模块化结构支持 “组件化复用”,例如多个页面共用的导航栏样式可抽离为单独的 CSS 文件,通用交互逻辑(如表单验证)可封装为 JS 模块,后续新增页面时直接引入即可,提升开发效率。

  • 引入规范

    • CSS 通过 <link><head> 中引入,确保浏览器解析 HTML 结构时能同步加载并解析样式,避免页面先显示 “无样式的毛坯房” 再突然渲染样式(减少 “闪屏” 体验)。
    • JS 通过 <script><body> 底部引入,是因为 JS 执行时可能需要操作 DOM 元素(如获取按钮、修改内容),放在底部可确保 HTML 结构先解析完成,避免因 “元素未加载” 导致的报错;同时,JS 加载和执行会阻塞 HTML 解析(传统同步加载方式),放在底部可优先保证页面静态内容的快速展示,提升首屏加载体验(现代开发中可通过 async/defer 进一步优化加载逻辑)。
    • 网站的渲染效率十分重要,即使是快 0.01 秒也有意义,潜在的网站闪屏会很大的影响客户对产品的体验。而先加载静态样式 HTML (毛坯房) 和 CSS (样式) 会很大的解决这个问题。也就是先加载静态页面,然后再加载 js 代码。JS 在页面 body 的最底部,因为这样才能先加载整个页面再加载 JS 的行为

image.png

image.png

浏览器的执行逻辑:前端性能优化的依据

浏览器加载 HTML5 应用的过程是 “渐进式解析与渲染” 的过程:

  • 首先下载 HTML 文件,从上到下解析标签,构建 DOM 树(页面结构);
  • 遇到 <link> 标签时,并行下载 CSS 并解析,构建 CSSOM 树(样式规则);
  • DOM 树与 CSSOM 树结合生成渲染树(Render Tree),浏览器根据渲染树计算元素位置和样式,最终绘制到屏幕上(这一步即 “页面可见”)。
  • 当解析到 <body> 底部的 <script> 时,DOM 已基本构建完成,JS 可安全操作元素,同时避免了对 DOM 解析的阻塞,确保 “静态页面先展示,交互逻辑后加载”—— 这符合前端 “快速呈现内容” 的核心目标,因为用户对页面的第一感知是 “能否看到内容”,而非 “能否交互”。

简言之,HTML5 Web 应用的开发规范,本质上是通过 “结构语义化、职责模块化、加载有序化”,实现 “开发高效、维护便捷、用户体验流畅” 的目标,而浏览器的执行机制则是这些规范设计的底层依据。

JS 的执行逻辑 - 敲击乐的键盘行为初步:

/* 页面的最底部,在静态页面执行后再执行 */
/* document整个文档,添加了一个时间监听 */
/* 首要渲染界面 html+css,不需要js参与 */
/* 文档加载完后在执行 */
// DOM文档结构
// script会阻塞HTML的下载
document.addEventListener('DOMContentLoaded',function(){
    //页面加载完后在执行
    //可以获得页面元素,添加事件监听器等
    //添加事件监听
    function playSound(event){
        //  事件对象,在事件发生时会给回调函数
        //  keyCode按下键的编码
​
        console.log(event.keyCode,'///////////')
        let keyCode = event.keyCode;
       let element = document.querySelector('.key[data-key="'+ keyCode +'"]');
        //
        console.log(element);
        // 动态DOM编程
        element.classList.add('playing');
    }
    //事件监听触发回调函数
    window.addEventListener('keydown', playSound)
});

设计方法之代理模式

代理模式(Proxy Pattern)是一种结构型设计模式,它通过引入一个 “代理对象” 来控制对原始对象的访问。代理对象可以在不改变原始对象接口的前提下,为其提供额外的功能(如权限控制、日志记录、延迟加载等),同时屏蔽了原始对象的直接访问。

先来看一段代码:

<script>
        /* 面向对象思想 */
        /* 表现力的JSON 对象字面量 */
        /* 对象自变量 */
        // let变量 关键字
        // key vau1e 格式
        // typeof   object  
        let zzp = {
            name: '杨',     //字符串   string
            hometown:'上饶鄱阳',
            age: 18,           //number  不适合计算   数值类型
            sex: '男',
            hobbies:['学习','乒乓球','羽球'],  //对象 
            isSingle: false,
            job: null,
            sendFlower: function(target){
                target.receiveFlower(zzp);
            }
        }
        let a;
        let xm = {
            xq: 30,
            name: '小美',
            hometown: '赣州',
            receiveFlower: function(sender){
                console.log('小美收到了'+sender.name+'花');
                if(this.xq < 80){
                    console.log('不约,我们不合适');
                }else{
                    console.log('我们去硕果吧!');
                }
​
            }
        }
        let xh = {
            name : '小红',
            hometown: '上饶',
            receiveFlower: function(sender){
                /* js定时器 */
                setTimeout(function(){
                    xm.xq=90;
                    xm.receiveFlower(sender);
                },3000)
                /* console.log('小红收到了'+sender.name+'花');
                xm.receiveFlower(sender); */
                /* if (sender.name==='杨'){
                    console.log('让我们在一起吧...')
                } */
            }
​
        }
    </script>

让我们从送花这个日常的场景来代入:

日常生活中,杨zzp 打算给小美 xm 送花,但没有调用 xm 的 receive 方法,反而通过小红来代理送花。

// 杨送花给小红(代理),而非直接送给小美(真实主题)
zzp.sendFlower(xh);

小红作为代理,做了两件关键的 “代理工作”:

  • 拦截并处理请求:小红接收花后,没有自己决定是否约会,而是通过setTimeout延迟 3 秒,修改了小美(真实主题)的xq值(从 30 改为 90),再调用小美自己的receiveFlower方法。
  • 控制对真实主题的访问: 代理 小红 xh 通过对 小美 xm 心情值 xq 的修改间接影响了结果。

代理模式的核心目的

  • 代理(小红)在不改变真实主题(小美)接口(receiveFlower方法)的前提下,为其添加了额外操作(延迟、修改状态)。
  • 代理控制了对真实主题的直接访问,杨无需知道小美是否会同意,只需通过代理传递请求即可。
总结

这段代码中,小红(xh)作为代理,隔离了杨和小美直接交互,并在中间层完成了延迟处理和状态调整,完全符合代理模式 “通过代理对象控制对真实对象的访问,并添加额外逻辑” 的核心思想。这类似于生活中 “找朋友帮忙牵线搭桥” 的场景,朋友就是代理,最终决策仍由目标对象(小美)完成。

JavaScript 数据类型:构建交互世界的基石

在 JavaScript 的世界里,数据类型是构建一切逻辑的基础。它们如同建筑中的砖瓦,决定了程序如何存储、处理和传递信息。JavaScript 定义了六种基本数据类型,每种类型都有其独特的特性和用途,共同支撑起复杂的交互逻辑。

字符串(string)

是最直观的数据类型,用于表示文本信息。无论是用户输入的用户名、页面上的标题,还是接口返回的描述性内容,都以字符串形式存在。它可以用单引号、双引号或反引号包裹,其中反引号支持多行文本和变量嵌入,为模板字符串提供了极大便利。字符串的不可变性是其重要特性 —— 一旦创建便无法修改,任何看似修改的操作实际都是生成了新的字符串。

数值(number)

统一处理整数和浮点数,消除了其他语言中 int 与 float 的严格区分。这种设计简化了数值运算,但也带来了精度问题,比如 0.1+0.2 的结果并非精确的 0.3。

布尔值(boolean)

是逻辑判断的核心,仅有 true 和 false 两个值。它在条件语句、循环控制中扮演关键角色,决定程序的执行路径。看似简单的布尔值背后,隐藏着 JavaScript 独特的 “真值” 与 “假值” 概念 —— 除了 false、0、""、null、undefined 和 NaN 外,其他值在逻辑判断中都被视为 true。

对象(object)

是 JavaScript 的核心概念,用于存储键值对集合。它可以包含字符串、数值等基本类型,也能嵌套其他对象或函数,从而构建复杂的数据结构。数组、日期、正则表达式等都是特殊的对象类型。对象的引用传递特性需要特别注意:当把对象赋值给变量时,变量存储的是内存地址而非实际数据,这意味着多个变量可能指向同一个对象,修改其中一个会影响所有引用。

空值(null)

表示 “故意缺少值”,常被用于主动清空变量的引用。它是一个独立类型,typeof 运算符检测时却会返回 "object",这是语言设计的历史遗留问题。与 null 不同,未定义(undefined)表示 “值不存在”,通常出现在未初始化的变量、函数未返回值、对象不存在的属性等场景中。

这六种数据类型构成了 JavaScript 的基本数据体系,其中字符串、数值、布尔值、null、undefined 属于原始类型,而对象属于引用类型,其值存储在堆内存中,变量仅保留引用地址。理解这种差异是掌握 JavaScript 内存管理的关键。

在实际开发中,数据类型的转换是高频操作。JavaScript 会根据上下文自动进行类型转换,比如字符串与数值相加时会自动转为字符串拼接。但过度依赖隐式转换容易引发 bug,因此显式转换(如 Number ()、String ()、Boolean ())更值得推荐。

下图为undefined和null的区别与辨析

维度undefinednull
含义自然未定义(被动状态)主动清空(主动操作)
类型检测typeof 返回 "undefined"typeof 返回 "object"(Bug)
数值转换转为 NaN转为 0
使用场景系统默认的 “无值” 状态开发者主动设置的 “空值” 状态