目录
14.webkit表单输入框placeholder的颜色值能改变吗?
11.vue的hash和history两种路由模式有什么区别
一、Html&Css
1.px、rem、rpx、em有什么区别
px是最常见的一个单位,表示一个像素,是一个绝对单位。
rem表示相对于根元素的字体大小,可以用于实现响应式布局,如根元素字体大小为18px那么1rem就是18px
rpx是小程序中的相对像素可以用于适配不同屏幕的大小,比如一个宽375px的屏幕,那么他的1rpx就相当于0.5px,1rpx相当于整个屏幕的1/750。
em是相对于父元素字体的大小的单位,根据父级字体的大小来决定,如父级设置字体大小为15px,则1em为15px
2.重绘和重排有什么区别
重排(回流)是指元素在页面的大小、尺寸等属性发生改变,需要重新计算布局信息
重绘是指元素的尺寸、位置不发生改变的情况下,重新绘制元素的属性,比如颜色、背景。
重排一定会导致重绘,重绘不一定导致重排
如何渐少重排和重绘呢?
①不要直接操作样式,可以设置class操作类名
②使用position:absolute和flex不会导致重排
③不要把Dom节点属性放在一个循环当成变量
④多次添加dom元素时可以使用createDocumentFragment创作一个盒子,向盒子中添加子元素,添加完成再插入元素中
3.让一个元素水平垂直居中
①定位+margin
②定位+transform
③flex布局
④grid布局
⑤table布局
4.哪些属性可以继承
①字体属性:font-family、font-size、font-weight等
②文本属性:color、text-align、line-height等
③元素可见性display、visibility等
④表格布局属性border-collapse、border-spacing
⑤列表属性:list-style
5.BFC(块级格式化上下文)的理解
BFC是一个块级元素,BFC可以看作一个内部容器,容器内部无论怎么变化都不会影响外面的样式,属于同一个BFC的两个盒子,外边距会重叠,取最大值。特点就是:内部的元素会在垂直方向排列、同时边距会折叠而不是叠加。
BFC怎么设置,可以给父级添加浮动、overflow:hidden、绝对定位、相对定位、固定定位等,
能够解决如父级元素没有高度,子级元素设置浮动的话导致塌陷问题,只需要给父级添加overflow:hidden设置成BFC就可以了
还能解决子级元素外边距会使父级元素塌陷的问题
6.精灵图和base64的区别
①精灵图是多个小图片合成的一个大的图像,而base则是将单个图像装换成字符串的形式嵌入到html、css、js中
②精灵图只是将图像合成到一个大图像中,并不会减少图片的大小;而base64编码的图片体积通常比原来的图片大
③精灵图能够减少http请求的次数,提高性能和加载速度;而base64能减少图片文件的加载
④精灵图通过css背景位置截取特定的部分;base64编码的图片可以直接引用
总的来说精灵它适用于多个小图标的情况,减少http请求。而base64适用于单个图片的情况,减少文件加载;
7.Html5和Css3的新特性
①h5:
语义化标签、新增音频视频标签、画布、数据存储、表单的控件(占位符、表单验证、输入类型)
②Css3
新增选择器(属性选择器、伪类选择器、伪元素选择器)、媒体查询、动画、盒子模型、边框、文字阴影、渐变、过度、2D和3D
二、Js
1.js检测数据类型的方法
①typeof (typeof "abc")
②instance of ( "abc" instance of String)
③constructor ("abc".constructor == String)
④object.protype.toString.call (object.protype.toString.call("abc"))
2..说一下闭包特点
闭包是指外部函数嵌套内部函数,内部函数被外部函数返回并保存下来,就会产生闭包,
特点:能够在外部调用函数内部的变量,且这个变量不会污染全局,一直保存在内存中,不会被垃圾回收机制回收
缺点:闭包较多的时候,会消耗内存,导致页面性能下降。
使用场景:防抖、节流、函数嵌套避免全局污染。
function fn(a){
return function(){
console.log(a)
}
}
var f=fn("ab")
f() //ab
3.原型和原型链
原型:每个对象创建的时候都会关联一个对象,这个关联的对象就是原型对象,原型共享对象的所有属性和方法。
原型链又叫隐式原型链,创建对象时,每一个对象都有一个__proto__属性,他指向该对象的原型,当调用属性和对象的方法时,__proto__会在对象自身查找,然后去构造函数查找,然后去原型的原型上查找,这个查找的过程就叫做原型链。
function Person(){}
Person.prototype.name='张三'
var person1=new Person()
console.log(persson1.name) //张三
person1
的原型就是 Person.prototype
。
关联首先会在person1实例本身查找name这个属性,没有他就会去构造函数Person找,此时Person函数本身也没有name属性,他就是去Person的原型上找,此时找到name属性。则个查找的过程就是原型链
4.内存泄漏
Js已经分配内存地址的对象,由于长时间的没有释放或者没办法清楚,会造成长期占用的现象,会造成内存资源大量浪费,最终导致程序运行速度慢甚至崩溃。
为了避免内存泄漏,可以采取以下措施:
-
妥善管理变量:避免创建意外的全局变量,使用
var
、let
或const
关键字声明变量,并在不再需要时及时销毁变量。 -
手动解除引用:当不再需要对象时,手动解除对该对象的引用,以便垃圾收集器可以回收内存。
-
正确清理定时器和事件监听器:在不再需要时,确保清理定时器和事件监听器,以防止它们继续引用对象。
-
避免循环引用:确保避免对象之间形成循环引用,或者在不再使用时手动断开循环引用。
-
使用内存分析工具:使用浏览器的开发者工具或内存分析工具来检测和诊断潜在的内存泄漏问题。
5.事件委托
事件委托的原理是利用事件冒泡的特性。当一个元素发生事件时,会将该事件依次传递给它的父元素,一直传递到文档的根元素。因此,如果事件处理程序被绑定到父元素上,就可以通过事件冒泡机制捕获到子元素上触发的事件,并在父元素上进行处理。
<ul id="parentList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
</ul>
<script>
var parentList = document.getElementById('parentList');
// 通过事件委托,将点击事件委托给父元素ul
parentList.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
console.log('You clicked on item:', event.target.textContent);
}
});
</script>
6.null和undefined的区别
①null表示一个对象被赋值为空,数值是0,undefined表示一个对象被声明但是未被赋值,是NaN,
②null表示空值,一般用于表示某个数据没有引用任何对象,undefined一般用于函数的没有返回值时的默认值或者变量没有赋值的情况
③在设计时是现有null后有undefined的,null也是一个对象,并不适合表示“无”,于是就有了undefined。
7.事件循环
事件循环是js代码执行的过程,在 Chrome 的源码中,它开启一个不会结束的 for 循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时候将任务加入到队列末尾即可。 每次浏览器会按照同步任务、异步任务、等待同步任务执行完毕以后检查消息队列,如果消息队列有待处理的事件,就会取出放到渲染主线程,若没有就会进入等待阶段。浏览器会按照这种先后顺序不停的进行。浏览器的异步任务优也是有优先级,首先是微队列(如promise)然后是交互队列(事件交互)最后是延时队列(延时器)。
8.如何理解 JS 的异步?
js是一门单线程语言,它是运行在浏览器主线程的,而渲染主线程只有一个。渲染主线程任务执行需要工作,如渲染页面、执行js、解析css等、如果采用同步的方式去执行的话,就很容易造成堵塞,从而导致后面很多内容无法执行,例如渲染页面中有一个延时器任务,如果同步运行的话,就会导致后面所有任务都被后延。所以浏览器采用了异步的方式运行,具体的做法就是当执行类似定时器、网络等任务时,会将任务交给其他线程去完成,而自己就会立刻结束任务执行然后执行后续代码,当其他线程完成以后就会将事先传递的回调函数包装成任务,加入到消息队列的尾部排队执行,等待主线程调用。
9.JS判断变量是不是数组,你能写出哪些方法?
①isArray (Array.isArray(arr))
②instanceof (arr instanceof Array)
③object.protype.toString.call(arr)
④ isProtypeOf(Array.protype.isProtypeOf(arr))
10.找出多维数组最大值
function fnArr(arr){
var newArr = [];
arr.forEach((item,index)=>{
newArr.push( Math.max(...item) )
})
return newArr;
}
console.log(fnArr([
[4,5,1,3],
[13,27,18,26],
[32,35,37,39],
[1000,1001,857,1]
]));
11.new操作符具体做了什么
1. 创建了一个空的对象
2. 将空对象的原型,指向于构造函数的原型
3. 将空对象作为构造函数的上下文(改变this指向)
4. 对构造函数有返回值的处理判断
function Fun( age,name ){
this.age = age;
this.name = name;
}
function create( fn , ...args ){
//1. 创建了一个空的对象
var obj = {}; //var obj = Object.create({})
//2. 将空对象的原型,指向于构造函数的原型
Object.setPrototypeOf(obj,fn.prototype);
//3. 将空对象作为构造函数的上下文(改变this指向)
var result = fn.apply(obj,args);
//4. 对构造函数有返回值的处理判断
return result instanceof Object ? result : obj;
}
console.log( create(Fun,18,'张三') )
12.说一下call、apply、bind区别
共同点:都能够改变this指向。
区别:
1. call、apply可以立即执行。bind不会立即执行,因为bind返回的是一个函数需要加入()执行。 2. 参数不同:apply第二个参数是数组。call和bind有多个参数需要挨个写。
var a = {
name: '一一',
age: '22',
sex: '女',
hobby: '写代码',
say: function(sex, hobby) {
console.log(this.name, this.age, sex, hobby);
}
};
var b = {
name: '二二',
age: '23',
};
a.say.call(b, '男', '学习');
a.say.apply(b, ['男', '学习']);
// bind 方法返回的是一个新的函数,需要再次使用 () 运算符进行调用
a.say.bind(b, '男', '学习')();
// 也可以先使用 bind 方法绑定 this,然后在调用返回的函数时再传入参数
a.say.bind(b)('男', '学习');
//结果为:"二二","23","男","学习"
12.localStorage、sessionStorage、cookie的区别
公共点:在客户端存放数据
区别:
1. 数据存放有效期
sessionStorage : 仅在当前浏览器窗口关闭之前有效。【关闭浏览器就没了】
localStorage : 始终有效,窗口或者浏览器关闭也一直保存,所以叫持久化存储。
cookie : 只在设置的cookie过期时间之前有效,即使窗口或者浏览器关闭也有效。
2. localStorage、sessionStorage不可以设置过期时间,cookie 有过期时间,可以设置过期(把时间调整到之前的时间,就过期了)
3. 存储大小的限制,cookie存储量不能超过4k,localStorage、sessionStorage不能超过5M
13.如何关闭IOS键盘首字母自动大写
<input type="text" autocapitalize='off'>
14.webkit表单输入框placeholder的颜色值能改变吗?
<style type="text/css">
input::-webkit-input-placeholder{
color:red;
}
</style>
15.跨域解决方法
1.jsonp进行跨域请求
只需要使用一个script标签,利用src属性就可以实现跨域。网页通过添加一个script元素向服务器请求json数据,服务器收到请求后将数据放到一个指定名字的回调函数中传回来一个参数。
<script>
// jsonp跨域调用
function handleResponse(response) {
console.log(response.message);
}
</script>
<script src="http://localhost:8080/url?callback=handleResponse"></script>
2.使用cors实现跨域
const express =require("express")
const cors =require("cors")
let app =express()
app.use(cors()); // 使用 CORS 中间件
app.get("/",(req,res)=>{
const data={
message:"hello express"
}
res.send(data)
})
app.listen(8080,()=>{
console.log("express运行")
})
16.map和forEach的区别
map有返回值,在react中用于循环渲染,非常常用
forEach没有返回值,常用来循环遍历数组
17.localStorage如何设置过期时间
在保存数据时,以对象的形式保存,出来保存原有数据,再加上额外的创建时间以及保存的时长。当使用数据时获取到创建时间与当前时间的时间差,对比保存时间,判断是否过期,如果过期了就删除数据,否则正常调用。如保存name:"zs"的数据可以改成保存{name:"zs",starttime:"创建数据的时间",savetime:"保存的时间"}这样每次调用数据都能判断是否过去
18.JS如何实现继承
①原型链继承
function Parent(){
this.age = 20;
}
function Child(){
this.name = '张三'
}
Child.prototype = new Parent();
let o2 = new Child();
console.log( o2,o2.name,o2.age );
②es6的class继承
class Parent{
constructor(){
this.age = 18;
}
}
class Child extends Parent{
constructor(){
super();
this.name = '张三';
}
}
let o1 = new Child();
console.log( o1,o1.name,o1.age );
③构造函数继承
function Parent(){
this.age = 22;
}
function Child(){
this.name = '张三'
Parent.call(this);
}
let o3 = new Child();
console.log( o3,o3.name,o3.age );
④组合式继承
function Parent(){
this.age = 22;
}
function Child(){
this.name = '张三'
Parent.call(this);
}
let o3 = new Child();
console.log( o3,o3.name,o3.age );
19.Js的设计原理
①js是一门解释性语言,代码运行时逐行解释,便于开发和调试。
②js一般是基于浏览器运行的,一般js引擎是v8引擎,能够在浏览器中通过js访问和操作浏览器的对象和功能,比如操作dom,发起网络请求之类的。
③js有一个事件循环机制,js是单线程的语言,有一个主线程负责执行代码,所有任务都按照顺序放到这个执行队列中,每次按照先同步后异步,先微任务后宏任务的顺序执行,当每次执行队列完成以后,就会继续往队列中添加之前的异步任务,不断循环这个过程。
20.script标签中async和defer区别
共同点:两种都可以用于异步加载脚本
defer:等html全部解析完,才会下载脚本并执行js脚本,不会阻塞文档的解析。当解析多个带有defer的脚本时,执行顺序和它们在文档中的顺序一致
async:async会立即下载然后执行js脚本,而不会阻塞html文档的解析。当页面有多个带有async的脚本时,执行顺序是根据哪个脚本先下载完再执行的,脚本可以并行加载和执行。
21.setTimeout最小执行时间是多少?
在HTML5规范中,
setTimeout最小执行时间是4ms;
setInterval最小执行时间是10ms
22.Es6和Es5有什么区别?
①变量声明,es6引入了let和const变量声明代替var;
②箭头函数,es6引入了箭头函数,更简洁的定义方法
③es6可以使用解构赋值的方式
④es6有了promise解决回调地狱的问题
⑤es6引入了类和模块的概念,可以使用import导入模块
⑥es6新增了一些方法,比如set、map方法
⑦新增了拓展运算符
23.call、aply、bind有什么区别
call方法是函数对象的一个方法,可以传多个参数,第一个参数是设置为函数执行上下文的对象,之后的参数是传递给函数的参数
apply方法和call方法类似,不同的是apply传递的第二个参数要求是一个数组或类数组的对象.
bind方法与call\apply不同的是,bind方法相当于创建一个新的函数,并返回这个函数,使用的时候需要调用一下这个函数才能执行。
var a = {
name: '一一',
age: '22',
sex: '女',
hobby: '写代码',
say: function(sex, hobby) {
console.log(this.name, this.age, sex, hobby);
}
};
var b = {
name: '二二',
age: '23',
};
call方法和apply方法改变this指向第一个参数,把之后的参数传到say方法中
a.say.call(b, '男', '学习');
a.say.apply(b, ['男', '学习']);
// bind 方法返回的是一个新的函数,需要再次使用 () 运算符进行调用
a.say.bind(b, '男', '学习')();
// 也可以先使用 bind 方法绑定 this,然后在调用返回的函数时再传入参数
a.say.bind(b)('男', '学习');
//结果都为:"二二","23","男","学习"
24.promise和async/await的区别
promise和async/await都是用来解决异步操作的,
promise可以表示一个异步操作的最终完成或失败,并提供了一种链式的方式来处理异步操作的结果。使用Promise时,可以通过then
方法注册回调函数处理异步操作的成功结果,通过catch
方法注册回调函数处理异步操作的失败结果。
async/await可以使用同步方式执行异步操作,它有一个await语法糖,暂停函数的执行。等待Promise对象的解析结果,返回一个Promise对象。它也可以使用then/catch处理异步操作的结果
25.token的登录流程
①客户端使用账号密码请求登录
②登录成功后服务端向客户端发送一个token字符串。
③客户端接收到token后将它保存到loacastroage或者cookie中
⑤客户端每次向服务器发送请求时都会携带这个token
⑥服务端接受请求后会验证这个token,当token验证正确就会向客户端发送数据
26.http请求过程
①建立连接
客户端通过与服务端建立Tcp/Ip连接来发起Http请求。客户端会发送一个用于打开连接的请求(三次握手)。第一次,客户端向服务端发送一个syn包表示客户端请求建立连接。第二次是服务端接受包然后向客户端发送一个syn+ack包。第三次是客户端接收到了syn+ack包后向服务端响应一个ack包。这样三次握手以后连接建立完成。
②客户端发送请求
客户端向服务端发送http请求。请求包含了请求方法(get、post),请求url,请求参数,头部信息
③服务端接收请求
服务端接受客户端请求后,执行相应代码逻辑
④服务端响应
服务端向客户端响应处理结果,生成http响应,如(200,404)
⑤客户端接收响应
客户端接收到服务器返回的HTTP响应。客户端会解析响应头部信息和响应体,根据需要进行相应处理
⑥断开连接
响应完成,连接将被关闭,服务端和客户端之间的通信结束。
27.页面渲染过程
浏览器获取页面的html和css资源,将html解析成dom树、css解析成cssom树;再合并成渲染树,然后进行布局,最后渲染页面
28.JWT的了解
JWT全程json web token,是通过json的形式 进行身份验证的一种方式
工作流程:
①客户端向后台发送用户信息进行身份验证
②服务端对用户信息进行验证,验证通过就将用户信息作为JWT荷载,把它和头部进行拼接生成JWT令牌;
③服务端向客户端响应JWT,并存储该令牌作为后续验证
④客户端每次向服务端发送请求时都会将jwt作为请求头或参数发送给服务端
⑤服务端验证JWT令牌、解析用户信息
⑥如果验证JWT令牌正确,且未过期,则服务端会授权该请求并发送响应
29.浏览器缓存策略
浏览器的缓存策略是指浏览器在加载网页时如何处理和利用缓存来提高页面加载速度和减少网络流量。
①强缓存(本地缓存)
强缓存是不发请求的,浏览器在第一次请求资源时,会将一些资源保存到本地中,然后服务器返回一个响应头,里面保存了资源过期时间。在过期时间内请求相同的资源时浏览器会使用强缓存直接从本地获取资源
②协商缓存
当强缓存失效时,浏览器就会向服务器发送请求,服务器判断资源是否发生变化,如果没有发生变化则会返回一个状态码304响应,提示浏览器可以使用强缓存
30.无感登录
无感登录是指用户在使用网站或应用时,无需重复输入用户名和密码即可实现自动登录的功能。
①用户首次登录时,需要提供用户信息进行常规的身份验证,验证通过后就会生成一个token
②生成的token会保存在本地缓存中,包含一个过期时间和其他信息
③如果再次访问时token有效且未过期,则用户可以自动登录。
④如果token过期则会要求用户重新输入用户名密码进行身份验证生成新的token取代之前的token
31.大文件上传
①文件切片
将文件分割成相同的数据块
初始化一个分片上传任务,返回本次上传的唯一标识
按照一定规则把各个数据库上传,上传完成以后检查完整性,如果完整就会把数据库合并成原始文件;
②断点续传
用户在上传大文件时,可能面临网络不稳定、上传过程中断等情况。为了提供更好的用户体验,可以实现断点续传功能。这意味着当上传过程发生中断后,用户可以从中断的地方继续上传,而无需重新上传整个文件。为实现断点续传,服务器端需要记录已经成功上传的文件块信息,以及用户当前上传的位置。
以下是一个基于Node.js和Express框架的大文件上传示例:
<!DOCTYPE html>
<html>
<head>
<title>大文件上传示例</title>
</head>
<body>
<input type="file" id="file-input">
<button id="upload-btn">上传</button>
<div id="progress-bar"></div>
<script>
const fileInput = document.getElementById('file-input');
const uploadBtn = document.getElementById('upload-btn');
const progressBar = document.getElementById('progress-bar');
uploadBtn.addEventListener('click', () => {
const file = fileInput.files[0];
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.upload.addEventListener('progress', (event) => {
const percent = (event.loaded / event.total) * 100;
progressBar.style.width = `${percent}%`;
});
xhr.send(formData);
});
</script>
</body>
</html>
const express = require('express');
const multer = require('multer');
const fs = require('fs');
const app = express();
const upload = multer({ dest: 'uploads/' });
app.post('/upload', upload.single('file'), (req, res) => {
const file = req.file;
const fileName = file.originalname;
const filePath = `uploads/${fileName}`;
// 创建可读流和可写流
const readStream = fs.createReadStream(file.path);
const writeStream = fs.createWriteStream(filePath);
// 文件流传输
readStream.pipe(writeStream);
// 监听可读流的end事件,用于删除临时文件
readStream.on('end', () => {
fs.unlinkSync(file.path);
res.send('上传成功!');
});
});
app.listen(3000, () => {
console.log('服务器已启动,监听端口 3000');
});
32.图片懒加载
图片懒加载是一种优化网页性能的技术,它可以延迟加载页面上的图片,当图片即将进入可视区域时再加载,以减少网页的加载时间和带宽消耗。这在包含大量图片的长网页或移动设备上尤为有用。如果在vue中可以直接使用v-lazy实现,以下是一个简单的示例,使用原生 JavaScript 实现图片懒加载:
<!-- HTML -->
<img data-src="image.jpg" src="placeholder.jpg" alt="Image" />
<!-- JavaScript -->
<script>
window.addEventListener('DOMContentLoaded', function() {
var lazyImages = Array.from(document.querySelectorAll('img[data-src]'));
function lazyLoad() {
lazyImages.forEach(function(image) {
var rect = image.getBoundingClientRect();
if (rect.top >= 0 && rect.top <= window.innerHeight) {
image.src = image.dataset.src;
image.removeAttribute('data-src');
lazyImages = lazyImages.filter(function(img) {
return img !== image;
});
}
});
if (lazyImages.length === 0) {
window.removeEventListener('scroll', lazyLoad);
window.removeEventListener('resize', lazyLoad);
window.removeEventListener('orientationchange', lazyLoad);
}
}
window.addEventListener('scroll', lazyLoad);
window.addEventListener('resize', lazyLoad);
window.addEventListener('orientationchange', lazyLoad);
lazyLoad();
});
</script>
33.大文件上传-切片上传
主要思路是利用前端将大文件切片,然后分别对每个切片计算哈希值,最终得到整个文件的哈希值。这个哈希值可以用于校验文件完整性或者作为文件的唯一标识符。然后将哈希值与切片上传到后端服务器。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./package/spark-md5.js"></script>
</head>
<body>
<input type="file" id="file">
<button onclick="upload()">上传</button>
<script>
async function upload () {
const file = document.getElementById('file').files[0];
let chunks = createChunk(file, 5*1024*1024);
const hashResult = await hash(chunks);
console.log(hashResult);
}
// 切片
function createChunk(file,chunkSize) {
// 计算切片数量
const chunks = Math.ceil(file.size / chunkSize);
// debugger
const chunksArr = [];
for(let i = 0; i < file.size; i += chunkSize) {
chunksArr.push(file.slice(i,chunkSize+i));
}
return chunksArr;
}
// 获取hash
function hash(chunks){
return new Promise((resolve, reject) => {
const spark=new SparkMD5();
// 递归地读取chunks数组中的数据
function read(i) {
if(i>=chunks.length){
// 将计算结果返回
resolve(spark.end())
return;
}
const blob=chunks[i]
const reader=new FileReader()
reader.onload=function(e) {
// 将读取的结果追加到spark实例中
spark.append(e.target.result);
read(i+1)
}
// 读取blob对象的内容并将其作为ArrayBuffer返回
reader.readAsArrayBuffer(blob)
}
read(0)
});
}
</script>
</body>
</html>
spark-md5的文件如下:
链接:https://pan.baidu.com/s/1IrBCIgPsnwez6mPagSY2ew
提取码:qwer
三、vue
1.vue2和vue3的双向绑定原理
vue2的双向绑定是通过Object.defineProperty实现的。具体实现代码如下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
姓名:
<span id="name"></span>
<br />
<input type="text" id="inputName" onblur="blurInput()" />
<script>
let span=document.querySelector("#name")
let input=document.querySelector("#inputName")
// 只能监听对象中的属性而不能所有对象
let obj ={
name:""
}
// vue2的双向绑定实现原理
// 需要对原始数据进行克隆 防止后面双向绑定导致循环调用
let newObj=JSON.parse(JSON.stringify(obj))
Object.defineProperty(obj,"name",{
get(){
return newObj.name
},
set(val){
if(val===newObj.name) return
newObj.name=val
obServer()
}
})
function obServer(){
span.innerHTML=newObj.name
input.value=newObj.name
}
obServer()
function blurInput(){
console.log(input.value)
obj.name=input.value
}
</script>
</body>
</html>
vue3的双向绑定时通过proxy实现的,代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
姓名:
<span id="name"></span>
<br />
<input type="text" id="inputName" oninput="blurInput()" />
<script>
let span=document.querySelector("#name")
let input=document.querySelector("#inputName")
// vue3可以直接监听整个对象
let obj ={}
// vue3的双向绑定实现原理
// 不需要深克隆
//监听整个对象 不需要指定属性 相当于把对象里所有的属性都监听了 可以直接写整体的set get
obj=new Proxy(obj,{
get(target,prop){
console.log(target[prop])
return target[prop]
},
set(target,prop,value){
console.log(value)
target[prop]=value
obServer()
}
})
function obServer(){
span.innerHTML=obj.name
input.value=obj.name
}
// obServer()
function blurInput(){
// console.log(input.value)
obj.name=input.value
}
</script>
</body>
</html>
我们可以看到,与vue3相比,vue2的响应式存在几个不足之处:①只能监听对象的属性,如果一个对象有多个属性,就必须创建多个Object.defineProperty;②需要对原始对象进行深克隆,否则的话会导致无限循环;因此vue3更加的整洁易懂。
2.vue3新特性
①响应式的原理变了,vue2使用的时object.defineProperty而vue3使用的是proxy
②vue2渲染使用的是render而vue3使用的是createApp
③vue3能使用vite创建项目,体积更小、打包编译更快
④vue3支持ts和sass语法
⑤vue2使用的是选择式api而vue3使用的是组合式api
⑥vue3新增了传送门。传送门可以将组件的内容渲染到dom结构的任意位置,不仅限于组件所在的位置。可以用于弹窗、对话框、组件复用。
3.对vue是单向数据流的理解
在vue中数据流是指数据的传递和管理方式。数据是从父组件流向子组件的。子组件不能直接修改父组件的数据,父组件可以通过props向子组件传递的数据,子组件只能通过$emit向父组件发送事件;
在vue中可以通过数据流管理修改数据,数据流管理的方式有:状态提升(将组件的数据状态都放到同一个父组件中)、vuex管理数据(一种集中式存储管理应用的所有组件所需的状态)、provide/inject(Vue提供的一种高级选项)
4.vue是渐进式框架的理解
渐进式框架的意思就是给框架分层,不需要使用它的全部功能;核心部分为视图层渲染,往外是组件机制,在此基础上是路由机制,在添加状态管理,最外层是构建工具;刚开始使用的时候可以只做一些简单的基础操作(如页面渲染、表单提交);之后随着项目逐渐复杂可以不断地添加困难的操作,比如实现组件化开发、代码复用等;还可以实现前端路由、状态管理的操作。
总的来说就从一个简单的页面变成复杂页面,允许开发者根据实际需要选择使用和引入不同的功能和工具,由简单到复杂的渐进式使用。
5.如何理解MVVM
MVVM分为Model、ViewModel、View三个部分。
view(视图)是用户界面、viewMode是连接视图和模型的中间层、model是应用程序的数据源;
MVVM的核心就是view和viewmodel的双向数据绑定。当数据发生改变时,viewModel能够监听到数据的变化然后更新视图。当用户操作视图时也能够监听视图的变化然后同时Model层的数据进行改动。viewModel通过数据双向绑定将view和Model连接起来。
6.v-for的key有什么作用
①优化列表渲染
指定key以后可以确保在列表数据变化的时候正确的渲染变化的那个元素,避免出现用户输入和元素顺序不一致的问题。
②子组件的复用
在嵌套组件的列表中,使用key可以更好地管理子组件的状态和复用,避免出现不可预测的问题。
③提高性能
Vue.js使用虚拟DOM来进行页面渲染,而key可以帮助Vue更快地找到需要更新的元素而不用渲染遍历列表
7.vue的修饰符
①事件修饰符
.stop(阻止冒泡)
.prevent(阻止默认行为)
.once(事件只触发一次)
.self (只有在元素时event.target时触发)
.native(把当前元素当成原生标签看)
.passive(立即触发默认行为)
②按键修饰符
.keyup .keydown
③表单修饰符
.lazy .trim .number
8.keep-alive有什么用?
①组件缓存:keep-alive
可以将组件缓存起来,当组件被切换隐藏时,并不会销毁组件实例,而是将其保留在内存中。下次再次需要显示该组件时,会直接从缓存中取出,而不需要重新创建组件实例和渲染 DOM,这样可以显著提高组件的性能。
②保留组件状态:由于组件实例被保留在内存中,keep-alive
可以有效地保留组件的状态。当切换到之前被缓存的组件时,组件会恢复到之前的状态,包括数据、状态和用户交互等。
③避免重复请求:在某些场景下,组件加载需要进行数据请求等异步操作。使用 keep-alive
可以避免每次切换到该组件时都重新发送请求,而是直接使用之前缓存的数据,提高用户体验和减少服务器压力。
keep-alive
并不是适用于所有情况的。如果组件具有频繁更新的特点,或者需要每次都重新初始化的情况,可以考虑不使用 keep-alive
或动态控制它的缓存行为。
keep-alive有两个属性:
include - 逗号分隔字符串或正则表达式或一个数组来表示。只有名称匹配的组件会被缓存。exclude - 逗号分隔字符串或正则表达式或一个数组来表示。任何名称匹配的组件都不会被缓存。
需要注意的是如果你是在vue3中使用keep-alive的include或者exclude属性的话,必须要将你的组件命名,不能使用语法糖的形式,因为setup语法糖不能写name,只有给组件命名了属性才会生效。
9.axios如何封装以及使用?
①下载axios
npm i axios
②创建一个request.js文件封装 代码如下:
// 导入axios
import axios from "axios"
// 创建实例
const http =axios.create({
// 设置公共url
baseURL:"http://192.1.1.10",
// 设置响应时间
timeout:3000,
// 设置请求方法
method:"GET",
// 设置请求头
header:{
'Content-Type':'application/json'
}
})
// 设置请求拦截器
http.interceptors.request.use((config)=>{
config.headers.Authorization=localStorage.getItem("token")
return config
},(Error)=>{
//请求发生错误时的处理
console.log(Error)
return Promise.reject(Error)
}
)
// 设置响应拦截器
http.interceptors.response.use((res)=>{
let data=res.data
return data
},(Error)=>{
//请求发生错误时的处理
console.log(Error)
return Promise.reject(Error)
})
// 导出
export default http
③使用封装的axios
import http from "request.js" // 创建request.js的路径
//获取数据的GET请求
export const getDataList=(params)=>{
return http({
url:"/getdata" //写获取数据的api路径,
params
})
}
//获取数据的Post请求
export const getDataList=(params)=>{
return http({
url:"/getdata" //写获取数据的api路径,
method:"POST"
data:{params}
})
}
④使用封装好的请求
import {getDataList} from "保存刚才封装的请求的路径"
//获取数据
const dataList =await getDataList(将需要的载荷传进去)
10.vue路由传参
①动态路由传参
<router-link :to="/test/123"></router-link>
//或者
this.$router.push("/test/123")
②query传参
this.$router.push({name:test,query:{id:"123"}
③params传参
this.$router.push({name:test,params:{id:"123"}
11.vue的hash和history两种路由模式有什么区别
①hash模式的路由地址上有#号、history没有
②hash不会重新加载页面,单页面应用必备
③hash没有历史记录,history有历史记录,H5新增了pushState和replaceState两种路由API
④hash不需要使用后台配置,history需要使用后台配置
12.如何解决vuex刷新页面数据丢失的问题
①使用本地存储的形式,将数据保存到本地(sessionStorage、localStorage、cookie)
②在页面刷新的时候再次请求远程数据,使之动态更新vuex数据
③在父页面向后台请求远程数据,并且在页面刷新前将vuex的数据先保存至sessionStorage
13.computed和watch的区别
①computed是计算属性,watch是监听data中的数据。
②computed在页面加载的时候就开始了,watch等到第一次数据改变才监听
③computed有返回值,watch没有
④computed不支持异步,watch支持
⑤computed支持缓存,只有里面的值改变才会重新计算,否则缓存。watch不支持缓存
14.vuex有哪些属性?使用场景是什么
state 存储变量
getter state的计算属性
mutation 更新数据的方法
action 类似于mutation ,action提交的是mutation而不是直接变更状态,action可以包含异步module 可以将store分割成多个模块
适用场景:多个页面需要共享数据状态,如购物车模块,商品用户信息模块。
15.vue和jquery的区别
①原理不同
vue是数据绑定、jquery是获取dom元素绑定
②重点不同
vue是数据驱动,jquery注重页面
③操作不同
16.Seo如何优化
①使用预渲染
②提高页面加载速度
对网页代码进行精简、可以将图片合并,多张图片合并成一张(如精灵图),减少网页请求
③采用响应式设计
17.vue路由懒加载
①使用import()
函数来实现组件的动态导入
const Home = () => import('./views/Home.vue')
18.echart有用过吗
①title:标题
②tooltip:提示
③legend:展现了不同系列的标记(symbol),颜色和名字
④xAxis和yAxis:直角坐标系 grid 中的 x 轴和y轴
⑤dataZoom:组件 用于对数据进行区域缩放,从而能自由关注细节的数据信息,或者概览数据整体。
四、git
配置用户名:git config --global user.name "用户名"
配置用户邮箱:git config --global user.email "邮箱"
初始化仓库:git init
上传文件到暂存区:git add .
查看仓库当前状态:git status
绿色字体表示文件在暂存区
红色字体表示文件在工作区
白色字体无显示代表文件已经都上传到本地仓库了
将暂存区的文件上传到本地仓库:git commit -m "日志信息"
查看当前历史版本:git log
黄色字体commit后面是版本号,括号内代表上传到了那个分支
回退到最近一次上传的版本:git reset --hard
hard后面写版本号,则回退到指定版本
查看历史上传记录:>git reflog
前面小串的黄色字体可以当作版本号使用
分支:
查看分支:git branch
绿色字体表示当前分支
创建分支:git branch 分支名
分支里的文件与创建分支时的版本一致
切换分支:git checkout 分支名
分支与主分支相互独立,互不影响
合并分支:git merge fz1
删除分支:git branch -d fz1
远程仓库:
关联远程仓库:git remote add origin 远程仓库地址
上传文件到远程仓库:git push origin master
下载远程仓库的代码:git clone 远程仓库地址
更新本地仓库的代码为远程仓库:>git pull origin master
生成密钥:ssh-keygen -t ed25519 -C "Gitee SSH Key"
生成密钥文件后,xxxxx.pub文件,里面记录的就是你的密钥
将你的密钥发给领导,由领导负责部署,
可以通过命令:ssh -T git@gitee.com 来验证领导是否部署完成
已配置ssh以后一般将文件上传到gityhub或者gitee的命令如下:
①git init
②git add .
③git commit -m "做出的修改"
④git remote -v (查看当前连接的地址是不是自己要上传的那个分支,如果不是则进行⑤,是的话则跳过⑤)
⑤git remote add origin ”你的git地址“
如果已有的话就用修改:git remote set-url origin "你的git地址"
⑥git push -u origin master
如果是强制覆盖掉之前的可以使用:git push -force origin master
五、手写代码
1.防抖与节流
// 防抖函数
function debounce(func, delay) {
let timerId;
return function () {
clearTimeout(timerId);
timerId = setTimeout(() => {
func.apply(this, arguments);
}, delay);
};
}
let handle2=debounce(()=>{console.log('防抖函数执行了');},2000)
// 错误调用示范 必须使用上面的调用方式 因为第一种方式中,handle2 变量保存的是一个防抖函数,可以随时调用;而第二种方式中,handle2 函数只是一个简单的调用 debounce 函数的函数,没有保存防抖函数的引用。
// function handle2(){
// debounce(()=>{console.log('防抖函数执行了');},2000);
// }
// 节流函数
function throttle(func, delay) {
let lastCallTime = 0;
return function () {
const now = Date.now();
if (now - lastCallTime >= delay) {
func.apply(this, arguments);
lastCallTime = now;
}
};
}
let handle1=throttle(()=>{console.log('节流函数执行了');},1000)
2.手写promise
const PENDING = "pending";
const RESOLVED = "fulfilled";
const REJECTED = "rejected";
// 手写promise
class myPromise{
constructor(executor){
this.state=PENDING
this.value=undefined
this.reason=undefined
// 保存异步数据处理函数
this.handlers=[]
const resolve=(value)=>{
if(this.state==PENDING){
this.state=RESOLVED
this.value=value
// 执行成功的回调
this.handlers.forEach(({onFulfilled})=>{
onFulfilled(value)
})
}
}
const reject=(reason)=>{
if(this.state==PENDING){
this.state=REJECTED
this.reason=reason
// 执行失败的回调
this.handlers.forEach(({onFulfilled})=>{
onFulfilled(reason)
})
}
}
try{
executor(resolve,reject)
}catch(e){
reject(e)
}
}
// 添加then方法
then(onFulfilled,onRejected){
onFulfilled=typeof onFulfilled==='function'?onFulfilled:()=>onFulfilled
onRejected=typeof onRejected==='function'?onRejected:()=>onRejected
if(this.state==RESOLVED){
onFulfilled(this.value)
}else if(this.state==REJECTED){
onRejected(this.reason)
// 保存回调函数
}else if(this.state==PENDING){
this.handlers.push({
onFulfilled,onRejected
})
}
}
}
let p =new myPromise((resolve,reject)=>{
// resolve('123')
setTimeout(()=>{
reject('456')
},2000)
})
p.then(res=>{
console.log("then1",res)
},err=>{
console.log("err1",err)
})