前端知识点——快看看忘了多少

说说对浏览器缓存的了解

浏览器缓存是一种用于存储Web页面资源的临时存储机制,旨在提高网页加载速度和减少对服务器的请求。当你访问一个网站时,浏览器会下载并保存页面的各种资源,如HTML、CSS、JavaScript文件、图像以及其他多媒体内容。这些资源会被缓存在本地,以便在将来的访问中可以更快地加载相同的页面或相同的资源。

以下是浏览器缓存的一些详细信息:

  1. 缓存位置:浏览器缓存通常分为内存缓存和磁盘缓存两个部分。内存缓存用于临时存储最近的页面资源,而磁盘缓存则存储长期使用的资源。

  2. 缓存控制:网站可以通过HTTP响应头中的Cache-Control和Expires字段来控制浏览器对资源的缓存行为。这些字段可以指示浏览器何时需要重新验证资源、何时需要重新获取资源以及资源的过期时间等信息。

  3. 缓存验证:当浏览器尝试从缓存中获取资源时,服务器可以使用条件GET请求(If-Modified-Since、If-None-Match等)来验证资源是否仍然有效,如果有效则返回304 Not Modified响应,浏览器将继续使用缓存的资源。

  4. 强制刷新和硬刷新:强制刷新(Ctrl + F5)会忽略缓存并重新获取所有资源,而硬刷新(Ctrl + Shift + R)则会忽略缓存并重新加载当前页面。

  5. 缓存清理:用户可以手动清理浏览器缓存,以便释放磁盘空间或解决访问问题。

  6. 缓存策略:开发人员可以通过HTTP响应头设置缓存策略,以控制资源的缓存行为,包括设置缓存时间、是否允许缓存、是否允许代理服务器缓存等。

图片格式

图片的格式通常指的是图片文件的存储格式,不同的格式适用于不同的场景和需求。以下是一些常见的图片格式:

  1. JPEG (JPG):一种常见的有损压缩格式,适合存储照片和彩色图像。JPEG 文件通常具有较小的文件大小,但会损失一些图像质量。

  2. PNG:一种无损压缩格式,适合存储图标、图形和带有透明背景的图像。PNG 文件保持图像质量,但通常会比 JPEG 文件占用更大的空间。

  3. GIF:一种支持动画和透明度的格式。GIF 格式适合简单动画、图标和简单图形,但对于复杂图像来说可能质量不如 JPEG 或 PNG。

  4. BMP:一种无损格式,适合存储位图图像。BMP 文件通常较大,不太适合在 Web 等需要快速加载的场景中使用。

  5. TIFF:一种高质量的无损格式,适合存储印刷品质的图像。TIFF 文件通常用于专业印刷和照片处理领域。

  6. SVG:矢量图形格式,适合存储可缩放的图形和图标。SVG 文件可以无损缩放而不失真,适合在 Web 开发中使用。

在一般情况下,JPEG 格式的图片文件通常会比 PNG 格式的文件小。这是因为 JPEG 是一种有损压缩格式,可以在一定程度上牺牲图像质量以获得更小的文件大小,适合用于存储照片和彩色图像。

相比之下,PNG 格式是一种无损压缩格式,保留了更多的图像细节,但通常会导致较大的文件大小。PNG 格式适合存储图标、图形和带有透明背景的图像。

因此,如果你关心文件大小并且对图像质量要求不是特别高的话,可以选择 JPEG 格式来获得较小的文件大小。如果需要保留高质量的图像细节或透明度,并且可以接受较大的文件大小,那么可以选择 PNG 格式。

webP的了解

WebP 是一种由 Google 开发的现代图像格式,旨在提供更高的压缩率和更好的图像质量,以取代传统的 JPEG 和 PNG 格式。以下是关于 WebP 格式的一些了解:

  1. 压缩效率

    • WebP 使用先进的压缩算法,通常比 JPEG 格式具有更高的压缩率,可以显著减小图像文件的大小,有助于加快网页加载速度。
  2. 图像质量

    • 尽管 WebP 在压缩方面表现出色,但它仍然能够保持良好的图像质量,特别是对于包含大量细节和颜色的图像。
  3. 透明度支持

    • WebP 格式支持 alpha 透明度通道,即可以创建带有部分透明效果的图像,类似于 PNG 格式的透明度支持。
  4. 动态图像

    • WebP 也支持动态图像,类似于 GIF 格式,但通常具有更好的压缩率和图像质量。
  5. 浏览器支持

    • 大多数现代的主流浏览器(如 Chrome、Firefox、Edge 等)都已经支持 WebP 格式,使得开发人员可以在网页中广泛应用这种格式。
  6. 转换工具

    • 为了将现有的 JPEG 或 PNG 图像转换为 WebP 格式,可以使用各种图像处理工具或在线转换服务来进行转换。
  7. 兼容性考虑

    • 尽管 WebP 在现代浏览器中得到了广泛支持,但在考虑使用时仍需注意兼容性,特别是对于需要支持旧版本浏览器的项目。

总的来说,WebP 是一个强大的图像格式,可以帮助优化网站加载速度并改善用户体验

 事件冒泡事件委托和事件委托的介绍及场景

事件冒泡是指当一个元素上的事件被触发时,该事件会向其父元素一层层地冒泡,直至文档的根节点。相反,事件捕获是从文档的根节点一层层向下传播直至触发事件的元素。在 web 开发中,了解事件冒泡和事件委托是非常重要的概念。

  1. 事件委托

    • 事件委托是利用事件冒泡的机制,将事件绑定到父元素上,通过判断事件的目标元素来执行相应的操作。通过事件委托,可以减少事件处理程序的数量,提高性能,同时也可以动态绑定事件。
  2. 场景

    • 动态元素:当页面上有大量的动态生成的元素时,使用事件委托可以避免每个元素都添加事件监听器。
    • 列表/表格:对于列表或表格等包含多个子元素的结构,可以将事件绑定到父元素上,根据子元素的不同来执行相应的操作。
    • 性能优化:由于事件委托可以减少事件处理程序的数量,因此可以提高页面的性能。
    • 单页应用:在单页应用中,经常会有大量动态生成的内容,使用事件委托可以更好地管理事件。
  3. 例子

    • 假设有一个 ul 列表,需要为每个 li 元素添加点击事件处理程序,可以使用事件委托:
      
      <body>
          <ul id="myList">
              <li>Item 1</li>
              <li>Item 2</li>
              <li>Item 3</li>
          </ul>
      
          <script>
              document.getElementById('myList').addEventListener('click', function(event) {
                  // 移除之前高亮的元素的样式
                  var highlightedItem = document.querySelector('.highlight');
                  if (highlightedItem) {
                      highlightedItem.classList.remove('highlight');
                  }
      
                  // 高亮当前点击的 li 元素
                  if (event.target.tagName === 'LI') {
                      event.target.classList.add('highlight');
                  }
              });
          </script>
      </body>
      </html>
      <style>
          .highlight {
              background-color: yellow;
          }
      </style>

      在这个例子中,只需为父元素 ul 添加一个点击事件处理程序,通过判断 event.target 的 tagName 来确定点击的是哪个 li 元素,并执行相应的操作。

 前端文件上传

使用 <input type="file"> 元素以及相关的 JavaScript 和后端处理。

<form id="fileUploadForm" action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="fileToUpload" id="fileToUpload">
    <input type="submit" value="上传文件">
</form>
<script>
document.getElementById('fileUploadForm').addEventListener('submit', function(event) {
    event.preventDefault();
    var formData = new FormData(this);
    // 这里可以添加其他字段到 formData
    // formData.append('username', 'user123');

    fetch('/upload', {
        method: 'POST',
        body: formData
    })
    .then(response => response.json())
    .then(data => {
        console.log('上传成功:', data);
    })
    .catch(error => {
        console.error('上传失败:', error);
    });
});
</script>

同源策略

同源策略(Same-Origin Policy)是一个浏览器的安全策略,用于限制一个网页文档或脚本如何与另一个源(域、协议和端口的组合)的资源进行交互。同源策略有以下几个主要限制:

  1. Cookie、LocalStorage 和 IndexDB 限制:网页只能访问与自身相同域、相同路径和相同协议的 Cookie、LocalStorage 和 IndexDB。

  2. DOM 限制:网页只能使用与自身相同域的 DOM 对象。

  3. AJAX 限制:XMLHttpRequest 和 Fetch 请求只能发送到同源的地址,否则会被拒绝。

  4. Frame 和 Window 限制:一个窗口只能访问包含它的窗口的子窗口中的文档,只有在同源时才能够执行操作。

同源策略的存在可以有效防止恶意网站通过跨站点脚本攻击(XSS)等方式来窃取用户信息或进行其他恶意行为。它确保了不同源之间的数据隔离和安全性。

