前端常用的几种布局:
- 静态常规布局: 设置min-width,当小于这个宽度就出现滚动条,大于的时候让内容居中
- 流式布局: 当屏幕分辨率大小发生变化时,页面里的元素大小会变化但布局不会发生变化;使用%百分比来定义宽度,高度大都是用px来固定
- 自适应布局: 自适应布局就是为不同的屏幕定义不同的布局,就是创建多个静态布局,每个静态布局对应一个屏幕分辨率范围。改变不同的屏幕分辨率可以切换不同的静态布局(页面元素位置发生改变)
- 响应式布局: 一个页面在所有终端上(各种尺寸的PC、手机、平板、智能手表的Web浏览器等等)都能显示出令人满意的效果,可以把响应式布局看作是流式布局和自适应布局设计理念的融合。
- 弹性布局: 两者都是顺应不同网页字体大小展示而产生的。
em是相对其父元素,在实际应用中相对而言会带来很多不便;
rem则是始终相对于html大小,即页面根元素
缺点:这种rem+js只不过是宽度自适应,高度没有做到自适应,一些对高度,或者元素间距要求比较高的设计,则这种布局没有太大的意义。如果只是宽度自适应,更推荐响应式设计。
JS数据类型
基本数据类型:number,string,bol,undefined,null,symbol,bigInt
引用数据类型:object,array,set,map
事件绑定
1.在dom中直接绑定事件
<input type="button" value="click me" onclick="hello()">
2.使用事件监听绑定事件
element.addEventListener(event, function, useCapture)
//useCapture:(可选)指定事件是否在捕获或冒泡阶段执行。true,捕获。false,冒泡。默认false。true的话就是先执行最上层的事件
//事件监听的优点:可以绑定多个事件
3.事件委托:
提高JavaScript性能。事件委托可以显著的提高事件的处理速度,减少内存的占用。比如有n个子元素有点击事件,假如每个都绑定事件,太浪费性能了,可以委托给父元素
4.冒泡:
事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点
event.stopPropagation()阻止冒泡
5.捕获:
与冒泡相反,事件会从最外层开始发生,直到最具体的元素。
原型与原型链
1.所有对象都有__proto__属性,一般这个是被称为隐式的原型,该隐式原型指向构造该对象的构造函数的原型,即a.proto === A.prototype。
2.函数比较特殊,它除了和其他对象一样有__proto__属性,还有自己特有的属性----prototype,这个属性是一个指针,指向一个包含所有实例共享的属性和方法的对象,称之为原型对象。原型对象也有一个constructor属性,该属性指回该函数
var F = function () {}
Object.prototype.a = function () {}
Function.prototype.b = function () {}
var f = new F()
// 请问f有方法a 方法b吗
//f的__proto__指向F.prototype,F.prototype.__proto__指向Object.prototype,所以f 可以取到a方法, 由于f的原型链上没经过Function.prototype,所以取不到b方法。
//由于构造函数F是由Function new出来的,所以F.__proto__指向Function.prototype,所以F函数可以取到b方法。
3.对象的__proto__ 就等于它构造函数的prototype
Object.__proto__ === Function.prototype //Object是对象的构造函数,那么它也是一个函数,当然它的__proto__也是指向Function.prototype
4.constroctor属性
function A(){}
var a = new A
console.log(a.constroctor) // 函数A
a.constructor.prototype.show = function(){
console.log('aaaa')
}
//constructor就是将实例的构造器原型对象暴露出来,可以用 instance.constructor.prototype 去修改或扩展原型对象
5.原型的5条规则
- 所有的引用类型都可以自定义添加属性
- 所有的引用类型都有自己的隐式原型(proto)
- 函数都有自己的显式原型(prototype)
- 所有的引用类型的隐式原型都指向对应构造函数的显示原型
- 使用引用类型的某个自定义属性时,如果没有这个属性,会去该引用类型的proto(也就是对应构造函数的prototype)中去找
继承的6种方法
1.构造函数继承
//父类person;student继承person
function person(){
this.kind="person";
}
person.prototype.eat=function(food){
console.log(this.name+" is eating "+food);
}
function student(name) {
//直接利用call或者apply方法将父类构造函数的this绑定为子类构造函数的this就可以
person.apply(this,arguments)
this.name=name;
}
var martin=new student("martin");
console.log(martin.kind); //person
martin.eat("apple"); //报错
//构造函数继承的缺点:只能继承父类构造函数中的属性和方法,对于原型对象无法继承。
2.原型实例继承
/** 将student的prototype对象指向person的一个实例。
这句话相当于完全删除了student.prototype对象原本的内容,
然后赋予了它一个新的值。*/
student.prototype=new person();
/** 因为任何一个构造函数都有一个prototype对象,这个prototype对象
都有一个constructor属性指向自身的构造函数。
但是因为第一行对prototype对象重新进行了赋值,所以prototype对象
的constructor属性也发生了改变,变成指向person,
所以必须手动将student.prototype.constructor指回student。*/
student.prototype.construct=student;
var martin=new student("martin");
martin.eat("apple"); //martin is eating apple
console.log(martin.kind);//person
/** 注意:以后编程中如果要修改prototype,
必须要手动将prototype的constructor的指向指回原来的构造函数 */
3.原型直接继承
student.prototype=person.prototype;
student.prototype.constructor=student; //注意这一行产生的变化
var martin=new student("martin");
martin.eat("apple"); //martin is eating apple
console.log(martin.kind); //undefined
/**缺点:这种方式无法继承父类构造函数中的属性与方法,但是可以继承父类构造函数的原型对象。
student.prototype.constructor=student;
把person.prototype的constructor属性也一起改成了student。*/
4.ES6中 class继承
class person {
constructor(sex,age){ //constructor是一个构造方法,用来接收参数
this.kind = 'person'
this.sex = sex
this.age = age
}
eat(food){//这是一个类的方法,注意千万不要加上function
console.log(this.name + '吃了' + food)
}
}
class student extends person{
constructor(name,sex,age){
//但凡看到extends继承那这句就固定加上super
//相当于es5中person.call(this,name)
super(sex,age)
this.name = name
}
}
var ashin = new student('ashin','male',23)
ashin.eat('apple') // ashin吃了apple
console.log(ashin) //student {kind: 'person', sex: 'male', age: 34, name: 'ashin'}
5.深拷贝继承:主要通过对象间的深拷贝来实现继承
function deepCopy(parent,child) {
var child=child||{};
for(var i in parent){
if(typeof parent[i]==="object"){
child[i]=parent[i].constructor==="Array"?[]:{};
deepCopy(parent[i],child[i]); //递归
}else{
if(!(i in child)){ //i in child表示属性i是否在child对象中,如果有则返回true
child[i]=parent[i];
}
}
return child
}
var parent={
name:"martin",
say(){
console.log("say"+this.name);
}
};
var child={
name:"lucy",
kind:"person",
eat(){
console.log("eating"+this.name);
}
};
deepCopy(parent,child);
console.log(child); //{ kind: 'person',eat: [Function: eat],name: 'martin',say: [Function: say] }
HTML5新特性
-
语义标签
header
footer
nav
section
aside
article
details
summary
dialog
main
time -
增强型表单
新增了input输入属性:color,date,search等
新增了五个表单元素:progress,meter,datalist
新增了表单属性:placeholder,required,autofocus ,height/width -
音频和视频
-
Canvas
-
SVG绘图
-
地理定位
-
拖放API
CSS3新特性
- 过渡:transition:
- 动画 animation
- 形状转换(2d、3d):trasform
- 选择器
- 阴影:box-shadow
- border-image
- border-radius
*** background-clip** - background-origin
- bcakground-size
- rgba
- text-shadow
- 渐变
- 滤镜 filter
- 弹性布局 flex
- 盒模型 box-sizing:border-box
- 媒体查询 @media
BFC的理解和作用
- 什么是BFC: BFC就是块级化上下文,它是指一个独立的块级渲染区域,该区域拥有一套渲染规则来约束块级盒子的布局,且与区域外部无关
- 如何创建BFC:
-方法①.overflow:hidden
-方法②.float不是none
-方法③.position的值不是static或者relative
-方法④.display的值是inline-block、flex或者inline-flex - BFC的其他作用
①解决margin塌陷
②阻止元素被浮动元素覆盖
ES6新特性
- let const
- 解构
- …扩展运算符
- 箭头函数
- for of 遍历的是键值对中的值
- for in 遍历的是键值对中的键
- class类
- import export default
- promise
- async/await
- symbol 基本类型
- Set
- Map
- Number.isFinite()判断是否为有限数值
- Number.isNaN() 判断是否为NaN
- Number.isInteger()判断是否为整数
- Number.parseInt()转换为整数
- Number.parseFloat()转换为浮点型
- json格式转换为数组:Array.from()
渲染原理
从输入url到页面展示的详细过程:
答:1.输入网址,浏览器就开始匹配url,它会从历史记录,书签等地方找到曾经输入过的对应url给出智能提示,有的浏览器甚至会从缓存中把网页展示出来,没anenter页面就出来了
2.浏览器查找域名的IP地址,本地DNS服务器向域名的解析服务器发送请求,这时就能收到域名对应和IP地址对应的关系,返回给用户电脑
3.浏览器向web服务器发送HTTP请求,拿到域名对应的Ip地址后,浏览器会以一个随机端口向服务器的web程序(http,nginx等)80端口发起TCP连接请求,最终建立TCP/IP连接
4.服务器收到请求,将结果通过web服务器返回给浏览器客户端
5.四次挥手关闭TCP连接
6.浏览器解析资源,解析HTML,生成DOM树,解析CSS,生成CSS规则树,然后通过DOM树和CSS规则树生成渲染树。渲染树和DOM树不同,渲染树中并没有head,display为none等不必显示的节点
浏览器是如何进行渲染的
浏览器渲染过程因不同内核可能会有差异,现以webkit为例描述浏览器渲染原理,浏览器渲染过程主要分为三个阶段,先详述如下:
第一阶段:
- 用户输入URL时,webkit依赖网络模块加载网页或资源数据
- 网页被交给HTML解释器转变成一系列的词语
- 解释器根据词语构建节点并形成DOM树
- 如果节点是CSS、图片、视频等资源,会调用资源加载器加载他们,因该类资源加载是异步的,不会阻塞当前DOM树的继续创建
- 如果节点是javascript,停止当前DOM树的创建,直到javascript资源加载完成并被javascript引擎执行后才继续进行DOM的创建
第二阶段: - CSS解释器解析CSS文件成内部表示结构,并在DOM树上附加样式信息形成RenderObject树
- RenderObject节点在创建的同时,webkit会根据网页的层次结构创建RenderLayer树,同时创建一个虚拟的绘图上下文
第三阶段:
1.根据生成的绘图上下文和2D或3D图形库生成最终的图像
对于包含动画和用户交互的动态网页,浏览器的渲染过程会重复的执行,可能会触发不同程度的重排和重绘。
CSS和JS是否会阻塞DOM的渲染和解析
- CSS不会阻塞DOM解析,但是会阻塞DOM渲染
- 解析DOM生成DOM Tree,解析CSS生成CSSOM Tree,两者结合生成render Tree渲染树,最后浏览器根据渲染树生成页面,由此可以看出DOM Tree和CSSOM Tree的解析是互不影响的,两者是并行的,因此CSS不会阻塞DOM解析,但是由于Render Tree的生成是依赖DOM Tree和CSSOM Tree的,因此CSS必然会阻塞DOM的渲染
3.JS会阻塞DOM的解析
4.CSS会阻塞JS的执行
5.如果是css阻塞了DOM的解析,那么一定是css阻塞了JS的执行,进而阻塞了DOM
6.JS会触发页面渲染,浏览器无法预先知道script脚本的内容,因此在碰到
关键渲染路径CRP
浏览器从接收到服务器返回的 HTML 到渲染像素到屏幕上,中间经历了很多的步骤。浏览器初次绘制网页的必经过程称之为“关键渲染路径(Critical Rendering Path,以下称CRP)
CRP有六个阶段:
1.构建DOM树
2.构建CSSOM树
3.运行Javascript
4.创建渲染树
5.生成布局
6.绘制页面
回流重绘
重绘:当改变了字体颜色,背景颜色这些不影响结构的属性时就会触发重绘,如visibility:hidden
回流(重排):当改变了布局就会回流,如display:none,回流必然会触发重绘。回流问题是可以优化的,尽量减少不必要的回流,比如img图片载入问题,给图片设置宽高就可以了,这样浏览器就知道图片的占位面积,在载入图片前就预留好了位置
图层
HTTP状态码
200 :一切正常
301 :资源被永久转移到其他URL
400 :客户端方面的问题
500 :服务器错误
404 :服务器不知道客户端要请求哪个资源情况
Set 、Map 数据结构
能用map不用Array,如果对数据有唯一性可以使用set
let map = new Map()
map.set('a',1) //增
map.delete('a') //删
map.set('a',2) //改
map.has('a') //查
//采用map查找一个学生的成绩
let person = new Map([['ashin',100],['lixin',10]]) //初始化值
person.set('tom',111) //添加学生
person.get('ashin') //查找学生的分数为 100
let set = new Set([1,2,3,3,4,4]) //set是个伪数组
set.add(5) //增
set.delete(5)//删
set.has(3) //查
set.size() //获取长度
//set转化为数组
Array.from(set)
let res = [...set]
//数组合并去重
let arr1 = [1,2,3]
let arr2 = [2,3,4]
let s = new Set([...arr1,...arr2])
//求交集
let arr1 = [1,2,3,4,5,3,4]
let arr2 = [3,4,5,6,7]
let set1 = new Set(arr1)
let set2 = new Set(arr2)
const res = new Set(arr1.filter(item=>set2.has(item)))
浅拷贝与深拷贝
- 基本数据类型的值都是存在栈里面的,而引用类型的值是存在堆里面的
- 浅拷贝与赋值的区别:基本类型都是赋值。浅拷贝首先要创建一个新的对象,新的房间,然后引用类型拷贝的都是内存地址。
- 说的直白一点就是,浅拷贝碰到的是引用数据类型,那么拷贝前和拷贝后是互相影响的
- 深拷贝如果属性是引用数据类型,那么就会开辟一个新的房间存储空间(堆),拷贝的是内部数据的实体
- 数组的深拷贝例子:concat方法、slice方法、…扩展运算符;注意:如果数组项时引用类型的数据,就无法实现深拷贝,如[1,2,3[5,6]]
- 对象的深拷贝: object.assign(目标对象,源对象) 、JSON.parse(JSON.stringify(obj)) 、扩展运算符 var obj1 = {…obj}
防抖与节流
防抖:用setTimeout实现,当持续触发一个事件时,在n秒内,事件没有再触发时,此时才会执行回调事件;n秒内,又触发事件时,就又重新计时
var btn = document.getElementById('btn')
btn.addEventListener('click',debounce(submit,2000))
function submit(e){
console.log('防抖ajax发送成功')
console.log(e)
console.log(this)
}
function debounce(fun,time){
let t = null
return function(){
var firstClick = !t //t等于null,表示第一次点击,为真
if(t) {clearTimeout(t)}
if(firstClick){ //如果是第一次点击,直接调用
fun.apply(this,arguments)
}
t = setTimeout(()=>{ //两秒后设置t 为null,即重新变为初始值,让第一次点击又变为真
t = null
},time)
}
}
节流:减少一段时间触发频率,只有到了一定时间才触发第二次
function commit(e){
console.log('节流ajax发送成功')
console.log(this)
}
function throttle(commit,delay){
let begin = 0
return function(){
const cur = new Date().getTime()
if(cur - begin > delay){
commit.apply(this,arguments)
begin = cur
}
}
}
HTTP与HTTPS协议
1.安全性:http明文传输,易受攻击,无法确认双方身份,也无法保证数据的完整性,https使用ssl加密的一个传输协议,信息是密文可以认证双方的身份,防止信息被截取篡改
2.端口:http是80端口,https是443端口
3.灵活性:http简单快速,使用灵活,https技术门槛较高,个人或私人网站难以支撑
4.访问速度,http协议简单,通信速度会比较快
5.经济:http没有额外的费用要求,https需要CA机构颁发的正书,需要年费和技术支持
VUE2和VUE3的区别
- 生命周期变化了一些,没有了created,增加了setup替代,和大部分名字需要+on,和vue3需要先引入
- 多根节点,不像vue2需要包裹在一个根节点中
- 异步组件Suspense组件,在等待异步组件渲染内容的时候,可以加loading之类的,使用户体验更平滑,分别为<template #default> 和 <template #fallback>
- 组合式API,vue2是选项式API,导致代码可读性不好,需要来回跳转位置,vue3可以将一个逻辑的内容写到一起,增强了代码的可读性,内聚性
- 响应式原理:vue2是object.defineProperty数据劫持;vue3是proxy代理;
- vue3由TS重写
项目面试
登录功能
1.设置前置路由守卫,如果localstorage里面没有token,路由就强行跳转到登录注册页,有token了才能自由跳转
2.初次进入到页面的时候,因为没有token所以刚进去的时候就时登录注册页
3.登录成功后服务器返回一个token,该token是标识用户的一个key,将token存储到localStorage中,这样下次打开页面或者刷新页面就能记住用户的登录状态了,token一般设置24小时过期,24小时后就得重新登录
说说axios具体是怎么封装的
1.环境判断:根据环境变量区分接口地址,可选开发模式,生产模式,测试模式,更改地址可以通过config.js
2.设置超时时间和跨域是否允许携带凭证,设置post请求头,告知服务器请求主体的格式,可以是json格式也可以是表单格式application/x-www-form-urlencoded
3.设置请求拦截器,里面可以携带上token,设置加载动画
4.响应拦截器,关闭加载动画,设置错误提醒,获取错误状态码,可以清除localstorage里面的token,然后跳转到登录页面
1.省市县数据的一维数组,转换为树状结构
const list = [
{ id: 1, pid: 0, name: '四川' },
{ id: 2, pid: 1, name: '成都' },
{ id: 3, pid: 1, name: '宜宾' },
{ id: 4, pid: 1, name: '绵阳' },
{ id: 5, pid: 1, name: '德阳' },
{ id: 6, pid: 2, name: '高新区' },
{ id: 7, pid: 2, name: '武侯区' },
{ id: 8, pid: 3, name: '翠屏区' }
];
const func = (arr,pid)=>{
return arr.reduce((res,current)=>{
if(current['pid']===pid){
current.children = func(arr,current['id'])
return res.concat(current)
}
return res
},[])
}
console.log(func(list,0))
3. 输入 2020-1-1 ~ 2020-1-5 (考虑闰年) 输出 2020-1-1,2020-1-2,2020-1-3 2020-1-4 2020-1-5
function getYearBetween(str){
var startYear = str.split('~')[0]
var endYear = str.split('~')[1]
var st = startYear.split('-')
var end = endYear.split('-')
var startTime = new Date(st[0],st[1]-1,st[2]).getTime()
var endTime = new Date(end[0],end[1]-1,end[2]).getTime()
let res = []
for(let i=startTime;i<=endTime;){
res.push(formatTime(i))
i+=24*60*60*1000
}
return res
}
function formatTime(time){
var date = new Date(time)
var year = date.getFullYear()
var month = (date.getMonth()+1)>=10?(date.getMonth()+1):'0'+(date.getMonth()+1)
var day = date.getDate()>=10?date.getDate():'0'+date.getDate()
return `${year}-${month}-${day}`
}
var date = getYearBetween('2020-02-19 ~ 2020-03-03')
console.log(date)
数组去重
function noRepeat(arr){
const len = arr.length
for(let i=0;i<len;i++){
for(let j=i+1;j<len;j++){
if(arr[i]===arr[j]){
arr.splice(j,1)
j--
}
}
}
return arr
}
对前端路由的理解,前端路由和后端路由有什么区别?
前端路由是后来发展到SPA(单页应用)时才出现的概念。
前端路由主要有两种模式:
1.hash:优点是兼容性高,缺点是带有#不好看。hash模式是利用浏览器不会对#后面的路径对服务端发起请求。
2.history:优点是不带#好看,缺点是需要浏览器支持和后端服务器支持
前端路由应用最广泛的例子就是当今的SPA的web项目。不管是Vue、React还是Angular的页面工程,都离不开相应配套的router工具。
整个页面就只有一整套的HTMLCSS+JS,这一套HTML+CSS+JS中包含了许多个网页,当我们请求不同的URL时,
客户端会从这一整套HTML+CSS+JS中找到对应的HTML+CSS+JS,将它们解析执行,渲染在页面上。
前端路由带来的最明显的好处就是,地址栏URL的跳转不会白屏了——这也得益于客户端渲染带来的好处。
缺点:1.使用浏览器的前进后退键的时候会重新发送请求,没有合理的利用缓存
2.单页面无法记住之前滚动的位置,无法在前进后退的时候记住滚动的位置
后端路由:向服务器发送请求,会刷新页面,前后端不分离。
在浏览器的地址栏中切换不同的url时,每次向后端服务器发送请求,
服务器根据不同的地址响应不同的数据,浏览器接收到数据后再进行渲染,
所以后端路由会刷新页面,如果网速慢的话,就会看到一个空白页面等待服务器返回数据,
后台路由最大的问题就是不能前后端分离
css三角形
div {
width: 0;
height: 0;
border: 50px solid transparent;
border-bottom: 50px solid red;
}
new方法经历了哪些步骤
1.先创建了一个新对象
2.将这个空对象的proto成员指向whatnew函数的对象prototype成员对象。
3.改变this 的指向
4.返回新对象