最近准备实习,整理的前端常见问题

一、CSS

1.说一下CSS的盒模型

在HTML页面中的所有元素都可以看成是一个盒子
盒子的组成:内容content、内边距padding、边框border、外边距margin
盒模型的类型:
  标准盒模型
    margin + border + padding + content
    IE盒模型
     margin + content(border + padding)
控制盒模型的模式:
    box-sizing:content-box(默认值,标准盒模型)、border-box(IE盒模型)

2.CSS选择器的优先级?

CSS的特性:继承性、层叠性、优先级
优先级:写CSS样式的时候,会给同一个元素添加多个样式,此时谁的权重高就显示谁的样式
标签、类/伪类/属性、全局选择器、行内样式、id、!important
!important > 行内样式 > id > 类/伪类/属性 > 标签 > 全局选择器

3.隐藏元素的方法有哪些?

display:none;
元素在页面上消失,不占据空间
opacity:0;
设置了元素的透明度为0,元素不可见,占据空间位置
visibility:hidden;
让元素消失,占据空间位置,一种不可见的状态
position:absolute;
clip-pat
剪切掉

4.px和rem的区别是什么?

px是像素,显示器上给我们呈现画面的像素,每个像素的大小是一样,绝对单位长度
rem,相对单位,相对于html根节点的font-size的值,直接给html节点的font-size:62.5%;
1rem = 10px; (16px*62.5%=10px)

5.重绘重排有什么区别?

重排(回流):布局引擎会根据所有的样式计算出盒模型在页面上的位置和大小
重绘:计算好盒模型的位置、大小和其他一些属性之后,浏览器就会根据每个盒模型的特性进行绘制
浏览器的渲染机制
对DOM的大小、位置进行修改后,浏览器需要重新计算元素的这些几何属性,就叫重排
对DOM的样式进行修改,比如color和background-color,浏览器不需要重新计算几何属性的时候,直接绘制了该元素的新样式,那么这里就只触发了重绘

6.让一个元素水平垂直居中的方式有哪些?

1.定位+margin
{
   position:absolute;
   top:0;
   left:0;
   bottom:0;
   right:0;
   margin:auto;
}
2.定位+transform
{
   position:absolute;
   top:50%;
   left:50%;
   tranform:translate(-50%,-50%);	
}
3.flex布局(在父元素上设置)
{
   disflex:flex;
   justify-content:center:
   align-items: center;
}
4.grid布局
5.table布局

7.CSS的哪些属性哪些可以继承?哪些不可以继承?

CSS的三大特性:继承、层叠、优先级
继承概念:子元素可以继承父类元素的样式
1.字体的一些属性:font
2.文本的一些属性:line-height
3.元素的可见性:visibility:hidden
4.表格布局的属性:border-spacing
5.列表的属性:list-style
6.页面样式属性:page
7.声音的样式属性

8.有没有用过预处理器?

预处理语言增加了变量、函数、混入等强大的功能
SASS  LESS

二、JavaSscipt

1.JS由哪三部分组成?

ECMAScript:JS的核心内容,描述了语言的基础语法,比如var,for,数据类型(数组、字符串),
文档对象模型(DOM):DOM把整个HTML页面规划为元素构成的文档
浏览器对象模型(BOM):对浏览器窗口进行访问和操作

2.JS有哪些内置对象?

String Boolean Number Array Object Function Math Date RegExp...
Math
   abs() sqrt() max() min()
Data
   new Data() getYear() 
Array
String
   concat() length  slice() split()

3.操作数组的方法有哪些?

push() pop() sort() splice() unshift() shift() reverse() concat() join() map() filter()
ervery() some() reduce() isArray() findIndex()
哪些方法会改变原数组?
push() pop() unshift() shift() sort() reverse() splice()

4.JS对数据类的检测方式有哪些?

1. typeof() 对基本数据类型没问题,遇到引用数据类型不管用
console.log(typeof 666)   //number
console.log(typeof [1,2,3])   //object
2. instanceof()  只能判断引用数据类型,不能判断基本数据类型
console.log([] instanceof Array);  //true
console.log('abc' instanceof String);  //false
3. constructor  基本可以判断基本数据类型和引用数据类型;如果声明了一个构造函数,并把它的原型指向了Array则不能判断
console.log(('abc').constructor === String);  //object
4.  Object.prototype.toString.call() 完美方案

5.说一下闭包,闭包有什么特点?

什么是闭包?函数嵌套函数,内部函数被外部函数返回并保存下来时,就会产生闭包
例子:
function createCounter() {
    let count = 0;
    
    return function() {
        count++;
        console.log(count);
    }
}
const counter = createCounter();
counter(); // 输出: 1
counter(); // 输出: 2
特点:可以重复利用变量,并且这个变量不会污染全局的一种机制;这个变量是一直保存再内存中,不会被垃圾回收机制回收
缺点:闭包较多的时候,会消耗内存,导致页面的性能下降,在IE浏览器中才会导致内存泄漏
使用场景:防抖,节流,函数嵌套函数避免全局污染的时候

6.前端的内存泄漏怎么理解?

JS里已经分配内存地址的对象,但是由于长时间没有释放或者没办法清除,造成长期占用内存的现象,会让内存资源大幅浪费,最终导致运行速度慢,甚至崩溃的情况。
垃圾回收机制
因素:一些为声明直接赋值的变量;一些未清空的定时器;过度的闭包;一些引用元素没有被清除。

7.事件委托是什么?

又叫事件代理,原理就是利用了事件冒泡的机制来实现,也就是说把子元素的事件绑定到了父元素的身上
如果子元素组织了事件冒泡,那么委托也就不成立
组织事件冒泡:event.stopPropagation()
addEventListener('click',函数名,true/false) 默认是false(事件冒泡),true(事件捕获)
好处:提高性能,减少事件的绑定,也就减少了内存的占用。

8.基本数据类型和引用数据类型的区别?

基本数据类型:String Number Boolean undefined null
基本数据类型保存在栈内存当中,保存的就是一个具体的值
引用数据类型(复杂数据类型):Object Function Array
保存在堆内存当中,声明一个引用类型的变量,它保存的是引用类型数据的地址
假如声明两个引用类型同时指向了一个地址的时候,修改其中一个那么另外一个也会改变

9.说一下原型链。

原型就是一个普通对象,它是为构造函数的实例共享属性和方法;所有实例中引用的原型都是同一个对象
使用prototype可以把方法挂在原型上,内存只保存一份
function Person() {
  this.say = function () {
       console.log('唱歌');
   }
}
Person.prototype.look = function () {
   console.log("西游记");
}
var p1 = new Person()
var p2 = new Person()
__proto__可以理解为指针,实例对象中的属性,指向了构造函数的原型(prototype)
console.log(p1.__proto__ === Person.prototype);  //true
原型链:一个实例对象在调用属性和方法的时候,会依次从实例本身、构造函数原型、原型的原型上去查找的过程

10.new操作符具体做了什么?

// 伪代码实现
function _new(constructor, ...args) {
  // 步骤1:创建空对象
  const obj = Object.create(null)
  
  // 步骤2:把空对象和构造函数通过原型链进行链接
  obj.__proto__ = constructor.prototype
  
  // 步骤3:把构造函数的this绑定到新的空对象身上
  const result = constructor.apply(obj, args)
  
  // 步骤4:确定返回值(根据构建函数返回的类型判断,如果是值类型,则返回对象,如果是引用类型,就要返回这个引用类型)
  return typeof result === 'object' ? result : obj
}
示例验证:
function Person(name) {
  this.name = name
}
Person.prototype.say = function () {
  console.log('fdfd');
}
const p1 = _new(Person,'张三')

11.JS是如何实现继承的?

1.原型链继承
2.借用构造函数继承
3.组合式继承
4.ES6的class类继承

12.JS的设计原理是什么?

JS引擎 运行上下文 调用栈 事件循环 回调

13.JS中关于this指向的问题

1. 全局对象中的this指向
指向的是window
2. 全局作用域或者普通函数中的this
指向全局window
3. this永远指向最后调用它的那个对象
在不是箭头函数的情况下
4.new 关键词改变了this的指向
5.apply,call,bind
可以改变this指向,不是箭头函数
6. 箭头函数中的this
它的指向在定义的时候就已经确定了
箭头函数它没有this,看外层是否有函数,有就是外层函数的this,没有就是window
7. 匿名函数中的this
永远指向了window,匿名函数的执行环境具有全局性,因此this指向window

14.script标签里的async和defer有什么区别?

当没有async和defer这两个属性的时候,
浏览器会立刻加载并执行指定的脚本
有async
加载和渲染后面元素的过程将和script的加载和执行并行进行(异步)
有defer
加载和渲染后面元素的过程将和script的加载并行进行(异步),但是它的执行事件要等
所有元素解析完成之后才会执行

15.setTimeout最小执行时间是多少?

HTML5规定的内容:
setTimeout最小执行时间是4ms
setInterval最小执行时间是10ms

16.ES6和ES5有什么区别?

JS的组成:ECMAScript BOM  DOM
ES5:ECMAScript5,2009年ECMAScript的第五次修订,ECMAScript2009
ES6:ECMAScript6,2015年ECMAScript的第六次修订,ECMAScript2015,是JS的下一个版本标准

17.ES6的新特性有哪些?

1.新增块级作用域(let,const)
    不存在变量提升
    存在暂时性死区的问题
    块级作用域的内容
    不能在同一个作用域内重复声明
2.新增了定义类的语法糖(class)
3.新增了一种基本数据类型(symbol),独一无二的值,不能用new关键字
4.新增了解构赋值
    从数组或者对象中取值,然后给变量赋值
5.新增了函数参数的默认值
6.给数组新增了API
7.对象和数组新增了扩展运算符
8.Promise
    解决回调地狱的问题。
    自身有all,reject,resolve,race方法
    原型上有then,catch
    把异步操作队列化
    三种状态:pending初始状态,fulfilled操作成功,rejected操作失败
    状态:pending -> fulfilled;pending -> rejected 一旦发生,状态就会凝固,不会再变
    async  await
        同步代码做异步的操作,两者必须搭配使用
        async表明函数内有异步操作,调用函数会返回promise
        await是组成async的表达式,结果是取决于它等待的内容,如果是promise那就是promise的结果,如果是普通函数就进行链式调用
        await后的promise如果是reject状态,那么整个async函数都会中断,后面的代码不执行
        
9.新增了模块化(import,export)
10.新增了set和map数据结构
    set就是不重复
    map的key的类型不受限制
11.新增了generator
12.新增了箭头函数(箭头函数和普通函数的区别)
    不能作为构造函数使用,不能用new
    箭头函数就没有原型
    箭头函数没有arguments
    箭头函数不能用call,apply,bind去改变this的执行
    this指向外层第一个函数的this

18.call,aply,bind三者有什么区别?

都是改变this指向和函数的调用,call和apply的功能类似,只是传参的方法不同
call方法传的是一个参数列表
apply传递的是一个数组
bind传参后不会立刻执行,会返回一个改变了this指向的函数,这个函数还是可以传参的,bind()()
call方法的性能要比apply好一些,所以call用的更多一点
实例:
let fn = function (a, b) {
    console.log(this, a, b);
}
let obj = { name: "obj" };
fn.call(obj, 1, 2);    // this:obj    a:1         b:2
fn.call(1, 2);        // this:1      a:2         b:undefined
fn.call();           // this:window a:undefined b:undefined
fn.call(null);       // this=window a=undefined b=undefined
fn.call(undefined);  // this=window a=undefined b=undefined
fn.apply(obj, [1, 2]);

19.用递归的时候有没有遇到什么问题?

如果一个函数内可以调用函数本身,那么这个就是递归函数
函数内部调用自己
特别注意:写递归必须要有退出条件return

20.如何实现一个深拷贝?

深拷贝就是完全拷贝一份新的对象,会在堆内存中开辟新的空间,拷贝的对象被修改后,原对象不受影响
主要针对的是引用数据类型
1.扩展运算符(只能实现第一层,当有多层的时候还是浅拷贝)
let obj = {
    name:'张三',
    age:18
}
let obj1 = {...obj}
2.JSON.parse(JSON.stringify())  (该方法不会拷贝内部函数)
let obj = {
    name:'张三',
    age:18,
    say(){
        console.log("rere");
    }
}
let obj1 =  JSON.parse(JSON.stringify(obj))
3.利用递归函数实现
let origin = {
    name: '张三',
    age: 18,
    say() {
        console.log("rere");
    }
}
function exten(origin, deep) {
    let obj = {}
    if (origin instanceof Array) {
        obj = []
    }
    for (let key in origin) {
        let value = origin[key]
        obj[key] = (!!deep && typeof value === 'object' && value !== null) ? exten(value, deep) : value
    }
    return obj
}
const oo = exten(origin, true)

21.说一下事件循环。

JS是一个单线程的脚本语言
主线程 执行栈 任务队列(应对异步任务)  宏任务 微任务
主线程先执行同步任务,然后才去执行任务队列里的任务,如果在执行宏任务之前有微任务,那么要先执行微任务
全部执行完之后等待主线程的调用,调用完之后再去任务队列中查看是否有异步任务,这样一个循环往复的过程就是事件循环!

22.ajax是什么?怎么实现的?

创建交互式网页应用的网页开发技术
    在不重新加载整个网页的前提下,与服务器交换数据并更新部分内容
通过XmlHttpRequest对象向服务器发送异步请求,然后从服务器拿到数据,最后通过JS操作DOM更新页面
1.创建XmlHttpRequest对象 xmh
2.通过xmh对象里的open()方法和服务器建立连接
3.构建请求所需的数据,并通过xmh对象的send()发送给服务器
4.通过xmh对象的onreadystatechange事件监听服务器和你的通信状态
5.接收并处理服务器响应的数据结果
6.把处理的数据更新到HTML页面上
例子:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'your-server-url', true);
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
        console.log(xhr.responseText); // 处理返回的数据
    }
};
xhr.send();

23.get和post有什么区别?

1.get一般是获取数据,post一般是提交数据
2.get参数会放在url上,所以安全性比较差,post是放在body中
3.get请求刷新服务器或退回是没有影响的,post请求退回时会重新提交数据
4.get请求时会被缓存,post请求不会被缓存
5.get请求会被保存在浏览器历史记录中,post不会
6.get请求只能进行url编码,post请求支持很多种

24.promise的内部原理是什么?它的优缺点是什么?

Promise对象,封装了一个异步操作并且还可以获取成功或失败的结果
Promise主要就是解决回调地狱的问题,之前如果异步任务比较多,同时他们之间有相互依赖的关系,
就只能使用回调函数处理,这样就容易形成回调地狱,代码的可读性差,可维护性也很差
有三种状态:pending初始状态  fulfilled成功状态  rejected失败状态
状态改变只会有两种情况,
    pending -> fulfilled; pending -> rejected 一旦发生,状态就会凝固,不会再变
缺点:
    首先就是我们无法取消promise,一旦创建它就会立即执行,不能中途取消
	如果不设置回调,promise内部抛出的错误就无法反馈到外面
	若当前处于pending状态时,无法得知目前在哪个阶段。
原理:
    构造一个Promise实例,实例需要传递函数的参数,这个函数有两个形参,分别都是函数类型,一个是resolve一个是reject
    promise上还有then方法,这个方法就是来指定状态改变时的确定操作,resolve是执行第一个函数,reject是执行第二个函数
示例:
let promise = new Promise((resolve, reject) => {
          let success = true;
          if (success) {
              resolve("Operation successful");
          } else {
              reject("Operation failed");
          }
      });

promise
      .then(result => {
          console.log(result); // "Operation successful"
      })
      .catch(error => {
          console.log(error); // 不会执行
      });

25.promise和async await的区别是什么?

1.都是处理异步请求的方式
2.promise是ES6,async await 是ES7的语法
3.async await是基于promise实现的,他和promise都是非阻塞性的
优缺点:
1.promise是返回对象我们要用then,catch方法去处理和捕获异常,并且书写方式是链式,容易造成代码重叠,不好维护,async await 是通过tra catch进行捕获异常
2.async await最大的优点就是能让代码看起来像同步一样,只要遇到await就会立刻返回结果,然后再执行后面的操作
3.promise.then()的方式返回,会出现请求还没返回,就执行了后面的操作

26.浏览器的存储方式有哪些?

1.cookies
    H5标准前的本地存储方式
    兼容性好,请求头自带cookie
    存储量小,资源浪费,使用麻烦(封装)
2.localstorage
    H5加入的以键值对为标准的方式
    操作方便,永久存储,兼容性较好
    保存值的类型被限定,浏览器在隐私模式下不可读取,不能被爬虫
3.sessionstorage
    当前页面关闭后就会立刻清理,会话级别的存储方式
4.indexedDB
    H5标准的存储方式,,他是以键值对进行存储,可以快速读取,适合WEB场景

27.token存在cookie还是loaclstorage?

token:验证身份的令牌,一般就是用户通过账号密码登录后,服务端把这些凭证通过加密等一系列操作后得到的字符串
1.存loaclstorage里,后期每次请求接口都需要把它当作一个字段传给后台
2.存cookie中,会自动发送,缺点就是不能跨域
如果存在localstorage中,容易被XSS攻击,但是如果做好了对应的措施,那么是利大于弊
如果存在cookie中会有CSRF攻击

28.token的登录流程。

1.客户端用账号密码请求登录
2.服务端收到请求后,需要去验证账号密码
3.验证成功之后,服务端会签发一个token,把这个token发送给客户端
4.客户端收到token后保存起来,可以放在cookie也可以是localstorage
5.客户端每次向服务端发送请求资源的时候,都需要携带这个token
6.服务端收到请求,接着去验证客户端里的token,验证成功才会返回客户端请求的数据

29.页面渲染的过程是怎样的?

DNS解析
建立TCP连接
发送HTTP请求
服务器处理请求
渲染页面
    浏览器会获取HTML和CSS的资源,然后把HTML解析成DOM树
    再把CSS解析成CSSOM
    把DOM和CSSOM合并为渲染树
    布局
    把渲染树的每个节点渲染到屏幕上(绘制)
断开TCP连接

30.DOM树和渲染树有什么区别?

DOM树是和HTML标签一一对应的,包括head和隐藏元素
渲染树是不包含head和隐藏元素