开发者可以通过使用跨域资源共享(CORS)机制来解决跨域资源访问的问题,以允许某些跨源请求。通过在服务器端设置适当的响应头,可以允许指定源的网页访问服务器资源,从而绕过同源策略的限制。

总的来说,同源策略是浏览器提供的一种安全机制,帮助保护用户信息和数据安全。开发者在进行跨域资源访问时需要遵守同源策略

TS

  1. 什么是 TypeScript?

    • TypeScript 是一种由微软开发的开源编程语言,是 JavaScript 的一个超集,添加了静态类型和其他特性。
  2. TypeScript 的优势是什么?

    • 静态类型检查、更好的代码提示和 IntelliSense、更易于重构和维护、更好的可读性和可维护性、更丰富的面向对象编程能力等。
  3. 什么是类型注解和类型推断?

    • 类型注解是在声明变量、函数参数或返回值时显式指定类型;类型推断是 TypeScript 编译器根据代码上下文自动推断出变量的类型。
  4. 什么是接口(Interface)?

    • 接口是 TypeScript 中用于定义对象的结构的一种方式,可以描述对象的属性和方法的形状。
  5. 什么是泛型(Generics)?

    • 泛型是一种在编写可重用、灵活和类型安全的代码时使用的特性,允许在定义函数、类或接口时延迟指定具体类型。
  6. 如何定义枚举类型(Enum)?

    • 枚举类型可以通过关键字 enum 来定义,用于表示一组命名的常量值。
  7. TypeScript 中的命名空间和模块有什么区别?

    • 命名空间用于避免全局命名冲突,而模块则用于组织代码和提供封装性,模块可以导出和导入功能。
  8. 什么是类型断言(Type Assertion)?

    • 类型断言是一种在 TypeScript 中告诉编译器某个值的类型的方式,类似于强制类型转换。

     9. TS啥时用泛型?

                 TypeScript 中的泛型(Generics)是一种在编写可重用、灵活和类型安全的代码         时非常有用的特性。通常情况下,你可以考虑以下情况使用泛型:

  1. 提高代码的灵活性:当你编写的函数、类或接口需要与多种数据类型一起使用时,可以使用泛型来实现代码的灵活性,而不是针对特定的数据类型。

  2. 类型安全:通过使用泛型,可以在编译时捕获类型错误,从而避免在运行时出现类型错误。

  3. 代码重用:泛型可以帮助你编写更通用的代码,减少代码重复,提高代码的可维护性和可复用性。

  4. 容器和数据结构:在编写容器类(例如数组、堆栈、队列等)或其他数据结构时,泛型可以帮助你支持多种类型的数据。

  5. 函数操作:在需要对不同类型的数据执行相似操作的情况下,可以使用泛型来编写通用的函数操作。

    // 定义一个泛型函数,用于打印任意类型数组的元素
    function printArray<T>(arr: T[]): void {
        arr.forEach(item => {
            console.log(item);
        });
    }
    
    // 使用泛型函数打印不同类型数组的元素
    const numberArray: number[] = [1, 2, 3, 4, 5];
    const stringArray: string[] = ["apple", "banana", "orange"];
    
    printArray<number>(numberArray); // 打印数字数组
    printArray<string>(stringArray); // 打印字符串数组
    

总的来说,当你需要编写可重用性高、灵活性强且类型安全的代码时,可以考虑使用 TypeScript 的泛型功能

元组和联合类型 

// 元组 
// 定义:元组是一种固定长度且类型固定的数组结构,在元组中的每个位置可以指定特定的类型。
// 定义一个包含字符串和数字的元组
let tuple: [string, number];
tuple = ["hello", 10]; // 合法赋值
tuple = [10, "world"]; // 错误,类型不匹配


// 联合类型
// 定义:联合类型允许一个值可以是多种类型之一,通过使用 | 分隔不同类型。
// 定义一个联合类型变量
let unionVar: string | number;
unionVar = "hello"; // 合法赋值
unionVar = 10; // 合法赋值
unionVar = true; // 错误,布尔类型不在联合类型中

单页面和多页应用的区别

单页面应用(SPA):

  1. 架构特点

    • SPA 是指整个应用程序在加载初始页面后,通过 AJAX 和动态加载内容的方式来实现页面的更新和切换,而不是每次页面跳转都向服务器请求新的页面。
    • SPA 通常包括一个单一的HTML文件,通过前端路由控制不同视图的显示,从而实现页面的切换和交互。
  2. 优点

    • 用户体验好,页面切换流畅,无需每次都重新加载整个页面。
    • 前后端分离,提高开发效率,前后端可以并行开发。
    • 可以更好地实现页面状态的保存和管理。
  3. 缺点

    • 首次加载可能较慢,因为需要加载整个应用的 JavaScript 资源。
    • 对SEO不友好,需要额外的处理来优化搜索引擎索引。
    • 复杂度较高,需要处理路由、状态管理等方面的逻辑。

多页面应用(MPA):

  1. 架构特点

    • MPA 指的是应用程序由多个页面组成,每个页面之间通过超链接跳转,每次跳转都会请求服务器加载新页面。
  2. 优点

    • 初次加载速度快,因为每个页面只加载所需的资源。
    • 更利于SEO,每个页面都有独立的URL,容易被搜索引擎收录。
  3. 缺点

    • 用户体验相对差,页面切换时需要重新加载整个页面,可能会有闪烁感。
    • 开发效率较低,前后端耦合度高,不利于团队协作和维护。

总结:

  • SPA适合对用户体验要求较高、复杂交互逻辑的应用,如社交网络、在线办公工具等。
  • MPA适合内容较为独立、SEO要求高的应用,如新闻门户、官方网站等。

懒加载的实现原理 

懒加载(Lazy Loading)是一种优化技术,用于延迟加载应用程序中的资源(如图片、JavaScript文件、CSS文件等),以提高页面加载速度和性能。懒加载的实现原理主要涉及到以下几个方面:

图片懒加载的实现原理:

  1. 初始状态:在页面加载时,img标签的src属性为空或为占位图片的URL。
  2. 触发加载:当图片进入用户可视区域(如滚动到可视范围内)时,通过JavaScript动态设置img标签的src属性为真实图片的URL。
  3. 加载图片:浏览器开始加载图片资源并显示在页面中。

JavaScript懒加载的实现原理:

  1. 初始状态:将需要延迟加载的JavaScript代码替换为占位符或空函数。
  2. 触发加载:当特定条件满足(如用户交互、页面滚动等),通过动态创建script标签,并设置其src属性为延迟加载的JavaScript文件的URL。
  3. 加载脚本:浏览器开始异步加载对应的JavaScript文件并执行代码。

CSS懒加载的实现原理:

  1. 初始状态:将需要延迟加载的CSS文件替换为占位符或内联样式。
  2. 触发加载:当特定条件触发时(如用户操作、页面状态变化等),通过JavaScript动态创建link标签,并将延迟加载的CSS文件的URL赋给href属性。
  3. 加载样式:浏览器开始异步加载对应的CSS文件并应用样式。

总体原理:

懒加载的核心思想是根据用户行为或页面状态,延迟加载部分资源,从而减少初始加载时间和网络请求,提升页面性能和用户体验。通过监听事件或监测元素位置等方式,动态加载资源,避免一次性加载过多内容导致性能下降。

vue操作dom的方式 

1. 数据绑定:

Vue通过双向数据绑定将数据与DOM元素关联起来,当数据发生变化时,相关的DOM也会相应地更新。

<div id="app">
    <p>{{ message }}</p>
</div>
var app = new Vue({
    el: '#app',
    data: {
        message: 'Hello, Vue!'
    }
});

message属性的变化会自动更新到<p>标签中。

2. 指令(Directives):

Vue提供了一系列指令,用于操作DOM元素和数据之间的关系。

  • v-bind:动态绑定HTML属性。
  • v-model:实现表单元素和数据的双向绑定。
  • v-if / v-show:根据条件控制元素的显示与隐藏。
  • v-for:循环渲染列表数据到DOM中。
  • v-on:绑定事件监听器,响应DOM事件。

3. 计算属性和侦听器:

Vue提供了计算属性(Computed Properties)和侦听器(Watchers),用于在数据变化时执行自定义逻辑,进而更新DOM。

var app = new Vue({
    el: '#app',
    data: {
        firstName: 'John',
        lastName: 'Doe'
    },
    computed: {
        fullName: function() {
            return this.firstName + ' ' + this.lastName;
        }
    },
    watch: {
        firstName: function(newValue, oldValue) {
            // 监听firstName的变化
        }
    }
});

4. Refs:

通过ref属性可以在Vue组件中获取DOM元素的引用,以便直接操作DOM。

<div id="app">
    <input type="text" ref="inputField">
</div>
var app = new Vue({
    el: '#app',
    mounted() {
        this.$refs.inputField.focus(); // 聚焦到输入框
    }
});

