跳到主要内容

79 篇博文 含有标签「前端」

前端开发技术文章

查看所有标签

HTML meta 标签常见属性

· 阅读需 2 分钟
素明诚
Full stack development

HTML meta 标签可以用来提供关于 HTML 文档的元数据(metadata),即数据的数据,可以包含文档的描述、作者、关键字、编码方式、是否重新加载等等信息,有助于浏览器正确地显示页面内容,提高 SEO 优化、跨平台分享等效果。

下面是一些常见的 meta 标签及其作用:

  1. <meta name="description" content="网页描述">:指定网页的描述信息,有助于搜索引擎正确地显示搜索结果。
  2. <meta charset="UTF-8">:指定文档的字符集编码方式。
width=device-width——将页面宽度设置为跟随屏幕宽度变化而变化
initial-scale=1.0——设置浏览器首次加载页面时的初始缩放比例(0.0-10.0正数)
maximum-scale=1.0——允许用户缩放的最大比例(0.0-10.0正数),必须大于等于minimum-scale
minimum-scale=1.0——允许用户缩放的最小比例(0.0-10.0正数),必须小于等于maximum-scale
user-scalable=no——是否允许用户手动缩放(yes或者no)
  1. <meta name="viewport" content="width=device-width, initial-scale=1.0">:指定移动设备的视口(viewport)大小,有助于页面在不同设备上展示更好的效果。
  2. <meta name="keywords" content="关键词1,关键词2,关键词3">:指定网页的关键词,有助于搜索引擎正确地理解页面内容。
  3. <meta http-equiv="refresh" content="5;url=https://www.example.com/">:指定页面的刷新和跳转,可以在指定的时间内自动跳转到其他页面。
  4. <meta name="robots" content="index,follow">:指定搜索引擎的抓取行为,如何索引页面内容。
  5. <meta name="author" content="作者名称">:指定网页作者的名称。
  6. <meta http-equiv="Cache-Control" content="no-transform">的作用是告诉浏览器不要对页面内容进行转换,例如压缩、编码转换等,这样可以保证页面内容不被改变。这个标签的 content 属性的值是 no-transform,表示不进行转换。
  7. <meta http-equiv="Cache-Control" content="no-siteapp">的作用是告诉搜索引擎和浏览器不要将页面转换为移动应用的展示方式。这个标签的 content 属性的值是 no-siteapp,表示不要展示为移动应用。

除了上面列举的几个 meta 标签,还有很多其他的 meta 标签可以用来提供不同的元数据信息。

Vue Provide 和 Inject

· 阅读需 3 分钟
素明诚
Full stack development

Vue 的依赖注入主要通过 Provide 和 Inject 两个 API 来实现

  • Provide: 在父组件中通过 provide 提供一个变量,该变量可以是一个值或一个对象。这个变量可以被子组件通过 inject 来注入。
  • Inject: 在子组件中通过 inject 来注入 provide 提供的变量。这个变量可以是一个值或一个对象。

父组件通过 provide 提供一个变量或对象,子组件通过 inject 来注入这个变量或对象,实现了父组件向子组件传递数据的功能。

例如,父组件提供了一个名为 user 的变量:

export default {
provide: {
user: {
name: 'John Doe',
age: 30
}
}
}

在子组件中通过 inject 来注入这个变量:

 export default {
inject: ['user'],
mounted() {
console.log(this.user.name); // John Doe
}
}

这样,在子组件中就可以访问到父组件提供的 user 变量了。这种依赖注入的方式可以让代码更加灵活,易于维护和扩展。

什么是依赖注入

举个例子来说,假设我们正在开发一个电商网站,我们需要一个购物车(Cart)对象,该对象依赖于一个商品列表(ProductList)对象。如果我们不使用依赖注入,那么购物车对象就需要负责创建商品列表对象,这样会导致购物车对象和商品列表对象紧密耦合在一起。而如果使用依赖注入,我们可以将商品列表对象的创建和维护交给外部容器(比如框架或组件),购物车对象只需要接收到这个商品列表对象,然后就可以使用它来添加商品、删除商品等操作,而不需要关心商品列表对象的创建和维护。