31.精灵图和base64的区别是什么?

精灵图:把多张小图整合到一张大图上,利用定位的一些属性把小图显示在页面上,当访问页面可以减少请求,提高加载速度
base64:传输8Bit字节代码的编码方式,Base64就是一种基于64个可打印字符来表示二进制数据的方法
base64是会和html css一起下载到浏览器中,减少请求,减少跨域问题,但是一些低版本不支持,若base64体积比原图片大,不利于css的加载。

32.svg格式了解多少?

基于XML语法格式的图像格式,可缩放矢量图,其他图像是基于像素的,SVG是属于对图像形状的描述,本质是文本文件,体积小,并且不管放大多少倍都不会失真
1.SVG可直接插入页面中,成为DOM一部分,然后用JS或CSS进行操作
    <svg></svg>
2.SVG可作为文件被引入
    <img src="pic.svg" />
3.SVG可以转为base64引入页面

33.了解过JWT吗?

JSON Web Token 通过JSON形式作为在web应用中的令牌,可以在各方之间安全的把信息作为JSON对象传输
可以用作信息传输、授权
JWT的认证流程
1.前端把账号密码发送给后端的接口
2.后端核对账号密码成功后,把用户id等其他信息作为JWT 负载,把它和头部分别进行base64编码拼接后签名,形成一个JWT(token)。
3.前端每日请求时都会把JWT放在HTTP请求头的Authorization字段内
4.后端检查是否存在,如果存在就验证JWT的有效性(签名是否正确,token是否过期)
5.验证通过后后端使用JWT中包含的用户信息进行其他的操作,并返回对应结果
优点:简洁、包含性、因为Token是JSON加密的形式保存在客户端,所以JWT是跨语言的,原则上是任何web形式都支持。

34.npm的底层环境是什么?

node package manager,node的包管理和分发工具,已经成为分发node模块的标准,是JS的运行环境
npm的组成:网站、注册表、命令行工具

35.HTTP协议规定的协议头和请求头有什么?

1.请求头信息:
    Accept:浏览器告诉服务器所支持的数据类型
    Host:浏览器告诉服务器我想访问服务器的哪台主机
    Referer:浏览器告诉服务器我是从哪里来的(防盗链)
    User-Agent:浏览器类型、版本信息
    Date:浏览器告诉服务器我是什么时候访问的
    Connection:连接方式
    Cookie
    X-Request-With:请求方式
2.响应头信息:
    Location:这个就是告诉浏览器你要去找谁
    Server:告诉浏览器服务器的类型
    Content-Type:告诉浏览器返回的数据类型
    Refresh:控制了的定时刷新

36.说一下浏览器的缓存策略。

强缓存(本地缓存)、协商缓存(弱缓存)
强缓:不发起请求,直接使用缓存里的内容,浏览器把JS,CSS,image等存到内存中,下次用户访问直接从内存中取,提高性能
协缓:需要像后台发请求,通过判断来决定是否使用协商缓存,如果请求内容没有变化,则返回304,浏览器就用缓存里的内容
强缓存的触发:
    HTTP1.0:时间戳响应标头
    HTTP1.1:Cache-Control响应标头
协商缓存触发:
    HTTP1.0:请求头:if-modified-since 响应头:last-modified
    HTTP1.1:请求头:if-none-match 响应头:Etag

37.说一下什么是“同源策略”?

http:// www.  aaa.com:8080/index/vue.js
协议    子域名 主域名  端口号     资源
同源策略是浏览器的核心,如果没有这个策略就会遭受网络攻击
主要指的就是协议+域名+端口号三者一致,若其中一个不一样则不是同源,会产生跨域
三个允许跨域加载资源的标签:img  link  script
跨域是可以发送请求,后端也会正常返回结果,只不过这个结果被浏览器拦截了!
JSONP(利用script标签解决)
CORS
websocket
反向代理(利用中间代理)

38.防抖和节流是什么?

都是应对页面中频繁触发事件的优化方案
防抖:避免事件重复触发(多次触发只执行最后一次)
使用场景:1.频繁和服务端交互 2.输入框的自动保存事件
节流:把频繁触发的事件减少,每隔一段时间执行(在一定时间内只执行一些)
使用场景:scroll事件

39.解释一下什么是json?

JSON是一种纯字符串形式的数据,它本身不提供任何方法,适合在网络中进行传输
JSON数据存储在.json文件中,也可以把JSON数据以字符串的形式保存在数据库、Cookise中
JS提供了JSON.parse() JSON.stringify()
什么时候使用json:定义接口;序列化;生成token;配置文件package.json

40.当数据没有请求过来的时候,该怎么做?

可以在渲染数据的地方给一些默认的值
if判断语句

41.有没有做过无感登录?

1.在响应其中拦截,判断token返回过期后,调用刷新token的接口
2.后端返回过期时间,前端判断token的过期时间,去调用刷新token的接口
3.写定时器,定时刷新token接口
第一种方法流程:
    1.登录成功后保存token 和 refresh_token(调用新接口的token)
    2.在响应拦截器中对401状态码引入刷新token的api方法调用
    3.替换保存本地新的token
    4.把错误对象里的token替换
    5.再次发送未完成的请求
    6.如果refresh_token过期了,判断是否过期,过期了就清楚所有token重新登录

42.大文件上传是怎么做的?

分片上传:
    1.把需要上传的文件按照一定的规则,分割成相同大小的数据块
    2.初始化一个分片上传任务,返回本次分片上传的唯一标识
    3.按照一定的规则把各个数据块上传
    4.发送完成后,服务端会判断数据上传的完整性,如果完整,那么就会把数据库合并成原始文件
断点续传:
    服务端返回,从哪里开始  浏览器自己处理

三、HTML5CSS3

1.语义化的理解。

在写HTML页面结构时所用的标签有意义
   头部用head  主体用main  底部用foot...
   怎么判断页面是否语义化了?
       把CSS去掉,如果能够清晰的看出来页面结构,显示内容较为正常
   为什么要选择语义化?
       1.让HTML结构更加清晰明了
       2.方便团队协作,利于开发
       3.有利于爬虫和SEO
       4.能够让浏览器更好的去解析代码
       5.给用户带来良好的体验

2.H5C3有哪些新特性?

H5的新特性:
     1.语义化的标签
     2.新增音频视频
     3.画布canvas
     4.数据存储localstorage sessionstorage
     5.增加了表单控件 email url search...
     6.拖拽释放API
 CSS3的新特性:
     1.新增选择器:属性选择器、伪类选择器、伪元素选择器
     2.增加了媒体查询
     3.文字阴影
     4.边框
     5.盒子模型box-sizing
     6.渐变
     7.过度
     8.自定义动画
     9.背景的属性
     10.2D和3D

3.rem是如何做适配的?

rem是相对长度,相对于根元素(html)的font-size属性来计算大小,通常来做移动端的适配
rem是根据根元素font-size计算值的倍数
比如html上的font-size:16px,给div设置宽为1.5rem,1.2rem = 16px*1.2 = 19.2px.

4.解决了哪些移动端的兼容问题?

1.当设置样式overflow:scroll/auto时,IOS上的华东会卡顿
      -webkit-overflow-scrolling:touch;
  2.在安卓环境下placeholder文字设置行高时会偏上
      input有placeholder属性的时候不要设置行高
  3.移动端字体小于12px时异常显示
      应该先把在整体放大一倍,然后再用transform进行缩小
  4.ios下input按钮设置了disabled属性为true显示异常
      input[typy=button]{
          opcity:1
      }
  5.安卓手机下取消语音输入按钮
      input::-webkit-input-speech-button{
          display:none
      }
  6.IOS下取消input输入框在输入引文首字母默认大写
      <input autocapitalize='off' autocorrect='off'/>
  7.禁用IOS和安卓用户选中文字
      添加全局CSS样式:-webkit-user-select:none
  8.禁止IOS弹出各种窗口
      -webkit-touch-callout:none
  9.禁止IOS识别长串数字为电话
      添加meta属性 <meta conten='telephone=no' name='format-detection'>

四、Vue

1.v-if和v-show的区别?

都可以控制元素的显示和隐藏
1.v-show时控制元素的display值来让元素显示和隐藏;v-if显示隐藏时把DOM元素整个添加和删除
2.v-if有一个局部编译/卸载的过程,切换这个过程中会适当的销毁和重建内部的事件监听和子组件;v-show只是简单的css切换
3.v-if才是真正的条件渲染;v-show从false变成true的时候不会触发组件的声明周期,v-if会触发声明周期
4.v-if的切换效率比较低  v-show的效率比较高

2.如何理解MVVM的?

是Model-View-ViewModel的缩写。前端开发的架构模式
M:模型,对应的就是data的数据
V:视图,用户界面,DOM
VM:视图模型:Vue的实例对象,连接View和Model的桥梁
核心是提供对View和ViewModel的双向数据绑定,当数据改变的时候,ViewModel能监听到数据的变化,自动更新视图,当用户操作视图的时候,ViewModel也可以监听到视图的变化,然后通知数据进行改动,这就实现了双向数据绑定
ViewModel通过双向绑定把View和Model连接起来,他们之间的同步是自动的,不需要认为干涉,所以我们只需要关注业务逻辑即可,不需要操作DOM,同时也不需要关注数据的状态问题,因为她是由MVVM统一管理

3.v-for中的key值的作用是什么?

key属性是DOM元素的唯一标识
作用:
    1.提高虚拟DOM的更新
    2.若不设置key,可能会触发一些bug
    3.为了触发过度效果

4.说一下你对vue生命周期的理解。

组件从创建到销毁的过程就是它的生命周期
创建
   beforeCreat
       在这个阶段属性和方法都不能使用
   created
       这里时实例创建完成之后,在这里完成了数据监测,可以使用数据,修改数据,不会触发updated,也不会更新视图
挂载
   beforeMount
       完成了模板的编译,虚拟DOM也完成创建,即将渲染,修改数据,不会触发updated
   Mounted
       把编译好的模板挂载到页面,这里可以发送异步请求也可以访问DOM节点
更新
   beforeUpdate
       组件数据更新之前使用,数据是新的,页面上的数据时旧的,组件即将更新,准备渲染,可以改数据
   updated
       render重新做了渲染,这时数据和页面都是新的,避免在此更新数据
销毁
   beforeDestroy
       实例销毁前,在这里实例还可以用,可以清楚定时器等等
   destroyed
       组件已经被销毁了,全部都销毁
使用了keep-alive时多出两个周期:
   activited
       组件激活时
   deactivited
       组件被销毁时

5.在created和mounted去请求数据,有什么区别?

created:在渲染前调用,通常先初始化属性,然后做渲染
mounted:在模板渲染完成后,一般都是初始化页面后,在对元素节点进行操作
        在这里请求数据可能会出现闪屏的问题,created里不会
一般用created比较多
请求的数据对DOM有影响,那么使用created
如果请求的数据对DOM无关,可以放在mounted

6.vue中的修饰符有哪些?

1.事件修饰符
    .stop       组织冒泡
    .prevent    组织默认行为
    .capture    内部元素触发的事件先在次处理
    .self       只有在event.target是当前元素时触发
    .once       事件只会触发一次
    .passive    立即触发默认行为
    .native     把当前元素作为原生标签看待
    <div class="father-box" @click="fatherClick">
          <!-- .stop 阻止事件冒泡 -->
        <div class="son-box" @click.stop="sonClick"></div>
    </div>
2.按键修饰符
    .keyup      键盘抬起
    .keydown    键盘按下
    <input @keyup.delete="handleDelete" />
3.系统修饰符
    .ctrl
    .alt
    .meta
4.鼠标修饰符
    .left       鼠标左键
    .right      鼠标右键
    .middle     鼠标中键
    <div @keydown.alt="handleAltKey">
    按下 Alt + C 尝试触发
    </div>
5.表单修饰符
    .lazy       等输入完之后再显示
    .trim       删除内容前后的空格
    .number     输入是数字或转为数字
    <input v-model.lazy="searchQuery" placeholder="Search...">

7.elementui是怎么做表单验证的?

1.在表单中加rules属性,然后再data里写校验规则
<template>
  <el-form :model="ruleForm" :rules="rules" ref="ruleForm">
    <el-form-item label="活动名称" prop="name">
      <el-input v-model="ruleForm.name"></el-input>
    </el-form-item>
    <!-- 更多表单项 -->
  </el-form>
</template>