在Vue中尽量避免直接操作DOM,而是通过数据驱动视图的方式来管理页面状态

webpack的编译与构建

Webpack是一个现代的JavaScript应用程序的静态模块打包工具。它主要用于将多个模块(如JavaScript、样式表、图片等)打包成一个或多个静态资源文件。Webpack的编译与构建过程通常包括以下几个步骤:

1. 配置文件:

首先,需要在项目根目录下创建一个webpack.config.js文件,用于配置Webpack的各种参数和规则,包括入口文件、输出路径、加载器(Loader)、插件(Plugin)等。

2. 入口文件:

在配置文件中指定一个或多个入口文件,Webpack会从这些入口文件开始解析应用程序的依赖关系,构建整个应用程序的依赖图。

3. 加载器(Loader):

Webpack通过加载器来处理各种类型的文件,如转换ES6语法、处理样式表、压缩图片等。常用的加载器有babel-loadercss-loaderfile-loader等,通过配置可以将这些加载器应用到特定类型的文件上。

4. 插件(Plugin):

插件可以用于执行各种任务,如代码压缩、打包优化、资源管理等。常用的插件有HtmlWebpackPluginMiniCssExtractPluginUglifyJsPlugin等,通过配置可以将这些插件应用到Webpack的构建过程中。

5. 输出(Output):

通过配置输出路径和文件名等参数,Webpack会将打包后的资源输出到指定目录下,可以是单个文件或多个文件。

6. 运行Webpack:

运行Webpack时,Webpack会根据配置文件中的设置,对项目进行编译和构建。可以通过命令行工具或集成到构建工具中来执行Webpack的编译过程。

7. 开发模式与生产模式:

在开发阶段,通常会使用Webpack的开发服务器(webpack-dev-server)来实时监测文件变化并自动重新构建。而在生产环境中,通常会对代码进行优化、压缩等处理,以提高性能和减少文件大小。

 最长回文串

function longestPalindrome(s) {
    // 如果字符串为空,直接返回空字符串
    if (s.length === 0) {
        return "";
    }

    let start = 0;
    let end = 0;

    for (let i = 0; i < s.length; i++) {
        let len1 = expandAroundCenter(s, i, i); // 以一个字符为中心进行扩展
        let len2 = expandAroundCenter(s, i, i + 1); // 以两个字符为中心进行扩展
        let len = Math.max(len1, len2);

        if (len > end - start) {
            start = i - Math.floor((len - 1) / 2);
            end = i + Math.floor(len / 2);
        }
    }

    return s.substring(start, end + 1); // 返回最长回文子串
}

function expandAroundCenter(s, left, right) {
    while (left >= 0 && right < s.length && s[left] === s[right]) {
        left--;
        right++;
    }

    return right - left - 1; // 返回回文串的长度
}

// 测试
let inputString = "babad";
console.log(longestPalindrome(inputString)); // 输出最长回文串

父子组件加载顺序

在 Vue 中,父子组件的生命周期加载顺序如下:

  1. 父组件

    • beforeCreate:父组件实例被创建之前调用
    • created:父组件实例已经创建完成后调用
    • beforeMount:父组件模板开始解析之前调用
    • mounted:父组件模板已经解析完成并挂载到页面之后调用
  2. 子组件

    • beforeCreate:子组件实例被创建之前调用
    • created:子组件实例已经创建完成后调用
    • beforeMount:子组件模板开始解析之前调用
    • mounted:子组件模板已经解析完成并挂载到页面之后调用

卸载顺序

  1. 父组件

    • beforeUnmount:父组件即将被卸载前调用
    • unmounted:父组件已经被卸载后调用
  2. 子组件

    • beforeUnmount:子组件即将被卸载前调用
    • unmounted:子组件已经被卸载后调用

 rem rm vw分别是什么,实现原理

在前端开发中,remempxvw 等单位常用于设置元素的尺寸。这些单位有不同的工作原理和用途:

  1. rem(root em)

    • rem 是相对于根元素(<html>元素)的字体大小来定义大小的单位。
    • 例如,如果根元素的字体大小(font-size)为16px,那么1rem就等于16px
    • 使用rem可以方便地实现响应式设计,根据根元素的字体大小来调整整个页面的布局。
  2. em

    • em 是相对于父元素字体大小来定义大小的单位。
    • 例如,如果父元素的字体大小为16px1em就等于16px
    • em单位可以方便地实现相对大小的布局,但可能会受到嵌套元素字体大小影响。
  3. px

    • px 是绝对长度单位,指定一个元素的固定大小。
    • px 不随父元素或根元素的大小变化而改变,是最常见的单位之一。
  4. vw(viewport width)

    • vw 是视窗宽度的百分比单位,1vw 等于视窗宽度的1%。
    • 例如,如果视窗宽度为1000px1vw 就等于10px
    • 使用 vw 单位可以根据视窗大小来设置元素的大小,实现响应式设计。

实现原理:

  • remempx 是相对单位,分别相对于根元素、父元素、屏幕像素进行计算。
  • vw 是相对于视窗宽度的百分比单位,在不同视窗大小下会自动进行缩放。

rem怎么通过改变根元素大小来调整屏幕呢,原理是啥 

通过改变根元素的大小来调整屏幕,通常是指使用 CSS 中的 rem 单位进行响应式设计。rem 是相对长度单位,它相对于根元素的字体大小(font-size)来计算实际的长度值。因此,改变根元素的字体大小会影响整个页面中使用 rem 单位的元素的大小。

原理

  1. 根元素字体大小:浏览器默认的根元素字体大小通常是 16px,即 1rem 等于 16px。通过修改根元素的字体大小,可以改变整个页面中使用 rem 单位的元素的大小。
  2. 相对比例:rem 单位是相对于根元素的字体大小来计算的,例如,如果根元素的字体大小为 20px,那么 1rem 就等于 20px。

改变根元素大小的方法

在实际开发中,可以通过 JavaScript 动态修改根元素的字体大小,从而实现页面的响应式设计。以下是一个示例代码

// 在页面加载时设置根元素的字体大小
function setRootFontSize() {
  var screenWidth = document.documentElement.clientWidth; // 获取屏幕宽度
  var fontSize = screenWidth / 20; // 假设 1rem 等于屏幕宽度的 1/20
  document.documentElement.style.fontSize = fontSize + 'px'; // 设置根元素字体大小
}

// 监听窗口大小变化,并在窗口大小变化时重新设置根元素字体大小
window.addEventListener('resize', setRootFontSize);

// 页面加载时先执行一次设置根元素字体大小的操作
setRootFontSize();

首先获取屏幕宽度,然后根据需要的比例计算出根元素的字体大小,并将其应用到根元素上。随后,我们通过监听窗口大小变化事件,在窗口大小变化时重新计算并设置根元素的字体大小,从而实现页面的响应式设计。 

sass less 实现原理 

Sass 和 Less 都是 CSS 预处理器,它们提供了许多便利的功能和语法来帮助开发人员更高效地编写和管理样式表。下面我来简单介绍一下它们的实现原理:

Sass 的实现原理

  1. 编译过程:Sass 使用 Ruby 编程语言编写,其实现原理是将 Sass 文件编译成普通的 CSS 文件。在编译过程中,Sass 解析并转换 Sass 文件中的 SCSS 或 Sass 语法,将其转换为标准的 CSS 样式表。
  2. 特性转换:Sass 提供了许多特性,如变量、嵌套、混合(Mixin)、继承等,这些特性在编译过程中会被转换成相应的 CSS 代码,并且可以帮助简化样式表的编写和维护。
  3. 模块化支持:Sass 支持模块化开发,可以将样式代码分割成多个文件,并通过 @import 指令引入,最终会在编译过程中将这些文件合并为一个 CSS 文件。

Less 的实现原理

  1. 编译过程:Less 使用 JavaScript 编写,其实现原理也是将 Less 文件编译成普通的 CSS 文件。在编译过程中,Less 解析 Less 文件中的语法,将其转换为标准的 CSS 样式表。
  2. 特性转换:Less 同样提供了类似 Sass 的特性,如变量、嵌套、混合、继承等,这些特性在编译过程中会被转换成对应的 CSS 代码,有助于提高样式表的可维护性和重用性。
  3. 动态功能:Less 还支持一些动态功能,如函数、运算符等,可以在样式表中实现一些动态计算和逻辑处理。

 promise实现原理

Promise 是 JavaScript 中用于处理异步操作的对象,它表示一个异步操作的最终完成(或失败)及其结果值。Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。下面是 Promise 的简单实现原理:

Promise 实现原理

  1. 状态管理:Promise 内部维护一个状态(state),初始状态为 pending。状态可以转变为 fulfilled(resolved)或 rejected,分别表示异步操作成功完成和失败。
  2. then 方法:Promise 对象具有 then 方法,用于指定异步操作成功(onFulfilled)和失败(onRejected)时的回调函数。当异步操作完成后,根据状态调用相应的回调函数。
  3. 异步操作处理:Promise 的执行顺序是同步执行 then 方法中的回调函数,异步操作完成后再执行对应的回调函数,保证了异步操作的顺序性。
  4. 链式调用:Promise 支持链式调用,每次调用 then 方法都会返回一个新的 Promise 对象,实现了串行执行异步操作的功能。
  5. 错误处理:Promise 对象可以通过 catch 方法捕获异常,避免在异步操作过程中出现未捕获的错误。

简单实现示例

下面是一个简单的 Promise 实现示例,仅包含基本功能,实际 Promise 实现要更复杂和完善:

class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onFulfilledCallbacks.forEach(callback => callback(this.value));
      }
    };

    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.value = reason;
        this.onRejectedCallbacks.forEach(callback => callback(this.value));
      }
    };

    executor(resolve, reject);
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        onFulfilled(this.value);
      } else if (this.state === 'rejected') {
        onRejected(this.value);
      } else {
        this.onFulfilledCallbacks.push(onFulfilled);
        this.onRejectedCallbacks.push(onRejected);
      }
    });
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }
}

 谈谈异步编程

异步编程在现代编程中扮演着至关重要的角色,特别是在处理网络请求、文件 I/O、定时任务等需要花费时间的操作时显得尤为重要。以下是一些关于异步编程的讨论:

1. 背景

随着计算机系统的发展,程序的复杂性和功能需求不断增加,传统的同步编程模型往往无法满足需求。异步编程的提出为解决这一问题提供了新的思路。

2. 优势

  • 提高性能:异步编程可以充分利用多核处理器和非阻塞 I/O 操作,提高程序的性能。
  • 提升用户体验:在 Web 开发中,异步编程可以使得页面更加流畅响应,提升用户体验。
  • 简化逻辑:通过异步编程,可以将耗时的操作放在后台执行,简化了程序逻辑,提高代码的可读性和维护性。
  • 并发处理:异步编程允许多个任务同时执行,实现并发处理,提高了程序的效率。

3. 实现方式

  • 回调函数:最传统的方式,但容易导致回调地狱,代码可读性差。
  • Promise:提供更好的链式调用和错误处理机制,解决了回调地狱问题。
  • Async/Await:基于 Promise 的语法糖,使异步代码看起来更像同步代码,进一步简化了异步编程。

4. 挑战

  • 错误处理:异步编程中错误处理相对复杂,需要额外小心处理异常。
  • 调试困难:由于异步操作的特性,调试时可能会遇到一些困难,需要使用适当的工具进行排查。
  • 并发控制:在涉及多个并发操作时,需要谨慎处理并发控制,避免数据竞争等问题。

js事件循环

JavaScript 中的事件循环是指 JavaScript 运行时处理代码和事件的一种机制,用于管理代码执行顺序和处理异步操作。事件循环主要涉及到以下几个部分:调用栈(Call Stack)、任务队列(Task Queue)、微任务队列(Microtask Queue)和事件循环。

  1. 调用栈(Call Stack): 调用栈是用来存储函数调用的栈结构,在执行 JavaScript 代码时,会将函数调用按顺序压入调用栈,直到函数执行完成后再从栈顶弹出。JavaScript 是单线程执行的语言,因此只有一个调用栈。

  2. 任务队列(Task Queue): 任务队列存储了待执行的异步任务,如 setTimeout、setInterval 等。当异步任务完成后,会被推送到任务队列中等待执行。

  3. 微任务队列(Microtask Queue): 微任务队列存储了需要尽快执行的任务,如 Promise 的回调函数、MutationObserver 等。微任务会在当前宏任务执行完成后立即执行,确保在下一个宏任务之前执行完毕。

  4. 事件循环(Event Loop): 事件循环负责不断地从任务队列和微任务队列中获取任务,并将其压入调用栈中执行。事件循环会根据一定的规则决定优先执行哪种任务,通常遵循以下规则:

    • 从微任务队列中取出所有任务依次执行,直到微任务队列为空。
    • 从任务队列中取出一个任务执行,执行完后检查是否有微任务需要执行,如果有则依次执行所有微任务,然后再取出一个任务执行。
    • 重复以上步骤,直到任务队列和微任务队列都为空。

js底层的打包过程

1. 代码分析(Code Analysis)

  • 入口文件识别: 打包工具首先会从指定的入口文件开始,分析整个项目的依赖关系。
  • AST 构建: 打包工具会通过抽象语法树(AST)来解析 JavaScript 代码,构建代码的抽象语法树表示。

2. 依赖图谱构建(Dependency Graph)

  • 依赖收集: 打包工具会递归分析每个模块的依赖关系,构建出完整的依赖图谱。
  • 模块解析: 根据 import、require 等语句,确定模块之间的引用关系,包括本地模块和第三方模块。

3. 模块转换和优化(Module Transformation and Optimization)

  • 模块转换: 将模块中使用的新语法或格式转换为浏览器可识别的标准 JavaScript 代码,如 Babel 转换 ES6+ 语法为 ES5。
  • 代码压缩: 对转换后的代码进行压缩和优化,删除空格、注释,并且进行代码混淆以减小文件体积。

4. 打包输出(Bundle Output)

  • 生成输出文件: 将经过转换和优化后的模块组装成一个或多个输出文件,通常是一个 bundle 文件。
  • 资源管理: 处理图片、字体等静态资源文件,并将其嵌入到打包生成的文件中或单独输出。

5. 模块分块和按需加载(Code Splitting and Lazy Loading)

  • 按需加载: 实现代码分割和按需加载,将不同功能模块拆分成独立的 chunk,在需要时动态加载。
  • 懒加载: 将一些不必要立即加载的模块延迟加载,提高页面加载速度和性能。

6. 插件支持和扩展(Plugin Support and Extensibility)

  • 插件应用: 打包工具通常提供丰富的插件系统,使得开发者可以根据需要扩展打包过程,实现自定义优化和功能增强。
  • 定制化配置: 允许开发者根据项目需求进行灵活的配置,包括代码分割策略、优化规则等。

7. 性能优化(Performance Optimization)

  • 缓存和持久化: 利用缓存机制(如文件 hash 命名、chunkhash)提高再次构建的效率。
  • 并行处理: 使用多进程或多线程并行处理提高打包速度。
  • 懒加载策略: 合理使用代码分割和动态导入技术,减小首次加载体积。

将 JavaScript 代码打包成浏览器识别的代码通常需要经过以下几个步骤:

1. 模块化管理

在项目中使用模块化的开发方式,例如使用 ES6 的模块化语法(import/export)或 CommonJS 规范(require/exports)来组织代码。

2. 选择合适的打包工具

选择一个适合的 JavaScript 打包工具,比如 Webpack、Parcel、Rollup 等,用于将多个模块打包成浏览器可以识别的单个文件。

3. 配置打包工具

配置打包工具,指定入口文件、输出文件路径、处理不同类型资源的 loader(如 Babel 处理 ES6+ 语法、CSS/Sass 处理等)、插件等。

4. 执行打包命令

运行打包命令,让打包工具根据配置文件去处理项目中的所有模块,并生成最终的打包文件。

5. 代码转换和优化

打包工具会对 JavaScript 代码进行语法转换、压缩和优化,确保最终生成的代码在浏览器中能够正确运行并且具有更好的性能。

6. 输出浏览器可识别的文件

打包工具将所有模块打包成一个或多个浏览器可识别的文件,通常是一个 bundle.js 文件,在 HTML 中引入这个文件即可。

7. 部署到服务器

将打包生成的文件上传到服务器,通过 <script> 标签引入到 HTML 页面中,使浏览器能够加载并执行该文件。

let const

let:

  • let 关键字用于声明一个块级作用域的变量。
  • 使用 let 声明的变量可以被重新赋值,但不能被重复声明。
  • 声明的变量仅在其声明的块级作用域内可用,不存在变量提升。
  • 适合用于需要在后续代码中修改的变量。  

    const:

  • const 关键字用于声明一个只读的常量,一旦赋值就不能再改变。
  • 使用 const 声明的变量必须在声明时进行初始化赋值。
  • 声明的常量也具有块级作用域,不存在变量提升。
  • 适合用于不需要重新赋值的常量

 箭头函数

箭头函数具有一个特殊的特性,即其内部的 this 指向在定义函数时所处的上下文,而不是在执行时所处的上下文。这与传统的函数声明方式有所不同。以下是关于箭头函数 this 指向的一些重要信息:

1. 箭头函数的特点

  • 箭头函数没有自己的 this,它会继承父作用域中的 this 值。
  • 因此,箭头函数内部的 this 指向的是定义该箭头函数时所处的词法作用域的 this 值,而不是调用时的 this

2. 示例