在 Vue 中,通过 Provide 和 Inject 可以实现依赖注入。例如,我们可以在父组件中使用 Provide 来提供一些数据或方法,然后在子组件中使用 Inject 来注入这些数据或方法,从而实现子组件依赖于父组件的功能。这种方式可以帮助我们减少组件之间的耦合,提高代码的可维护性和可测试性。

总结

Provide 和 Inject 适用于在组件树中需要共享数据的场景,不需要手动逐层地传递 props 属性,提高了代码的可读性和可维护性。

Webpack 常见 LoaderPlugin

· 阅读需 2 分钟
素明诚
Full stack development

什么是 Loader、Plugin

简单的说 Loader 用于转换各种资源,例如 Babel-loader:将 ES6+ 代码转换为 ES5 代码,以便在低版本浏览器中运行

而 Plugin 是用于增强 Webpack 的功能,例如 HtmlWebpackPlugin:自动生成 HTML 文件,并将打包生成的 JavaScript 和 CSS 文件自动引入 HTML 中。

Loader

(1) Babel-loader:将 ES6+ 代码转换为 ES5 代码,以便在低版本浏览器中运行。

(2) Style-loader:将 CSS 插入到 HTML 中的 style 标签中。

(3) CSS-loader:解析 CSS 文件,并处理 CSS 中的依赖关系,例如 @import 和 url()。

(4) File-loader:处理图片和其他文件,将它们打包到 output 目录,并返回它们的 URL。

(5) Url-loader:与 File-loader 类似,但是它可以将文件转换为 base64 URL,以减少 HTTP 请求的数量。

(6) Vue-loader:处理 .vue 文件,将它们转换为 JavaScript 模块。

(7) Less-loader:解析 Less 文件并转换为 CSS。

(8) Sass-loader:解析 Sass 文件并转换为 CSS。

Plugin

(1) HtmlWebpackPlugin:自动生成 HTML 文件,并将打包生成的 JavaScript 和 CSS 文件自动引入 HTML 中。

(2) CleanWebpackPlugin:自动清理 output 目录中的旧文件。

(3) mini-css-extract-plugin:将 CSS 提取到单独的文件中。

(4) CopyWebpackPlugin:一些静态资源或者打包好的文件复制到输出到 output 目录中。

(5) UglifyWebpackPlugin:压缩 JavaScript 代码。

(6) DefinePlugin:定义全局变量。

(7) ProvidePlugin:自动加载模块,而不必使用 require 或 import 导入。

(8) HotModuleReplacementPlugin:实现模块热更新,用于提高开发效率。

Vue Event bus 使用案例

· 阅读需 1 分钟
素明诚
Full stack development

假设我们有两个组件:一个是父组件 Parent,一个是子组件 Child。我们希望在 Child 中触发一个事件,然后在 Parent 中监听这个事件并进行处理。这时我们可以使用 Event bus 来实现:

首先在 main.js 中创建一个全局的 Event bus:

Vue.prototype.$eventBus = new Vue()

接着在 Child 组件中触发事件:

methods: {
handleClick() {
this.$eventBus.$emit('myEvent', 'hello')
}
}

这里我们使用 $emit 方法来触发一个名为 myEvent 的事件,并传递一个参数 'hello'

最后在 Parent 组件中监听事件并进行处理:

 created() {
this.$eventBus.$on('myEvent', (message) => {
console.log(message)
})
}

这里我们使用 $on 方法来监听名为 myEvent 的事件,并在回调函数中输出传递的参数。

通过这种方式,我们可以实现跨组件通信,将数据从一个组件传递到另一个组件。需要注意的是,Event bus 作为全局对象,可能会导致代码难以维护和调试,因此需要谨慎使用。

Vue history 和 hash 两种模式的优缺点

· 阅读需 2 分钟
素明诚
Full stack development

前端路由有两种常见的方式,一种是基于 history API 的 HTML 5 提供的模式,另一种是 URL 中的 Hash 模式。