<script>
export default {
  data() {
    return {
      ruleForm: {
        name: ''
      },
      rules: {
        name: [
          { required: true, message: '请输入活动名称', trigger: 'blur' },
          { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' },
          {pattern: /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/, message: '请输入正确格式,可保留两位小数',trigger: 'change' }

        ]
      }
    };
  }
};
</script>
2.内部添加规则
data() {
    let reg = /(?!^(\d+|[a-zA-Z]+|[~!@#$%^&*?]+)$)^[\w~!@#$%^&*?]{6,12}$/
    var validateNewPwd = (rule, value, callback) => {
        if (!reg.test(value)) {
            callback(new Error('密码应是6-12位数字、字母或字符!'))
        } else if (this.form.oldPasswd === value) {
            callback(new Error('新密码与旧密码不可一致!'))
        } else {
            callback()
        }
    }
    var validateComfirmPwd = (rule, value, callback) => {
        if (!reg.test(value)) {
            callback(new Error('密码应是6-12位数字、字母或字符!'))
        } else if (this.form.newPasswd !== value) {
            callback(new Error('确认密码与新密码不一致!'))
        } else {
            callback()
        }
    }
    return {
        form: {
            newPasswd: '',
            comfirmPwd: ''
        },
        rules: {
            newPasswd: [
                { required: true, message: '请输入新密码', trigger: 'blur' },
                { validator: validateNewPwd, trigger: 'blur' }
            ],
            comfirmPwd: [
                { required: true, message: '请输入确认密码', trigger: 'blur' },
                { validator: validateComfirmPwd, trigger: 'blur' }
            ]
        }
    }
}
3.自定义函数校验(在methods中定义规则,在data中使用)

8.vue如何进行组件通信?

1.父传子
   props
       父组件使用自定义属性,然后子组件使用props
   $ref
       引用信息会注册在父组件的$refs对象上
2.子传父
   $emit
       子组件绑定自定义事件,触发执行后,传给父组件,父组件需要用事件监听来接收参数
3.兄弟传
		new一个新的vue实例,用on和emit来对数据进行传输
		import Vue from 'vue';export const eventBus = new Vue();
4.vuex传值

9.keep-alive是什么?怎么使用?

Vue的一个内置组件,包裹组件的时候,会缓存不活跃的组件实例,并不是销毁他们
作用:把组件切换的状态保存在内存里,防止重复渲染DOM节点,减少加载时间和性能消耗,提高用户体验
<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>
缓存名字为a或b的组件

10.axios是怎么做封装的?

下载 创建实例 接着封装请求响应拦截器  抛出 最后封装接口
import axios from 'axios';

// 创建一个Axios实例
const service = axios.create({
    baseURL: process.env.VUE_APP_BASE_API, // 接口基础URL,可根据环境变量配置
    timeout: 5000 // 请求超时时间
});

// 请求拦截器
service.interceptors.request.use(
    config => {
        // 在发送请求之前做些什么,例如添加请求头
        // 假设需要在请求头中添加token
        const token = localStorage.getItem('token');
        if (token) {
            config.headers['Authorization'] = `Bearer ${token}`;
        }
        return config;
    },
    error => {
        // 处理请求错误
        console.log(error); // 打印错误信息
        return Promise.reject(error);
    }
);

// 响应拦截器
service.interceptors.response.use(
    response => {
        const res = response.data;
        // 根据后端返回的状态码进行处理
        if (res.code !== 200) {
            // 处理错误情况,例如提示错误信息
            console.log(res.message || 'Error');
            return Promise.reject(new Error(res.message || 'Error'));
        } else {
            return res;
        }
    },
    error => {
        // 处理响应错误
        console.log('err' + error); // 打印错误信息
        return Promise.reject(error);
    }
);

// 封装请求方法
const request = {
    get(url, params = {}) {
        return service.get(url, { params });
    },
    post(url, data = {}) {
        return service.post(url, data);
    },
    put(url, data = {}) {
        return service.put(url, data);
    },
    delete(url, params = {}) {
        return service.delete(url, { params });
    }
};

export default request;   

//组件中使用
import request from './axiosInstance';

// 发送GET请求
request.get('/api/data', { id: 1 })
  .then(response => {
    console.log(response);
  })
  .catch(error => {
    console.log(error);
  });

// 发送POST请求
request.post('/api/login', { username: 'admin', password: '123456' })
  .then(response => {
    console.log(response);
  })
  .catch(error => {
    console.log(error);
  });

11.vue路由时怎么传参的?

params传参
   this.$router.push({name:'index',params:{id:item.id}})
   this.$route.params.id
路由属性传参
   this.$router.push({name:'/index/${item.id}'})
   路由要配置id属性 { path:'/index:id' }
query传参(可以解决页面刷新参数丢失的问题)
   this.$router.push({
       name:'index',
       query:{id:item.id}
   })

12.vue路由的hash模式和history模式有什么区别?

1.hash的路由地址上有#号,history模式没有
2.在做回车刷新的时候,hash模式会加载对应页面,history会报错404
3.hash模式支持低版本浏览器,history不支持,因为是H5新增的API
4.hash不会重新加载页面,单页面应用必备
5.history有历史记录,H5新增了pushState和replaceState()去修改历史记录,并不会立刻发送请求
6.history需要后台配置

13.路由拦截是怎么实现的?

路由拦截 axios拦截
需要在路由配置中添加一个字段meta(路由配置中添加自定义元数据),它是用于判断路由是否需要拦截
{
   name:'index',
   path:'/index',
   component:Index,
   meta:{
       requirtAuth:true
   }
}
router.beforeEach((to,from,next) => {
   if(to.meta.requirtAuth){
       if(store.satte.token ){
           next()
       }else{
       }
   }
})

14.说一下vue的动态路由。

要在路由配置里设置meat属性,扩展权限相关的字段,在路由导航守卫里通过判断这个权限标识,实现路由的动态增加和跳转
根据用户登录的账号,返回用户角色
前端再根据角色,跟路由表的meta.role进行匹配
把匹配搭配的路由形成可访问的路由

15.如何解决刷新后二次加载路由?

1.window.location.reload()
2.matcher
    const router = createRouter()
    export function resetRouter(){
        const newRouter = creatRouter()
        router.matcher = newRouter.matcher
    }

16.vuex刷新数据会丢失吗?怎么解决?

vuex肯定会重新获取数据,页面也会丢失数据
1.把数据直接保存在浏览器缓存里(cookie  localstorage  sessionstorage)
2.页面刷新的时候,再次请求数据,达到可以动态更新的方法
监听浏览器的刷新事件,在刷新前把数据保存到sessionstorage里,刷新后请求数据,请求到了用vuex,如果没有那就用sessionstorage里的数据

17.computed和watch的区别?

1.computed是计算属性,watch是监听,监听的是data中数据的变化
2.computed是支持缓存,依赖的属性值发生变化,计算属性才会重新计算,否则用缓存;watch不支持缓存
3.computed不支持异步,watch是可以异步操作
4.computed是第一次加载就监听,watch是不监听
5.computed函数中必须有return  watch不用

18.vuex在什么场景会去使用?属性有哪些?

state       存储变量
getters     state的计算属性
mutations   提交更新数据的方法
actions     和mutations差不多,他是提交mutations来修改数据,可以包括异步操作
modules     模块化vuex
使用场景:
  用户的个人信息、购物车模块、订单模块

19.vue的双向数据绑定原理是什么?

通过数据劫持和发布订阅者模式来实现,同时利用Object.defineProperty()劫持各个属性的setter和getter,
在数据发生改变的时候发布消息给订阅者,触发对应的监听回调渲染视图,也就是说数据和视图时同步的,数据发生改变,视图跟着发生改变,视图改变,数据也会发生改变。
第一步:需要observer的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter
第二步:compile模板解析指令,把模板中的变量替换成数据,然后初始化渲染视图,同时把每个指令对应的节点绑定上更新函数,添加订阅者,如果数据变化,收到通知,更新视图
第三步:Watcher订阅者是Observer和Compile之间的通信桥梁,作用:
        1.在自身实例化的时候往订阅器内添加自己
        2.自身要有一个update()方法
        3.等待属性变动时,调用自身的update方法,触发compile这种的回调
第四步:MVVM作为数据绑定的入口,整合了observer、compile和watcher三者,通过observer来监听自己的数据变化,通过compile解析模板指令,最后利用watcher把observer和compile联系起来,最终达到数据更新视图更新,视图更新数据更新的效果

20.了解diff算法和虚拟DOM吗?

虚拟DOM,描述元素和元素之间的关系,创建一个JS对象
如果组件内有响应的数据,数据发生改变的时候,render函数会生成一个新的虚拟DOM,这个新的虚拟DOM会和旧的虚拟DOM进行比对,找到需要修改的虚拟DOM内容,然后去对应的真实DOM中修改
diff算法就是虚拟DOM的比对时用的,返回一个patch对象,这个对象的作用就是存储两个节点不同的地方,最后用patch里记录的信息进行更新真实DOM
步骤:
    1.JS对象表示真实的DOM结构,要生成一个虚拟DOM,再用虚拟DOM构建一个真实DOM树,渲染到页面
    2.状态改变生成新的虚拟DOM,跟就得虚拟DOM进行比对,这个比对的过程就是DIFF算法,利用patch记录差异
    3.把记录的差异用在第一个虚拟DOM生成的真实DOM上,视图就更新了。

21.vue和jquery的区别是什么?

1.原理不同
    vue就是数据绑定;jq是先获取dom再处理
2.着重点不同
    vue是数据驱动,jq是着重于页面
3.操作不同
4.未来发展不同

22.vuex的响应式处理。

vuex是vue的状态管理工具
vue中可以直接触发methods中的方法,vuex是不可以的。为了处理异步,当触发事件的时候,会通过dispatch来访问actions中的方法,actions中的commit会触发mutations中的方法从而修改state里的值,通过getter把数据更新到视图
Vue.use(vuex),调用install方法,通过applyMixin(vue)在任意组件内执行this.$store就可以访问到store对象。
vuex的state是响应式的,借助的就是vue的data,把state存到vue实例组件的data中
例子:
import { createStore } from 'vuex';​
​
export default createStore({state: {sharedValue: ''},mutations: {updateSharedValue(state, value) {​
      state.sharedValue = value;}},actions: {updateValueAction({ commit }, value) {commit('updateSharedValue', value);}}});

23.vue中遍历全局的方法有哪些?

1.普通遍历,对象.forEach()
    arr.forEach(function(item,index,arr){
        console.log(item,index)
    })
2.对元素统一操作  对象.map()
    var newarr = arr.map(function(item){
        return item+1
    })
3.查找符合条件的元素 对象.filter()
    arr.filter(function(item){
        if(item > 2){
            return false
        }else{
            return true
        }
    })
4.查询符合条件的元素,返回索引 对象.findindex()
    arr.finindex(function(item){
        if(item>1){
            return true
        }else{
            return false
        }
    })
对象.evening()  遇到不符合的对象会停止
对象.some()  找到符合条件的元素就停止

24.如何搭建脚手架?

下载:node  cnpm  webpack vue-cli
创建项目:
    1.找到对应的文件,然后利用node指令创建(cmd)
    2.vue init webpack xxxx
    3.回车项目描述
    4.作者回车
    5.选择vue build
    6.回车
    7.输入n
    8.不按照yarn
    9.输入npm run dev

25.如何封装一个组件?

1.使用Vue.extend()创建一个组件
2.使用Vue.components()方法注册组件
3.如果子组件需要数据,可以在props中接收定义
4.子组件修改好数据,要把数据传递给父组件,可以用emit()方法
原则:
    把功能拆开
    尽量让组件原子化,一个组件做一件事情
    容器组件管数据,展示组件管视图
// 使用Vue.extend创建组件
const MyButton = Vue.extend({
  template: '<button>{{ message }}</button>',
  data() {
    return {
      message: '点击我'
    }
  }
});
Vue.component('my-button', MyButton);
注册后使用:
<div id="app">
  <my-button></my-button>
</div>

26.封装一个可复用的组件,需要满足什么条件?

1.低耦合,组件之间的依赖越小越好
2.最好从父级传入信息,不要在公共组件中请求数据
3.传入的数据要进行校验
4.处理事件的方法写在父组件中

27.vue的过滤器怎么使用?

vue的特性,用来对文本进行格式化处理
使用它的两个地方,一个是插值表达式,一个是v-bind
分类:
   1.全局过滤器
       Vue.filter('add',function(v){
           return v < 10 ? '0' + v : v
       })
       <div>{{33 | add}}</div>
   2.本地过滤器
       和methods同级
       filter:{
           add:function(v){
               return v < 10 ? '0' + v : v
           }
       }

28.vue中如何做强制刷新?

1.localtion.reload()
2.this.$router.go(0)
3.provide和inject

29.vue3和vue2有哪些区别?

1.双向数据绑定的原理不同
2.是否支持碎片
3.API不同
4.定义数据变量方法不同
5.生命周期的不同(vue3开始是setup)
6.传值不同
7.指令和插槽不同
8.main.js不同

30.vue的性能优化怎么做?

1.编码优化
    不要把所有数据都放在data中
    v-for时给每个元素绑定事件用事件代理
    keep-alive缓存组件
    尽可能拆分组件,提高复用性、维护性
    key值要保证唯一
    合理使用路由懒加载,异步组件
    数据持久化存储的使用尽量用防抖、节流优化
2.加载优化
    按需加载
    内容懒加载
    图片懒加载
3.用户体验
    骨架屏(显示一个加载动画)
4.SEO优化
    预渲染
    服务端渲染ssr
5.打包优化
    CDN形式加载第三方模块
    多线程打包
    抽离公共文件
6.缓存和压缩
    客户端缓存、服务端缓存
    服务端Gzip压缩

31.首屏优化该如何去做?

1.使用路由懒加载
2.非首屏组件使用异步组件
3.首屏不中要的组件延迟加载
4.静态资源放在CDN上
5.减少首屏上JS、CSS等资源文件的大小
6.使用服务端渲染
7.尽量减少DOM的数量和层级
8.使用精灵图请求
9.做一些loading
10.开启Gzip压缩
11.图片懒加载

32.vue3的性能为什么比vue2好?

1.diff算法的优化(向之后可能发生变化的dom节点添加标记,之后只比较这种标记点)
2.静态提升(针对没有参与更新的元素复用)
3.事件侦听缓存(缓存事件,之后复用)

33.vue3为什么使用proxy?

1.proxy可以代理整个对象,defineproperty只代理对象上的某个属性
2.proxy对代理对象的监听更加丰富
3.proxy代理对象会生成新的对象,不会修改被代理对象本身
4.proxy补兼容ie浏览器

34.说一下你对组件的理解。

可以重复使用的vue实例,独一无二的组件名称
可以抽离单独的公共模块
提高代码的复用率

35.你是如何规划项目文件的?

public
     图标、index.html、img
 src
     api
     assets
     components
         按分类再次划分子目录
     plugins
     router
     static
     styles
     utils
     views
 App.vue
 main.js
 package.json
 vue.config.js

36.是否使用过nuxt.js?

是基于vue的应用框架,关注的是渲染,可以开发服务端渲染应用的配置
SSR:服务端渲染
    好处:
        SSR生成的是有内容的HTML页面,有利于搜索引擎的搜索
        优化了首屏加载时间
SEO:优化搜索引擎
SPA(单页应用)的应用不利于搜索引擎SEO的操作

37.SEO(搜索引擎优化)如何优化?

1.SSR
2.预渲染 prerender-spa-plugin

五、Echarts

1.echarts有用过吗?常用的组件有哪些?

title标题组件 show  text  link
toolbox工具栏 导出图片 数据视图 切换 缩放 show orient feature
tooltip tigger 触发类型
markPoint标注点
markLine图标的标线

六、Uni-APP

1.uni-app有没有做过分包?

优化小程序的下载和启动速度
小程序启动默认下载主包并启动页面,当用户进入分包时,才会下载对应的分包,下载完进行展示

七、Weabpack

1.webpack打包和不打包的区别?

1.运行效率
2.对基础的支持不够

2.webpack是怎么打包的,babel是做什么的?

webpack会把js css image看作一个模块,用import/require引入
找到入口文件,通过入口文件找到关联的依赖文件,把他们打包到一起
把bundle文件,拆分成多个小的文件,异步按需加载所需要的文件
如果一个被多个文件引用,打包时只会生成一个文件
如果引用的文件没有调用,不会打包,如果引入的变量和方法没有调用也不会打包
对于多个入口文件,加入引入了相同的代码,可以用插件把他抽离到公共文件中

八、Git

1.git如何合并、拉取代码?

拉取代码 git pull '仓库地址'
查看状态 git sattus 
提交到本地缓存区  git add .
提交本地仓库 git commit -m '修改描述'
提交到远程仓库 git push '仓库地址' master
创建分支 git branch -b xxx
合并分支 git merge '合并分支的名字'

2.git如何解决冲突问题?

1.两个分支中修改了同一个文件
2.两个分支中修改了同一个文件的名字
1.解决:当前分支上,直接修改代码  add  commit
2.解决:在本地当前分支上,修改冲突代码 add commit push

九、HR

1.你的离职原因是什么?

疫情 社保 薪资问题 个人发展 技术提升 家庭因素

2.工作到现在,项目中遇到最难的问题是什么?怎么解决的?

1.不要回答,没有问题
2.不要说一些常见的简单的问题,比如:数据请求不过来、渲染页面时出现了问题、跳转路由不会...
首先应该时自行去查找资料寻求解决办法,然后再去请教同时或者组长

3.你的优势在哪里?

1.尽量不要暴露自己的缺点
2.不要过度美化自己

4.如何协同工作?

1.开发前会开个会议,最后形成一个开发文档
2.利用工具保证项目的正常进度,规范化

十、综合问题

1.什么是AJAX?如何使用JS发起AJAX请求?

AJAX(Asynchronous JavaScript and XML)是一种用于创建交互式和动态网页的技术。它允许使用JavaScript在不重新加载整个页面的情况下与服务器进行数据交换。

使用JavaScript发起AJAX请求的步骤如下:

  1. 创建一个XMLHttpRequest对象。可以通过以下代码创建一个XMLHttpRequest对象:
var xhr = new XMLHttpRequest();
  1. 设置请求的类型、URL和是否异步。可以使用open方法设置请求的类型(GET或POST)、URL和是否异步。例如:
xhr.open('GET', 'example.com/data', true);
  1. 注册一个回调函数用于处理服务器响应。可以使用onreadystatechange属性注册一个回调函数,该函数在readyState属性改变时被触发。通常,在readyState等于4(表示请求已完成)且status等于200(表示成功响应)时处理服务器响应。例如:
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    var response = xhr.responseText;
    // 在这里处理服务器响应
  }
};
  1. 发送请求。可以使用send方法发送请求。如果是POST请求,可以将数据作为参数传递给send方法。例如:
xhr.send();

以上是使用纯JavaScript发起AJAX请求的基本步骤。但是,现代的JavaScript框架(如jQuery、axios等)提供了更简单和更强大的方式来处理AJAX请求。这些框架封装了底层的AJAX操作,简化了代码和兼容性问题,并提供了更好的错误处理和更丰富的功能。

2. 什么是JSONP?如何使用JS实现JSONP跨域请求?

JSONP(JSON with Padding)是一种解决跨域请求的技术。通常,由于浏览器的同源策略限制,JavaScript不允许从不同域名的服务器请求数据。但是,通过使用JSONP,我们可以绕过这个限制,实现跨域请求并获取数据。

JSONP的实现原理是通过动态创建<script>标签,从包含JSON数据的URL中获取数据。JSONP请求将回调函数名称作为参数传递到服务器,服务器将数据包装在回调函数中返回。浏览器会自动执行该回调函数,从而使数据可用于客户端的JavaScript代码。

使用JavaScript实现JSONP跨域请求的步骤如下:

  1. 创建一个用于处理回调的全局函数。该函数将在获取到数据时被调用,并通过参数接收服务器返回的数据。例如:
function handleResponse(data) {
  // 在这里处理从服务器返回的数据
}
  1. 动态创建<script>标签,并将回调函数名称作为参数传递给服务器。例如:
var script = document.createElement('script');
script.src = 'https://example.com/data?callback=handleResponse';
document.body.appendChild(script);

上述代码中,handleResponse是回调函数的名称,它作为参数传递给服务器。服务器将数据包装在该回调函数中返回。

  1. 服务器端返回数据时,使用回调函数来包装数据,并将数据作为参数传递给回调函数。例如,服务器返回的响应可能如下所示:
handleResponse({"name": "John", "age": 30});

在客户端,浏览器会执行全局的handleResponse函数,并将服务器返回的数据作为参数传递给该函数。

使用JSONP进行跨域请求需要服务器端的支持,服务器端需要根据传递的回调函数名称来返回合适的响应。在客户端,可以通过编写处理返回数据的回调函数来处理服务器返回的数据。

需要注意的是,由于JSONP的安全性和可靠性问题,现在更多的倾向于使用跨域资源共享(CORS)来进行跨域请求。CORS提供了一种更安全和可控的方法来处理跨域请求。

3.如何使用JS实现异步编程?

JavaScript中有多种方法可以实现异步编程,以下是一些常见的方式:

  1. 回调函数(Callbacks):通过传递一个回调函数作为参数,在异步操作完成后调用该回调函数来处理结果。例如,使用Ajax进行异步请求时可以提供一个回调函数来处理响应数据。
function fetchData(callback) {
  // 异步操作,例如Ajax请求
  // ...
  // 在异步操作完成后调用回调函数处理结果
  callback(result);
}

function handleData(data) {
  // 处理异步操作返回的数据
}

fetchData(handleData);
  1. Promise(承诺):Promise是一种表示异步操作最终完成或失败的对象。它可以让你以更直观和可读的方式编写异步代码。通过使用Promise对象,可以使用链式调用的方式处理异步操作的结果,包括成功和失败的情况。
function fetchData() {
  return new Promise(function(resolve, reject) {
    // 异步操作,例如Ajax请求
    // ...
    // 在异步操作完成后调用resolve()或reject()来处理结果
    if (success) {
      resolve(result);
    } else {
      reject(error);
    }
  });
}

fetchData()
  .then(function(result) {
    // 处理异步操作成功的情况
  })
  .catch(function(error) {
    // 处理异步操作失败的情况
  });
  1. Async/Await:Async/Await是ES2017引入的一种异步编程语法糖,它可以使用同步的方式编写异步代码。通过在函数前面添加async关键字,可以在函数内部使用await关键字等待异步操作的结果。使用Async/Await可以使异步代码更加简洁和易读。
async function fetchData() {
  try {
    // 异步操作,例如Ajax请求
    // ...
    // 使用await等待异步操作的结果
    const result = await asyncOperation();
    // 处理异步操作的结果
  } catch (error) {
    // 处理异步操作的错误
  }
}

fetchData();

这些是JavaScript中实现异步编程的一些常见方式。根据具体的应用场景和需求,选择适合的异步编程方式可以使代码更加清晰和可维护。

4.什么是事件委托?如何使用JS实现事件委托?

事件委托(Event Delegation)是一种在前端开发中常用的技术,可以将事件处理逻辑绑定在一个父元素上,通过事件冒泡的机制来处理子元素上的事件。这样可以减少事件处理程序的数量,提高性能和代码的可维护性。

使用 JavaScript 实现事件委托的步骤如下:

  1. 确定一个父元素,它将成为事件委托的目标。
  2. 绑定父元素的事件处理程序,通常是绑定常见的事件类型,例如 click、keydown、mouseover 等。
  3. 在事件处理程序中,通过判断事件的目标元素(event.target)来确定实际触发事件的子元素。
  4. 执行针对子元素的相应逻辑操作。

下面是一个简单的示例,演示如何使用 JavaScript 实现事件委托:

<!DOCTYPE html>
<html>
  <head>
    <title>事件委托示例</title>
  </head>
  <body>
    <ul id="list">
      <li>选项1</li>
      <li>选项2</li>
      <li>选项3</li>
      <li>选项4</li>
      <li>选项5</li>
    </ul>

    <script>
      var list = document.getElementById('list');

      // 绑定父元素的事件处理程序
      list.addEventListener('click', function(event) {
        // 通过判断事件的目标元素,执行相应逻辑操作
        if (event.target.tagName === 'LI') {
          console.log('点击了选项:', event.target.innerText);
        }
      });
    </script>
  </body>
</html>

在上述示例中,通过给 ul 元素添加点击事件处理程序,然后在事件处理程序中判断点击事件的目标元素是否为 li,如果是则输出对应的选项内容。

使用事件委托可以在动态添加或删除子元素时,无需重新绑定事件处理程序,简化代码逻辑。

5.什么是模块化编程?如何使用ES6的模块化语法实现模块化编程?

模块化编程是一种将代码分离成独立、可重用的模块的编程方法。模块化编程可以使代码更加清晰、可维护和可重用,特别是在大型项目中,其重要性更加突出。

ES6提供了一种标准化的模块化编程方法,支持在JavaScript中以模块的方式编写代码,并通过importexport关键字来组织和引用模块中的代码。

以下是使用ES6模块化语法实现模块化编程的基本方法:

  1. 导出(Export)模块中的代码

使用export关键字将定义的函数、变量、类或对象导出供其他模块使用:

// 导出一个函数
export function add(a, b) {
  return a + b;
}

// 导出一个变量
export const name = 'John';

// 导出一个类
export class Person {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    console.log(`Hi, my name is ${this.name}`);
  }
}

// 导出一个对象
export const config = {
  apikey: '123456',
  url: 'https://example.com/api',
};
  1. 导入(Import)其他模块中的代码

使用import关键字导入其他模块中已经导出的函数、变量、类或对象:

// 导入一个函数
import { add } from './math';

// 导入一个变量
import { name } from './constants';

// 导入一个类
import { Person } from './person';

// 导入一个对象
import { config } from './config';

默认情况下,每个模块都是严格限制在其自身的作用域内的,无法访问其他模块中的代码。如果想要向外暴露一个默认的导出(Default Export),可以使用export default关键字。同时,可以使用import语句导入默认导出的代码:

// 导出一个默认的函数
export default function add(a, b) {
  return a + b;
}

// 导入默认导出的函数
import add from './math';

需要注意的是,ES6模块化语法需要在支持的浏览器中才能正常使用。如果需要在较旧的浏览器中使用模块化编程,可以考虑使用Babel等工具进行转换。

6.移动端的Viewport是什么?如何设置Viewport?

移动端的Viewport是指浏览器渲染页面时可见的区域。由于移动设备的屏幕尺寸较小,为了适应不同设备的屏幕,并提供更好的可视化体验,Viewport起到了重要的作用。

设置Viewport可以控制浏览器如何缩放和渲染页面的内容。以下是设置Viewport的常用方法:

  1. 使用meta标签设置Viewport:

    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    

    这个meta标签将Viewport的宽度设置为设备的宽度,并将初始缩放比例设置为1.0(不进行缩放)。这样可以确保页面在移动设备上以正确的比例显示。

  2. 设置Viewport的宽度:

    <meta name="viewport" content="width=600">
    

    这个meta标签将Viewport的宽度设置为600像素。这样可以使页面在不同设备上显示相同的宽度,但可能会导致内容超出屏幕而产生横向滚动条。

  3. 设置初始缩放比例:

    <meta name="viewport" content="initial-scale=1.5">
    

    这个meta标签将初始缩放比例设置为1.5。可以根据实际情况适当调整初始比例,以满足页面的展示需求。

  4. 禁止用户缩放:

    <meta name="viewport" content="user-scalable=no">
    

    这个meta标签禁止用户对页面进行缩放操作。在某些情况下,可能需要禁止用户缩放以保持页面布局的稳定性。

通过设置Viewport,可以根据设备的屏幕尺寸和用户行为,提供更好的页面展示效果和用户体验。注意,在设置Viewport时,要确保正确的设置缩放比例、宽度和禁止缩放等选项,以满足设计和功能需求。

7.移动端常见的适配方案有哪些?各有什么优缺点?

移动端常见的适配方案有以下几种:

  1. 媒体查询 (Media Queries):通过CSS中的@media规则,根据不同设备的屏幕宽度应用不同的样式。优点是简单易用,可以根据不同屏幕宽度进行灵活适配,缺点是需要编写大量的媒体查询规则,维护成本较高。

  2. 百分比布局 (Percentage-based layout):使用百分比作为元素的宽度和高度,使元素相对于父容器进行自适应布局。优点是简单易实现,适用于响应式设计,缺点是对于复杂布局可能需要大量的计算。

  3. 响应式框架 (Responsive frameworks):例如Bootstrap、Foundation等,这些框架提供了一套响应式的网格系统和组件,方便开发者构建适应不同设备的网页。优点是提供了完整的解决方案,缺点是框架较大,可能需要额外的学习成本,有些情况下可能过于笨重。

  4. REM(Root-Em)布局:使用REM作为单位,在CSS中根据根元素(html)的字体大小进行适配,通过改变根元素的字体大小来调整整个页面的布局。优点是相对简单,适用于不同屏幕尺寸的适配,缺点是需要计算和关注字体大小的变化。

  5. Viewport单位:使用Viewport单位(vw、vh、vmin、vmax)代替像素进行布局,根据设备的宽度和高度进行相对适配。优点是简单易用,适用于响应式设计,缺点是对于低版本浏览器和某些移动设备的兼容性不佳。

每种适配方案都有其适用的场景和优缺点。在选择适配方案时,需要根据项目需求、用户群体和开发的复杂度进行权衡。同时,结合多种适配方案也是一种常见的做法,可以根据不同的场景采用不同的适配策略,以达到最佳的适配效果。

8.如何检测当前浏览器是否为移动设备?

在Web开发中,可以使用JavaScript代码检测当前浏览器是否为移动设备。以下是常用的几种方法:

  1. 通过判断User-Agent字符串:

    function isMobile() {
      return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
    }
    

    这个方法中,利用正则表达式匹配当前浏览器User-Agent字符串,以检测是否为移动设备。返回值为真或假,表示当前浏览器是否为移动设备。

  2. 通过检测屏幕分辨率:

    function isMobile() {
      return screen.width <= 768 && screen.height <= 1024;
    }
    

    这个方法中,通过检测当前屏幕分辨率是否小于移动设备的典型分辨率(例如iPad的768x1024),来判断当前浏览器是否为移动设备。

  3. 通过检测触摸事件:

    function isMobile() {
      return 'ontouchstart' in window;
    }
    

    这个方法中,利用浏览器解析JavaScript代码的机制,判断当前浏览器是否支持触摸事件,以此来判断是否为移动设备。

这些方法都比较简单易用,但可能存在一定的误判。在实际项目中,需要根据具体的需求和浏览器兼容性考虑,选择最合适的判断方法。

9.如何检测当前设备的屏幕尺寸?

在Web开发中,可以使用JavaScript代码获取当前设备的屏幕尺寸。以下是常用的几种方法:

  1. 使用screen对象获取当前屏幕的宽度和高度:

    var screenWidth = window.screen.width;
    var screenHeight = window.screen.height;
    

    这个方法通过获取screen对象的width和height属性,可以获取当前设备屏幕的宽度和高度。

  2. 使用document.documentElement获取当前文档根元素的宽度和高度:

    var screenWidth = document.documentElement.clientWidth;
    var screenHeight = document.documentElement.clientHeight;
    

    这个方法中,使用document.documentElement获取文档根元素的宽度和高度。这个值是相对于视窗的宽度和高度,可以用来检测设备的屏幕尺寸。

  3. 通过CSS3媒体查询获取屏幕尺寸:

    @media screen and (min-device-width: 768px) and (max-device-width: 1024px) {
      /* … */
    }
    

    这个方法中,通过CSS3媒体查询获取设备的屏幕尺寸。在这个样式规则中,使用min-device-width和max-device-width指定设备的最小和最大宽度,从而对不同屏幕尺寸应用不同样式。

这些方法都可以用来获取设备的屏幕尺寸,根据具体的使用场景和需求,可以选择最合适的方法。注意,获取到的屏幕尺寸值可能和实际屏幕尺寸存在一定的误差,需要进行适当的处理和兼容。

10.移动端开发中如何处理兼容性问题?

在移动端开发中,处理兼容性问题是非常重要的。以下是一些处理移动端兼容性问题的常用方法:

  1. 使用媒体查询(Media Queries):通过使用媒体查询,可以根据屏幕尺寸、分辨率或者设备类型等条件,为不同的设备提供不同的样式。这可以帮助你在不同的移动设备上提供更好的用户体验。

  2. 使用流式布局(Fluid layout):相比于固定宽度的布局,使用流式布局可以根据屏幕大小自适应调整布局。这样可以使你的网站或应用在不同尺寸的设备上正常显示,并且避免出现滚动条或者被截断的内容。

  3. 避免使用Flash和特定于浏览器的技术:移动设备中并不支持Flash,并且各个浏览器对于特定的CSS属性和JavaScript API支持程度也有所不同。因此,你应该尽量避免使用这些技术,而是使用HTML5和CSS3等标准技术。

  4. 使用自适应图片:移动设备的屏幕尺寸差异较大,为了提供更好的用户体验,应该根据设备屏幕大小加载适当尺寸的图片,以减少加载时间和带宽消耗。

  5. 测试和调试:在开发过程中,及时在多个真实设备和不同浏览器中进行测试和调试是至关重要的。可以使用模拟器、真机调试工具和浏览器开发者工具等来进行测试,解决兼容性问题。

  6. 使用现成的UI框架或库:使用经过广泛测试和优化的UI框架或库,如Bootstrap、Semantic UI、Material-UI等,可以大大减少兼容性问题的出现,以及开发时间和工作量。

  7. 了解常见兼容性问题和解决方案:对于常见的移动设备兼容性问题,如不支持的CSS属性或JavaScript API、事件处理差异等,可以进行研究和了解,通过特定的解决方案来解决这些问题。

处理移动端兼容性问题需要有一定的经验和技巧。将上述方法结合具体项目需求和实际情况,可以最大限度地提高移动端应用的兼容性和用户体验。

11.uni-app中的小程序页面和H5页面有以下区别:

  1. 页面生命周期:小程序页面和H5页面的生命周期有些许差异。比如在小程序中,页面的生命周期包括onLoad、onShow、onReady、onHide、onUnload等;而在H5页面中,常见的生命周期是onLoad、onDOMContentLoaded、onLoad事件等。

  2. 调试方式:在小程序中,可以使用小程序开发者工具进行调试,并提供实时预览功能;而在H5页面中,可以在浏览器中进行调试,使用浏览器的开发者工具进行查看和调试。

  3. API支持:小程序页面可以直接调用小程序的API,访问小程序的底层能力;而H5页面则可以使用Web浏览器的API,具备更广泛的Web功能和能力。

  4. 页面传参方式:在小程序页面中,可以通过参数传递、全局变量、缓存等方式进行页面间的数据传递;而在H5页面中,可以通过URL参数传递、cookie、localStorage等方式进行数据传递。

  5. 样式适配:由于小程序和H5的样式表现有所不同,需要针对不同平台分别进行样式适配。在uni-app中,可以使用条件编译,在样式文件中使用@platform规则或@media媒体查询来区分不同平台和设备。

需要注意的是,uni-app会在编译过程中将源码转换为各个平台的代码,以实现跨平台运行。因此,开发者通常无需过多关心不同平台的差异,而是专注于通用的开发和调试工作。

12.uni-app如何实现组件的复用和开发者之间的共享?

在uni-app中,可以通过以下方式来实现组件的复用和开发者之间的共享:

  1. 内置组件的复用:uni-app提供了一系列内置组件,如<view><button><image>等,它们可以在不同页面之间进行复用并共享样式和功能。

  2. 自定义组件的复用:可以通过创建自定义组件来实现更高度的复用性。对于需要在多个页面中使用的相似或相同的组件,可以将其抽取为一个单独的自定义组件,然后在不同页面中引用和复用。需要注意的是,自定义组件的目录需要放在/components下。

  3. 组件库的使用:可以使用第三方组件库或官方提供的uni-ui组件库来扩展和丰富组件的复用性。这些组件库通常提供一系列封装好的可复用组件,可以直接在uni-app项目中引入和使用。

  4. npm模块的使用:uni-app支持通过npm方式引入和使用第三方的npm模块,这包括一些常用的功能库、UI框架等。通过使用npm模块,可以快速引入和共享各类功能和组件,提高开发效率。

  5. 插件的开发和使用:通过编写插件可以实现可复用的功能和组件,然后将其发布到插件市场或与其他开发者共享。其他开发者可以根据需求安装插件并在自己的uni-app项目中使用,从而实现共享。

  6. 开发者社区和资源共享:在uni-app的开发者社区中可以与其他开发者交流和分享经验,通过互相学习和借鉴来提升开发技能,共享各种开发资源和技巧。

总体而言,uni-app支持多种方式来实现组件的复用和开发者之间的共享,提高开发效率和代码复用性。通过合理地使用内置组件、自定义组件、组件库、npm模块和插件,并积极参与开发者社区,可以更好地利用已有资源和开发者之间的共享来提高开发效率和质量。

13.uni-app项目如何上线和部署?需要注意哪些问题?

要上线和部署uni-app项目,可以按照以下步骤进行:

  1. 编译项目:在开发完成后,需要首先将uni-app项目编译成各个平台所需的代码。通过使用uni-app提供的命令行工具或者使用开发工具(如HBuilderX)进行编译,生成对应平台的代码。

  2. 常见平台的部署方式:

    • 微信小程序平台:将编译生成的代码,通过微信开发者工具进行上传并发布为正式版本。需要注意的是,在上传前需要进行相应的配置,如配置AppID等。

    • 支付宝小程序平台:将编译生成的代码,通过支付宝小程序开发者工具进行上传并发布为正式版本。同样需要进行相应的配置,如配置AppID。

    • H5平台:将编译生成的代码,上传至Web服务器上,确保可以通过URL访问到对应的文件。这样在浏览器中访问对应的URL即可运行uni-app应用。

  3. 部署相关问题:

    • 配置问题:在部署到对应平台之前,需要配置对应平台的相关信息,如微信小程序的AppID。确保配置正确,以确保项目能够正常在指定的平台上线和运行。

    • 跨域问题:在部署H5平台时,可能会遇到跨域访问的问题。可以通过在服务器端设置响应头的方式解决跨域问题,或者使用相关代理技术进行解决。

    • 性能优化:在上线之前,可以进行一些性能优化,如合并压缩代码、减少HTTP请求、使用CDN等,以提高应用的加载速度和用户体验。

    • 安全性:确保在上线之前对项目进行安全审查,以避免潜在的安全漏洞,比如输入验证、防止XSS攻击等方面的处理。

总结起来,上线和部署uni-app项目需要进行编译、配置平台信息、解决相关问题(如跨域问题、性能优化、安全性等),然后按照各个平台的要求进行上传和发布。确保项目能够在指定平台上正常运行,并保证用户的访问安全和良好的用户体验。

14.描述一下 uni-app 和其他前端框架(如Vue、React)之间的区别。

Uni-app 是一个跨平台的前端框架,它可以帮助开发者使用 Vue.js 编写一次代码,然后将其编译成多个平台上运行的应用程序,如微信小程序、H5 应用、iOS 和 Android 原生应用等。

与 Vue 和 React 这样的前端框架相比,Uni-app 具有以下区别:

  1. 跨平台支持:Uni-app 提供了一套统一的 API,可以同时在多个平台上运行,如微信小程序、支付宝小程序、百度智能小程序等。开发者只需编写一次代码,即可发布至不同平台,减少了重复开发的工作量。

  2. 组件库兼容性:Uni-app 默认集成了一套基于 Vue 的组件库,但与原生的 Vue 和 React 组件库不完全兼容。开发者需要使用 Uni-app 提供的组件库或适配好的第三方组件库,以确保在多个平台上的表现一致。

  3. 性能优化:Uni-app 在代码编译时会对代码进行优化,提供了各种性能调优的选项。这些优化可以帮助开发者在跨平台开发时获得更好的性能和用户体验。

  4. 扩展能力:Uni-app 提供了丰富的扩展能力,允许开发者使用原生插件、扩展插件和自定义组件等方式增强应用程序的功能和界面。

总的来说,Uni-app 是一个具有跨平台能力的前端框架,可以帮助开发者使用一套代码在不同平台上构建应用程序。它在某些方面与 Vue 和 React 相似,但也有一些独特的特性和限制。开发者可以根据项目需求和技术要求选择适合自己的前端框架。

15.请解释一下 uni-app 的跨平台原理是如何实现的。

Uni-app 的跨平台原理是通过基于 DCloud 开发的运行时框架来实现的。Uni-app 的运行时框架将开发者编写的代码转换为不同平台上的原生代码,以在各个平台上运行。

具体来说,Uni-app 的跨平台原理包括以下几个主要步骤:

  1. 代码编译:开发者使用 Vue.js 编写 Uni-app 的代码,包括页面、组件、样式和逻辑等。Uni-app 提供了一个专门的编译器,将开发者的代码转换为各个平台上的原生代码。

  2. 运行时框架:Uni-app 在各个平台上都内置了一个运行时框架,在应用程序运行时负责解析和执行编译后的代码。该框架充分利用了各个平台的特性和能力,以保证应用程序在不同平台上具有良好的性能和用户体验。

  3. 平台适配:Uni-app 的运行时框架会根据当前运行平台的特性对代码进行适配。例如,对于微信小程序平台,Uni-app 的运行时框架会将代码转换为微信小程序支持的原生代码,并与微信小程序的 API 进行交互。

  4. 打包发布:开发者在完成开发后,可以使用 Uni-app 的打包工具将应用程序打包成不同平台上的安装包或上传至各个平台的开发者平台进行发布。

通过上述步骤,Uni-app 实现了一套代码编写,多平台运行的跨平台能力。开发者只需编写一次代码,然后通过编译和适配,即可在不同平台上运行应用程序。这种跨平台原理可以显著减少开发成本和工作量,提高开发效率。

16.uni-app 中如何进行本地存储和缓存管理?

在 uni-app 中进行本地存储可以使用 uni.storage API,该API提供了以下方法:

  1. setStorage({key, data, success, fail, complete}):用于将数据存储在本地缓存中,其中 key 为键名,data 为键值,success、fail 和 complete 均为回调函数。

  2. getStorage({key, success, fail, complete}):用于从本地缓存中异步获取指定的 key 对应的内容,其中 success、fail 和 complete 均为回调函数。

  3. removeStorage({key, success, fail, complete}):用于从本地缓存中异步移除指定的 key,其中 success、fail 和 complete 均为回调函数。

除了 uni.storage API,uni-app 还提供了以下一些本地缓存管理的方法:

  1. uni.getStorageSync(key):同步获取本地缓存中指定 key 的内容。

  2. uni.setStorageSync(key, data):同步将数据存储在本地缓存中。

  3. uni.removeStorageSync(key):同步从本地缓存中移除指定的 key。

  4. uni.clearStorageSync():同步将本地缓存全部清空。

需要注意的是,uni.storage 和 uni.get/set/remove/clearStorageSync 存储到本地的数据大小有限制,如果需要存储大量数据,可以考虑使用其他存储方式,如调用原生 API 进行存储。

此外,uni-app 还提供了一个全局的应用状态管理器 uni. s t a t e , 通 过 该 状 态 管 理 器 可 以 在 不 同 页 面 之 间 实 现 数 据 共 享 。 使 用 此 方 法 需 要 在 A p p . v u e 中 注 册 u n i . state,通过该状态管理器可以在不同页面之间实现数据共享。使用此方法需要在 App.vue 中注册 uni. state使App.vueuni.state,并在需要使用 s t a t e 的 页 面 中 引 入 即 可 。 通 过 u n i . state 的页面中引入即可。通过 uni. stateuni.state 可以方便地进行全局数据的读写操作,但需要注意在不同页面同时改变 $state 变量中的值时,可能会出现因多线程导致出现值错乱的情况,需要注意使用时的线程排队等问题。

17.请解释一下 uni-app 开发的工作原理和流程。

Uni-app 的开发工作原理和流程如下:

  1. 创建项目:首先,在使用 Uni-app 开发前,你需要安装 Node.js 和 HBuilderX(或其他支持 Uni-app 的开发工具)。然后,通过开发工具创建一个新的 Uni-app 项目。

  2. 编写代码:使用你熟悉的前端框架(如 Vue.js)和 HTML/CSS/JavaScript,在项目中编写页面、组件、样式和逻辑等。

  3. 运行和调试:在开发工具中,你可以直接在模拟器或真机上运行和调试你的应用程序。这样可以快速查看效果,并进行调试和修复。

  4. 平台适配:当你开发完成后,Uni-app 的运行时框架会将你的代码转换为不同平台上的原生代码。这意味着你编写的代码可以在微信小程序、支付宝小程序、H5 应用、iOS 原生应用和 Android 原生应用等平台上运行。

  5. 打包发布:当你准备好发布应用时,你可以通过开发工具的打包功能将应用程序打包成不同平台上的安装包。然后,你可以将这些安装包上传至各个平台的开发者平台进行发布。

Uni-app 的开发流程大致如上所述。开发者可以借助 Uni-app 提供的工具和特性,使用一套代码开发多个平台的应用程序。这样可以减少重复开发的工作量,提高开发效率,并方便应用程序的跨平台发布和管理。

18.uni-app 中如何进行性能优化?

在 Uni-app 中进行性能优化可以从多个方面入手,以下是几个常见的优化方法:

  1. 减少数据绑定:在 Vue 组件中,使用双向数据绑定或{{}}插值表达式会增加响应式系统的负担。因此,尽量减少数据绑定的使用,尤其是在频繁更新的列表中。

  2. 避免频繁触发渲染:尽量减少对组件进行频繁的数据改变操作,例如避免在循环中直接修改数组或对象中的某个成员,可以通过创建新的对象或数组来更新数据。

  3. 使用动态组件:对于需要频繁切换的组件,可以使用动态组件,只渲染当前需要展示的组件,避免不必要的渲染,提高性能。

  4. 合理使用条件渲染:使用 v-if 和 v-show 指令进行条件渲染时,可以根据实际情况选择合适的指令,v-if 在条件不满足时将不渲染整个组件,v-show 则只是控制 CSS 中的 display 属性。

  5. 避免过度使用计算属性:计算属性虽然方便,但如果计算逻辑较复杂或计算频率较高,可能会导致性能下降。在这种情况下,可以考虑使用 methods 方法进行替代。

  6. 懒加载和分包加载:对于页面中的图片、视频或其他资源,可以考虑使用懒加载技术,当资源在可视区域内时再进行加载。另外,对于较大的应用程序,可以使用分包加载将不同模块拆分成不同的包,按需加载,减少首次加载的时间。

  7. 合理使用列表渲染指令:对于较长的列表,使用 v-for 指令时可以添加 key 值,以便框架可以正确地跟踪每个项的变化,减少重新渲染的时间。

  8. 优化网络请求:合理使用缓存、批量请求等技术来减少网络请求次数,减轻服务器和客户端的压力,提高性能。

以上是一些常见的性能优化方法,具体优化策略还需要根据实际项目情况进行评估和调整。开发者可以使用性能分析工具、打包体积分析工具等来辅助优化工作,以提供更好的用户体验。

19.uni-app 中遇到的常见问题及解决方法有哪些?

在 Uni-app 开发中,常见问题及解决方法如下所示:

  1. 样式不生效:确保样式文件正确引入,检查样式名是否正确、样式规则是否符合语法,使用开发工具的样式调试工具查看样式是否被覆盖。

  2. 页面渲染异常:检查页面结构是否嵌套正确,判断组件绑定的数据格式是否正确,确保数据加载异步操作正确处理,可以使用开发工具的数据调试工具查看数据是否正确。

  3. 小程序限制:在转换成小程序版本后,由于小程序的限制,部分元素或样式可能不生效。可以通过小程序的替代方案来解决,如使用 image 标签代替 background-image,使用 iconfont 代替自定义字体等。

  4. 打包体积过大:检查是否引入了不必要的库、组件或图片等,可通过分包加载来减小打包体积。还可以考虑按需引入第三方库,压缩代码,减少冗余代码等。

  5. 远程调试问题:如果需要在手机上进行远程调试,确保手机和电脑在同一局域网下。若无法满足条件,可以使用微信开发者工具的远程调试功能,将手机与开发者工具连接,进行调试。

  6. 适配问题:由于不同平台具有不同的屏幕分辨率或比例,在开发过程中可能会出现适配问题。可以使用 vw/vh、flex 布局、媒体查询等方式进行跨平台适配。

  7. 网络请求问题:不同平台的网络协议可能有所不同,可以使用平台原生的网络请求接口或使用封装好的网络请求库来解决。同时,确保网络请求的相关配置正确无误。

  8. 发布问题:在发布应用程序时,可能会遇到配置错误、密钥失效或网络故障等问题。要仔细检查应用程序的配置信息,确保密钥有效,并在合适的时间进行发布,避免敏感时刻发布。

针对不同的问题,可以通过查阅官方文档、参考社区中的解决方案和经验等,结合具体情况来解决。同时,也可以利用开发工具提供的调试工具和分析工具来辅助定位和解决问题,以提高开发效率和代码质量。

20.uni-app 中的生命周期函数有哪些,分别在什么时候触发?

在 Uni-app 中,常用的生命周期函数如下:

  1. beforeCreate:在实例初始化之后,数据观测(data observer)和事件配置(event/event-binding)之前触发。

  2. created:在实例创建完成后立即触发,此时可以访问数据、方法、生命周期函数等,但无法操作 DOM。

  3. beforeMount:在组件挂载之前被调用,此时还未渲染模板(template)。

  4. mounted:在页面挂载后调用,此时组件已经渲染成真实的 DOM,并插入到页面中。

  5. beforeUpdate:在组件更新之前被调用,在数据更新之前,可以在此处进行准备工作。

  6. updated:在组件更新之后被调用,此时页面已经被重新渲染。

  7. activated:在页面被激活时调用,对应 App.vue 中的 onShow 生命周期函数。

  8. deactivated:在页面被隐藏时调用,对应 App.vue 中的 onHide 生命周期函数。

  9. beforeDestroy:在组件销毁之前被调用,此时组件仍然可用。

  10. destroyed:在组件销毁之后被调用,此时组件已经被销毁,清空了所有数据和事件。

在 Vue 的语法基础上,Uni-app 还新增了一些生命周期函数:

  1. onLaunch:小程序启动时调用,对应 App.vue 中的 onLaunch 生命周期函数。

  2. onShow:小程序启动或从后台进入前台时调用,对应 App.vue 中的 onShow 生命周期函数。

  3. onHide:小程序从前台进入后台时调用,对应 App.vue 中的 onHide 生命周期函数。

  4. onError:小程序发生错误时调用,对应 App.vue 中的 onError 生命周期函数。

请注意,针对不同的平台,生命周期函数的触发时机和调用方式可能会有所不同,具体的使用和注意事项可以参考官方文档或相关资源。

21.JS中的内存泄露是什么意思?

内存泄漏指的是在程序运行过程中,分配的内存无法被及时释放或回收,导致内存占用不断增加的情况。在 JavaScript 中,内存泄漏是指某些对象占用的内存空间没有被正确地释放,而这些对象实际上不再被程序使用。

内存泄漏可能发生在多种情况下,下面是一些常见的内存泄漏情况:

  1. 无意的全局变量:将变量定义为全局变量时,即使在不再需要它们时,它们也会继续存在于内存中,导致内存泄漏。

  2. 被遗忘的定时器或回调函数:当设置了定时器或回调函数后,如果忘记清除它们,那么它们会一直保持活动状态,直到浏览器关闭或手动清除。

  3. 闭包:闭包可以在内部函数中引用外部函数的变量,如果闭包的生命周期超过了需要,那么外部函数中的变量将无法被释放,导致内存泄漏。

  4. DOM 引用:如果从 DOM 中移除对象时,没有正确地删除对该对象的引用,那么这个对象可能无法被垃圾回收器回收。

  5. 循环引用:当两个或多个对象相互引用,形成无法被访问的闭环时,它们将无法被垃圾回收器回收。

内存泄漏会导致程序占用越来越多的内存,最终可能导致程序运行缓慢、崩溃或响应变慢。要避免内存泄漏,可以采取以下措施:

  • 及时释放不再使用的变量和对象。
  • 确保定时器、回调函数和事件监听器等被及时清除。
  • 避免创建不必要的全局变量。
  • 在使用闭包时小心管理变量的作用域。
  • 在处理 DOM 对象时,避免意外地保留对其的引用。

通过合理的内存管理和注意细节,可以减少内存泄漏的发生,并确保 JavaScript 程序的性能和稳定性。

22.JS中的垃圾回收机制是什么意思?

在 JavaScript 中,垃圾回收(Garbage Collection)是一种自动的内存管理机制,用于自动检测和释放不再使用的对象所占据的内存空间。它的主要目标是减轻开发人员的内存管理负担,防止内存泄漏和过度内存使用。

JavaScript 的垃圾回收机制工作原理如下:

  1. 标记-清除(Mark and Sweep):这是最常见的垃圾回收算法。它通过标记所有活动对象,并清除未被标记的对象来回收内存。垃圾回收器从称为“根”的一组全局对象开始,然后遍历所有可访问的对象并标记它们。然后清除阶段将检查所有未被标记的对象并释放它们占用的内存。

  2. 引用计数(Reference Counting):该算法会为每个对象维护一个引用计数器。当对象被引用时,引用计数器加一;当引用被移除或销毁时,引用计数器减一。当引用计数器达到零时,该对象不再被使用,因此可以被垃圾回收器回收。然而,引用计数算法无法解决循环引用问题,它可能导致不再被需要的对象无法被回收。

  3. 分代回收(Generational Collection):该算法基于一个观察:大部分对象都是生命周期较短的,而只有一小部分对象是长时间存活的。因此,该算法将内存分为不同的代(Generation),对象被分配到不同的代中。新创建的对象分配到年轻代,当年轻代垃圾回收满足一定条件后,会将存活的对象晋升到老年代。老年代的垃圾回收相对较少频繁。

垃圾回收机制通过持续监测内存中的对象使用情况,自动回收不再需要的内存,以提供更好的性能和避免内存泄漏。尽管具体的实现和行为可能因 JavaScript 引擎而异,但现代的 JavaScript 引擎都会使用一种或多种垃圾回收算法来帮助开发人员管理内存。开发人员通常无需显式地进行内存回收,垃圾回收器会在适当的时候自动执行。

23.栈和堆是怎么理解的?

在 JavaScript 中,数据类型可以分为基本数据类型和引用数据类型。栈和堆是内存中存储不同类型数据的两个主要区域。下面是它们的区别:

  1. 栈(Stack):栈是一种存储方式,用于存储基本数据类型的值和引用数据类型的引用(地址)。当基本数据类型的值被创建时,它们会直接存储在栈中,而引用数据类型的引用则会存储在栈中。栈采用后进先出(LIFO)的原则,也就是说最后进栈的数据首先被移除。

    基本数据类型包括:undefined、null、boolean、number 和 string。它们的值存储在栈中,且占据固定大小的内存空间。

  2. 堆(Heap):堆是另一种存储方式,用于存储引用数据类型的值。引用数据类型包括对象、数组和函数等。在堆中,引用类型的值保存在内存中的任意位置,并在栈中存储对其引用的指针。引用数据类型的大小可以动态调整。

    当创建一个对象时,它将存储在堆中,而在栈中会分配一个指向该对象的指针。通过将指针存储在栈中,可以找到堆中的对象。

需要注意以下几点:

  • 基本数据类型是按值访问的,因为它们直接存储在栈上,可以直接访问和操作它们的值。
  • 引用数据类型是按引用访问的,因为它们存储在堆上,而栈上只存储对它们的引用,需要通过引用找到堆中的实际值。
  • 基本数据类型的复制是独立的,一份数据的修改不会影响另一份数据。
  • 引用数据类型的赋值是通过引用传递的,即改变一个对象的属性会影响所有引用该对象的地方。

理解这些概念对于正确的变量使用和内存管理非常重要,尤其是在处理大量数据和性能敏感的代码时。

24.解释一下 JavaScript 中的执行上下文

JavaScript 中的执行上下文指的是代码执行时的环境,包括变量、函数、对象等相关信息的集合。可以理解为是一个函数或者一个代码块执行时创建的一个环境对象,这个环境对象会包含当前执行函数或者代码块的所有变量、函数等信息。执行上下文在 JavaScript 引擎中非常重要,它决定了代码执行时变量和函数的作用域、定义在函数内的变量如何被访问和修改,并协助处理函数调用和变量的生命周期。

JavaScript 的执行上下文是一个栈的数据结构,即执行栈(Execution Context Stack);当前代码的执行上下文总是位于执行栈的栈顶。在创建执行上下文时,JavaScript 引擎将在内存中创建一个新的执行环境,并将其压入执行栈顶部,以便在函数执行时利用这个环境。当函数执行完成后,执行栈会将执行上下文弹出,并销毁该执行上下文中的所有变量和函数,将控制权交还给上一个执行上下文。

执行上下文包含以下三个重要的属性:

  1. 变量对象(Variable Object):变量对象包含该上下文中定义的所有变量、函数声明和函数的形参,并且前两者的初始化不是在当前上下文,而是在代码编写阶段通过编译器完成。
  2. 作用域链(Scope Chain):它是一个指向全局上下文的链,在函数执行时使用,方便获取函数(或者变量)的作用域并准确访问其父级变量和函数声明,它由当前上下文的变量对象和所有父级上下文的变量对象构成。
  3. this 值:指向当前函数执行时所处的对象,this 值在函数执行时确定,由调用方式决定。

JavaScript 中的执行上下文在每次函数执行时都会被创建,具有独立的作用域、变量对象、作用域链和 this 值等属性,并通过执行栈(Execution Context Stack)来管理函数执行和控制变量的生命周期。

25.如何理解面向对象编程?

"在JavaScript中,面向对象编程是一种将程序设计的核心组件抽象为对象,并通过对象之间的交互来完成任务的编程范式。JavaScript中的面向对象编程有以下几个特点:

首先,JavaScript中使用原型继承机制来实现对象之间的继承关系。每个对象都有一个原型对象,并通过原型链来查找和继承属性和方法。这种灵活的继承方式使得对象可以轻松地共享和继承代码,提高代码的重用性。

其次,JavaScript中的封装特性通过函数和闭包来实现。我们可以使用函数来创建私有变量和方法,并且通过闭包来隐藏和保护这些私有性,避免直接访问和修改。这样可以确保数据的安全性和封装性,同时也可以提供公共的接口供外部使用。

另外,JavaScript的动态类型特性使得它支持多态的概念。也就是说,同一个方法名可以根据对象的实际类型来选择调用不同的方法实现。这种灵活性使得我们能够根据具体情况来处理不同类型的对象,提高代码的灵活性和可扩展性。

在实际项目中,我也应用了面向对象编程的技术来提高代码的可维护性和扩展性。比如,通过封装数据和方法,我可以确保数据的安全性,并提供清晰的接口供其他模块使用。使用继承和多态机制,我可以通过基类创建不同的子类,实现代码的重用和扩展。另外,我还运用了一些常见的面向对象设计模式,如工厂模式、单例模式和观察者模式,来解决复杂的问题。

总结起来,JavaScript中的面向对象编程通过原型继承、封装、多态和继承等特性来提供灵活且有力的编程范式。在实际项目中,我通过运用这些特性来提高代码的可维护性、可扩展性和重用性,从而有效地解决了很多实际问题。"

注意,在回答过程中,要以清晰的语言表达你的观点,并结合适当的示例来支持你的回答。同时,也展示你在实际项目中如何应用面向对象编程的技术,这会给面试官一个更具体的印象。

26.http是如何实现缓存的?

HTTP 协议本身并没有直接实现缓存的机制,但它提供了一些用于缓存的头部字段和指令,这些字段和指令告诉浏览器如何缓存响应,以及如何使用缓存。

以下是一些在 HTTP 缓存中常用的头部字段和指令:

  1. Cache-Control: 这是最常用的缓存控制头部字段,它可以指定缓存的行为。常见的指令有:

    • public: 响应可以被任何缓存保存;
    • private: 响应只能被浏览器缓存,不能被共享缓存如 CDN 缓存等保存;
    • no-cache: 缓存必须先发送到服务器进行验证;
    • max-age: 缓存最大有效时间,以秒为单位;
    • s-maxage: 代理服务器缓存的最大有效时间。
  2. Expires: 这个头部字段指定了响应的过期时间,是一个绝对时间点,过了这个时间点后缓存将被认为是过期的。

  3. ETag: 这个头部字段用于识别唯一的响应版本。当服务器返回一个响应时,会给响应一个 ETag 值,浏览器在发送下一次请求时,会将此值作为 If-None-Match 头部字段的值发送给服务器。服务器可以使用此值来判断响应是否发生了变化。

  4. Last-Modified: 这个头部字段指定了资源的最后修改时间。服务器在返回一个响应时,会在 Last-Modified 头部字段中告诉浏览器资源的最后修改时间。浏览器可以在下一次请求中使用 If-Modified-Since 头部字段将此时间发送给服务器,服务器判断资源是否有更新。

结合上述头部字段和指令,浏览器可以根据缓存策略,判断是否从缓存中获取响应或从服务器重新请求资源。如果缓存可用且有效,浏览器将直接使用缓存中的响应,否则会向服务器发送请求,服务器可以根据请求头部字段的值来判断是否需要返回完整的响应或仅返回一个返回码表示资源未变化。

值得注意的是,缓存的实际行为受到浏览器和服务器的支持程度以及缓存策略的影响,因此缓存的行为可能因不同的环境而有所差异。

27.GET和POST区别

GET和POST是HTTP协议中的两种不同的请求方法,它们在以下几个方面有所区别:

  1. 安全性:GET请求是幂等和安全的,意味着它只用于获取数据,不会对服务器上的资源产生副作用。POST请求用于向服务器提交数据,可能会对服务器上的资源产生副作用,因此在安全性上相对较低。

  2. 数据传输位置:GET请求将数据附加在URL的查询参数中,而POST请求将数据放在请求体中。因为数据在URL中传输,所以GET请求有长度限制,而POST请求没有固定的长度限制。

  3. 数据类型和可见性:GET请求将数据作为URL的一部分发送,数据对于用户可见,也可能被浏览器历史记录、服务器日志等记录下来。POST请求将数据放在请求体中,对用户来说不可见,也不会被记录在浏览器历史记录中。

  4. 缓存:GET请求可以被浏览器缓存,而POST请求默认情况下不能被缓存。

  5. 使用场景:GET请求适合用于读取数据、获取资源,不会修改服务器上的数据。POST请求适合用于提交、更新或删除数据,可能会对服务器上的数据进行修改。

需要注意的是,根据HTTP规范,GET请求不应该对服务端产生副作用,即使某些实际应用中可能会使用GET请求来修改数据,但并不符合规范。因此,为了符合设计原则和安全性,推荐在设计中遵循GET和POST请求的使用场景。

28.TCP和UDP有哪些区别

TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)是传输层协议中两种不同的协议,它们之间的主要区别如下:

  1. 应用场景:TCP协议主要用于传输可靠的数据流,适合于要求数据完整性、可靠性和高可用性的应用场景。UDP协议则更适合需要实时传输数据的应用场景,如视频、音频等流媒体传输。

  2. 连接状态:TCP是面向连接的协议,即在通信之前需要先建立连接,包括三次握手过程和四次挥手过程,来保证数据传输的可靠性。UDP则是非连接的协议,即可以直接发送数据包,不需要建立和断开连接。

  3. 数据传输方式:TCP使用流式传输方式,数据在传输过程中会缓存到缓存区中,等待后续数据一同传输,确保数据按顺序传输,保证数据的可靠性。UDP则使用数据报传输方式,每个数据包之间相互独立,发送和接收之间没有顺序控制。

  4. 包大小:TCP目前通常使用的数据包大小为MSS(maximum segment size),即每次可以传输的最大数据量,通常被限制在MTU(maximum transmission unit)以下。而UDP则没有限制数据包的大小,通常会根据应用场景和网络状况而设置合适的数据包大小。

  5. 拥塞控制:TCP具有拥塞控制机制,会根据网络状况和反馈信息动态调整发送和接收数据的速率,保证网络质量和数据可靠性。UDP则不提供拥塞控制机制,数据发送时不进行检查,可能会导致网络拥塞和数据丢失。