function Person() {
    this.age = 0;

    // 普通函数
    setInterval(function growUp() {
        // 此处的 this 指向全局对象,不是 Person 对象
        this.age++;
        console.log('普通函数中的 this.age:', this.age);
    }, 1000);

    // 箭头函数
    setInterval(() => {
        // 箭头函数中的 this 指向 Person 对象
        this.age++;
        console.log('箭头函数中的 this.age:', this.age);
    }, 1000);
}

let person = new Person();

3. 总结

  • 在使用箭头函数时,需要注意其内部的 this 指向是静态的,并且取决于箭头函数的定义位置。
  • 箭头函数常用于简化代码和解决传统函数中 this 指向问题的情况。

通过了解箭头函数的 this 指向规则,您可以更好地利用箭头函数来避免 this 混乱的问题。

+++   拓展:

之所以采用词法作用域的 this,是为了解决传统函数中 this 指向易混乱的问题。这种设计有以下几个优点:

1. 易于理解

由于箭头函数的 this 指向是静态的,取决于函数定义时的上下文,因此可以更容易地理解代码在不同地方的 this 指向情况,避免了传统函数中 this 动态绑定导致的混乱。

2. 简化代码

箭头函数的静态 this 绑定简化了代码逻辑,尤其是在嵌套函数和回调函数中,避免了频繁地对 this 进行手动绑定或使用额外的变量保存 this 的值。

3. 避免意外改变

传统函数中的 this 容易受到调用方式的影响而发生改变,而箭头函数通过固定的 this 绑定避免了这种情况,减少了意外错误的发生。

4. 保持一致性

箭头函数的 this 行为始终保持一致,不会因调用方式、对象绑定等因素而改变,这种一致性能够让开发者更容易预测和理解代码的行为。

 Babel

Babel 是一个广泛使用的 JavaScript 编译器,它主要用于将当前或未来版本的 JavaScript 代码转换为向后兼容的版本,以确保在旧版浏览器或环境中也能够正常运行。以下是关于 Babel 的一些重要信息:

1. 功能和特点

  • 语法转换: Babel 可以将 ES6+(ES2015+)及以上版本的 JavaScript 代码转换为 ES5 或更低版本的代码,以便支持更多的环境。
  • 插件系统: Babel 提供了丰富的插件系统,允许开发者根据需求自定义转换规则,扩展 Babel 的功能。
  • 平台支持: Babel 不仅可以用于 Web 开发,还可以用于 Node.js、React Native 等平台的项目中。

2. 工作原理

  • Babel 的工作原理包括三个阶段:解析(Parsing)、转换(Transformation)和生成(Generation)。
  • 解析阶段将代码解析成抽象语法树(AST),转换阶段对 AST 进行修改和转换,生成阶段将修改后的 AST 转换回代码。

3. 应用场景

  • 新特性支持: Babel 可用于支持最新的 ECMAScript 标准,如箭头函数、模板字符串、解构赋值等特性。
  • 浏览器兼容性: Babel 可用于转换新语法以确保在各种浏览器中的兼容性。
  • 框架和库开发: 许多前端框架和库(如 React、Vue)都使用 Babel 来转换其代码。

4. 安装和配置

  • 使用 npm 或 yarn 安装 Babel:npm install @babel/core @babel/cli
  • 创建 .babelrc 配置文件,指定需要的转换插件和预设。
  • 运行 Babel:npx babel src -d dist

 实现深拷贝

1. 递归实现深拷贝

// 使用 ES6 语法和方法实现深拷贝
const deepCopy = (obj) => {
    // 如果对象为 null 或者不是对象类型,则直接返回该值
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }

    // 初始化一个空对象或数组,用于存储深拷贝后的值
    const copy = Array.isArray(obj) ? [] : {};

    // 遍历对象的属性
    for (const [key, value] of Object.entries(obj)) {
        // 递归深拷贝每个属性的值
        copy[key] = deepCopy(value);
    }

    return copy; // 返回深拷贝后的对象
};

2. JSON 序列化与反序列化

3. 使用第三方库(如 lodash)

许多第三方库提供了更强大和健壮的深拷贝方法,比如 lodash 中的 _.cloneDeep

const _ = require('lodash'); let copiedObj = _.cloneDeep(originalObj);

  • 数组深拷贝

方法一:使用 ES6 的展开运算符(Spread Operator)

const originalArray = [1, 2, 3]; const deepCopyArray = [...originalArray];

方法二:使用 Array.from() 方法

const originalArray = [1, 2, 3]; const deepCopyArray = Array.from(originalArray);

方法三:使用 Array.prototype.slice() 方法

const originalArray = [1, 2, 3]; const deepCopyArray = originalArray.slice();

方法四:使用 JSON 序列化和反序列化

const originalArray = [1, 2, 3]; const deepCopyArray = JSON.parse(JSON.stringify(originalArray));

执行上下文

执行上下文(Execution Context)是 JavaScript 中管理代码执行的概念,它包含了代码在执行过程中需要的环境信息。而执行栈(Execution Stack),也称为调用栈(Call Stack),是用来管理执行上下文的一种数据结构,遵循先进后出的原则。

当 JavaScript 代码执行时,会创建全局执行上下文,然后在调用函数时会创建函数执行上下文,这些执行上下文被按照调用顺序以栈的形式排列,当前正在执行的执行上下文位于栈顶。当一个函数执行完成后,其对应的执行上下文会从栈中弹出,控制权转移到上一个执行上下文。

下面是一个简单的示例,演示了执行上下文执行栈的概念:

function func1() {
    console.log("func1");
    func2();
}

function func2() {
    console.log("func2");
}

func1();

 在这个示例中,首先将全局执行上下文推入执行栈,然后调用 func1 函数,创建 func1 的执行上下文并推入执行栈顶部,输出 "func1" 后调用 func2 函数,创建 func2 的执行上下文并推入执行栈顶部,输出 "func2"。当 func2 执行完成后,其执行上下文从执行栈中弹出,控制权回到 func1,最后 func1 执行完成,全局执行上下文成为栈顶,整个执行过程结束。

 渲染页面的流程

渲染页面的流程通常涉及到浏览器的整个渲染过程,可以简单地分为以下几个步骤:

  1. HTML 解析: 浏览器首先会下载 HTML 文件,并解析其中的标记,构建 DOM(文档对象模型)树。DOM 树表示了页面的结构,包括各个元素的嵌套关系。

  2. CSS 解析和样式计算: 浏览器会下载外部 CSS 文件并解析其中的样式规则,然后计算出每个元素最终的样式。这些样式信息会与 DOM 树结合,形成带有样式信息的渲染树(Render Tree)。

  3. 布局(Layout): 浏览器根据渲染树中每个元素的样式和尺寸信息,计算它们在页面上的位置和大小。这个过程也被称为回流(Reflow)。

  4. 绘制(Painting): 浏览器将布局好的元素按照层级关系进行绘制,将它们转换为屏幕上的像素。这个过程也被称为重绘(Repaint)。

  5. 合成(Composite): 浏览器将不同层级的绘制结果合成在一起,显示在用户的屏幕上。这个过程利用硬件加速来提高性能。

在整个页面渲染的过程中,浏览器会根据需要执行 JavaScript 代码,可能会触发 DOM 更新、重新计算样式、布局等操作,从而影响页面的渲染。

注意,在现代的 Web 开发中,为了提高性能和用户体验,会采用一些优化技术,比如异步加载资源、懒加载、缓存等方法,以加快页面的加载和渲染速度。

路由守卫

路由守卫是在前端框架(如Vue.js、React等)中用于控制页面跳转的一种机制。通过路由守卫,可以在页面跳转前、跳转过程中和跳转后执行一些逻辑,比如验证用户身份、检查权限、重定向等。

在Vue.js中,常见的路由守卫包括以下几种:

  1. 全局前置守卫 (Global Before Guards):

    • router.beforeEach(to, from, next):在路由跳转前执行,可以用于进行全局的身份验证、权限检查等操作。
  2. 全局解析守卫 (Global Resolve Guards):

    • router.beforeResolve(to, from, next):在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后执行。
  3. 全局后置钩子 (Global After Hooks):

    • router.afterEach(to, from):在路由跳转后执行,可以用于记录页面访问日志等操作。
  4. 路由独享的守卫 (Per-Route Guard):

    • 在定义路由规则时,可以通过 beforeEnter 参数添加特定路由独享的守卫。
  5. 组件内的守卫 (In-Component Guard):

    • 在Vue组件内部通过 beforeRouteEnterbeforeRouteUpdate 和 beforeRouteLeave 钩子函数来实现对当前组件的守卫逻辑。

webpack  插件和加载机的区别 