日常我们不喜欢使用 URL 中有(#)的 Hash 模式

Hash 模式

路由默认使用的 hash 模式,因为在开发单页面应用中,我们不想因为刷新页面而失去应用的状态。当我们使用 hash 模式的时候,URL 中的 # 符号实际上是一个特殊的字符串,用来标识 URL 的锚点,当井号#后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发hashchange事件来更新路由状态,实现页面的切换。

所以,当我们在 Vue 中使用这章模式的时候,# 实际上是一个虚拟字符,用于标识当前页面的状态,不会影响正常页面的访问。

优缺点

优点:不会重新加载页面,支持 IE9 及以下浏览器

缺点:丑

HTML5 History 模式

history API 是 H5 提供的新特性,允许开发者直接更改前端路由,即更新浏览器 URL 地址而不重新发起请求。

优缺点

优点:好看

缺点:需要后端支持

一定要后端配合支持才行,否则会出现大量的 404。以最常用的 Nginx 为例,只需要在配置的 location / 中增加下面一行即可:

try_files $uri /index.html;

发现共同点没,这两种模式都是为了不刷新页面,这也就契合当下我们制作的单页面应用。

Vue 自定义指令的使用

· 阅读需 3 分钟
素明诚
Full stack development

我们可以通过 Vue.directive 方法来自定义指令。该方法接受两个参数,第一个参数是指令名称,第二个参数是一个对象,包含了指令的定义。

定义一个背景变红的指令

Vue.directive('bgColor', {
bind(el, binding) {
el.style.backgroundColor = binding.value
}
})

HTML

    <h1 v-bgColor="'red'">这是一个h1</h1>
<h1>这是一段文字</h1>
<h1>这是一个标题</h1>
<h1>这是一段内容</h1>

b22c25704f345cbe60a8265c261a19c9

当我们自定义一个指令时,可以定义一些钩子函数,这些钩子函数会在指令绑定到元素上、元素更新、元素插入到文档流中、元素更新并且包含组件的 VNode 更新等生命周期中触发。以下是这些钩子函数的详细说明:

bind: 只调用一次,在指令第一次绑定到元素时调用。在这里可以进行一些初始化设置,例如添加事件监听器,设置元素样式等。bind函数接收四个参数:el表示绑定指令的元素,binding表示指令相关信息的对象,vnode表示渲染指令的 VNode 节点,oldVnode表示上一个渲染指令的 VNode 节点。

inserted: 在被绑定元素插入到父元素中时调用,只调用一次。此时可以进行一些 DOM 操作,例如获取元素的宽高、定位等。inserted函数接收和bind一样的参数。

update: 在指令所在元素的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。所以如果需要先处理子 VNode,应该在 componentUpdated 钩子中完成。update函数接收和bind一样的参数,可以通过比较 binding.value 和 binding.oldValue 的值来判断是否需要进行更新操作。

componentUpdated: 在指令所在的 VNode 及其子 VNode 全部更新后调用。componentUpdated函数接收和bind一样的参数。

unbind: 只调用一次,在指令被解绑时调用。可以在这里进行一些清理工作,例如移除事件监听器,清除定时器等。unbind函数接收两个参数:el表示解绑指令的元素,binding表示指令相关信息的对象。

自定义指令使用场景

  1. 操作 DOM:例如修改样式、绑定事件等;
  2. 封装插件:例如集成第三方插件,提供更便捷的调用方式;
  3. 表单验证:例如自定义验证规则,便于表单验证代码的重用;
  4. 优化性能:例如自定义指令实现图片懒加载等功能,可以提高页面性能。
  5. 权限控制:控制某些显示或者隐藏

vue scoped 的原理

· 阅读需 2 分钟
素明诚
Full stack development

Vue 中的 scoped 属性可以将样式作用域限制在当前组件的范围内,避免全局污染。其实现原理是通过给当前组件的所有选择器加上一个唯一的标识符(如 _v-xxxxxx),从而让当前组件的样式仅对带有这个标识符的元素生效。

具体来说,当一个 Vue 组件中使用了 scoped 属性时,Vue 在编译组件时会通过 PostCSS 插件(如 postcss-selector-scope)将组件中的所有样式选择器添加一个特殊的属性选择器,以确保样式只作用于当前组件。例如,对于以下 Vue 组件中的样式:

<template>
<div class="foo">
<p class="bar">Hello, world!</p>
</div>
</template>

<style scoped>
.foo {
background-color: blue;
}
.bar {
color: red;
}
</style>

Vue 会将其编译成类似以下代码:

 <template>
<div class="foo" data-v-xxxxxx>
<p class="bar" data-v-xxxxxx>Hello, world!</p>
</div>
</template>

<style>
.foo[data-v-xxxxxx] {
background-color: blue;
}
.bar[data-v-xxxxxx] {
color: red;
}
</style>

006cef8a81c51c2e6643eeb07fa58d09

可以发现他们的 data-v-xxxx 是相同的

注意,Vue 会自动生成一个唯一的标识符(如 _v-xxxxxx)并将其添加到当前组件的所有选择器中,以确保样式仅作用于当前组件。而对于 HTML 元素的原有类名或 ID,在使用 scoped 属性时并不会添加标识符,因此样式规则仍然会被应用。

什么是 keep-alive

· 阅读需 4 分钟
素明诚
Full stack development

keep-alive 是 Vue.js 中的一个内置组件,用于缓存组件实例。当组件被包裹在 keep-alive 组件中时,该组件不会被销毁,而是被缓存起来,直到缓存被清除或组件被激活重新渲染。这样可以提高组件的渲染效率,减少不必要的性能开销。

keep-alive 组件提供了 includeexclude 两个属性,用于指定需要缓存的组件和需要排除的组件。同时还提供了一些生命周期钩子函数,用于在缓存组件的过程中进行一些额外的操作,例如激活缓存组件时自动更新数据。

需要注意的是,keep-alive 组件只能缓存有状态的组件,即带有 data 属性的组件。同时,被缓存的组件可能会因为缓存而带来一些副作用,例如缓存的组件会保留之前的状态,如果没有及时更新可能会导致一些问题,因此需要根据具体的场景进行使用和管理。

动态/异步组件

异步组件

  1. 异步组件会被分割成代码块文件
  2. 按需从服务器上下载组件xx.js
  // Vue2
AsyncComp:() => import('./xxx');

// Vue3
import { defineAsyncCompontent } from 'vue';
const { defineAsyncCompontent } = 'Vue';

动态组件

在交互中,组件的渲染是不确定的,根据交互的操作来决定渲染哪个组件

<component :is=""></component>
<template>
<div class="login-tab">
<div class="login-nav">
<div
v-for="tab of tabData"
:key="tab"
:class="['nav-item', { active: currentTab === tab }]"
@click="changeTab(tab)"
>{{ tab }}</div>
</div>
<div class="login-component">
<keep-alive>
<component :is="currentTabComponent"></component>
</keep-alive>
</div>
</div>
</template>

<script>
import AccountLogin from './AccountLogin';

export default {
name: 'MainLogin',
components: {
AccountLogin,
QrcodeLogin: () => import('./QrcodeLogin'),
MobileLogin: () => import('./MobileLogin')
},
data () {
return {
currentTab: 'Account',
tabData: ['Account', 'Qrcode', 'Mobile']
}
},
methods: {
changeTab (tab) {
this.currentTab = tab;
}
},
computed: {
// 拼出来组件名
currentTabComponent () {
return this.currentTab.toLowerCase() + '-login';
}
}
}
</script>

keep-alive 使用

keep-alive包裹的组件,组件切换时会缓存组件,保持组件的状态,避免反复渲染导致性能问题

  1. vDOM:Virtual DOM
  2. vNode:Virtual Node
  3. rDOM:Real DOM
  4. rNode:Real Node

keep-alive 的原理

  1. 用户操作视图,视图变化,影响组件显示变化
  2. keep-alive包裹的组件会缓存组件的vNode
  3. 有现成的vNode就用现成的vNode来更新DOM
  4. 这种更新不经过unmounted,再次进入也不会走mounted
  5. 使用activated代替mounted,使用deactivated代替unmounted

keep-alive 属性

正常匹配组件的name属性或如果没有就匹配局部注册组件名称

exclude

排除某个组件

<keep-alive exclude="List,Intro">
<component :is="currentComponent"></component>
</keep-alive>

include

允许某个组件

<keep-alive include="List,Intro">
<component :is="currentComponent"></component>
</keep-alive>

max

最多缓存几个组件

<keep-alive :max="5">
<!-- 需要缓存的组件 -->
</keep-alive>

正则匹配

<keep-alive :include="/n|c/">
<component :is="currentComponent"></component>
</keep-alive>

bug

异步组件不能使用include,缓存了但是没办法正确显示。异步组件请使用exclude

<keep-alive include="List">
<component :is="currentComponent"></component>
</keep-alive>

keep-alive 实现

import comp1 from './components/Comp1';
import comp2 from './components/Comp2';
import comp3 from './components/Comp3';

const oButtons = document.getElementById('buttons');
const oWrapper = document.getElementById('wrapper');

const App = {
components: {
comp1,
comp2,
comp3
},
compCache: {},
init() {
this.bindEvent();
},
mounted(callback) {
callback && callback();
},
activated(callback) {
callback && callback();
},
bindEvent() {
oButtons.addEventListener('click', this.handleBtnClick.bind(this), false);
},
handleBtnClick(e) {
const tar = e.target,
tagName = tar.tagName.toLowerCase();

if (tagName === 'span') {
const key = tar.dataset.key;

let vNode = null;

if (this.compCache[key]) {
vNode = this.compCache[key];
} else {
vNode = this.setVNode(this.components[key]);

if (this.checkKeepAlive(oWrapper)) {
this.compCache[key] = vNode;
}
}

const rNode = this.setRNode(vNode);
oWrapper.innerHTML = '';
oWrapper.appendChild(rNode);

if (this.checkKeepAlive(oWrapper)) {
this.activated(() => {
console.log(key, 'activated');
});
} else {
this.mounted(() => {
console.log(key, 'mounted');
});
}
}
},
setVNode(comp) {
const { template, name } = comp;

const regTag = template.match(/\<(.+?)\>/)[1],
regContent = template.match(/\>(.+?)\</)[1];

return {
tag: regTag,
children: regContent,
mark: name
};
},

setRNode(vNode) {
const tag = vNode.tag;
const content = vNode.children;

const node = document.createElement(tag);
node.innerText = content;

return node;
},

checkKeepAlive(wrapper) {
const outerWrapper = wrapper.parentNode.tagName.toLowerCase();
return outerWrapper === 'keep-alive';
}
};

App.init();

JavaScript 搜索栏联想词搜索功能

· 阅读需 1 分钟
素明诚
Full stack development

前端实现的联想词,如果是输入词请求后端可以直接传关键字给后端

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<div>
<input type="text" id="search" placeholder="请输入搜索关键词">
<ul id="suggestion-list"></ul>
</div>
<script>
const searchInput = document.getElementById('search');
const suggestionList = document.getElementById('suggestion-list');
let suggestionData = []; // 保存联想词搜索结果的数组

// 监听搜索框的输入事件
searchInput.addEventListener('input', debounce(handleInput, 100));

// 处理搜索框输入事件
function handleInput() {
const keyword = searchInput.value.trim(); // 获取搜索关键词
if (keyword === '') {
// 如果搜索关键词为空,则清空联想词搜索结果
suggestionData = [];
renderSuggestionList();
return;
}

// 发送 Ajax 请求获取联想词搜索结果
fetch(`https://jsonplaceholder.typicode.com/posts`)
.then(response => response.json())
.then(data => {
suggestionData = data.map((e) => e.title).filter((n) => n.includes(keyword));
// 将请求结果保存到 suggestionData 数组中
renderSuggestionList(); // 渲染联想词搜索结果列表
})
.catch(error => {
console.error(error);
});
}

// 渲染联想词搜索结果列表
function renderSuggestionList() {
suggestionList.innerHTML = ''; // 先清空列表
suggestionData.forEach(item => {
const li = document.createElement('li');
li.innerText = item;
suggestionList.appendChild(li);
});
}

// debounce 函数,用于防抖
function debounce(fn, delay) {
let timer = null;
return function () {
clearTimeout(timer);
timer = setTimeout(fn, delay);
};
}

</script>
</body>

</html>

ObjectdefineProperty和ObjectdefineProperties的区别

· 阅读需 1 分钟
素明诚
Full stack development

Object.defineProperty()Object.defineProperties()都是用于定义对象属性的方法,不同之处在于它们的作用范围不同。

Object.defineProperty()用于定义单个对象属性,它接收三个参数:对象、属性名、属性描述符,例如:

56355177cba62dceb2eb4064b1fa5f27

Object.defineProperties()则可以用于定义多个对象属性,它接收两个参数:对象和一个属性描述符对象(该对象的属性名对应要定义的属性名),例如:

d9585335e55d924129c36967fece038f

因此,如果只需要定义单个对象属性,使用Object.defineProperty()即可;如果需要定义多个对象属性,使用Object.defineProperties()更为方便。