总体而言,TCP和UDP协议的选择应根据具体使用场景来确定,需要权衡数据传输的可靠性、响应时间和网络质量等因素。

29.浏览器从输入url到渲染页面,发生了什么?

  1. URL解析:浏览器首先解析输入的URL,将其分解成协议、主机名、端口、路径等组成部分。

  2. DNS解析:浏览器通过DNS(Domain Name System)将主机名解析为IP地址,以便建立与服务器的连接。如果有缓存,则直接返回缓存的IP地址;否则,进行DNS查询并获取IP地址。

  3. 建立连接:浏览器使用TCP协议与服务器建立连接。通过三次握手完成连接的建立,确保双方都能正确地通信。

  4. 发起HTTP请求:浏览器发送HTTP请求,包括请求方法(GET、POST等)、请求头部(常见的如用户代理、Cookie等)、请求体(对于POST请求)等信息,并将URL和其他数据发送给服务器。

  5. 服务器处理:服务器接收到请求后,根据请求的URL和其他信息,处理请求,在服务器上查找所需的资源,并生成HTTP响应。

  6. 响应返回:服务器将生成的HTTP响应返回给浏览器。响应包括响应状态码、响应头部(包括服务器类型、内容类型等)、响应体(HTML、CSS、JavaScript等)等信息。

  7. 接收响应:浏览器接收到服务器返回的响应,开始解析响应头部和响应体。根据响应的内容类型,浏览器会采用对应的渲染引擎进行进一步的处理。

  8. 解析HTML:浏览器解析HTML标记,构建DOM(Document Object Model)树,将HTML文档转换为树形结构。

  9. 加载资源:浏览器解析HTML过程中,会发现需要加载其他资源,如CSS文件、JavaScript文件、图像等。浏览器会再次发送请求来获取这些资源。

  10. 渲染页面:浏览器根据DOM树、CSS样式和JavaScript计算,渲染页面,将所有的内容显示在用户界面上。

  11. JavaScript执行:如果页面中存在JavaScript代码,浏览器会对其进行解析和执行,根据脚本逻辑修改DOM树和样式,实现页面的动态效果和交互。

  12. 页面加载完成:当所有资源都被加载和执行完成后,页面加载过程结束,用户可以与页面进行交互。