在Webpack中,插件(Plugin)和加载器(Loader)是两个不同的概念,它们分别用于处理不同的任务。

  1. 加载器(Loader):

    • 加载器是用于对特定类型的资源文件进行转换和处理的工具。比如,在Webpack中,当需要加载一个 CSS 文件时,可以使用 css-loader 来处理CSS文件,style-loader来将CSS注入到页面中。
    • 加载器的作用是将不同类型的文件转换为模块,以便Webpack能够处理这些模块并将其包含到最终的构建结果中。
    • 加载器通常在module.rules配置中进行设置,用于匹配特定文件类型,并指定相应的转换规则。
  2. 插件(Plugin):

    • 插件是用于执行更广泛范围任务的工具,它可以用来执行各种任务,例如打包优化、资源管理、注入环境变量等。
    • 通过使用插件,可以对Webpack的整个编译过程进行干预和定制,以满足特定的需求。
    • 插件通常在Webpack配置文件中通过 plugins 配置项来添加,每个插件都是一个独立的JavaScript对象,通过实例化插件对象并添加到配置中来启用。

 双向绑定原理

Vue 2.x 中的双向绑定是通过数据劫持和发布-订阅模式来实现的。下面是 Vue 2.x 中双向绑定的基本实现原理:

  1. 数据劫持(Data Binding):

    • 在 Vue 实例化过程中,会对 data 对象中的每个属性使用 Object.defineProperty() 进行劫持,为每个属性创建一个 Dep 实例。
    • 每个属性的 getter 负责收集依赖(将 Watcher 添加到 Dep 的订阅者列表),setter 负责触发更新(通知 Dep 更新所有订阅者)。
  2. 观察者(Watcher):

    • 每个 Vue 实例对应一个 Watcher 实例,当模板中使用到 data 中的属性时,会创建一个 Watcher 对象并且将其添加到对应属性的 Dep 订阅者列表中。
    • 当属性被修改时,它会触发对应 Dep 中所有 Watcher 的更新操作。
  3. 模板编译:

    • Vue 2.x 中使用模板编译器将 template 编译为 render 函数,render 函数包含了对数据的引用。
    • 在编译过程中,会为指令和插值表达式创建相应的更新逻辑,确保当数据改变时能够触发视图更新。
  4. 响应式系统:

    • 当数据发生变化时,触发 setter,Dep 通知所有订阅者 Watcher 进行更新。
    • Watcher 接收到通知后,执行更新视图的操作,保持视图和数据的同步。

在 Vue 3 中,双向绑定的实现原理与 Vue 2 有所不同,主要是基于 ES6 的 Proxy 对象而不再使用 Object.defineProperty()。以下是 Vue 3 中双向绑定的基本实现原理:

  1. Proxy 对象:

    • Vue 3 中使用 ES6 中的 Proxy 对象来代替 Object.defineProperty(),用于劫持(监听)数据对象的操作。
    • 通过 Proxy,可以监听到对象的读取、写入、删除等操作,并进行相应的处理。
  2. Reactivity(响应式):

    • 在 Vue 3 中,利用 Reactive API 来创建响应式的数据对象。
    • 当数据对象被创建时,会使用 Proxy 对象对其进行劫持,从而实现对数据的监控和响应。
  3. 响应式更新:

    • 当数据对象发生变化时,Proxy 会捕获到相应的操作,并触发更新通知。
    • 通过触发更新通知,Vue 3 可以实现视图的自动更新,确保数据与视图保持同步。
  4. 模板编译:

    • 与 Vue 2 类似,Vue 3 也会将模板编译为 render 函数,render 函数中包含了对数据的引用。
    • 在编译过程中,会为指令和插值表达式创建相应的更新逻辑,以实现视图与数据的绑定。

 nextTick函数实现原理

nextTick 函数是 Vue 中用于在下次 DOM 更新周期之后执行延迟回调的方法。它的实现原理涉及到浏览器的事件循环机制和微任务队列。下面是 nextTick 函数的基本实现原理:

  1. 宏任务与微任务:

    • 在浏览器中,任务被分为宏任务(macrotask)和微任务(microtask)两种类型,宏任务包括 setTimeout、setInterval 等,而微任务包括 Promise、MutationObserver 等。
  2. Vue 的 nextTick:

    • 在 Vue 中,nextTick 利用微任务队列的特性,在当前任务执行完毕后(DOM 更新周期结束前),执行传入的回调函数,确保在 DOM 更新后立即执行。
  3. 具体实现步骤:

    • Vue 在不同环境(如浏览器、Node.js 等)中会选择不同的技术实现 nextTick,通常会使用 Promise 或 MutationObserver 来实现微任务。
    • 当调用 nextTick 方法时,Vue 会将传入的回调函数存储到一个队列中。
    • 在当前任务执行完毕后,Vue 会利用微任务队列将队列中的回调函数依次执行,从而实现在 DOM 更新周期之后执行延迟回调。

总体来说,nextTick 函数的实现原理是利用浏览器的微任务队列,在当前任务执行完成后执行传入的回调函数,确保在 DOM 更新周期结束后立即执行延迟回调,以便处理 DOM 更新后的操作。

js数据类型有哪些,基本数据类型和引用数据类型的区别

基本数据类型(Primitive Data Types):

  1. Number(数字):整数或浮点数,如 42 或 3.14
  2. String(字符串):由单引号或双引号括起来的文本,如 'Hello' 或 "world"
  3. Boolean(布尔值):表示真或假的值,即 true 或 false
  4. Null(空值):表示空值或不存在的对象,只有一个值,即 null
  5. Undefined(未定义):表示声明了变量但未赋值,只有一个值,即 undefined
  6. Symbol(符号):ES6 新增,表示唯一的、不可改变的值,通常用于对象属性的标识符。

引用数据类型(Reference Data Types):

  1. Object(对象):复杂数据类型,包括对象、数组、函数等。
  2. Array(数组):一种特殊的对象,用于存储多个值。
  3. Function(函数):一种特殊的对象,具有可被调用的行为。

区别:

  1. 存储方式:基本数据类型的值直接存储在栈内存中,而引用数据类型的值存储在堆内存中,并且栈中存储的是指向堆内存的地址。
  2. 复制方式:基本数据类型的赋值是拷贝变量的值,而引用数据类型的赋值是拷贝变量的引用(地址)。
  3. 比较方式:基本数据类型可以通过值进行比较,而引用数据类型比较的是引用是否指向同一对象。

js闭包和作用域 

作用域(Scope):

  1. 全局作用域:在代码中任何地方都可以访问的作用域,全局作用域中定义的变量对整个程序可见。
  2. 函数作用域:在函数内部定义的变量只能在该函数内部访问,函数外部无法访问其中的变量。

闭包(Closure):

  1. 闭包定义:闭包是指函数和声明该函数的词法环境的组合。在 JavaScript 中,函数可以作为返回值被返回,也可以在其他函数中被定义,形成闭包。
  2. 作用:闭包可以访问其外部函数作用域中的变量,即使外部函数已经执行完毕,闭包仍然可以访问和操作外部函数中的变量。

什么数据存在对象中,什么数据存在prototype中

在 JavaScript 中,对象中存储实例特有的数据,而共享的数据和方法通常存储在原型(prototype)中。下面是它们的一些区别和作用:

数据存在对象中:

  1. 实例特有的数据:每个对象实例都可以拥有自己的属性和数据,这些数据存储在对象本身中。
  2. 私有属性:在构造函数或对象字面量中定义的属性通常会存储在对象实例中,每个实例都会有一份独立的数据。

示例:

function Person(name, age) {
    this.name = name; // 存储在对象中
    this.age = age;   // 存储在对象中
}

const person1 = new Person('Alice', 30);
const person2 = new Person('Bob', 25);

数据存在 prototype 中:

  1. 共享的数据和方法:原型对象包含所有实例共享的属性和方法,被所有实例共享。当访问对象的属性或方法时,如果对象本身没有,则会去原型链上查找。
  2. 节省内存:将方法和共享数据存储在原型中可以节省内存,因为所有实例共享相同的方法和数据。

示例:

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

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

const person1 = new Person('Alice', 30);
const person2 = new Person('Bob', 25);

person1.sayHello(); // 输出:Hello, my name is Alice
person2.sayHello(); // 输出:Hello, my name is Bob

typeofinstanceof 

  typeof 运算符:

  • 作用:用于确定变量的数据类型。
  • 返回值:返回一个表示变量类型的字符串。
  • 适用对象:主要用于基本数据类型(如字符串、数字、布尔值)、函数和 undefined
  • 注意事项:对于对象和数组返回的结果不够精确,都会被归类为 'object'
    typeof 'Hello';  // 返回 'string'
    typeof 42;       // 返回 'number'
    typeof true;     // 返回 'boolean'
    typeof undefined;  // 返回 'undefined'
    typeof [];       // 返回 'object'
    typeof {};       // 返回 'object'
    typeof function() {};  // 返回 'function'
    
    instanceof 运算符:
  • 作用:用于检测对象的原型链中是否存在指定构造函数的 prototype 属性。
  • 返回值:返回一个布尔值,表示对象是否是特定构造函数的实例。
  • 适用对象:主要用于判断对象是否是某个特定构造函数创建的实例。
  • 注意事项instanceof 只能用于对象(包括数组和函数等)的判断,无法用于基本数据类型。

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

