针对单个文件上传如何知道上传进度?
不同的开发场景下,了解单个文件上传进度的方法有所不同,以下为你介绍常见场景的实现方式:
Web前端(使用原生 JavaScript)
可以利用 XMLHttpRequest 对象的 upload 属性添加 progress 事件来获取上传进度信息。示例代码如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<input type="file" id="fileInput">
<button onclick="uploadFile()">上传</button>
<div id="progressBar" style="width: 300px; border: 1px solid #ccc;">
<div id="progress" style="width: %; height: 20px; background-color: #007BFF;"></div>
</div>
<script>
function uploadFile() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (!file) return; const xhr = new XMLHttpRequest();
xhr.open('POST', 'your_upload_url', true);
// 监听上传进度事件
xhr.upload.addEventListenerprogress', function (e) {
if (e.lengthComput) {
const percentComplete = (e.loaded / e.total) * 100;
const progressDiv = document.getElementById('progress');
progressDiv.style.width = percentComplete + '%';
}
});
xhr.send(new FormDataappend('file', file));
}
</script>
</body>
</html>
Java 开发
整体流程如下:
- 创建 HTML 页面:包含文件上传的表单。
- 创建后端处理文件上传的 Servlet。
- 使用 Ajax 定时获取上传进度。
- 在 HTML 页面中显示上传进度。
Android 开发
步骤如下:
- 创建用户界面:包含选择文件和上传按钮。
- 处理文件选择。
- 创建网络请求:上传文件并实时更新进度。
- 显示上传进度到用户界面。
jQuery Ajax
在使用 jQuery $.ajax 方法上传文件时,要注意将 contentType 和 processData 属性设置为 false。部分框架(如 layui可通过定义 progress 属性接收上传进度。示例代码如下:
javascript
$.ajax({
url: 'your_upload_url',
type: 'POST',
data: formData,
contentType: false,
processData: false,
xhr: function () {
const xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener('progress', function (e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
// 更新进度条等操作
}
}, false);
return xhr;
},
success: function (response) {
// 处理成功响应
},
error: function (error) {
// 处理错误
}
});
大文件分片上传
对于大文件上传,可通过前端实时计算已上传的分片大小和总文件大小的比例来展示上传进度。
webpack分块是如何进行分的,根据什么来进行分的,分的是依赖还是 chunk
在 Webpack 中,分块(Chunk)是其打包过程中的一个重要概念,下面详细介绍 Webpack 分块的方式、依据以及分的内容:
分块方式
Webpack 主要通过以下几种方式进行分块:
1. 入口起点分块
当配置多个入口起点时,Webpack 会为每个入口起点创建一个独立的块。例如,在 webpack.config.js 中配置多个入口:
javascript
const path = require('path');
module.exports = {
entry: {
main: './src/index.js',
another: './src/another.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
这里 main 和 another 是两个不同的入口,Webpack 会分别为它们生成对应的块 main.bundle.js 和 another.bundle.js。
2. 动态导入分块
使用动态导入(import() 语法)可以实现代码的懒加载,Webpack 会为动态导入的模块创建单独的块。示例代码如下:
javascript
// index.js
document.getElementById('button').addEventListener('click', async () => {
const { add } = await import('./math.js');
console.log(add(1, 2));
});
当点击按钮时,math.js 模块会被动态加载,Webpack 会为 math.js 生成一个单独的块。
3. 代码分割插件
Webpack 提供了 SplitChunksPlugin 插件来进行代码分割。可以在 webpack.config.js 中进行配置:
javascript
module.exports = {
// ...其他配置
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
SplitChunksPlugin 可以根据一定的规则将公共模块提取到单独的块中。
分块依据
Webpack 进行分块主要依据以下几点:
1. 模块的使用频率
对于被多个入口或模块频繁使用的公共模块,Webpack 会将其提取到单独的块中,以避免重复打包,减少代码冗余。例如,多个页面都使用了 lodash 库,SplitChunksPlugin 会将 lodash 提取到一个单独的块中。
2. 模块的大小
如果一个模块的体积较大,将其单独打包成一个块可以提高加载性能。例如,一些大型的第三方库可以单独打包。
3. 按需加载
为了实现按需加载,提高应用的首屏加载速度,Webpack 会将动态导入的模块单独分块。只有在需要时才会加载这些模块。
分的内容
Webpack 分块分的是模块(Module),而不是依赖或 chunk 本身。模块可以是 JavaScript 文件、CSS 文件、图片等各种资源。Webpack 会根据模块之间的依赖关系,将相关的模块打包到同一个块中。例如,一个 JavaScript 文件中引入了多个其他的 JavaScript 模块和 CSS 模块,Webpack 会将这些模块打包成一个或多个块。
综上所述,Webpack 通过多种方式进行分块,依据模块的使用频率、大小和按需加载等因素,将模块打包成不同的块,以优化应用的加载性能和代码结构。
按需引入的背后逻辑是什么?
按需引入是一种优化策略,它允许在需要时才加载特定的模块或功能,而不是一次性加载整个库或框架,这样可以显著减少初始加载时间和资源消耗。以下从不同场景介绍按需引入的背后逻辑:
前端框架中的按需引入
以 Vue 和 React 框架为例,通常借助插件或工具来实现按需引入。
Vue
在 Vue 项目里,借助 unplugin-vue-components 和 unplugin-auto-import 这类插件,就能实现组件和 API 的按需引入。
javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
上述代码中,unplugin-vue-components 插件会自动识别项目里使用的组件,并且按需引入对应的组件样式与脚本。
React
在 React 项目中,可使用 babel-plugin-import 插件来实现按需引入。
javascript
// .babelrc
{
"plugins": [
[
"import",
{
"libraryName": "antd",
"libraryDirectory": "es",
"style": "css"
}
]
]
}
此配置会让 Babel 在编译时按需引入 antd 组件库的组件和样式。
JavaScript 模块的按需引入
在 JavaScript 里,动态导入(import())语法能够实现模块的引入。
javascript
// 动态导入模块
document.getElementById('button').addEventListener('click', async () => {
const { add } = await import('./math.js');
console.log(add(1,2));
});
当点击按钮时,math.js 模块才会被加载,这样就避免了在页面加载时就加载该模块。
###按需引入的核心逻辑
- 静态分析:借助工具或者插件对代码进行静态分析,找出实际使用的模块和组件。
- 动态加载:在运行时依据需要动态加载模块,而非在初始加载时就加载所有模块。
- 代码分割:Webpack 等打包工具会把代码分割成多个小块,按需加载这些小块,从而减少初始加载的代码量。
按需引入的核心目标是提升应用的性能和加载速度,减少不必要的资源消耗。
移动端适配的方式有几种
前端移动端适配是为了让网页在不同尺寸的移动设备上都能有良好的显示效果,常见的适配方式有以下几种:
- 媒体查询(Media Queries)
- 原理:通过 CSS 的媒体查询功能,根据不同的设备屏幕宽度、高度、分辨率等条件,应用不同的 CSS 样式。
- 示例代码:
css
/* 当屏幕宽度小于 768px 时应用以下样式 */
@media screen and (max-width: 768px) {
body {
font-size: 14px;
}
.container {
width: 100%;
padding: 10px;
}
}
/* 当屏幕在 768px 到 992px 之间时应用以下样式 */
@media screen and (min-width: 768px) and (max-width: 992px) {
body {
font-size: 16px;
}
.container {
width: 75px;
margin: 0 auto;
}
}
/* 当屏幕宽度大于 992px 时应用以下样式 */
@media screen and (min-width: 992px) {
body {
font-size: 18px;
}
.container {
width: 970px;
margin: 0 auto;
}
}
2. 弹性布局(Flexbox 和 Grid)
- 原理:Flexbox(弹性盒模型)和 Grid(网格布局)是 CSS3 引入的布局模型,它们可以根据容器的大小自动调整子元素的大小和位置,具有很好的灵活性和响应性。
- 示例代码(Flexbox) :
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta="viewport" content="width=device-width initial-scale=1.0">
<style>
.flex-container {
display: flex;
flex-wrap: wrap;
}
.flex-item {
flex: 1 0 200px;
margin: 10px;
background-color: lightblue;
padding: 20px;
}
</style>
</head>
<body>
<div class="flex-container">
<div class="flex-item">Item 1</div>
<div class="flex-item">Item 2</div>
<div class="flex-item">Item 3</div>
</div>
</body>
</html>
### 3. 百分比布局
- **原理**:使用百分比作为元素的宽度、高度、边距等属性值,元素的大小会根据其父元素的大小自动调整。
- **示例代码**:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.parent {
width: 100%;
background-color: lightgray;
padding: 20px;
}
.child {
width: 50%;
background-color: lightblue;
padding: 10px;
}
</style>
</head>
<body>
<div class="parent">
<div class="child">This is a child element.</div>
</div>
</body>
</html>
4. rem 和 em 布局
-
原理:
em是相对单位,相对于元素的父元素的字体大小。rem是相对于根元素(<html>)的字体大小。通过设置根元素的字体大小,然后使用rem作为元素的尺寸单位,可以实现整体的缩放。
-
示例代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <style>
html {
font-size: 16px;
}
.box {
width: 10rem; /* 相当于 160px */
height: 5rem; 相当于 80px */
background-color: lightgreen;
padding: 1rem; /* 相当于 16px */
}
</style>
</head>
<body>
<div class="box">This is a box.</div>
</body>
</html>
5. vw 和 vh 布局
-
原理:
vw是视口宽度的百分比单位,1vw 等于视口宽度的 1%。vh是视口高度的百分比单位,1vh 等于视口高度的 1%。使用vw和vh可以根据视口的大小来调整元素的尺寸。
-
示例代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.box {
width: 50vw; /* 视口宽度的 50% */
height: 30vh; /*视口高度的 30% */
background-color: lightcoral;
}
</style>
</head>
<body>
<div class="box">This is a box.</div>
</body>
</html>
6. 响应式图片
- 原理:使用
<picture>元素或srcset和sizes属性,根据不同的设备屏幕尺寸和分辨率,加载不同尺寸的图片,以节省带宽和提高加载速度。 - 示例代码:
html
<picture>
<source media="(min-width: 1200px)" srcset="large-image.jpg">
<source media="(min-width: 768px)" srcset="medium-image.jpg">
<img src="small-image.jpg" alt="Responsive Image">
</picture>
以上这些适配可以单独使用,也可以结合使用,以达到更好的移动端适配效果。
你会更推荐使用 vw 还是 rem
vw 和 rem 都是前端开发中用于实现响应式布局的单位,它们各有优缺点,具体推荐使用哪一个取决于具体的项目需求,以下是对它们的分析:
推荐使用 vw 的场景
- 需要根据视口精确缩放:
vw是相对于视口宽度的单位,1vw 等于视口宽度的 1%。如果你的设计要求元素的大小严格根据视口的宽度进行缩放,那么vw是一个很好的选择。例如,设计一个全屏的背景图,希望它在不同设备上始终铺满整个屏幕宽度,使用vw可以轻松实现。
css
-screen-bg {
width: 100vw;
height: auto;
}
- 简单直接的响应式布局:当你想要实现简单的响应式布局,而不需要考虑根元素字体大小的影响时,
vw可以让布局更加直观。比如,设计一个导航栏,希望导航项的宽度根据视口宽度按比例变化。
css
.nav-item {
width: 20vw;
}
推荐使用 rem 的场景
- 整体布局的统一缩放:
rem是相对于根元素(html>)字体大小的单位。如果你的项目需要整体布局能够根据根元素字体大小进行统一缩放,那么rem` 会更合适。例如,在不同设备上通过 JavaScript 动态调整根元素的字体大小,从而实现页面整体的缩放。
css
html {
font-size: 16px;
}
.box {
width: 10rem; /* 相当于 160px */
height: 5rem; /* 相当于 80px */
}
- 与字体大小相关的布局:由于
rem与字体大小相关,在处理与文字排版相关的布局时,使用rem可以更好地保持元素之间的比例关系。比如,设置段落的边距和字体大小,使它们在不同设备上保持协调。
css
p {
font-size: 1rem;
margin: 1rem 0;
}
综上所述,如果更注重元素根据视口宽度精确缩放和简单直接的响应式布局,推荐使用 vw;如果需要整体布局统一缩放以及处理与字体大小相关的布局,推荐使用 rem。在实际项目中,也可以将两者结合使用,以达到更好的布局效果。
webpack 的 loader 的机制是什么
Webpack 是一个模块打包工具,Loader 是 Webpack 中一个非常重要的概念,它的主要作用是让 Webpack 能够处理不同类型的文件,将这些文件转换为 Webpack 能够识别和处理的模块。以下是 Webpack Loader 的机制详细介绍:
工作原理
Webpack 本身只能处理 JavaScript 和 JSON 文件,对于其他类型的文件(如 CSS、图片、字体等),就需要借助 Loader 来进行处理。Loader 本质上是一个函数,它接收源文件的内容作为输入,经过一系列处理后返回转换后的内容。Loader 可以链式调用,多个 Loader 可以按照顺序依次对文件进行处理。
执行流程
- 匹配规则:在 Webpack 配置文件中,通过
module.rules来定义 Loader 的匹配规则。每个规则包含一个test属性,用于匹配文件类型,以及一个use属性用于指定要使用的。
null
module.exports = {
module: {
rules: [
{
test: /.css$/, // 匹配所有 .css 文件
use: ['style-loader', 'css-loader'] // 使用 style-loader 和 css-loader 处理
}
]
}
};
-
Loader 调用:当 Webpack 遇到一个匹配规则的文件时,会按照
use数组中指定的 Loader 顺序依次调用。Loader 的调用顺序是从右到左(或者说从下到上),即先调用数组中最后一个 Loader然后依次向前调用。css-loader:首先,css-loader会处理 CSS 文件中的@import和url()等语句,将它们转换为对应的模块引用。style-loader:接着,style-loader会将处理后的 CSS 代码插入到 HTML 文件的<style>标签中。
常见 Loader 示例
- **
css-loader:处理 CSS 文件中的模块引用。 style-loader:将 CSS 代码插入到 HTML 文件的<style>标签中。less-loader:将 Less 文件编译为 CSS 文件。sass-loader:将 Sass/SCSS 文件编译为 CSS 文件。file-loader:处理文件类型的模块,如图片、字体等,将文件复制到输出目录,并返回文件的路径。url-loader:与file-loader类似,但可以将小文件转换为 Base64 编码的 URL,减少 HTTP 请求。
自定义 Loader
你也可以自定义 Loader 来满足特定的需求。自定义 Loader 是一个 Node.js 模块,它导出一个函数,该函数接收源文件的内容作为参数,并返回转换后的内容。
javascript
// my-loader.js
module.exports = function(source) {
// 对源文件内容进行处理
const newSource = source.replace(/hello/g, 'hi');
return newSource;
};
然后在 Webpack 配置文件中使用自定义 Loader:
javascript
module.exports = {
module: {
rules: [
{
test: /.js$/,
use: ['my-loader']
}
]
}
};
通过以上机制,Webpack 的 Loader 可以让开发者在项目中使用各种类型的文件,并且能够对这些文件进行灵活的处理和转换。
webpack 使用的是什么模块?AMD、UMD
Webpack 本身并不局限于使用 AMD 或 UMD 模块,它支持多种模块规范,并且可以根据配置来处理不同类型的模块。以下为你详细介绍:
支持的模块规范
-
CommonJS
- 这是 Node.js 中使用的模块规范。在 CommonJS 中,使用
require来引入模块,使用module.exports或exports导出模块。Webpack 可以很好地处理 CommonJS 模块,例如:
- 这是 Node.js 中使用的模块规范。在 CommonJS 中,使用
javascript
// moduleA.js
const message = 'Hello from module A';
module.exports = message;
// main.js
const msg = require('./moduleA');
console.log(msg);
-
AMD(Asynchronous Module Definition)
- AMD 是一种异步模块定义规范,主要用于浏览器环境,以解决模块异步加载的问题,使用
define函数来定义模块。Webpack 也支持 AMD 模块,示例如下:
- AMD 是一种异步模块定义规范,主要用于浏览器环境,以解决模块异步加载的问题,使用
javascript
// moduleB.js
define([], function() {
return {
sayHello: function() {
console.log('Hello from AMD module');
}
};
});
// main.js
require(['./moduleB'], function(moduleB) {
moduleB.sayHello();
});
-
UMD(Universal Module Definition)
- UMD 是一种通用的模块定义规范,它结合了 CommonJS 和 AMD 的特点,既可以在 Node.js 环境中使用,也可以在浏览器环境中使用。Webpack 同样支持 UMD 模块。
javascript
(function (root, factory) {
if (typeof define === 'function' && define.amd {
// AMD
define([], factory);
} if (typeof module === 'object' &&.exports) {
// CommonJS
module.exports = factory();
} else {
// 全局变量
root.returnExports = factory();
}
}(this, function () {
{
greet: function() {
console.log('Hello from UMD module');
}
};
}));
-
ES6 Modules
- ES6 引入了官方的模块语法,使用
import和export来导入和导出模块。Webpack 对 ES6 模块有很好的支持,示例如下
- ES6 引入了官方的模块语法,使用
javascript
// moduleC.js
export const greeting = 'Hello from ES6 module';
// main.js
import { greeting } from './moduleC';
console.log(greeting);
配置相关
在 Webpack 中,你可以通过配置来处理不同类型的模块。例如,使用 resolve 项来指定模块的解析规则,使用 module.rules 来处理不同类型的文件。默认情况下,Webpack 会自动识别和处理上述各种模块规范。
resolve 有几种模式,几种方法
在 Webpack 里,resolve 配置项主要用于设定模块解析规则,它有多种模式和方法,下面为你详细介绍:
模式
Webpack 的 resolve 并没有严格意义上的“模式”,不过依据不同的配置侧重点可大致分为以下常见的使用场景:
1. 常规解析模式
这是最常见的模式,按照默认规则解析模块,会在 node_modules 目录里查找模块,也会依据 extensions 配置尝试不同的文件扩展名。
javascript
module.exports = {
resolve: {
extensions: ['.js', '.json', '.jsx']
}
};
2. 别名解析模式
借助 alias 配置项,能为模块路径设置别名,这样在引入模块时可使用更简洁的路径。
javascript
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'components': path.resolve(__dirname, 'src/components')
}
}
};
3. 自定义解析器模式
可以通过 resolvers 配置项自定义解析器,以实现特殊的模块逻辑。
javascript
const path = require('path');
const { ResolverFactory } = require('enhanced-resolve');
const customResolver = ResolverFactory.createResolver({
fileSystem: new (require('fs'))(),
extensions: ['.js', '.json'],
alias: {
'@': path.resolve(__dirname, 'src')
}
});
module.exports = {
resolve: {
plugins: [
{
apply: (resolver) => {
resolver.hooks.resolve.tapAsync('CustomResolver', (request, resolveContext, callback) => {
customResolver.resolve({}, request.path, request.request, resolveContext, (err, result) => {
if (err) {
return callback(err);
}
if (result) {
return callback(null, result);
}
return callback();
});
});
}
}
]
}
};
方法
resolve 配置项有多个方法和属性,下面是一些常用的方法和属性:
1. extensions
用于指定在解析模块时尝试的文件扩展名列表。
javascript
module.exports = {
resolve: {
extensions: ['.js', '.jsx', '.json']
}
};
2. alias
为模块路径别名,方便在代码里引入模块。
javascript
module.exports = {
resolve: {
alias: {
'utils': path.resolve(__dirname, 'src/utils'),
'styles': path.resolve(__dirname, 'src/styles')
}
}
};
3. modules
指定模块的搜索目录,默认是 node_modules。
javascript
module.exports = {
resolve: {
modules: [path.resolve(__dirname, 'src'), 'node_modules']
}
};
4. mainFiles
指定在解析目录时默认使用文件名。
javascript
module.exports = {
resolve: {
mainFiles: ['index.js', 'main.js']
}
};
5. symlinks
是否解析符号链接(软链接),默认值为 true。
javascript
module.exports = {
resolve: {
symlinks: false
}
};
6. fallback
当模块解析失败时,指定备用的解析路径。
javascript
module.exports = {
resolve: {
fallback: {
'crypto': require.resolve('crypto-browserify')
}
}
};
webpack 的插件系统是什么?
Webpack 的插件系统是其核心特性之一,它允许开发者在 Webpack 构建过程的特定生命周期阶段插入自定义逻辑,从而扩展 Web 的功能。以下是关于 Web 插件系统的详细介绍:
插件的工作原理
Webpack 内部使用了 Tapable 库来实现插件系统。Tapable 是一个基于发布 - 订阅模式的事件流库,Webpack 在构建过程的不同阶段会触发各种钩子(Hook),插件可以监听这些钩子并在相应的时机执行特定的任务。
插件的使用方式
要使用一个插件,通常需要以下步骤:
- 安装插件:使用 npm 或 yarn 安装插件。
- 引入插件:在 Webpack 配置文件中引入插件。
- 配置插件:
plugins数组中实例化并配置插件。
以下是一个简单的 Webpack 配置示例,使用了 HtmlWebpackPlugin 插件:
javascript
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
};
常见的插件类型及用途
. 打包优化类插件
- UglifyJsPlugin:用于压缩 JavaScript 代码,减小文件体积。
- MiniCssExtractPlugin:将 CSS 提取到单独的文件中,避免将 CSS 内联到 JavaScript 中。
2. 资源管理类插件
- CleanWebpackPlugin:在每次构建前清理输出目录,确保输出目录只包含最新的构建文件。
- CopyWebpackPlugin:将指定的文件或目录复制到输出目录。
3. HTML 生成类插件
- HtmlWebpackPlugin:根据模板生成 HTML 文件,并自动注入打包后的 JavaScript 和 CSS 文件。
4. 环境变量注入类插件
- DefinePlugin:允许在编译时定义全局常量,常用于区分开发环境和生产环境。
自定义插件
开发者也可以自定义插件来满足特定的需求。自定义插件是一个 JavaScript 类,需要实现一个 apply 方法,该方法接收 compiler 对象作为参数,通过监听 compiler 上的钩子来执行自定义逻辑。
以下是一个简单的自定义插件示例:
javascript
class MyPlugin {
apply(compiler) {
// 监听编译完成的钩子
compiler.hooks.done.tap('MyPlugin', (stats) => {
console.log('编译完成!');
});
}
}
module.exports = MyPlugin;
在 Webpack 配置中使用自定义插件:
javascript
const MyPlugin = require('./MyPlugin');
module.exports = {
// ...其他配置
plugins: [
new MyPlugin()
]
};
通过插件系统,Webpack 可以灵活地扩展功能,满足不同项目的需求。
前端动画实现的方式主要有哪几种?
前端实现动画主要有以下几种常见方式:
CSS 动画
-
CSS 过渡(Transition)
- 原理:用于在两个状态之间平滑过渡,当元素的某个 CSS 属性值发生变化时,过渡效果会在指定的时间内逐渐改变该属性值。
- 示例代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.box {
width: 100px;
height: 100px;
background-color: red;
transition: 2s;
}
.box:hover {
width: 300px;
}
style>
</head>
<body>
<div class="box"></div>
</body>
</html>
-
CSS 动画(Animation)
- 原理:通过
@keyframes规则定义动画序列,然后将其应用到元素上,元素会按照定义的关键帧在指定时间内完成动画。 - 示例代码:
- 原理:通过
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.box {
width: 100px;
height: 100px;
background-color: blue;
animation: move 2s infinite;
}
@keyframes {
0% {
transform: translateX(0);
}
100% {
transform: translateX(200px);
}
}
</style>
</head>
<body>
<div class="box"></div>
</body>
</html>
JavaScript 动画
-
定时器动画
- 原理:使用
setInterval或setTimeout函数定时改变元素的 CSS 属性值,从而实现动画效果。 - 示例代码:
- 原理:使用
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.box {
width: 100px;
height: 100px;
background-color: green;
position: absolute;
}
</style>
</head>
<body>
<div class="box" id="box"></div>
<script>
const box = document.getElementById('box');
let position = 0;
const intervalId = setInterval(() => {
if (position >= 200) {
clearInterval(intervalId);
} else {
position++;
box.style.left = position + 'px';
}
}, 10);
</script>
</body>
</html>
-
requestAnimationFrame动画- 原理:
requestAnimationFrame是浏览器提供的一个用于优化动画的 API,它会在浏览器下次重绘之前执行回调函数,从而实现流畅的动画效果。 - 示例代码:
- 原理:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
.box {
width: 100px;
height: 100px;
background-color: orange;
position: absolute;
}
</style>
</head>
<body>
<div class="box" id="box"></div>
<script>
const box = document.getElementById('box');
let position = 0;
function animate() {
if (position >= 200) {
return;
}
position++;
box.style.left = position + 'px';
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
</script>
</body>
</html>
SVG 动画
-
SMIL 动画
- 原理:SMIL(Synchronized Multimedia Integration Language)是一种基于 XML 的语言,用于在 SVG 中创建动画。
- 示例代码:
xml
<svg width="200" height="200">
<rect x="50" y="50" width="50" height="50" fill="purple">
<animate attributeName="x" from="50" to="150" dur="2s" repeatCount="indefinite" />
</rect>
</svg>
-
JavaScript 控制 SVG 动画
- 原理:通过 JavaScript 动态修改 SVG 元素的属性来实现动画效果。
- 示例代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<svg width="200" height="200">
<rect id="rect" x="50" y="50" width="50" height="50" fill="brown" />
</svg>
<script>
const rect = document.getElementById('rect');
let position = 50;
const intervalId = setInterval(() => {
if (position >= 150) {
clearInterval(intervalId);
} else {
position
.setAttribute('x', position);
}
}, 10);
</script>
</body>
</html>
Canvas 动画
- 原理:通过 JavaScript 在
canvas元素上绘制图形,并不断更新图形的位置、大小等属性来实现动画效果。 - 示例代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<canvas id="canvas" width="200" height="200"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let x = 50;
let y = 50;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, y, 20, 0, Math.PI * 2);
ctx.fillStyle = 'pink';
ctx.fill();
++;
if (x > canvas.width) {
x = 0;
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
</script>
</body>
</html>
如何判断是否为可迭代对象
在前端 JavaScript 中,判断一个对象是否为可迭代对象可以通过以下几种方法:
方法一:检查对象是否具有 Symbol.iterator 属性
可迭代对象都有一个 Symbol.iterator 方法,该方法返回一个迭代器对象。可以通过检查对象是否具有这个来判断它是否为可迭代对象。
javascript
function isIterable(obj) {
return obj != null && typeof obj[Symbol.iterator] === 'function';
}
// 测试示例
const array = [1, 2, 3];
const string = 'hello';
const number = 123;
const obj = { a: 1, b: 2 };
console.log(isIterable(array)); // true
console.log(isIterable(string)); // true
console.log(isIterable(number)); // false
console.log(isIterable(obj)); // false
方法二:使用 try...catch 结合 for...of 循环
for...of 循环只能用于可迭代对象,如果尝试对一个不可迭代对象使用 for...of 循环,会抛出错误。可以利用这一点来判断对象是否可迭代。
javascriptfunction isIterable(obj) {
try {
for (let _ of obj) {
break;
}
return true;
} catch (e) {
return false;
}
}
// 测试示例
const array = [1, 2, 3];
const string = 'hello';
const number = 123;
const obj = { a: 1, b: 2 };
console.log(isIterable(array)); // true
console.log(isIterable(string)); // true
console.log(isIterable(number)); // false
console.log(isIterable)); // false
不过,这种方法的性能相对较差,因为它会尝试执行循环,并且在不可迭代时抛出异常。所以,通常推荐使用第一种方法。
如何将可迭代对象转为数组
在 JavaScript 中,有多种方法可以将可迭代对象转换为数组,以下为你详细介绍:
1. 使用扩展运算符(Spread operator)
扩展运算符 ... 可以将可迭代对象展开为一个数组。这种方法简洁直观,代码示例如下:
javascript
const iterable = 'hello';
const arr = [...iterable];
console.log(arr); // 输出: ['h', 'e', 'l', 'l', 'o']
2. 使用 Array.from() 方法
Array.from() 方法可以将类数组对象或可迭代对象转换为一个新的、浅的数组实例。该方法还可以接受一个映射函数作为第二个参数,用于对每个元素进行处理,示例代码如下:
javascript
const iterable = new Set([1, 2, 3]);
const = Array.from(iterable);
console.log(arr); // 输出: [1, 2, 3]
// 使用映射函数
const arrWithMap = Array.from(iterable, x => x * 2);
console.log(arrWithMap); // 输出: [2, 4, 6]
. 使用 Array.prototype.slice.call() 方法
Array.prototype.slice.call() 可以将可迭代对象转换为数组,不过这种方法相对不那么直观,代码示例如下:
javascript
const iterable = 'world';
const arr = Array.prototype.slice.call(able);
console.log(arr); // 输出: ['w', 'o', 'r', 'l', 'd']
4. 使用 for...of 循环手动转换通过 for...of 循环遍历可迭代对象,并将每个元素添加到一个新数组中,示例代码如下:
javascript
const iterable = new Map([['a', 1], ['b', 2]]);
const arr = [];
for (const item of iterable) {
arr.push(item);
}
console.log(arr); // 输出: [['a', 1], ['b', 2]]
综上所述,扩展运算符和 Array.from() 方法是比较常用且推荐的方式,它们代码简洁,易于理解。
watch 调用了多少次
在 Vue 里,watch 的调用次数并非固定,它取决于具体的使用场景和数据变化情况。下面为你详细分析不同情形下 watch 的调用次数:
1. 简单数据类型的监听
当监听简单数据类型(像字符串、数字、布尔值这类)时,只要数据发生变化,watch 就会被调用一次。示例代码如下:
vue
<template>
<div>
<button @click="count++">增加计数</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
};
},
watch: {
count(newVal, oldVal) {
console.log(`count 从 ${oldVal} 变为 ${newVal}`);
}
}
};
</script>
在这个例子中,每点击一次按钮,count 的值就会增加 1,watch 也会被调用一次。
2. 复杂数据类型的监听
- 浅监听:若监听对象或数组,默认是浅监听,只有对象或数组的引用发生变化时,
watch才会被调用。示例代码如下:
vue
<template>
<div>
<button @click="updateObject">更新对象</button>
</div>
</template>
<script>
export default {
data() {
return {
myObject: {
name: 'John'
}
};
},
watch: {
myObject(newVal, oldVal) {
console.log('myObject 发生了变化');
}
},
methods: {
updateObject() {
// 浅监听下,只有引用改变才会触发 watch
this.myObject = { ...this.myObject, age: 30 };
}
}
};
</script>
- 监听:若要监听对象或数组内部的变化,就得开启深度监听。开启后,对象或数组内部的任何变化都会触发
watch。示例代码如下:
vue
<template>
<div>
<button @click="updateNestedObject">更新嵌套对象</button>
</div>
</template>
<script>
export default {
data() {
return {
myObject {
name: 'John',
address: {
city: 'New York'
}
}
}; },
watch: {
myObject: {
handler(newVal, oldVal) {
console.log('myObject 发生了变化');
},
deep: true
}
},
methods: {
updateNestedObject() {
this.myObject.address.city = 'Los Angeles';
}
}
};
</script>
在深度监听的情况下,每次修改 myObject.address.city 的值,watch 都会被调用。
3. 初始值监听
如果设置了 immediate: true,watch 在组件创建时就会被调用一次。示例代码如下:
vue
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
message: 'Hello'
};
},
watch: {
message: {
handler(newVal, oldVal) {
console.log(`message 从 ${oldVal} 变为 ${newVal}`);
},
immediate: true
}
}
};
</script>
在这个例子中,组件创建时 watch 就会被调用一次,之后 message 发生变化时也会再次调用。
综上所述,watch 的调用次数要依据数据变化情况是否开启深度监听以及是否设置 `immediate: true 来确定。
分析输出值
1. 代码一分析
javascript
try {
Promise.reject(1);
} catch (err) {
console.log(1);
}
在这段代码中,Promise.reject(1) 会创建一个被拒绝的 Promise,但是这个 Promise 并没有被处理(比如使用 .then() 或者 await),并且 try...catch 语句只能捕获同步代码抛出的错误,而 Promise拒绝是异步的,所以 catch 块不会捕获到这个,这段代码不会有任何输出。
2. 代码二分析
javascript
async () => { try {
await.reject(1);
} catch (err) {
console.log('err', err);
}
}
这是一个异步箭头函数,在函数内部使用了 await 关键字来等待一个被拒绝的 Promise。当 await 遇到被拒绝的 Promise 时,会抛出一个错误,这个错误会被 try...catch 语句捕获。所以当调用这个异步函数时,会输出 err 1。示例调用代码如下:
javascript
asyncFunc = async () => {
try {
await Promise.reject(1);
} catch (err) {
console.log('err', err);
}
};
asyncFunc();
3. 代码三分析
javascript
Promise.resolve(Promise.reject(1));
Promise.reject(Promise.resolve(1));
第一行 Promise.resolve(Promise.reject(1))
Promise.resolve() 方法可以将一个值或者一个 Promise 对象转换为一个已解决的 Promise。当传入的是一个 Promise 对象时,它会直接返回这个 Promise 对象。所以 Promise.resolve(Promise.reject(1)) 实际上就是返回了 Promise.reject(1),这是一个被拒绝的 Promise,拒绝值为 1。
第二行 Promise.reject(Promise.resolve(1))
Promise.reject() 方法会创建一个被拒绝的 Promise,拒绝值就是传入的参数。这里传入的是 Promise.resolve(1),所以这个 Promise 的拒绝值就是 Promise.resolve(1) 这个 Promise 对象。
可以通过以下代码来验证:
javascript
Promise.resolve(Promise.reject(1)).catch((err) => {
console.log('Promise.resolve(Promise.reject(1)) 被拒绝,拒绝值为:', err);
});
Promise.reject(Promise.resolve(1)).catch(() => {
console.log('Promise.reject(Promise.resolve(1)) 被拒绝,拒绝值为:', err);
});
运行上述代码,会输出:
null
Promise.resolve(Promise.reject(1)) 被拒绝,拒绝值为:1
Promise.reject(Promise.resolve(1)) 被拒绝,拒绝值为: Promise