需要注意的是,这个过程是一个高度简化的描述,实际过程中可能会涉及更多细节,并且各种因素(如带宽、网络延迟、服务器性能等)会对整个过程产生影响。

30.什么是设计模式?

在 JavaScript 中,设计模式是指解决常见问题的可重复使用的解决方案或经验。这些设计模式帮助开发人员编写可维护、可扩展和高效的代码。

以下是一些常见的设计模式及其示例:

  1. 工厂模式:
    工厂模式用于创建对象的实例,根据不同的参数或条件返回不同类型的对象。例如,一个图形工厂可以根据输入的形状类型返回不同的图形对象,如圆形、矩形、三角形等。

  2. 单例模式:
    单例模式确保在整个应用程序中只有一个实例存在。例如,一个日志记录器类可以使用单例模式,以便在应用程序的各个部分中共享相同的日志实例。

  3. 观察者模式:
    观察者模式用于在对象之间建立一对多的依赖关系。当一个对象的状态发生改变时,它会通知所有依赖于它的对象。例如,一个新闻发布订阅系统中,订阅者将订阅某个主题,并在该主题有新消息时接收通知。

  4. 原型模式:
    原型模式通过克隆现有对象来创建新对象,而不是通过实例化来创建。这种方式可以减少对象创建的开销。例如,一个原型对象包含了一些默认值,当需要创建新对象时,可以通过克隆原型对象并修改特定的属性来创建新对象。

  5. 策略模式:
    策略模式允许在运行时根据需要选择不同的算法或行为。这种模式可用于根据用户选择不同的支付方式进行付款、根据不同的浏览器环境选择不同的渲染策略等。