const person = new Person('Alice');
console.log(person instanceof Person);  // 返回 true

const arr = [];
console.log(arr instanceof Array);  // 返回 true
console.log(arr instanceof Object);  // 返回 true,因为数组也是对象的实例

总结:

  • typeof 用于确定变量的数据类型,适用于基本数据类型和函数。
  • instanceof 用于检测对象是否是某个构造函数创建的实例,主要用于对象的判断。

 this指向是什么,如何改变this指向

在 JavaScript 中,this 是一个关键字,它指向当前函数执行的上下文,具体指向什么取决于函数被调用的方式。this 的指向可能会有以下几种情况:

  1. 全局上下文:在全局作用域中,this 指向全局对象(浏览器环境下指向 window 对象)。

  2. 函数上下文:在函数内部,this 的指向取决于函数的调用方式:

    • 作为函数调用this 指向全局对象或 undefined(严格模式下)。
    • 作为方法调用this 指向调用该方法的对象。
    • 作为构造函数调用this 指向新创建的实例对象。
    • 使用 call()apply() 或 bind() 方法:可以手动改变 this 的指向。
  3. 箭头函数:箭头函数没有自己的 this,它会捕获所在上下文的 this 值,指向定义时的外层作用域的 this 值。

如何改变 this 指向:

  1. 使用 call()apply()bind() 方法:这些方法可以显式地指定函数内部 this 的指向。

    示例:

    function greet() {
        console.log('Hello, ' + this.name);
    }
    
    const person = { name: 'Alice' };
    
    greet.call(person);  // 使用 call() 方法改变 this 指向
    greet.apply(person); // 使用 apply() 方法改变 this 指向
    
    const boundGreet = greet.bind(person); // 使用 bind() 方法返回一个新函数
    boundGreet();
    

    2. 使用箭头函数:箭头函数可以保持定义时的 this 指向,不会因为调用方式的改变而改变 this 的指向。

    示例:

    const obj = {
        name: 'Bob',
        greet: () => {
            console.log('Hello, ' + this.name); // this 指向外层作用域的 this
        }
    };
    
    obj.greet(); // 输出 'Hello, undefined'
    

    call() 和 apply()bind()区别

  • call() 和 apply() 都是立即调用函数,并改变函数内部 this 的指向,其中 apply() 接受参数数组,而 call() 接受一系列参数。

  • bind() 创建一个新的函数,该函数在被调用时将指定的对象作为 this 值,可以延迟调用。

原型链

  1. 原型:每个对象都有一个原型,它可以是另一个对象或 null
  2. 原型链:当访问一个对象的属性或方法时,如果该对象本身没有定义,则会沿着原型链向上查找,直到找到对应属性或方法或者到达原型链的终点。
  3. 终点:原型链的终点是 Object.prototype,它是所有对象的顶层原型。Object.prototype 并没有自己的原型,因此原型链在这里结束。

 async/await 底层原理是?generator的原理

底层原理是,当使用 async/await 时,JavaScript 引擎会将 await 关键字后面的表达式转换为 Promise,并调用该 Promise 的 thencatch 方法来处理异步操作的结果和异常。

关于 Generator 的原理,Generator 是一种特殊类型的函数,它可以在函数执行过程中暂停并在之后恢复。Generator 函数使用 function* 声明,内部使用 yield 关键字来定义暂停点。

当调用 Generator 函数时,并不会立即执行函数体,而是返回一个迭代器对象。通过迭代器对象可以控制 Generator 函数的执行,通过调用迭代器的 next() 方法可以让 Generator 函数继续执行直到遇到 yield 关键字暂停。调用 next() 方法时可以传入一个值作为上一个 yield 表达式的结果。

Generator 的实现原理是基于迭代器协议,通过迭代器对象来控制函数的执行流程,实现了函数的暂停和恢复。Generator 在异步编程中也有广泛的应用,例如配合 Promise 实现更灵活的异步操作控制。

function* myGenerator() {
  yield 'Apple';
  yield 'Banana';
  yield 'Cherry';
}

const generator = myGenerator();

console.log(generator.next().value); // 输出: 'Apple'
console.log(generator.next().value); // 输出: 'Banana'
console.log(generator.next().value); // 输出: 'Cherry'
console.log(generator.next().value); // 输出: undefined

http 状态码

  • 1xx(信息性状态码):表示接收到请求并且继续处理。

    • 100 Continue:服务器已经收到请求的部分,客户端可以继续发送剩余部分。
    • 101 Switching Protocols:服务器正在切换协议,客户端需要切换为新协议。
  • 2xx(成功状态码):表示请求被成功接收、理解、接受或处理。

    • 200 OK:请求成功。
    • 201 Created:请求已经被实现,新资源已经创建。
    • 204 No Content:服务器成功处理请求,但不需要返回任何内容。
  • 3xx(重定向状态码):表示需要客户端采取进一步操作才能完成请求。

    • 301 Moved Permanently:请求的资源已被永久移动到新位置。
    • 302 Found:请求的资源临时从不同的 URI 响应。
  • 4xx(客户端错误状态码):表示客户端发送的请求有错误。

    • 400 Bad Request:服务器无法理解请求。
    • 403 Forbidden:服务器拒绝请求。
    • 404 Not Found:请求的资源未找到。
  • 5xx(服务器错误状态码):表示服务器在处理请求时发生错误。

    • 500 Internal Server Error:服务器遇到了一个未曾预料的状况。
    • 503 Service Unavailable:服务器当前无法处理请求。

 协商缓存介绍,缓存过程,以及具体存在哪里

协商缓存是指客户端和服务器之间就资源是否需要重新获取进行协商的一种机制,它允许客户端在必要时获取新的资源,同时最大限度地减少对服务器的请求次数。在 HTTP 协议中,协商缓存主要通过以下两个标头来实现:Last-ModifiedETag

  1. Last-Modified / If-Modified-Since:

    • 服务器在响应请求时,通过 Last-Modified 标头返回资源的最后修改时间。
    • 客户端在下一次请求时,可以通过发送 If-Modified-Since 标头,并将上次获取到的 Last-Modified 时间值一起发送给服务器。
    • 如果服务器发现客户端发送的 If-Modified-Since 时间早于服务器上资源的最后修改时间,则返回 304 Not Modified 状态码,表示资源未被修改,客户端可以使用本地缓存。
  2. ETag / If-None-Match:

    • 服务器在响应请求时,通过 ETag 标头返回资源的特定版本标识符(通常是哈希值)。
    • 客户端在下一次请求时,可以通过发送 If-None-Match 标头,并将上次获取到的 ETag 值一起发送给服务器。
    • 如果服务器发现客户端发送的 If-None-Match 值与服务器上资源的当前 ETag 值匹配,则返回 304 Not Modified 状态码,表示资源未被修改,客户端可以使用本地缓存。

通过使用这些协商缓存机制,客户端可以在必要时向服务器获取新的资源,而无需每次都下载整个资源。这种方式可以减少网络流量,提高网站性能,同时减轻服务器的负载压力。

下面是协商缓存的典型过程:

  1. 客户端请求资源:客户端向服务器请求某个资源,比如一个网页或图片。

  2. 服务器响应:服务器收到客户端的请求后,会返回资源的响应,并在响应头中包含相关的缓存控制信息,比如 Last-ModifiedETag 等。

  3. 客户端缓存:客户端收到服务器返回的资源后,会将资源保存在本地缓存中,并记录相关的缓存信息,比如资源的最后修改时间 (Last-Modified) 或资源标识符 (ETag)。

  4. 再次请求资源:当客户端需要再次请求该资源时,会在请求头中携带上次缓存的信息,比如 If-Modified-SinceIf-None-Match

  5. 服务器验证:服务器接收到客户端的再次请求后,会根据客户端提供的缓存信息来验证资源是否有更新。

    • 如果资源未发生变化,则服务器返回 304 Not Modified,告知客户端可以使用本地缓存。
    • 如果资源已经更新,则服务器返回新的资源并更新客户端的缓存。

协商缓存主要存在于 HTTP 协议中,通过 Last-ModifiedETag 这两个头部字段来实现。这些信息被包含在 HTTP 请求和响应的头部中,用于在客户端和服务器之间交换资源的状态信息,以便判断是否需要重新获取资源。

浏览器本地存储方式,他们数据存在哪里,受不受同源策略制约

