前端面试(一)

前端常用的几种布局:

  • 静态常规布局: 设置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条规则

  1. 所有的引用类型都可以自定义添加属性
  2. 所有的引用类型都有自己的隐式原型(proto)
  3. 函数都有自己的显式原型(prototype)
  4. 所有的引用类型的隐式原型都指向对应构造函数的显示原型
  5. 使用引用类型的某个自定义属性时,如果没有这个属性,会去该引用类型的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的理解和作用

  • 什么是BFCBFC就是块级化上下文,它是指一个独立的块级渲染区域,该区域拥有一套渲染规则来约束块级盒子的布局,且与区域外部无关
  • 如何创建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为例描述浏览器渲染原理,浏览器渲染过程主要分为三个阶段,先详述如下:
第一阶段:

  1. 用户输入URL时,webkit依赖网络模块加载网页或资源数据
  2. 网页被交给HTML解释器转变成一系列的词语
  3. 解释器根据词语构建节点并形成DOM树
  4. 如果节点是CSS、图片、视频等资源,会调用资源加载器加载他们,因该类资源加载是异步的,不会阻塞当前DOM树的继续创建
  5. 如果节点是javascript,停止当前DOM树的创建,直到javascript资源加载完成并被javascript引擎执行后才继续进行DOM的创建
    第二阶段:
  6. CSS解释器解析CSS文件成内部表示结构,并在DOM树上附加样式信息形成RenderObject树
  7. RenderObject节点在创建的同时,webkit会根据网页的层次结构创建RenderLayer树,同时创建一个虚拟的绘图上下文
    第三阶段:
    1.根据生成的绘图上下文和2D或3D图形库生成最终的图像
    对于包含动画和用户交互的动态网页,浏览器的渲染过程会重复的执行,可能会触发不同程度的重排和重绘。
CSS和JS是否会阻塞DOM的渲染和解析
  1. CSS不会阻塞DOM解析,但是会阻塞DOM渲染
  2. 解析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.返回新对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值