这些设计模式是开发人员在开发过程中的指导,可以提高代码的可读性、可维护性和可重用性。根据不同的场景和需求,选择适合的设计模式有助于编写更优雅和高效的 JavaScript 代码。

31.vue 中使用了哪些设计模式?

  1. 观察者模式(Observer Pattern):Vue.js 利用观察者模式实现了数据双向绑定。通过使用响应式的数据劫持,当数据发生变化时,自动更新相关的 DOM 元素。Vue.js 中的观察者模式由 Vue 实例、渲染函数和 Watcher 组件等组成。

  2. 发布-订阅模式(Publish-Subscribe Pattern):Vue.js 的事件机制基于发布-订阅模式。Vue 实例可以触发事件并监听事件,允许组件之间以松耦合的方式进行通信。Vue 使用 emit 方法用于发布事件,使用 on 方法用于订阅事件。

  3. 工厂模式(Factory Pattern):Vue.js 中的组件系统充分利用了工厂模式。通过使用 Vue.extend() 方法创建组件构造函数,并使用 new 关键字实例化组件,从而生成组件实例。这种方式简化了组件的创建过程。

  4. 适配器模式(Adapter Pattern):Vue.js 的计算属性和监听属性实现了适配器模式。计算属性允许开发者创建派生属性,其值基于其他响应式属性的值计算得出。监听属性允许开发者监听数据对象的属性变化,并在变化时执行相关操作。

  5. 单例模式(Singleton Pattern):Vue.js 的全局事件总线(Global Event Bus)模式采用了单例模式。通过创建一个全局的 Vue 实例作为事件总线,可以在任何组件中通过 $emit() 触发事件,或者通过 $on() 监听事件。

这些设计模式的使用使得 Vue.js 可以提供高效、灵活和易用的开发体验,同时提供了一种优雅的方式来处理复杂的数据绑定和组件间通信。

32.keep-alive 缓存,在beforDestroy 还会执行吗?

在 Vue.js 中使用 keep-alive 组件可以用于缓存组件的状态,以避免组件被销毁和重新创建。keep-alive 组件的 beforDestroy 钩子函数不会在缓存的组件被销毁时执行。

当一个使用 keep-alive 包裹的组件离开视图后,它不会被销毁,而是被缓存起来。Vue.js 会将缓存的组件暂时从组件树中移除,但实例对象本身并不被销毁。这意味着 beforDestroy 钩子函数不会在这种情况下执行,因为组件实例仍然存在。

beforDestroy 钩子函数只在组件实例被销毁之前被调用,而在 keep-alive 中缓存的组件实例并没有被销毁,只是被隐藏起来。当缓存的组件再次进入视图时,Vue.js 会重用该组件实例,而不是重新创建新的实例,这样可以保留组件的状态。

如果你需要在组件被缓存时执行一些清理操作,可以使用 activated 钩子函数。activated 钩子函数在缓存的组件被激活并进入视图时执行。与之相对应的是 deactivated 钩子函数,在缓存的组件离开视图时执行。

总结起来,beforDestroy 钩子函数在使用 keep-alive 缓存时不会执行,因为组件实例没有被销毁。如果需要在组件被缓存时执行清理操作,可以使用 activated 钩子函数。

33.JS有哪些模块规范?

  1. CommonJS:CommonJS 是一个服务器端的模块规范,主要用于 Node.js。它使用 require 导入模块,使用 module.exportsexports 导出模块。在 CommonJS 中,每个文件都被视为一个独立的模块。

  2. AMD (Asynchronous Module Definition):AMD 是一个浏览器端的模块规范,主要用于异步加载模块。它通过使用 define 定义模块,使用 require 异步加载模块。AMD 使用回调函数来处理模块的依赖加载。

  3. UMD (Universal Module Definition):UMD 是一种通用的模块规范,旨在兼容 CommonJS 和 AMD 规范。它提供了一种兼容多种环境(包括浏览器和 Node.js)的模块定义方式。

  4. ES6 Modules (ESM):ES6 Modules 是 ECMAScript 2015 (ES6)新增的模块规范。它使用 import 导入模块,使用 export 导出模块。ES6 Modules 是静态的,意味着导入和导出的模块在代码解析阶段就已确定,而不是在运行时动态加载。

这些模块规范在不同的环境和场景中都有应用。CommonJS 适用于服务器端模块化开发,AMD 适用于浏览器端异步加载模块,而 ES6 Modules 则是目前最新的 JavaScript 模块规范,被广泛用于现代的 JavaScript 项目中。此外,UMD 则提供了通用性的兼容解决方案,以适应不同的模块规范和环境。

34.谈谈你对模块化开发的理解?

模块化开发是一种软件开发方法论,主要的思想是将复杂的系统划分成小型、独立、可维护的模块,模块之间具有独立性和可重用性。每个模块都有自己的接口,通过这些接口来与其他模块进行交互。

在 JavaScript 中,模块化开发早期没有明确的标准或约定,通常使用全局变量或命名空间来实现模块化。随着 CommonJS、AMD、UMD 和 ES6 Modules 等模块规范的出现,开发者可以更加方便地进行模块化开发。这些模块规范标准化了 JS 模块的定义、导出、引用和加载机制,通过规范化编写的模块可以跨平台、跨环境地被使用。

模块化开发的好处包括:

  1. 提高代码复用性:将功能划分到模块中,可以避免代码重复,提高了代码复用性。
  2. 提高代码可维护性:模块的独立性使得代码更容易被维护和升级,同时也方便了代码重构。
  3. 改善团队协作:模块化开发可以使多人协作开发变得更加容易,可以通过模块清晰地划分工作范围,减少代码冲突。
  4. 提高应用性能:模块化开发可以使得应用在需要时动态地加载代码模块,而不需要加载整个应用。这样可以提高应用的启动速度、降低服务器负载等。

总之,模块化开发是一种高效、方便、规范化的软件开发方法,变得越来越受欢迎,让 JavaScript 变得更加优秀。

35.什么是函数柯里化

函数柯里化(Currying)是一种将多个参数的函数转变为一系列只接受单个参数的函数的技术。柯里化的核心思想是将原函数的参数逐个传入,并返回中间函数,每次返回的中间函数都接受一个参数,最终返回结果。

在柯里化中,一般会定义一个接受部分参数的函数,该函数返回一个新的函数来接收剩余的参数。这个过程可以连续进行,直到所有参数都被传入为止。

下面是一个使用 JavaScript 实现的柯里化的例子:

function add(a) {
  return function(b) {
    return a + b;
  }
}

const addTwo = add(2); // 返回一个接受一个参数的函数
console.log(addTwo(3)); // 输出 5

在这个例子中,add 函数接受一个参数 a,并返回一个接受参数 b 的函数。通过这种方式,我们可以先传入一个参数 2,得到一个新的函数 addTwo,然后再传入参数 3,得到最终结果 5

函数柯里化的主要优点包括:

  1. 代码复用:通过柯里化可以实现将具有相似功能的函数抽象成一个函数模板,并根据不同的参数生成特定的函数,减少了重复编写相似代码的工作。
  2. 参数灵活性:柯里化允许我们根据需要逐渐传递参数,这对于处理可变参数的函数非常有用,可以提高代码的可扩展性和灵活性。
  3. 提前预置:通过柯里化可以提前预置一部分参数,生成一个新的函数,使得在后续使用时更加方便,特别适用于多次使用同样的参数的情况。

需要注意的是,函数柯里化并不是必须要使用的技术,它在一些特定的场景下才会被使用,如函数式编程、高阶函数等。柯里化可以提高代码的可读性和可维护性,但也会增加代码的复杂性,需要在实际应用中慎重使用。

36.如何提高前端代码的可维护性和可拓展性?

提高前端代码的可维护性和可拓展性是一个重要的目标,下面是一些方法可以帮助实现这个目标:

  1. 遵循设计原则和模式:使用合适的设计原则和设计模式,例如单一职责原则、开闭原则、依赖倒置原则等,可以使代码更加清晰、可扩展和易维护。
  2. 模块化开发:使用模块化的开发方式,将代码划分为各个独立的模块,有助于降低耦合度和依赖性,方便测试和维护。
  3. 使用合适的命名约定:命名规范和一致的命名风格有助于提高代码的可读性和可维护性。选择有意义的变量名、函数名和类名,命名要有一定的规范性,并注意使用注释来解释代码的功能和目的。
  4. 编写清晰的文档和注释:为重要的功能和复杂的代码块编写清晰的文档和注释,使其他开发人员能够快速理解代码的功能、用法和潜在问题。
  5. 使用代码规范和风格指南:遵循一致的代码规范和风格指南,可以使代码更易于阅读、理解和维护。可以使用工具如 ESLint 来自动化检查代码风格。
  6. 单元测试和自动化测试:编写单元测试和自动化测试可以提供更好的代码质量保证,降低代码修改和重构的风险,同时也能提高代码的可维护性。
  7. 持续集成和部署流程:建立持续集成和部署流程,自动化构建、测试和部署过程,可以加快开发周期,减少错误和冲突,提高代码的可靠性和可维护性。
  8. 使用合适的工具和框架:选择适合项目需求的优秀工具、框架和库,能够提高开发效率和代码质量,使得代码更易于维护和拓展。
  9. 不断学习和改进:跟踪前端技术的发展,不断学习新的语言特性、最佳实践和工具,对代码进行持续改进和重构,以提高代码的可维护性和可拓展性。

通过以上的方法和实践,可以提高前端代码的可维护性和可拓展性,减少代码维护和开发的难度,提高开发效率和代码质量。