浏览器本地存储方式主要包括 Web Storage(包括 localStorage 和 sessionStorage)以及 IndexedDB,它们的数据存储位置、受同源策略限制情况如下:

  1. Web Storage (localStorage 和 sessionStorage)

    • 数据存储位置:以键值对的形式存储在用户的本地文件系统中。
    • 同源策略限制:仅允许相同协议、域名和端口的页面共享对应的数据。
  2. IndexedDB

    • 数据存储位置:存储在用户的本地文件系统中。
    • 同源策略限制:不受同源策略限制,不同源的页面可以访问同一个 IndexedDB 数据库。
  3. Cookie

    • 数据存储位置:以文本文件的形式存储在用户的计算机上,通常保存在浏览器的特定目录下。
    • 同源策略限制:只能被设置在其所属的域名下,并且默认情况下只能被同一域名下的页面访问。

 Cookie 常用属性

  1. Name(名称):Cookie 的名称,用于唯一标识一个 Cookie。

  2. Value(值):Cookie 的值,存储在 Cookie 中的具体数据。

  3. Domain(域):指定 Cookie 可以发送到哪些域(域名)。默认情况下,Cookie 只能发送给设置它的页面所属的域。

  4. Path(路径):指定 Cookie 可以发送到哪些路径。默认情况下,Cookie 只能发送给设置它的页面所在的路径及其子路径。

  5. Expires(过期时间):指定 Cookie 的过期时间,超过这个时间后 Cookie 将失效并被浏览器删除。

  6. Max-Age(最大存活时间):设置 Cookie 的最大存活时间,单位为秒。与 Expires 不同的是,Max-Age 指定的是 Cookie 被创建后的存活时间长度。

  7. Secure(安全标志):如果设置了 Secure 属性,那么只有在使用 SSL 连接(HTTPS)时,浏览器才会发送这个 Cookie。

  8. HttpOnly(仅 HTTP 标志):如果设置了 HttpOnly 属性,那么 JavaScript 将无法操作这个 Cookie,有助于防止跨站脚本攻击(XSS)。

 object.defineProperty如何监听数组,为啥无法获取数组的变化

Object.defineProperty 这个方法主要用来设置对象的属性。但是对于数组来说,它并不适合用来监听数组的变化。

为什么呢?因为数组在 JavaScript 中是一种特殊的对象,它的元素是通过索引来访问的,而不是通过对象的属性。所以使用 Object.defineProperty 无法直接监听数组元素的变化。

如果你想监听数组的变化,可以使用另一种方法叫做 ProxyProxy 是一个功能强大的工具,可以帮助你监听对象的操作,并在操作发生时执行自定义的行为。通过使用 Proxy,你可以监听数组的读取、写入、删除等操作,从而实现对数组变化的监听。

简单来说,想要监听数组的变化,最好使用 Proxy 而不是 Object.defineProperty

 vue为啥使用data包裹属性

在 Vue.js 中,通常会使用 data 函数来包裹组件实例中的属性,而不直接在组件选项中声明属性。这是因为 Vue.js 在初始化组件实例时会对 data 函数返回的数据进行响应式处理,从而实现数据的双向绑定和响应式更新。

具体来说,当你将属性放在 data 函数中返回一个对象时,Vue.js 会将这个对象转换为响应式对象,使得当数据发生变化时,相关的视图会自动更新。这种机制使得开发者无需手动操作 DOM,只需关注数据的变化即可实现视图的更新。

另外,将属性放在 data 函数中还有利于组件的封装和复用。通过将数据封装在 data 函数中,可以更好地组织和管理组件的数据,避免全局变量污染和数据泄露。

总的来说,Vue.js 使用 data 函数包裹属性的方式是为了提供数据的响应式处理、双向绑定功能,并且有助于组件的封装和数据管理。这样的设计是 Vue.js 框架的核心特性之一,让开发者能够更轻松地构建交互性强、响应式的 Web 应用程序。

为什么使用vuex,vue组件间通信方式有哪些?react组件通信有哪些

使用 Vuex 的主要原因有以下几点:

  1. 集中式状态管理:Vuex 可以帮助管理 Vue 应用中的共享状态,将数据存储在一个全局的 store 中,方便统一管理和跟踪状态的变化。

  2. 组件间通信:通过 Vuex,不同组件可以方便地进行状态通信,避免了 props 和事件总线等方式可能带来的层层传递的问题。

  3. 方便的状态管理:Vuex 提供了一套清晰的规则和方法来修改状态,包括 mutation 和 action 等,使状态管理更加可控和可维护。

  4. 插件化:Vuex 支持插件机制,可以方便地扩展功能,比如调试工具、持久化等。

Vue 组件间通信方式:

  1. Props / Emit(父子组件通信):父组件通过 props 向子组件传递数据,子组件通过 emit 事件向父组件传递数据。

  2. Event Bus(任意组件通信):通过一个空的 Vue 实例作为事件中心,可以实现任意组件之间的通信。

  3. Vuex(任意组件通信):利用 Vuex 中的 store 实现组件间的状态共享和通信。

  4. Provide / Inject(祖先传递给后代):祖先组件通过 provide 向所有后代组件提供数据,后代组件通过 inject 注入数据。

  5. $attrs / $listeners(传递属性和事件):用于将父组件接收到的非 prop 属性和事件监听器传递给子组件。

React 组件通信方式:

  1. Props(父子组件通信):父组件通过 props 向子组件传递数据。

  2. State(组件内部管理状态):组件内部通过 state 管理状态,可以通过 setState 方法更新状态。

  3. Context API(跨层级通信):React 提供的 Context API 可以实现跨组件层级的数据传递。

  4. Redux / MobX(状态管理库):类似于 Vuex,在 React 中可以使用 Redux 或 MobX 等状态管理库来管理应用的状态。

  5. Hooks(函数式组件通信):通过 React Hooks 中的 useState、useEffect 等来实现函数式组件间的状态管理和副作用处理。

webpack怎么创建项目

步骤一:初始化项目

  1. 创建一个新的项目文件夹,并进入该文件夹:
    mkdir my-webpack-project
    cd my-webpack-project
    
    初始化 npm 项目(如果没有安装 npm,请先安装 Node.js):
    npm init -y
    

步骤二:安装 Webpack 及相关依赖

  1. 安装 Webpack 和 Webpack CLI(命令行工具)作为开发依赖:
    npm install webpack webpack-cli --save-dev
    

  2. 安装 Webpack 的 loader(如babel-loader、style-loader、css-loader等)和插件(如html-webpack-plugin、clean-webpack-plugin等)以及其他必要的依赖。  

步骤三:创建 Webpack 配置文件

在项目根目录下创建一个名为 webpack.config.js 的文件,用来配置 Webpack。

示例 webpack.config.js 文件内容:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
};

步骤四:创建示例文件

在项目根目录下创建一个 src 文件夹,并在其中创建一个 index.js 文件作为入口文件。同时可以创建其他需要的文件,如 CSS 文件等。

步骤五:编写代码并运行构建

编写你的 JavaScript 代码和其他相关文件,然后在命令行中运行 Webpack 构建项目:

npx webpack

步骤六:查看结果

Webpack 将会根据你的配置文件将文件打包到 dist 目录中,你可以打开生成的 index.html 文件查看项目运行情况。

 webpack能处理什么文件。不能处理什么文件

Webpack 可以处理各种类型的文件,包括但不限于:

  1. JavaScript 文件: Webpack 最常见的用途是处理 JavaScript 文件。它支持 ES6/ES7 语法,并且可以通过加载器(Loader)进行转译、压缩和优化。

  2. CSS 文件: Webpack 可以处理 CSS 文件,包括将 SASS、LESS 等预处理语言转换为纯粹的 CSS,并通过加载器将 CSS 应用到页面中。

  3. 图片文件: 通过加载器,Webpack 可以处理图片文件,包括对图片进行压缩、转换为 base64 码、或者将图片路径替换为打包后的路径等操作。

  4. 字体文件: 类似于图片文件,Webpack 也可以通过加载器处理字体文件,包括字体的压缩、格式转换等。

  5. HTML 文件: Webpack 可以通过插件生成 HTML 文件,并且在生成的 HTML 文件中自动引入打包后的资源文件。

  6. JSON 文件: Webpack 可以处理 JSON 文件,可以直接导入和使用 JSON 文件中的数据。

  7. TypeScript 文件: Webpack 4 以及更高版本可以直接处理 TypeScript 文件。

  8. 静态资源文件: 比如 XML 文件、SVG 文件等,都可以通过适当的加载器进行处理。

虽然 Webpack可以处理很多类型的文件,但也存在一些无法直接处理的文件,比如:

  1. 服务器端代码: Webpack 通常被用于处理前端资源,在处理服务器端(Node.js)代码时,可能并不是最佳选择。

  2. 大型二进制文件: 虽然可以通过加载器处理图片和字体等二进制文件,但对于非常大的二进制文件,Webpack并不是最好的工具。

未完待续 ... ... ...

  • 31
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值