1.如何实现网页加载进度条?
考虑原生进度条。HTML5 中提供了 progress 元素,可以通过它来实现一个原生的进度条。
<progress id="progressBar" value="0" max="100"></progress>
但是!我选择自己来:
<div id="app">
<div id="progressBar">0%</div>
</div>
#progressBar {
width: 0%;
height: 30px;
background-color: #4CAF50;
text-align: center;
line-height: 30px; /* Vertically center the text */
color: white;
transition: width 0.5s ease;
}
监听页面加载事件和资源加载事件:
document.addEventListener("DOMContentLoaded", function() {
let progress = 0;
const progressBar = document.getElementById('progressBar');
const resources = document.querySelectorAll('img, script, link[rel="stylesheet"]');
let totalResources = resources.length;
let loadedResources = 0;
resources.forEach(resource => {
resource.addEventListener('load', () => {
loadedResources++;
let newProgress = Math.round((loadedResources / totalResources) * 100);
progressBar.style.width = newProgress + '%';
progressBar.textContent = newProgress + '%';
});
});
});
eg:加载到100%
let progress = 0;
const progressBar = document.getElementById('progressBar');
const total = 100; // Total steps
function simulateLoading() {
for(let i = 0; i <= total; i++) {
setTimeout(() => {
progress = Math.min((i / total) * 100, 100);
progressBar.style.width = progress + '%';
progressBar.textContent = Math.round(progress) + '%';
}, 100 * i); // 加载时间 结束的时候完成加载
}
}
simulateLoading();
第二种方法:有插件:npm install --save nprogress
NProgress.start() // 进度条开始
NProgress.set() // 将进度设置到具体的百分比位置,取值范围是0到1.0
NProgress.inc() // 如果少量增加进度,进度将永远不会得到100%
NProgress.done() // 进度条结束消失
NProgress.configure() // 进度条参数配置
main.js
import Vue from 'vue'
import Router from 'vue-router'
import routers from './routers'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
NProgress.configure({ showSpinner: false })
Vue.use(Router)
const router = new Router({
mode: 'history',
routes: routers
})
export default router
router.beforeEach((to, from, next) => {
NProgress.start()
next()
})
router.afterEach(() => {
NProgress.done()
})
app.vue:
#nprogress .bar {
background: #66B1FF !important; // 自定义颜色
height: 2px !important; // 自定义高度
}
2.常见的图片懒加载方式有哪几种?
1.HTML5提供的懒加载方式loading="lazy"
<img src="placeholder.jpg" data-src="image-to-lazy-load.jpg" loading="lazy" alt="Lazy Loaded Image">
2.Intersection Observer API 经典视口观察:
document.addEventListener("DOMContentLoaded", function() {
//获取所有img
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.getAttribute('data-src');
imageObserver.unobserve(img);
}
});
});
images.forEach(image => {
imageObserver.observe(image);
});
});
3.Scroll事件监听 不太建议 消耗性能 建议加节流防抖
window.addEventListener('scroll', function() {
const images = document.querySelectorAll('img[data-src]');
images.forEach(img => {
if (img.offsetTop <= window.innerHeight + window.scrollY) {
img.src = img.getAttribute('data-src');
}
});
});
4Lozad.js第三方库 一行搞定
<script src="lazysizes.min.js" async=""></script>
<img data-src="image.jpg" class="lazyload" alt="image" />
or: vue-lazyload
npm install vue-lazyload--save
// main.js
import Vue from 'vue'
import App from './App.vue'
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload)
//或者添加选项,例如加载时的占位图或加载失败时的图像
Vue.use(VueLazyload, {
preLoad: 1.3,
error: 'error.png',
loading: 'loading.gif',
attempt: 1
})
new Vue({
render: h => h(App)
}).$mount('#app')
<img v-lazy="imgUrl" alt="image">
3.cookie构成部分
首先 它是什么?
HTTP Cookie(或简称Cookie)是由服务器发送到客户端浏览器并保存在客户端的一种数据。 Cookie通常用于识别用户和存储用户特定的信息,例如用户偏好、会话信息。
一个Cookie由以下几个基本部分组成:
-
名称(Name) :Cookie的唯一标识符,用于区分不同的Cookie。
-
值(Value) :与名称相对应的数据值,可以是任何字符串形式的数据。
-
域(Domain) :Cookie有效的域名,只有该域名下的页面才能访问该Cookie。
-
路径(Path) :Cookie有效的路径,只有该路径下的资源才能访问该Cookie。
-
过期时间(Expires/Max-Age) :
Expires:指定一个具体的日期和时间,在此之后Cookie将不再有效。Max-Age:指定从创建Cookie时开始的秒数,过期后Cookie将不再有效。
-
安全标志(Secure) :如果设置了Secure标志,那么Cookie只会在HTTPS连接中被发送。
-
HttpOnly标志(HttpOnly) :如果设置了HttpOnly标志,那么Cookie不能被客户端脚本(如JavaScript)访问,这可以提高安全性,防止跨站脚本攻击(XSS)。
-
SameSite属性:这个属性用来控制Cookie的跨站点请求,可以设置为
Strict、Lax或None,以防止CSRF攻击。 -
优先级(Priority) :某些浏览器支持设置Cookie的优先级,如
Low、Medium(默认)、High。 -
SameParty:这个属性可以控制跨Party的Cookie共享行为,与SameSite类似,但基于第一方域的概念。
-
Partitioned:某些浏览器支持将Cookie分区存储,以保护用户隐私。
4.扫码登陆实现方式
就个人而言 ,这个问题更偏向与问实现逻辑。前端基本上是展示后端生成的二维码,重要的是这个查询是否登录的方式。 查询方式很多,包括,轮询(setIntervel),websocket,worker等方式。
- **PC端**:进入二维码登录页面,请求**服务端**获取二维码的ID。
- **服务端**:生成二维码ID,并将其与请求的设备绑定后,返回有效的二维码ID。
- **PC端**:根据二维码ID生成二维码图片,并展示出来。
- **移动端**:扫描二维码,解析出二维码ID。
- **移动端**:使用移动端的token和二维码ID请求**服务端**进行登录。
- **服务端**:解析验证请求,绑定用户信息,并返回给移动端一个用于二次确认的临时token。
- **PC端**:展示二维码为“待确认”状态。
- **移动端**:使用二维码ID、临时token和移动端的token进行确认登录。
- **服务端**:验证通过后,修改二维码状态,并返回给PC端一个登录的token。
5.DNS协议了解多少
还是那句话 ,dns是谁,是什么?
DNS 即域名系统,全称是 Domain Name System。当我们在浏览器输入一个 URL 地址时,浏览器要向这个 URL 的主机名对应的服务器发送请求,就得知道服务器的 IP,对于浏览器来说,DNS 的作用就是将主机名转换成 IP 地址。
DNS服务器
- 根服务器:全球有13台根服务器,负责解析顶级域名(如.com、.org)。
- 顶级域(TLD)服务器:负责解析特定顶级域下的域名。
- 权威服务器:负责解析特定域名的服务器,通常是域名注册时指定的服务器。
- 递归服务器:通常由ISP(互联网服务提供商)提供,负责处理用户的DNS查询请求,并将查询结果缓存以提高响应速度。
DNS查询过程
- 用户发起查询:用户在浏览器中输入域名。
- 递归服务器查询:用户的设备首先向本地DNS服务器(通常是ISP的递归服务器)发起查询。
- 根服务器查询:如果本地DNS服务器没有缓存结果,它会向根服务器发起查询。
- TLD服务器查询:根服务器返回TLD服务器的地址,递归服务器继续查询。
- 权威服务器查询:TLD服务器返回权威服务器的地址,递归服务器继续查询。
- 返回结果:权威服务器返回域名对应的IP地址,递归服务器将结果返回给用户设备。
DNS缓存
- DNS服务器会缓存查询结果,以减少对权威服务器的查询次数,提高响应速度。缓存的有效期由TTL(Time to Live)值决定。
6.函数式编程
函数式编程是一种强调以函数使用为主的软件开发风格。 enmmm,就是说 纯函数 无副作用
核心理念包括以下几点:
- 纯函数:纯函数是指没有副作用并且只依赖于其输入的函数。这种函数对于相同的输入始终返回相同的输出,而且不会改变任何外部状态。
- 不可变性:不可变性意味着数据一旦创建就不能再被修改。在函数式编程中,我们通常创建新的数据结构来代替修改已有的数据。
- 函数组合:函数组合是将多个函数按照一定顺序组合起来运行的过程。通过将函数组合起来,我们可以轻松地实现复杂的功能。
- 递归:递归是函数式编程的重要特征之一,在函数式编程中,我们经常使用递归来处理列表和其他数据结构。
function add(a, b) { return a + b; } 纯函数
const list1 = [1, 2, 3];
const list2 = [...list1, 4]; // 创建一个新列表 不可变
function add(a, b) { return a + b; } function double(x) { return x * 2; }const addAndDouble = (a, b) => double(add(a, b)); 函数组合
function sum(list) {
if (list.length === 0) {
return 0;
}
return list[0] + sum(list.slice(1));
}
const result = sum([1, 2, 3, 4, 5]);
console.log(result); // 输出 15 递归
eg: 一般人的helloword:
document.querySelector('#msg').innerHTML = '<h1>Hello World</h1>'
不一般的人:
function printMessage(elementId, format, message) {
document.querySelector(elementId).innerHTML = `<${format}>${message}</${format}>`
}
printMessage('msg', 'h1', 'Hello World')
大佬:
const printMessage = compose(addToDom('msg'), h1, echo)
//addToDom 是插入职责的函数
printMessage('Hello World')
将一个任务拆分成多个最小颗粒的函数,然后通过组合的方式来完成我们的任务,这跟我们组件化的思想很类似,将整个页面拆分成若干个组件,然后拼装起来完成我们的整个页面。在函数式编程里面,组合是一个非常非常非常重要的思想。
7.前端水印
- 方案一:fixed 定位的 div 元素,重复渲染 div 元素来添加水印。会创建很多无关的 DOM 元素
- 方案二:fixed 定位 canvas 元素,重复填充水印。始终会创建一个无关的 canvas 元素
- 方案三:canvas + 伪类。不会创建无关元素,且兼容性好
- 方案四:svg + 伪类。不会创建无关元素,但兼容性略差于 canvas
eg:
body::after {
content: "版权所有,翻版必究";
position: fixed;
bottom: 10px;
right: 10px;
color: #ccc;
font-size: 16px;
opacity: 0.3;
}
or:
<canvas id="watermark"></canvas>
const canvas = document.getElementById('watermark');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.font = '30px Arial';
ctx.fillStyle = 'rgba(45, 132, 216,0.6)';
ctx.textAlign = 'center';
ctx.fillText('版权所有,翻版必究', canvas.width / 2, canvas.height / 2);
or:
document.addEventListener('DOMContentLoaded', function() {
const text = '版权所有,翻版必究';
const waterMark = document.createElement('div');
waterMark.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0.1;
color: white;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
`;
waterMark.textContent = text;
document.body.appendChild(waterMark);
});
or:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="0">
<text x="50%" y="50%" fill="red" font-size="30" text-anchor="middle">版权所有1,翻版必究</text>
</svg>
当然 。花点时间写完啦:
<template>
<div className="my-page-container" id="my-page-container">
<div className="my-info">
<div className="title">这是测试标题</div>
<div className="content">
// ...我想这是机密内容 * n
</div>
</div>
</div>
</template>
<script>
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const createImgBase = (options) => {
const { content, canvasHeight, canvasWidth } = options;
const canvas = document.createElement('canvas'); // 创建一个画布
const ctx = canvas.getContext('2d');
// 设置画布的宽高
canvas.width = canvasHeight;
canvas.height = canvasWidth;
if (ctx) {
ctx.rotate((-10 * Math.PI) / 180); // 偏移一点距离
ctx.fillStyle = 'rgba(100,100,100,0.2)'; // 设置绘制的颜色
ctx.font = '40px'; // 设置字体的大小
// 遍历水印内容
content.forEach((text, index) => {
ctx.fillText(text, 10, 30 * (index + 1)); // 拉开30的间距
});
}
return canvas.toDataURL('image/png'); // 转换程data url,可供img直接使用
};
const listenerDOMChange = (className) => {
const targetNode = document.querySelector(`.${className}`);
if (!targetNode) return; // 如果找不到节点,直接返回
const observer = new MutationObserver((mutationsList) => {
for (let mutation of mutationsList) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const curClassVal = targetNode.getAttribute('class') || '';
if (!curClassVal.includes(className)) { // 使用includes来检查className是否存在
targetNode.setAttribute('class', `${curClassVal.trim()} ${className}`);
}
}
}
});
observer.observe(targetNode, {
attributes: true,
attributeFilter: ['class'], // 只观察class属性的变化
});
// 考虑到性能和内存泄漏问题,可以在适当的时候调用observer.disconnect()来停止观察
};
const genWaterMark = ({
content,
className,
canvasHeight = 140,
canvasWidth = 150,
}) => {
// 监听class的变更
listenerDOMChange(className);
const dataURL = createImgBase({ content, canvasHeight, canvasWidth });
const defaultStyle = document.createElement('style');
defaultStyle.innerHTML = `.${className}::after {
content: '';
display: block;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-image: url(${dataURL});
background-repeat: repeat;
pointer-events: none;
}`;
document.head.appendChild(defaultStyle);
};
genWaterMark({
content: ['userName', 'userPhone', '内部机密材料', '严禁外泄!'],
className: 'my-page-container',
});
return {
genWaterMark,
createImgBase,
listenerDOMChange
};
},
});
</script>
<style>
.my-page-container {
height: calc(100vh - 104px);
overflow: hidden;
}
.my-info {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
padding: 24px;
overflow-y: auto;
}
</style>
效果:
8.什么是领域模型?
enmmm 好像闻所未闻。
解释一下: "领域模型"(Domain Model)是一个概念,它源自领域驱动设计(Domain-Driven Design, DDD)的理念,用于表示特定业务领域内的概念和实体。领域模型是业务逻辑和操作的核心,它帮助开发者将业务需求转化为代码结构。后端同学肯定听的更多哈、
问题处于下两个方面:
-
UI层复用性差: 前端UI代码大量采用VM(view + model)的方案,面向不同的设计稿进行开发,代码差异化无法收敛。
-
Data层复用性差: 服务端通信给前端的数据虽然已经是标准视图对象(view object),但是明显是带有鲜明的领域(Domain)属性,即不同业务不同视图对象。
我的理解是 每次业务迭代或者新项目进来 必须重复性操作,复用性和逻辑性需要从头设计。
那么领域模型就至关重要了。
主要就是解决开发效率和代码复用性。
大佬救命....🤔 补充下吧
9.一直在window上搜东西有什么风险?
en?有啥风险? 噢噢 应该是window对象吧1!
全局命名空间污染:window对象是全局作用域的一部分,在它上面添加属性或方法会增加全局变量的数量,可能导致命名冲突
内存泄漏:如果在window对象上添加了引用,而这些引用没有在适当的时候被清除,可能会导致内存泄漏,尤其是在长时间运行的应用中。
性能问题:频繁地在window对象上搜索属性可能会影响性能,尤其是在属性很多的情况下,因为属性查找可能需要遍历整个原型链。
代码可维护性:依赖于全局window对象的代码通常难以维护和测试,因为它们与全局状态紧密耦合,难以模拟或替换
安全性问题:在window对象上存储敏感信息可能会增加安全风险,因为前端代码通常可以被用户或其他脚本访问和修改。
依赖注入问题:如果在window对象上动态添加依赖,可能会使得代码的依赖关系不明确,增加理解和维护的难度
框架/库的冲突:不同的前端框架或库可能会在window对象上添加自己的属性或方法,如果你的应用中使用了多个框架或库,这可能会导致冲突
测试困难:在window对象上添加的属性或方法可能会干扰测试,因为测试环境可能需要模拟或重置window对象的状态。
单页应用(SPA)问题:在单页应用中,如果在window对象上存储了状态信息,当页面重新加载或导航到不同的路由时,这些状态可能会丢失或产生意外的行为。
10.深度SEO优化方式有哪些?
1.TDK:TDK是Title(标题)、Description(描述)和 Keywords(关键词)的缩写,是网站SEO的关键 页面标题,是搜索引擎最重视的元素之一,它直接决定了页面在搜索结果中的标题显示。 避免过于冗长。
<title>标题</title>
description:页面描述,内容通常会被用作页面的摘要信息展示。不宜太短。 30《description 《160
<meta name="description" content="网站的描述">
keywords :页面关键词. 尽量不重复
<meta name="Keywords" content="网站的关键词">
- OG 协议
由 Facebook 提出的一种元数据标准,它允许页面成为丰富的社交对象。通过在网页中添加特定的 Open Graph 协议,可以帮助提供更丰富的预览信息。
常见的 Open Graph 标签包括:
<meta property="og:title" content="页面标题">
<meta property="og:description" content="页面描述">
<meta property="og:image" content="图片URL">
<meta property="og:url" content="页面URL">
<meta property="og:type" content="网页类型,如website,article">
<meta property="og:release_date" content="定义网页内容的发布时间">
3.HTML语义化
- 方便其他设备进行解析,例如盲人阅读器。
- 有利于SEO,搜索引擎更容易理解语义化页面的内容结构和主题。
- 便于团队开发和维护,语义化更具有可读性。
eg:
h1、h2、h3、h4、h5 和 h6
strong em
<a rel="nofollow" href="http://www.baidu.com/">百度</a>
<a rel="external" href="http://www.baidu.com/">百度</a>
<img alt="imgalt">
<p> ppppppppppppp文字描述</p>
ul:标签表示无序列表
ol:标签表示有序列表
li:标签表示列表的一项
header:定义页眉,通常包含网站标志、主导航、搜索框等
nav:定义导航
article:定义独立的、完整的内容块,如博客文章、新闻报道等
section:定义页面上的独立的、有语义的内容块,如章节
aside:定义侧边栏
footer:定义页脚,通常包含版权信息、联系方式、备案信息等
4.sitemap sitemap 站点地图一般是xml格式的文件,用于帮助搜索引擎更好地发现和抓取网站上的所有页面,并提供页面优先级和更新频率的信息,帮助搜索引擎更好地分配抓取资源. eg:
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://acme.com</loc>
<lastmod>2023-04-06T15:02:24.021Z</lastmod>
<changefreq>yearly</changefreq>
<priority>1</priority>
</url>
<url>
<loc>https://acme.com/about</loc>
<lastmod>2023-04-06T15:02:24.021Z</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
5.robots 文件
用来告诉搜索引擎机器人(又称爬虫或蜘蛛)应该如何抓取和索引该网站的一种标准
robots.txt 文件需放置在网站的根目录下,以“域名+/robots.txt”的形式直接访问.
User-Agent: *
Disallow: /private/
Sitemap: https://acme.com/sitemap.xml
6.其他:
向各搜索引擎站长平台手动提交网址,以缩短爬虫发现网站链接时间,加快爬虫抓取速度。
使用服务端渲染,eg:nuxt,next
网址规范化,使用规范标准的网址。
网站打开速度越快,识别效果越好