言
前面两篇文章总结了 Vue 开发的大部分技巧和内容,最后一篇文章来对它进行一个收尾
这篇文章我们来谈谈一些 Vue 理解和实践要求高一点的问题
首先是生命周期这一块内容,随着实践越多它的意义越大,理解也越深刻
mixin 功能强大,对代码复用组织都有很高的要求,算是 Vue 后期发力的高级技巧
服务端渲染可能是学习 Vue 最后一块阵地了,对于 SPA 框架的一个里程碑
最后,总结一下我在使用 Vue 中使用的技巧和经验
常规操作,先点赞后观看哦!你的点赞是我创作的动力之一!
我将从 16 个方面来论述 Vue 开发过程中的一些技巧和原理。如果你还未观看上节文章,可以移步至
Vue 生命周期大概就是:一个从 Vue 实例的创建到组件销毁的一个的过程。
具体情况下,我们分为几个核心的阶段,并且每个阶段都有一套钩子函数来执行我们需要的代码。
我们整理分类一下这些生命周期钩子,为了记忆方便分为 4 大核心阶段:
方便读者记忆,这里尽量使用图示:
可以看到每一个阶段中的钩子命名都很好记忆,阶段开始前使用 beforeXxx,阶段后结束后使用xxxed
除这 8 个核心钩子,另外还有 3 个新增功能型钩子,目前总共是 11 个钩子 顺带提一下这 3 个钩子的功能
我们看看官方的图解,在 Vue 教程实例这节 官方教程直接跳转
官方图解并没有详细解释这张图。我猜一方面原因是这个图里面涉及的细节都是在 vue 源码里面体现,要真正解释起来也没那么简单。另一方面是希望我们多在实践中去理解里面的意义。
对于上面那张图的理解,我们需要对 Vue 源码进行梳理,才能真正的理解。大概根据现有的源码,我梳理了一下大致的流程:
我们可以清楚地看到,从 Vue 实例创建、组件挂载、渲染的一些过程中,有着明显的周期节点。
结合上面源码的流程和相关实践,简化每一个阶段做了哪些时期,每一个钩子里面是组件处于什么状态。
下面我们提出一些问题:
<template>
<div>
<div class="message">
{{message}}
</div>
</div>
</template>
<script>export default {
data() {
return {
message: '1'
}
},
methods: {
printComponentInfo(lifeName) {
console.log(lifeName)
console.log('el', this.$el)
console.log('data', this.$data)
console.log('watch', this.$watch)
}
}
}</script>
复制代码
// ...
beforeCreate() {
this.printComponentInfo('beforeCreate')
},
created() {
this.printComponentInfo('created')
},
beforeMount() {
this.printComponentInfo('beforeMount')
},
mounted() {
this.printComponentInfo('mounted')
},
beforeUpdate() {
this.printComponentInfo('beforeUpdate')
},
updated() {
this.printComponentInfo('updated')
},
beforeDestroy() {
this.printComponentInfo('beforeDestroy')
},
destroyed() {
this.printComponentInfo('destroyed')
},
// ...
复制代码
beforeCreate 中methods中方法直接报错无法访问,直接访问 el 和 data 后
发现只能访问到 watch, el 和 data 均不能访问到
created 时期 el 无法访问到,但是可以访问到 data 了
beforeMount 中可以访问 data 但是仍然访问不到 el
mounted 中可以访问到 el 了
首次加载页面,更新阶段和销毁阶段到钩子都未触发
我们增加一行代码
this.message=this.message + 1
复制代码
如果增加在 created 阶段,发现 update钩子仍然未触发,但是 el 和 data 的值都变成了 2
如果增加在 mounted 阶段,发现 update钩子此时触发了
怎样触发销毁的钩子呢? 大概有这几种方法
this.$destory('lifecycle')
复制代码
发现beforeDestory 和 destoryed 都触发了,而且el、data都一样还是可以访问到
beforeCreate 无法访问到 this 中的 data、method
// 错误实例
beforeCreate() {
// 允许
console.log('ok')
// 不允许
this.print() // 报错找不到
this.message=1 // 报错找不到
}
复制代码
created 可以访问 this,但无法访问 dom,dom 未挂载
created() {
// 允许并推荐
this.$http.get(xxx).then(res=> {
this.data=res.data
})
// 不允许
this.$el
this.$ref.demo
const a=document.getElementById('demo')
}
复制代码
mounted 已经挂载 dom,可以访问 this
mounted() {
// 允许
this.$el
this.$ref.demo
let a=document.getElementById('')
}
复制代码
生命周期相关demo 代码见github-lifecycle-demo
当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项 大致原理就是将外来的组件、方法以某种方式进行合并。合并的规则有点像继承和扩展。
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”
我们看一下一个组件里面有哪些东西是可以合并的
// mixins/demo
export default {
data() {
return {}
},
mounted() {},
methods: {},
computed: {},
components: {},
directives: {}
}
复制代码
data、methods、computed、directives、components 生命周期钩子
没错这些都可以混入
import demoMixin form '@/mixins/demo'
export default {
mixins: [demoMixin]
}
复制代码
这样看来,很多页面重复的代码我们都可以直接抽取出来
或者是封装成一个公共的 mixin
比如我们做 H5 页面,里面很多短信验证的逻辑固有逻辑,但是需要访问到 this。使用工具函数肯定不行。
这时候就可以考虑使用 mixin,封装成一个具有响应式的模块。供需要的地方进行引入。
首先是优先级的问题,当重名选项时选择哪一个为最后的结果
默认规则我这里分为 3 类
当然如果想改变规则,也可以通过配置来改变规则
Vue.config.optionMergeStrategies.myOption=function (toVal, fromVal) {
// 返回合并后的值
}
复制代码
我们知道 Vue 最能复用代码的就是组件。一般情况,我们通过 props 来控制组件的,将原有组件封装成 HOC 高阶组件。而控制 props 的生成不一样的功能的代码还是写在基础组件里。
<template lang="pug">
div.dib
van-button.btn(
@click="$emit('click')"
:class="getClass" v-bind="$attrs"
:style="{'width': size==='large' ? '345px': '', 'backgroundColor': getBgColor, borderColor: getBgColor, color: getBgColor}")
slot
</template>
<script>import { Button } from 'vant'
import Vue from 'vue'
import { getColor } from '@/utils'
Vue.use(Button)
export default {
name: 'app-button',
props: {
type: {
type: String,
default: 'primary'
},
theme: {
type: String,
default: 'blue'
},
size: {
type: String,
default: ''
}
}
}复制代码
以这个组件为例,我们还是通过公共组件内部的逻辑,来改变组件的行为。
但是,使用 mixin 提供了另一个思路。我们写好公共的 mixin,每一个需要使用 mixin 的地方。我们进行扩展合并,不同与公共 mixin 的选项我们在当前组件中进行自定义,也就是扩展。我们新的逻辑是写在当前组件里面的,而非公共 mixins 里。
画个图理解一下:
最后总结一下 mixin 的优点
第一,千万不能滥用全局 mixins 因为会影响所有多子组件
第二,由于 mixins 的合并策略固有影响,可能在一些极端情况达不到你想要的效果。
比如:我已经存在一个 mixins,我的页面里面也有一些方法,我要引入mixins 就要做很多改动,来保证我的代码按我的需要运行。
页面 data 有一个 message 值,mixins 里面同样有一个。
按照默认规则,mixins 里面的 message 会被页面里面 message 覆盖。但是这两个 message 可能代表的意义不一样,都需要存在。那么我就需要改掉其中的一个,如果业务太深的话,可能这个 message 没那么好改。
有时候需要考虑这些问题,导致使用 mixins 都会增加一些开发负担。当然也是这些问题可以使用规范来规避。
SSR 是 Serve Side Render 的缩写,翻译过来就是我们常说的服务端渲染
但是它还存在以下问题
总得来说,SSR 是必要的但不是充分的,SPA 的 SEO 现在没有更好的方案,有这方面强烈需求的网站来说,SSR 确实很有必要
通过上面图我们可以得大致几点内容
分别尝试用这 3 种方式搭建 SSR
这里主要加深理解,vue-cli3+ 实现基本 SSR
分为 2 个入口,将 main.js 定为通用入口, 并额外增加entry-client.js 和 entry-serve.js 两个
1.改造主要入口,创建工厂函数
// main.js
import Vue from 'vue'
import App from './App.vue'
import { createRouter } from "./router"
// app、router
export function createApp () {
const router=createRouter()
const app=new Vue({
router,
render: h=> h(App)
})
return { app, router }
}
复制代码
2.客户端入口
// client.js
import { createApp } from './main'
// 客户端特定引导逻辑
const { app }=createApp()
app.$mount('#app')
复制代码
3.服务端入口
// serve.js
import { createApp } from "./main";
export default context=> {
// 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise
return new Promise((resolve, reject)=> {
const { app, router }=createApp();
// 设置服务器端 router 的位置
router.push(context.url);
// 等到 router 将可能的异步组件和钩子函数解析完
router.onReady(()=> {
const matchedComponents=router.getMatchedComponents();
// 匹配不到的路由,执行 reject 函数
if (!matchedComponents.length) {
return reject({
code: 404
});
}
// Promise 应该 resolve 应用程序实例,以便它可以渲染
resolve(app);
}, reject);
});
};
复制代码
const VueSSRServerPlugin=require("vue-server-renderer/server-plugin");
const VueSSRClientPlugin=require("vue-server-renderer/client-plugin");
const nodeExternals=require("webpack-node-externals");
const merge=require("webpack-merge");
const TARGET_NODE=process.env.WEBPACK_TARGET==="node";
const target=TARGET_NODE ? "server" : "client";
module.exports={
configureWebpack: ()=> ({
entry: `./src/entry-${target}.js`,
devtool: 'source-map',
target: TARGET_NODE ? "node" : "web",
node: TARGET_NODE ? undefined : false,
output: {
libraryTarget: TARGET_NODE ? "commonjs2" : undefined
},
externals: TARGET_NODE
? nodeExternals({
whitelist: [/\.css$/]
})
: undefined,
optimization: {
splitChunks: undefined
},
plugins: [TARGET_NODE ? new VueSSRServerPlugin() : new VueSSRClientPlugin()]
}),
//...
};
复制代码
export function createRouter(){
return new Router({
mode: 'history',
routes: [
//...
]
})
}
复制代码
这一步主要是让 node 服务端响应 HTML 给浏览器访问
const Vue=require('vue')
const server=require('express')()
const renderer=require('vue-server-renderer').createRenderer()
server.get('*', (req, res)=> {
const app=new Vue({
data: {
url: req.url
},
template: `<div>访问的 URL 是: {{ url }}</div>`
})
renderer.renderToString(app, (err, html)=> {
if (err) {
res.status(500).end('Internal Server Error')
return
}
res.end(`
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>${html}</body>
</html>
`)
})
})
server.listen(8080)
复制代码
简单几步体验下 nuxt
简单看了一下源码,nuxt 把我们之前提到的重要的改造,全部封装到 .nuxt 文件夹里面了
跑一下 dev 发现有两个端,一个 clinet 端,一个 server 端
最后查看一下效果,整个过程挺丝滑的。目录结构也比较符合我的风格,新项目需要 SSR 会考虑使用 nuxt
解决 SEO 问题是不是只有 SSR 呢?其实预渲染也能做到,首先
yarn prerender-spa-plugin
复制代码
const path=require('path')
const PrerenderSPAPlugin=require('prerender-spa-plugin')
const Renderer=PrerenderSPAPlugin.PuppeteerRenderer
module.exports={
plugins: [
//...
new PrerenderSPAPlugin({
staticDir: path.join(__dirname, 'dist'),
outputDir: path.join(__dirname, 'prerendered'),
indexPath: path.join(__dirname, 'dist', 'index.html'),
routes: [ '/', '/about', '/some/deep/nested/route' ],
postProcess (renderedRoute) {
renderedRoute.route=renderedRoute.originalPath
renderedRoute.html=renderedRoute.html.split(/>[\s]+</gmi).join('><')
if (renderedRoute.route.endsWith('.html')) {
renderedRoute.outputPath=path.join(__dirname, 'dist', renderedRoute.route)
}
return renderedRoute
},
minify: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
keepClosingSlash: true,
sortAttributes: true
},
renderer: new Renderer({
inject: {
foo: 'bar'
},
maxConcurrentRoutes: 4
]
}
复制代码
从 5 个大的角度来提升开发效率和体验,代码美观和代码质量,用户体验
作者面临 “失业” 和 “禁足” 在家里的双重打击下,仍然坚持完成了这个系列的最后一篇文章。
如果能对你有帮助,便是它最大的价值。都看到这里还不点赞,太过不去啦!
由于技术水平有限,文章中如有错误地方,请在评论区指出,感谢!
言
在网络安全领域,暗链接(或隐藏链接)是一种常见的网络欺诈手段。它们通常隐藏在网页代码中,对普通用户不可见,但可能指向恶意网站或用于执行不安全的操作。因此,从网页源代码中检测并识别这些暗链接变得尤为重要。本文将探讨如何从提取的HTML源代码中检测暗链接,并将这些链接进行输出。
一、理解暗链接
什么是暗链接:
暗链接通常指的是隐藏在网页中,对用户不明显,但可能含有恶意内容的链接。
暗链接的常见形式:
完全隐藏的链接,如使用CSS将链接设为透明或大小为零。
使用微小的文本或图像作为链接。
将链接嵌入到不相关元素中。
二、HTML源代码的检测方法
分析HTML结构:
介绍如何查看和理解网页的HTML源代码。
解释如何识别HTML中的链接元素(通常是<a>标签)。
检测暗链接的技术:
使用HTML和CSS属性来识别隐藏的链接,如display: none;、opacity: 0;或width: 0px; height: 0px;等。
检查链接的文本内容和尺寸,判断是否可疑。
三、实现暗链接检测
工具和技术选择:
介绍可用于解析HTML和CSS的工具,如Python的BeautifulSoup库。
代码实现:
提供一个基本的脚本示例,展示如何从HTML中提取链接并检查其属性以识别暗链接。
四、案例分析
实际网页案例:
选取一个包含暗链接的网页作为案例,展示如何使用工具检测这些链接。
结果展示和分析:
展示检测到的暗链接,并对其可能的风险进行分析。
五、总结与预防建议
总结:
强调检测暗链接在网络安全中的重要性。
预防建议:
提供一些基本的网络安全建议,帮助用户识别和避免访问暗链接。
小结
通过本文的讨论,读者可以了解如何从HTML源代码中检测暗链接,并了解这些链接可能带来的风险。虽然这需要一定的技术知识,但通过合适的工具和方法,可以有效地识别和防范这种网络安全威胁。
示例代码(Python使用BeautifulSoup检测暗链接)
python
from bs4 import BeautifulSoup
import requests
# 加载网页内容
url="https://example.com"
html_content=requests.get(url).text
soup=BeautifulSoup(html_content, 'html.parser')
# 检测暗链接
dark_links=[]
for link in soup.find_all('a'):
if link.get('style'):
styles=link.get('style').split(';')
if any(s in styles for s in ['display:none', 'opacity:0', 'width:0px', 'height:0px']):
dark_links.append(link.get('href'))
# 输出检测到的暗链接
print("Detected Dark Links:", dark_links)
这段代码演示了如何使用BeautifulSoup库来解析HTML,并检查每个链接的CSS样式以识别是否为暗链接。这只是一个基本的示例,实际应用中可能需要更复杂的逻辑来处理各种隐藏技术。
说之前也是对 chunk 这个概念有些模糊,并且很多时候网上的文章大部分介绍重点在将代码分离动态加载之类的。写这篇文章的目的也是想让其他那些跟我一样曾经对这个概念不是很清楚的童鞋有个清晰的认识。废话不多说,开始干!
Let's Dive in!
Webpack 文件分离包括两个部分,一个是 Bundle 的分离,一个是 Code 代码的分离:
准备工作
在进行文件分离之前的准备工作,我们先写一些代码:
入口文件 src/index.js:
const { getData }=require('./main') const { findMaxIndex }=require('./math') let arr=[1,2,123,21,3,21,321,1] findMaxIndex(arr) getData('./index.html')
两个依赖模块:
src/main.js:
const axios=require('axios') const getData=url=> { axios.get(url).then(d=> { console.log(d.status) console.log(d.data.length) }) } module.exports={ getData }
src/math.js:
const _=require('lodash') const findMaxIndex=arr=> { let x=_.max(arr) let r=Array.prototype.indexOf.call(arr, x) console.log(r); } module.exports={ findMaxIndex }
增加一个 webpack 配置文件 webpack.config.js:
const path=require('path') module.exports={ mode: 'development', entry: path.resolve(__dirname, 'src/index.js'), output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[contenthash].js' }, }
文件分离之前打包效果
在 bundle split 和 code split 操作之前,我们先看一下当前默认打包的效果:
全部依赖都被打包到 main.xxx.js 中去,大小是 609k
开始分离操作
Bundle Split
Bundle Split 的主要任务是将多个引用的包和模块进行分离,避免全部依赖打包到一个文件下
基本用法
Webpack 4 中需要使用到 optimization.splitChunks 的配置:
const path=require('path') module.exports={ mode: 'development', entry: path.resolve(__dirname, 'src/index.js'), output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[contenthash].js' }, optimization: { splitChunks: { chunks: 'all' } } }
optimization.splitChunks 的意思是将所有来自 node_modules 中的依赖全部打包分离出来,这个时候我们再看打包的文件是个什么样子:
增加了 splitChunks 的配置,我们第三方模块都被打包到了 vendors~main.xxx.js 中去了,这个文件大小有 604k,而入口文件 main.xxx.js 则只有 7k
虽然说这样将第三方模块单独打包出去能够减小入口文件的大小,但这样仍然是个不小的文件;这个小的测试项目中我们使用到了 axios 和 lodash 这两个第三方模块,因此我们希望的应该是将这两个模块单独分离出来两个文件,而不是全部放到一个 vendors 中去,那么我们继续配置 webpack.config.js:
将每个 npm 包单独分离出来
这里我们需要使用到 webpack.HashedModuleIdsPlugin 这个插件
参考官方文档(https://webpack.js.org/plugins/split-chunks-plugin/#optimization-splitchunks)
直接上代码:
const path=require('path') const webpack=require('webpack') module.exports={ mode: 'development', entry: path.resolve(__dirname, 'src/index.js'), plugins: [ new webpack.HashedModuleIdsPlugin() // 根据模块的相对路径生成 HASH 作为模块 ID ], output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[contenthash].js' }, optimization: { runtimeChunk: 'single', splitChunks: { chunks: 'all', // 默认 async 可选值 all 和 initial maxInitialRequests: Infinity, // 一个入口最大的并行请求数 minSize: 0, // 避免模块体积过小而被忽略 minChunks: 1, // 默认也是一表示最小引用次数 cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, // 如果需要的依赖特别小,可以直接设置成需要打包的依赖名称 name(module, chunks, chcheGroupKey) { // 可提供布尔值、字符串和函数,如果是函数,可编写自定义返回值 const packageName=module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1] // 获取模块名称 return `npm.${packageName.replace('@', '')}` // 可选,一般情况下不需要将模块名称 @ 符号去除 } } } } } }
这里我们主要做了几件事:
为了避免每次打包的文件哈希变化,我们可以使用 webpack 内置的 HashedModuleIdsPlugin,这样可以避免每次打包的文件哈希值变化首先增加 maxInitialRequests 并设置成 Infinity,指定这个入口文件最大并行请求数然后将 minSize 和 minChunks 分别设置成 0 和 1,即使模块非常小也将其提取出来,并且这个模块的引用次数只有 1 也要提取最后配置匹配的依赖以及分离出的文件名格式另外,我们还将运行时代码分离出来,这块代码还可以配合 InlineManifestWebpackPlugin 直接插入到 HTML 文件中。这里我们将这个配置设置成 single,即将所有chunk的运行代码打包到一个文件中
这样 Bundle Split 的操作基本就完成了,让我们看看效果如何:
所依赖的几个模块都被分离出去了。
使用 HtmlWebpackPlugin 这个插件将 js 代码注入 html 文件中。
npm i -D html-webpack-plugin
修改 webpack.config.js 文件:
// 配置文件引入这个插件 var HtmlWebpackPlugin=require('html-webpack-plugin'); // ... module.exports={ // ... plugins: [ new HtmlWebpackPlugin(), new webpack.HashedModuleIdsPlugin() // 根据模块的相对路径生成 HASH 作为模块 ID ], // ... }
安装 http-server 或使用 vscode 的插件 Live Server 将代码放入一个本地服务器中,打开浏览器的调试窗口进入到 Network 面板:
可以看到我们将模块单独分离出来并行加载,这样比单独加载一个庞大的包要快不少,接下来我们还要进行代码分离,将不必要加载的模块延迟加载
Code Split
代码分离实际上就是只加载用户需要使用到的部分代码,不是必须的就暂时不加载。
这里我们要用到 require.ensure 这个方法去获取依赖,这样 webpack 打包之后将会增加运行时代码,在设定好的条件下才会触发获取这个依赖。
在 ES6 中我们可以用 Dynamic Imports 来替代上述方案,如果使用 ES6 语法那么需要使用到 babel 以及 babel 的插件 plugin-syntax-dynamic-import(https://babeljs.io/docs/en/babel-plugin-syntax-dynamic-import/#installation),在浏览器中为了保证兼容性,还需要安装 promise 的 polyfill,用法大同小异,可直接观摩 webpack 的官方文档(https://webpack.js.org/guides/code-splitting/#dynamic-imports)
修改我们的代码:
const { getData }=require('./main') let arr=[1,2,123,21,3,21,321,1] getData('./index.html') setTimeout(()=> { require.ensure(['./math'], function(require) { const { findMaxIndex }=require('./math') console.log(findMaxIndex(arr)) }) }, 3000)
我们设定了一个定时器,只有在 3000 毫秒以后,浏览器才会去请求这个 math 模块
编译之后,打开调试面板刷新浏览器:
在页面刚加载完毕后,浏览器会尝试获取上述这么几个模块,因为模块都很小很快就加载完成了
在 3500ms 左右,浏览器才会去获取 requie.ensure 方法定义的 math 模块,因 math 模块又包含依赖 lodash,因此这个 lodash 第三方模块也会被按需加载。
这样我们就完成了代码分离的操作,这样做的优势就是不需要第一时间加载的模块,可以推迟加载,以页面的加载速度。当然,上面的 timeout 定时器完全可以换成其他诸如按钮点击之类的事件来触发。
*请认真填写需求信息,我们会在24小时内与您取得联系。