37.你如何处理前端的异常和错误,有哪些常见的调试工具和技巧。

  1. 错误捕获与处理:在代码中使用try-catch语句来捕获可能出现的异常,并在catch块中处理错误。通过这种方式,可以避免错误导致整个应用崩溃,并同时提供错误信息用于调试和修复。

  2. 错误日志记录:对于捕获到的错误,我会将相关信息记录到错误日志中,包括错误发生的位置、错误堆栈、用户行为等。这样有利于后续跟踪和分析问题,快速定位错误原因。

  3. 异常监控与报警:使用第三方的异常监控工具,如Sentry、Bugsnag等,来实时监控并捕获前端异常。当错误发生时,这些工具会自动发送报警通知,方便快速响应问题。

  4. 调试工具:常见的调试工具有Chrome开发者工具、Firefox开发者工具等。这些工具提供了调试JavaScript、查看DOM结构、监控网络请求等功能,可以帮助快速定位和调试前端问题。

  5. 控制台打印:使用console.log()等方法,在浏览器的开发者工具控制台中输出相关信息,比如变量的值、函数的执行状态等。这可以帮助我追踪代码的执行过程,查看可能的错误或异常情况。

  6. 断点调试:在开发者工具中设置断点,以暂停代码执行。这样可以逐行或逐步调试,观察变量的值以及代码的执行流程,快速定位问题。

  7. 使用错误监测工具:一些开源库和工具,如React、Vue等,提供了错误边界机制和错误处理方法,可以帮助我们更好地处理和捕获前端异常。

总结起来,我会结合上述方法和工具来处理前端的异常和错误。通过捕获和记录错误、使用调试工具和技巧,以及使用错误监测工具等,可以快速定位和修复前端问题,提高应用的稳定性和可靠性。

38.请简述一下React和Vue的区别,并说明在什么场景下你会选择使用它们。

React和Vue是当前最流行的前端框架之一,它们有一些区别:

  1. 学习曲线:React相对来说更加灵活和自由,但学习曲线也更陡峭一些,需要学习JSX语法和生命周期函数的概念。Vue则更加容易上手,因为它提供了一套简单直观的模板语法。

  2. 生态系统:React有着非常庞大且活跃的生态系统,有许多第三方库和工具可供选择。Vue生态系统相对较小一些,但也非常成熟,并且有着非常完善的文档和社区支持。

  3. 组件化:React采用了更加灵活的组件化方式,可以更好地处理复杂的应用场景。Vue也支持组件化,但更加提倡直接在模板中编写HTML和CSS,对于简单的应用场景更加友好。

选择React的场景:

  • 复杂的应用场景,需要更好的性能和灵活性。
  • 拥有大型开发团队,需要更好的代码组织和可维护性。
  • 喜欢函数式编程和更加灵活的开发风格。

选择Vue的场景:

  • 初学者或团队中不具备大量前端开发经验的开发者。
  • 追求开发速度和便捷性,希望快速构建出漂亮的界面。
  • 喜欢模板语法或希望使用HTML和CSS直接编写组件。

综上所述,选择React还是Vue取决于项目需求、团队经验和个人偏好。无论选择哪个,都能够构建出高质量的Web应用程序。

39.如何进行跨浏览器测试,并解释一下浏览器兼容性问题的处理方法。

进行跨浏览器测试是Web开发中非常重要的一步,可以确保Web应用程序在各种常见的浏览器和设备上都能正确地工作和显示。以下是进行跨浏览器测试的一些步骤:

  1. 选择测试工具:可以选择一些专业的跨浏览器测试工具,如Selenium、BrowserStack、CrossBrowserTesting等等。这些工具可以轻松模拟各种操作系统和浏览器,确保应用程序在不同平台上都能正常工作。

  2. 手动测试:手动测试是跨浏览器测试的重要部分,可以通过自己在不同浏览器中打开应用程序或网站来检查页面是否正常显示、功能是否正常等等。

  3. 特殊注意:需要注意的一些特殊情况,如不同浏览器的不同版本之间的兼容性差异、不同的设备分辨率、不同的网络和延迟等等。

针对浏览器兼容性问题,Web开发中常见的解决方法有以下几种:

  1. 使用Polyfill:使用Polyfill(垫片)可以在较旧的浏览器中实现新的JS、CSS、HTML特性,例如通过使用babel-polyfill来支持ES6的特性。

  2. 浏览器前缀:在CSS样式中使用浏览器前缀,以适应不同的浏览器,例如-webkit-、-moz-、-o-等前缀。

  3. 优雅降级:根据不同的浏览器版本采取不同的措施,例如使用JavaScript来检测浏览器版本,并在不支持的浏览器中提供优雅降级的体验。

  4. 全局重置:为不同浏览器设置不同的重置样式。

总的来说,浏览器兼容性是Web开发中需要面临的一个重要问题,通过使用适当的解决方法来解决兼容性问题可以确保应用程序的质量和稳定性。

40.降低加载时间和优化页面性能的技巧有哪些?

  1. 延迟JavaScript加载:将JavaScript文件的加载推迟到页面的最后,或者使用async或defer属性来控制脚本的加载和执行时机,以确保首次渲染的速度不受阻塞。

  2. 压缩和合并文件:对JavaScript文件进行压缩和合并,减少文件大小和请求数量,从而减少下载时间。可以使用工具如UglifyJS等来压缩JavaScript文件。

  3. 按需加载:根据页面的需求,使用需要时才加载的技术(如懒加载或按需加载库)来延迟加载部分JavaScript代码,以减少初始加载的负载。

  4. 使用缓存:使用适当的缓存策略,让浏览器缓存已下载的文件,避免重复的网络请求。可以通过设置Cache-Control、Expires等HTTP头信息来控制缓存机制。

  5. 使用异步加载:使用异步加载的方式加载JavaScript文件,在页面加载过程中并行下载其他资源。使用动态创建

以上是一些常见的使用JavaScript降低页面加载时间和优化页面性能的技巧。根据具体项目的需求和情况,可以选择适合的优化方法来提升页面性能。

41.解释JavaScript中的闭包与内存管理的关系。

在JavaScript中,闭包(Closure)是指在一个函数内部定义的函数,且该内部函数可以访问外部函数作用域中的变量。闭包使得内部函数可以继续访问其词法环境(即声明时的作用域),即使外部函数已经执行完毕。闭包形成了一个特殊的引用关系,保留了外部函数的作用域链,使得外部函数中的变量在内部函数中仍然可用。

闭包的实现通常涉及创建一个持有外部函数作用域的引用的内部函数。这个引用会阻止垃圾回收器对外部函数的作用域进行垃圾回收,从而保留了相关变量的内存。

内存管理在JavaScript中非常重要,因为JavaScript使用自动内存管理(垃圾收集器)来分配和释放内存。垃圾收集器负责检测不再使用的对象,并自动释放其占用的内存。对于闭包,当内部函数仍然存在,并且有引用外部函数的变量时,垃圾收集器不能自动释放外部函数的作用域,因为它仍然被内部函数引用。

如果闭包没有被正确管理,就可能导致内存泄漏,即存在不再需要的变量占用内存。为了避免内存泄漏,应谨慎使用闭包,并确保及时释放闭包引用的外部函数作用域,如果不再需要。可以通过解除对闭包的引用来实现这一点,例如将闭包赋值为null。

总结来说,闭包在JavaScript中可以带来方便的编程技巧,但也需要注意合理管理内存,以避免内存泄漏。

42.说一下基本数据类型和引用数据类型有什么区别

  1. 存储方式:基本数据类型被存储在栈内存中,而引用数据类型则被存储在堆内存中。

  2. 大小不同:基本数据类型的大小是固定的,每个类型的大小都是确定的,而引用数据类型的大小是不确定的,因为它们可以动态地添加、删除属性和方法。

  3. 处理方式:基本数据类型直接以值的形式进行处理,而引用数据类型则是通过引用来处理。这意味着,当复制一个基本类型的变量时,将会创建一个新的变量并赋值,两个变量相互独立。但是,当复制一个引用类型的变量时,只会复制一个指向原始对象的指针,两个变量将指向同一个对象,改变其中一个变量的值,会影响到另外一个变量的值。

  4. 传参方式:基本数据类型作为参数传递时,传递的是值的副本,即在函数内部对参数的修改不会影响到函数外部传递的变量。而引用数据类型作为参数传递时,传递的是引用的副本,即在函数内部对参数对象的修改会影响到原始对象。

JavaScript的基本数据类型包括字符串、数字、布尔值、空值和undefined,而引用数据类型包括对象、数组、函数等。了解基本数据类型和引用数据类型的区别对于正确理解JavaScript的内存模型,以及开发和调试复杂的应用程序都非常重要。

43.JS中的栈和堆有什么区别

在JavaScript中,栈(Stack)和堆(Heap)是两种不同的内存分配区域,用于存储变量和数据。

  1. 存储方式:

    • 栈:栈是一种线性数据结构,采用先进后出(Last-In-First-Out)的原则。它通过栈指针来控制变量的分配和释放。基本数据类型和引用数据类型的值(或者说引用)可以直接存储在栈中。
    • 堆:堆是一种用于动态分配内存的内存池,采用哈希表的数据结构。引用数据类型(如对象、数组、函数)的实际值被存储在堆中,栈中存储的是该对象的引用。
  2. 内存管理:

    • 栈:栈的内存管理是自动的,通过栈指针的上移和下移来自动分配和释放内存,无需程序员手动管理。
    • 堆:堆的内存管理相对复杂,需要程序员手动分配和释放内存。JavaScript中的垃圾回收器会自动回收堆中不再使用的对象的内存。
  3. 大小:

    • 栈:栈的大小是固定的,在编译阶段就能确定。
    • 堆:堆的大小是动态变化的,需要在运行时根据实际需求动态调整。
  4. 生命周期:

    • 栈:栈中的变量具有短暂的生命周期,当函数执行完毕或者代码块执行完毕时,栈中的变量会被自动释放。
    • 堆:堆中的数据具有较长的生命周期,需要手动释放,否则会产生内存泄漏。

在JavaScript中,栈和堆的不同用途和特性使得它们各自在内存管理和存储数据方面扮演不同的角色。了解栈和堆的区别有助于理解JavaScript中的内存模型,并帮助我们编写高效、有效的代码。

44.简述HTTP协议中GET和POST的区别

HTTP(HyperText Transfer Protocol)是现今最为广泛使用的Web协议,其中,GET和POST是最常用的HTTP方法。

  1. GET方法

    • GET用于向服务器请求数据。它把请求参数(和附带的数据)包含到请求URI中,即URL后面的?号之后,参数之间用&连接。因此,GET方法会在浏览器的历史记录中留下明文记录。
    • GET方法的优点是访问简单,效率较高,可以被缓存。但请求参数长度通常有限制,且数据传输不安全,因此不适合传递敏感数据。
    • GET方法使用的场景包括:数据查询、获取资源等操作。
  2. POST方法

    • POST用于向服务器提交数据。它把请求参数(和附带的数据)包含到请求体中,并在请求头中指定请求体的类型(如application/x-www-form-urlencoded、multipart/form-data或application/json等)。
    • POST方法的优点是可以传递大量数据,而且传输数据更加安全、隐蔽(请求数据在请求体中,不会留在URL历史记录中)。但由于涉及请求体,请求的格式和规范比较复杂,因此相对于GET来说耗时间、效率低。
    • POST方法使用的场景包括:将数据存储到服务器、上传文件、提交表单等操作。

两种方法的区别主要在数据传输方式和数据的安全性方面。根据实际的需求和场景,我们可以选择使用其中一种方法,或者根据特定情况结合使用。在实际的Web开发中,通常会给出一些通用的规范和建议,以便于更好地使用HTTP协议,提升网站的性能和安全性。

45.简述HTTP和HTTPS的区别,HTTPS的作用是什么?

HTTP(HyperText Transfer Protocol)和HTTPS(HTTP Secure)都是TCP/IP协议上的应用层协议,其中,HTTP是明文传输数据的协议,而HTTPS则是SSL/TLS加密传输协议,两者主要区别在于安全性方面。

  1. 安全性

    • HTTP协议传输的内容都是明文的,网络上的窃听者可以截获这些数据并查看。而HTTPS协议通过使用SSL/TLS加密传输数据,使得数据在传输过程中不受到窃听者的威胁。
    • HTTPS的安全效果体现在两个方面:一是用于证明网站身份的数字证书,二是用于加密数据传输的SSL/TLS协议加密算法。
  2. 连接方式

    • HTTP协议是无状态连接,即每次请求和响应都是一个新的、独立的连接,不能持久化。
    • 而HTTPS协议使用了SSL/TLS协议,可在客户端和服务器之间建立一条类似管道的通道,保证数据传输安全、可靠,数据完整性得到保障。HTTPS连接是比较耗费资源的,但具有较高的安全性。
  3. 端口号

    • HTTP协议的默认端口号是80,HTTPS协议的默认端口号为443。

HTTPS的主要作用是确保浏览器与Web服务器之间的安全传输,保护用户输入的数据不被黑客窃取、篡改。HTTPS主要在以下场景中使用:

  1. 传输敏感信息:如银行账号、密码、个人身份证号等敏感信息。
  2. 电子商务网站:对于需要进行在线支付的电子商务网站,为了确保用户的支付信息安全,必须使用HTTPS。
  3. 社交网站:社交网站中用户隐私信息的保护尤为重要,如密码、个人信息等。

因为HTTPS能加密访问量和身份验证信息,避免信息被破解、串改和篡改,所以使用HTTPS可以有效提高网站或应用的安全性和信任度。

46.比较Vue 2与Vue 3之间的主要区别。

  1. 性能优化:Vue 3 中的编译器进行了重构和优化,使其具有更快的速度、更小的体积和更好的用户体验。
  2. 更好的TypeScript集成:Vue 3 对TypeScript提供了原生支持,并且可以使用新的组合式 API 编写代码。
  3. Composition API:Vue 3 引入了 Composition API,这是一种全新的API风格,可以帮助开发者更好地组织和重用代码,提高代码的可读性和可维护性。
  4. 更好的响应式控制:Vue 3 中的响应式 API 被重构,使其更加高效和稳定,包括了Proxy的实现以及使用WeakMap取代原先的Object.defineProperty进行依赖追踪等。
  5. 更好的Tree-Shaking支持:Vue 3 引入了静态树结构,使得加载只需要需要加载必要的组件。
  6. 更短的模板语法:Vue 3 通过新的编译器实现了更短的模板语法,包括了更加紧凑的模板语法和更好的错误消息展现等功能。

总体而言,Vue 3 在性能、类型支持、组件复用等方面都有了显著的提升和改进,Vue 2 用户可以考虑使用 Vue 3 并得益于其新的特性,而新用户更应当直接使用 Vue 3 以获取更好的开发体验。

47.Vue 3中引入了哪些新的特性?

  1. Composition API 组合式 API:Vue 3 中引入了组合式 API,提供了新的函数API来解决混入(mixin)的问题,使得组件代码更加模块化、可复用、易于维护和测试。

  2. Teleport(传送门):该特性允许我们将模板代码渲染到任意DOM节点上。这使得我们可以将跨层级的组件附加到指定的DOM元素上,例如模态框组件。

  3. Fragments(片段):Vue 3 支持片段,可以允许多个根元素组成的时候使用Fragment进行包裹,而不需要再在外面包裹一个无用的父级元素。

  4. 静态树和基于标记的模板编译:Vue 3 更改了模板编译器以便于Tree-Shaking,使用更先进的标记来减小打包大小,提高模板渲染性能。

  5. Proxy 取代 Object.defineProperty:Vue 3 引入了 Proxy API 来替代 Vue 2.x 中的 Object.defineProperty,以获得更好的性能和更加强大的响应式表现。

  6. 更好的TypeScript支持:在 Vue3 中加入了对 TypeScript 的原生支持,可自动推断类型和检查类型。

  7. Vite:Vite 可以基于原生 ES 模块提高开发体验,具有路由、热重载、许多插件和出色的构建速度。

总的来说,这些新特性帮助Vue 3简化了开发过程,提高了应用程序的性能、可维护性和开发体验。

48.解释Vue中的响应式原理,并描述Vue 2和Vue 3之间响应式系统的不同

Vue中的响应式原理是通过“数据劫持”和“依赖追踪”来实现的。在Vue中,当数据发生变化时,关联的视图会自动更新,这是通过Vue的响应式系统实现的。

在Vue 2中,响应式是通过Object.defineProperty()方法来实现的。具体而言,Vue在初始化时会遍历data中的属性,将它们转换为getter和setter,并在setter中触发更新相关的视图。这样,当修改data中的属性时,Vue会自动触发setter来通知相关的依赖进行更新。

而在Vue 3中,响应式系统发生了重大改变,使用了ES6的Proxy对象来实现。Proxy对象可以拦截对目标对象的访问,使得我们可以在访问和修改属性时进行自定义操作。Vue 3使用Proxy来代替Vue 2中的Object.defineProperty,以提供更好的性能和更广泛的支持。

在Vue 3中,当访问响应式对象时,会自动创建一个Proxy对象来进行代理,并在访问或修改属性时触发相应的操作。通过Proxy,Vue能够动态地捕获对对象的读取和写入行为,从而实现了更精确的依赖追踪和重新渲染。

Vue 3的响应式系统采用了更高效和灵活的依赖追踪机制,使得响应式的更新更加准确和精细。此外,Vue 3还引入了一些其他优化,如WeakMap来进行依赖追踪,提供了更好的性能和扩展性。总体而言,Vue 3的响应式系统相较于Vue 2有更好的性能和表现,提高了应用程序的效率和开发体验。

49.描述Vue中的计算属性和侦听器的区别及使用场景

计算属性(computed)是在模板中声明的属性,该属性的值根据相关的响应式数据进行计算,只有当相关的数据发生改变时,该计算属性才会重新计算。计算属性可以实现复杂的逻辑计算,因为它们具有缓存的特性,只有在相关数据发生改变时才会重新计算,从而避免了不必要的计算。使用场景:当某个属性的值需要根据其他响应式数据计算得到时,可以使用计算属性。例如,在一个购物车应用中,需要根据所有商品的数量计算出购物车中商品的总价,就可以使用计算属性来实现。

侦听器(watch)是一个观察者,用于监听特定的数据变化,并在数据变化时执行一些操作,比如异步操作、API调用等。侦听器的值通常是一个函数,在函数内部可以访问新值和旧值。使用场景:当我们需要执行异步操作或复杂的数据操作,需要监听数据变化时,可以使用侦听器。例如,在一个表单中,需要判断某个字段输入的值是否合法,如果不合法需要进行提示,就可以使用侦听器来监听这个字段的变化。如果需要调用API或其他异步操作来进行校验,就可以在侦听器中执行这些操作。

总体而言,计算属性适用于对数据进行简单的计算和处理,而侦听器适用于需要监听特定数据变化并在触发时执行异步或复杂操作的场景。

50.解释Vue3中Composition API的好处,以及它与Options API的不同之处

  1. 更好的组织代码:Composition API 通过将逻辑按功能进行组织,使得代码更加模块化、可复用和易于维护。相比之下,Options API 的开发方式是通过将功能分散在不同的选项中,容易导致代码逻辑分散和难以理解。

  2. 更灵活的逻辑复用:Composition API 提供了一系列的函数 API,让我们可以更灵活地组合逻辑功能。我们可以通过自定义函数来创建可复用的逻辑模块,并在需要时进行组合,而不需要依赖于混入(mixin)方式。这种方式可以避免混入可能带来的命名冲突和代码重复的问题。

  3. 更好的类型推导和支持:Composition API 基于函数编程的思想,代码结构和类型推导更加清晰,能够更好地与 TypeScript 配合使用。它充分利用了 TypeScript 的类型系统来捕获错误,并提供更好的开发体验。

  4. 更好的性能:Composition API 不会像 Options API 那样有潜在的重复渲染问题。由于 Composition API 使用了更强大的响应式系统,能够更准确地追踪依赖关系,从而避免不必要的重新渲染,提高应用程序的性能。

  5. 更容易进行代码迁移:对于使用 Vue 2 的开发者来说,Composition API 提供了更平滑的迁移路径。开发者可以逐步地将 Vue 2 中的代码重构为 Composition API 的方式,而不需要一次性地进行大规模的重写。这为现有的 Vue 2 项目提供了更好的可维护性和扩展性。

总体而言,Composition API 提供了更灵活、更模块化、更可维护的方式来组织和复用代码,更好地支持 TypeScript,并具有更好的性能表现。而 Options API 则是 Vue 2 中主要使用的开发方式,它的主要特点是通过选项来定义组件的不同部分,适合于开发小型简单的组件。

51.如何在Vue中实现路由鉴权?

在Vue中实现路由鉴权通常是通过路由守卫(router guard)来完成的。

具体的实现逻辑如下:

  1. 首先,在定义Vue路由时,需要将需要进行鉴权的路由添加一个标记。例如,可以在路由的meta字段中添加一个属性来表示该路由是否需要鉴权。

  2. 接着,我们需要利用Vue Router提供的路由守卫函数(router guard function)来进行路由鉴权。路由守卫是Vue Router提供的一套钩子函数,包括beforeEach、beforeResolve和afterEach等,在路由发生变化时会依次执行这些钩子函数。

  3. 在beforeEach路由守卫函数中,我们需要根据路由是否需要鉴权来进行相应的操作。如果需要鉴权,那么我们需要判断用户是否已经登录。如果没有登录,那么可以直接跳转到登录页面;如果已经登录,那么可以正常访问该路由。

  4. 如果路由不需要鉴权,那么我们可以直接放行,使其可以正常访问。

  5. 另外,我们还可以在beforeEach路由守卫函数中添加其它功能,例如记录路由变化的日志信息、处理路由参数等等。

在Vue中实现路由鉴权的逻辑就是这样,需要在Vue中定义需要进行鉴权的路由并利用路由守卫来进行鉴权判断。路由鉴权是一个相当常见的需求,这种方法可以确保用户只能访问其具有访问权限的路由,提升了系统的安全性。

52. vue中动态路由是怎么实现的?

在Vue中,动态路由是指根据不同的参数或条件生成不同的路由。

实现动态路由的逻辑如下:

  1. 首先,需要定义路由的模板或基础路径。这个模板或基础路径是一个通用的路由规则,具体的参数或条件将根据需求而变化。

  2. 根据参数或条件,动态生成具体的路由路径和对应的组件。这可以通过在Vue的路由配置文件中,使用动态路由匹配模式来实现。

  3. 当满足某种条件时,动态生成具体的路由并将其添加到路由配置中。

  4. Vue Router会根据这些动态生成的路由信息来渲染相应的组件。

动态路由的实现可以有许多方式,以下是其中几种常见的方式:

  • 使用路由参数:可以通过在路由路径中添加占位符来匹配动态参数。在路由配置中定义一个带有参数的路径,然后在组件中通过$route.params来获取参数的值。

  • 使用路由元信息(meta):可以在路由配置中的meta字段中添加额外的信息,用于根据条件动态生成路由。在路由守卫中,根据条件动态添加或修改meta字段的值,然后根据这些信息来生成具体的路由。

  • 使用函数式路由:可以将路由配置定义为一个返回路由配置对象的函数,然后根据条件来动态生成路由配置。在函数中,根据条件生成具体的路由配置,并返回该配置对象。

无论使用哪种方式,关键是在逻辑上根据不同的参数或条件生成对应的路由配置。通过动态路由,可以实现根据需求来生成不同的路由,并根据路由的不同渲染相应的组件,从而使应用具备更大的灵活性和扩展性。

53.简述Vue项目的构建流程,并解释Webpack在这个过程中的作用。

  1. 代码开发:在开发环境下,使用Vue CLI等工具创建一个Vue项目,并在该项目中编写Vue组件、页面和逻辑代码。

  2. 代码打包和编译:在开发过程中,使用ES6+语法、SCSS等技术来编写代码。在构建过程中,Webpack会将这些高级语法或语言转换为浏览器可以理解的JavaScript、CSS、HTML等文件。

  3. 模块解析和依赖管理:Vue项目中通常会使用许多第三方包、模块和插件。Webpack会根据代码中的import和require等模块化语法,解析项目代码中的模块依赖关系,并将这些模块打包到最终的生产文件中。Webpack还可以对依赖进行优化、合并和拆包,以提高项目的加载性能。

  4. 静态资源处理:在开发Vue项目中,除了JavaScript、CSS和HTML文件外,通常还包含其他静态资源如图片、字体文件等。Webpack可以通过识别这些文件的导入和使用,将它们拷贝、压缩、合并到打包输出的最终目录中。

  5. 开发服务器和热重载:在开发阶段,我们需要一个开发服务器来运行项目,并实现热重载功能,即在代码修改后实时预览变化。Webpack提供了开发服务器、文件监听和模块热替换等功能,使我们能够在开发过程中迅速地看到修改的效果。

  6. 生产环境打包:在完成开发和调试后,我们需要将项目打包为生产环境可部署的文件。Webpack会执行代码压缩、文件合并、CSS文件提取和缓存等优化操作,将最终的项目文件输出到指定目录。

Webpack在Vue项目构建过程中发挥了重要作用。它是一个现代的静态模块打包工具,能够处理各种类型的资源文件,并将它们转换为最终的输出文件。Webpack不仅能解析和处理JavaScript、SCSS等文件,还能处理并优化项目中的各种静态资源。它能够将Vue项目中的各种依赖关系解析为可执行的模块,并在构建过程中自动分析和处理这些模块之间的依赖关系。Webpack还提供了丰富的插件和配置选项,使我们能够根据项目需求进行自定义配置和优化。通过Webpack,我们能够轻松构建出高性能、可部署的Vue项目。

54.Vue.js中的单向数据流是如何工作的?

在Vue.js中,单向数据流是指数据从父组件向子组件单向流动的原则。这意味着父组件可以将数据通过props属性传递给子组件,在子组件中可以读取和使用这些数据,但不能直接修改它。子组件可以通过向父组件派发事件来通知父组件进行数据的更新。

单向数据流的工作过程可以概括为以下几个步骤:

  1. 父组件传递数据:父组件通过在子组件上使用props属性,将数据传递给子组件。props属性定义了子组件期望接收的数据和其数据类型。

  2. 子组件接收数据:子组件在接收父组件传递的数据后,可以在组件内部使用这些数据。可以通过在子组件的模板或JavaScript代码中引用props属性来访问这些数据。

  3. 子组件修改数据:子组件不能直接修改通过props传递的数据。如果子组件需要修改数据,它需要通过派发事件的方式来通知父组件进行数据更新。

  4. 父组件响应事件:父组件监听子组件派发的事件,并定义相应的方法来处理子组件的请求。在方法中,父组件可以改变数据,并将修改后的数据再次传递给子组件。

通过这种单向数据流的方式,Vue.js实现了父子组件之间的数据传递和交互。这种架构模式使得数据的流动更加可控和可追踪,减少了数据变化引起的副作用和难以调试的问题。同时,它也鼓励了组件的复用和拆分,增加了代码的可维护性和可扩展性。

55.在Vue中,如何封装和重用代码?你有哪些考虑

  1. 组件封装:将可复用的功能或UI元素封装成组件,以便在不同的页面或应用中重复使用。组件应该具有明确的职责和可配置的选项,以适应不同的需求。

  2. Mixins混入:使用Mixins可以将一些通用的逻辑和方法提取出来,然后混入到多个组件中,以实现代码的重用。但要注意混入可能带来的命名冲突和复杂性,所以使用时需要谨慎。

  3. 自定义指令:如果你有一些复用的DOM操作或行为,可以封装成自定义指令,并在需要时在组件中使用。这样可以使代码更具可读性和可维护性。

  4. 过滤器:过滤器是用于文本格式化的函数,可以在模板中使用。如果你有一些常用的文本格式化需求,可以将其封装成过滤器,并在多个组件中重用。

  5. 使用函数式组件:如果一个组件只需要根据输入参数生成一个输出结果,那么可以考虑使用函数式组件。函数式组件没有状态和实例,只是接收输入参数并返回一个渲染结果。这种组件更轻量、更容易测试和复用。

  6. 利用Vue的生命周期钩子:Vue的生命周期钩子函数提供了多个时机来操作组件,你可以在这些钩子函数中封装和重用代码。比如,在created钩子中进行一些初始设置,或在beforeDestroy钩子中进行一些清理操作。

  7. 使用工具函数和插件:如果你有一些常用的功能或常见的任务,可以将其封装成工具函数或插件,然后在需要时引入和使用。工具函数可以用来处理一些通用的逻辑,而插件可以扩展Vue的功能。

在封装和重用代码时,需要注意易用性、可维护性和可扩展性。代码应该具有清晰的接口和功能,易于理解和使用。同时,代码应该易于维护和修改,以适应未来的需求变化。此外,还要注意代码的可扩展性,使其能够应对更多的用例和变化。

56.vue项目开发过程中,是如何体现面向对象思想的?

  1. 组件化设计:Vue项目中,组件是面向对象的基本单位。使用组件化的方式可以将项目分解为多个独立的组件,每个组件具有自身的功能和状态。通过组件之间的组合、继承和多态等关系,可以实现面向对象的设计和开发。

  2. 单一责任原则:面向对象思想中的单一责任原则要求每个类或组件应该只有一个单一的责任。在Vue项目中,通过封装和组织组件,可以使每个组件只负责特定的功能,使代码更加可读、可维护和可测试。

  3. 封装和抽象:面向对象思想强调封装和抽象的概念,将数据(属性)和方法封装到类或组件中,并通过接口对外暴露适当的方法。在Vue项目中,可以使用Vue组件进行封装和抽象,将相关的数据和方法封装到组件中,并通过props和emit等机制与其他组件进行通信。

  4. 继承与多态:面向对象思想中的继承和多态提供了代码复用和灵活性。在Vue项目中,可以通过继承基础组件,创建新的子组件并覆盖或扩展父组件的功能,实现代码的重用和扩展。同时,通过多态的方式,可以在同一组件的不同实例中拥有不同的行为和样式。

  5. 依赖注入:面向对象思想中的依赖注入通过解耦依赖关系,提高代码的可测试性和可扩展性。在Vue项目中,可以使用组件的props和provide/inject来进行依赖注入,将依赖的数据或服务注入到组件中,使得组件之间的依赖关系更加灵活和可控。

总而言之,Vue项目开发中的组件化设计、封装和抽象、继承与多态、依赖注入等方式,都是体现面向对象思想的实践。它们帮助我们以更合理、更可维护和可扩展的方式组织和开发Vue项目。

57.JS和TS有什么区别?

  1. 类型系统:最明显的区别是类型系统。JavaScript是一种动态类型语言,变量的类型在运行时确定。而TypeScript是在JavaScript基础上添加了静态类型系统的超集,可以在编译阶段进行类型检查,提供更好的代码可靠性和开发效率。

  2. 类型注解:TypeScript使用类型注解来声明变量、函数参数、函数返回类型等,以明确变量和表达式的类型。这样可以提前发现潜在的类型错误,并提供更好的代码提示和自动补全。

  3. 编译过程:JavaScript是一种解释型语言,代码在运行之前无需编译。而TypeScript需要经过编译器编译成JavaScript,然后在浏览器或Node.js中运行。这使得TypeScript具有更多复杂的特性和语法,但也需要额外的编译步骤。

  4. 扩展功能:TypeScript通过添加了一些新的功能和语法来扩展JavaScript,例如类、接口、泛型、枚举等。这些功能使得TypeScript更适合于大型项目和团队开发,提供了更好的代码结构、代码复用和维护性。

  5. 生态系统:JavaScript具有广泛的生态系统和支持,拥有丰富的开源库、框架和工具。而TypeScript作为JavaScript的超集,可以无缝地与现有的JavaScript生态系统集成,可以使用绝大部分JavaScript库和工具,并且具有更好的类型支持。

总之,TypeScript是JavaScript的超集,通过添加静态类型检查和扩展功能,提供了更好的代码可靠性和可维护性。它适用于大型项目和团队开发,而JavaScript适用于更快速的原型开发和小型项目,两者可以根据具体的需求和场景进行选择和使用。

58.vite为什么比webpack快?

Vite 和 Webpack 都是前端构建工具,但它们的核心设计理念有很大不同,所以 Vite 相比 Webpack 会更快,原因如下:

  1. 热更新:Vite 使用了基于浏览器原生 ES 模块的编译,利用浏览器自带的模块加载器来实现动态更新,而不是像 Webpack 一样常规的热更新。这个设计基于浏览器直接支持 ES 模块的特性,速度比 Webpack 的热更新快得多。

  2. 按需编译:Vite 使用了一种叫做 “按需编译” 的技术,即只有真正需要的模块才会进行编译,而不是像 Webpack 一样把整个项目打包成一个文件。这样可以减少不必要的编译,加快打包速度。

  3. 缓存:Vite 在编译时会缓存已经编译过的模块,下次重新构建时可以直接使用缓存,不会再次编译已经编译过的模块,提高了构建速度。

  4. 预构建:Vite 在生产环境中可以通过预构建的方式提前编译好,不会再需要编译的时候进行构建,这样可以减少了构建时间。

综上所述,Vite 相比于 Webpack,采用了更加高效的 ES 模块加载方式,按需编译,缓存和预构建等技术,从而提高了构建速度,特别是在开发环境下,使得开发者可以更加快速地实现热更新和重新编译。

59.Vite 的工作原理是什么?

  1. 开发环节:

    • 首先,当你启动 Vite 开发服务器时,Vite 会检测你的项目中的入口文件(如 index.html)。
    • 当你在浏览器中访问你的应用程序时,Vite 将拦截对入口文件以及被模块导入的文件的请求。
    • Vite 使用浏览器内置的 ES 模块系统(ESM)来加载需要的模块,这是浏览器原生支持的一种模块加载方式。这与传统的基于打包的构建工具(如 Webpack)有所不同。
    • Vite 会在浏览器中运行一个开发服务器,通过代理的形式将模块请求重定向到实际的模块文件路径上,不需要像 Webpack 一样先打包再推送到浏览器。
    • 当代码中有修改时,Vite 会根据代码的依赖关系对只有发生变化的模块进行重新构建,并将变化传递给浏览器,实现热更新。
  2. 生产环节:

    • 在生产环境中,Vite 会使用预编译的方式来构建应用程序。
    • Vite 会根据项目的入口文件和依赖关系图,逐个分析模块的导入关系。
    • Vite 会为每个模块生成单独的、按需加载的优化过的 ES 模块,这样每个模块都可以单独缓存和加载。
    • Vite 还会生成用于生产环境的最终打包文件,将所有的模块和资源文件合并并进行压缩。

总体而言,Vite 的工作原理是利用现代浏览器对 ES 模块的原生支持,以及按需编译和缓存的思想来提供快速的开发体验。它通过减少构建和打包的时间,实现了更快的开发和热更新的效果。

60.webpack的工作原理是什么?

Webpack 的工作原理可以简单概括为以下几个步骤:

  1. 入口文件:Webpack 会通过指定的入口文件(Entry)开始分析整个应用程序的依赖关系,生成一个依赖图谱(Dependency Graph)。

  2. 模块加载:通过模块加载器(Loader),Webpack 可以将所有类型的文件转换为 JavaScript 模块,以便在浏览器中运行。

  3. 插件处理:JavaScript 模块和其他类型的文件被加载并编译后,Webpack 会使用各种插件(Plugins)对它们进行生成、优化、压缩和其他处理。

  4. Chunk生成:Webpack 会根据配置项和插件的设置,将所有模块组合成一个或多个 JavaScript 文件(也称为 Chunk),这些文件包含整个应用程序的所有代码和依赖项。

  5. 资源输出:最后,Webpack 会将 Chunk 写入指定的输出目录(Output Directory),以便可以在 Web 服务器或浏览器中使用。

在构建过程中,Webpack 具有以下特点:

  1. 基于配置:Webpack 可以通过配置文件的方式来定义构建过程中的各种规则和流程。用户可以自定义各种 Loader、Plugin,以及输出的文件名、路径等。

  2. 模块化管理:Webpack 支持一种“万物皆模块”的理念,几乎可以将所有的资源都作为模块处理,包括 JavaScript、CSS、图片、字体等文件。

  3. Tree shaking:Webpack 支持 Tree shaking,它会在打包时,只将被使用的代码打包进最终的文件,减少文件的大小,提高加载速度。

  4. 按需加载:Webpack 支持代码分割,可以将不同的代码分割成不同的 Chunk,可以按需加载,提高应用程序性能。

总的来说,Webpack 将整个应用程序当做一个静态资源,通过分析这些资源之间的依赖关系来生成最终的打包文件。它可以轻松地处理代码分割、按需编译等方面的需求,同时支持多种资源类型和多种定制化的构建设置,因此在现代 Web 应用程序中得到了广泛的应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值