目录

前言

一、flex布局手写题目

二、移动端点击事件为什么有延迟?时间多久?如何解决这个问题?

1.meta标签里面content属性,设置禁止缩放。

2.设置默认宽度为浏览器宽度。

3.设置touch-action:manipulation。

4.fastClick

三、简述sessionStorage、localStorage、cookies区别?

四、浏览器会缓存哪些内容?如何解决浏览器缓存问题?

1.浏览器会缓存哪些内容?如何解决浏览器缓存问题?

2.浏览器缓存策略

2.1 强缓存

2.2协商缓存

2.3用户操作方面

2.4如何解决缓存问题

五、浏览器从url输入到页面完全展示都经历了什么?

六、介绍npm模块安装机制,为什么输入npm就能自动安装对应模块?

1.npm安装机制

2.为什么输入npm就能自动安装对应模块?

2.1 执行工程自身preinstall

2.2 确定首层依赖模块

2.3 获取模块

2.4模块扁平化(dedupe)

2.5安装模块

2.6工程自身生命周期

2.7.生成版本描述文件

七、写一个正则精确判断邮箱?

八、为什么会有AJAX跨域?如何解决?

1.jsonp

2.cors

3.vue代理devserver

4.nginx反向代理

5.websokect

九、如何取消AJAX跨域?

十、vue中哪些情况会导致数据更新页面不更新?

1.data未定义变量

2.通过索引修改数组的项

3.修改数据长度

4.对象的添加或移除

4.异步更新之前操作dom

5.动态路由共用router-view

6.keep-alive

7.深层嵌套

十一、组件传值方式?

1.父传子 prop 

2.子传父 $emit

3.$parent /$children(父子之间传值)

4. $ref /$refs(父子传值)

5.vuex / evenBus(兄弟、跨级传值 )

6.provide/inject(父子、跨级传值)

7.$attrs / $listeners(父子、跨级传值)

十二、数字精度问题?

十三、es6熟悉哪些特性?

十四、扩展运算符复制一个数组,是深拷贝还是浅拷贝?

1.如何实现深拷贝?缺陷?

十五、const定义的数据可以更改值吗?

​编辑

十六、css哪些熟悉可以继承

十七、重排和重绘?

十八、代码题目

第一题:输出题

第二题:输出题

第三题:手写字符串倒序

第四题:手写数组去重

第五题:手写递归

总结


前言

记录一下面试题及答案~啥都有把,大杂烩~


一、flex布局手写题目

代码结构如下,请实现以下效果(忽略样式只写布局)

<div class="box">
    <span class="item"></span>
    <span class="item"></span>
    <span class="item"></span>
</div>

 解答:

.box{
    width: 100px;
    height:100px;
    background:lightgray;
    display: flex; //重点1
    justify-content: space-between; //重点2
}
 .item{
      width: 20px;
      height: 20px;
      background:deeppink;
      border-radius: 50%;
}

 //重点3
 .item:nth-child(2){

      align-self: center;
}
 //重点4
.item:nth-child(3){

    align-self: flex-end;
}

 主要考察flex熟练程度,align-self表示自身在交叉轴(主轴为横着的,交叉轴就为竖着的)的位置,有三个值:flex-start、center、flex-end。

参考:阮一峰flex布局

二、移动端点击事件为什么有延迟?时间多久?如何解决这个问题?

移动端的设计之初为了判断用户是否还有其他操作(主要为了区分双击和单击,双击缩放),延迟时长为3000ms。

解决方案(四种):

1.meta标签里面content属性,设置禁止缩放。

 //方式一:设置用户不可缩放 
  <meta name="viewport" content="user-scalable=no">  
 //方式二:初始化比例为1
  <meta name="viewport" content="initial-scale=1,maximum-scale=1">  

2.设置默认宽度为浏览器宽度。

<meta name="viewport" content="width=device-width">

3.设置touch-action:manipulation。

设置css样式,touch-action有多个可选值,none表示禁止一切事件,会引起左右滑动不行;

manipulation表示禁止双击操作,这可以完美解决300ms延迟得问题。

注意:除ie之外得浏览器可能无这个属性。

html{
touch-action:manipulation
}

4.fastClick

在window.onload事件得时候引用attach方法。

原理是在检测到touch事件得时候,通过dom模拟一个click事件阻止浏览器默认300ms

window.addEventListener( "load", function() { FastClick.attach( document.body );}, false );

三、简述sessionStorage、localStorage、cookies区别?

数据都存储在客户端

1.存储量方面。cookies存储量为5k,seesionStorage和localStorage存储了为3M

2.有效期方面。cookies设置了时长,过期后数据清除。sessionStorage关闭标签后失效。localStorage不主动清除,永久存在客户端

3.作用域方面。cookies、localStorage同源页面可以共享数据。seesionStorage不可共享。

4.请求方面。cookies会将数据放在http请求里面,sessionStorage和localStorage不会。

5.seesionStorage、localStorage支持事件通知机制,可以将更新得数据通知给监听折,且他们api接口使用更方便。

四、浏览器会缓存哪些内容?如何解决浏览器缓存问题?

1.浏览器会缓存哪些内容?如何解决浏览器缓存问题?

浏览器会缓存图片视频及数据资源。

浏览器缓存针对相同url,解决方法是在url后面加上时间戳

2.浏览器缓存策略

浏览器缓存图片及数据至内存中,缓存分两种强缓存、协商缓存。

2.1 强缓存

浏览器第一次加载得时候会将图片、数据缓存,当再次访问相同网站时直接获取缓存中的资源,不需要重新发起请求。

主要是在加载资源的时候验证时间是否过期,验证时间的两种方式:设置header中得expries(HTTP/1.0)、cache-control(HTTP/1.1)。

expries

客户端第一次发起请求时,服务端返回一个expries,再次发送请求时,客户端携带这个exprise发起,对该expeise进行验证,时间未过期则使用缓存。否则再次发送请求

缺点:但是这个方式浏览器和服务端和时间可能会不一致,造成偏差,客户端也可以单方面修改exprise造成失效。为了解决这个问题提出了cache-contorol

cache-control

有多个至,常用的有以下几个:

no-cache:表示使用协商缓存

max-age:表示缓存多少秒

public:公有缓存,可被代理服务器共享,可被多个用户共享

private:私有缓存,不可被代理服务器共享,不可被用户共享。

注意:cache-control中的max-age优先级高于expries

2.2协商缓存

每次都要重新发起请求,判断是否使用本地缓存。

协商缓存没有缓存时间,而且生成一个标识,服务端用这个表示去验证是否更新,如果不需要更新即表示可以用本地缓存就会返回304,否则就会返回200并且返回新的资源和新的标识。

资源标识有两种:last-Modefied、Etag

last-Modified

第一次发起请求的时候,服务端会返回一个last-Modified,再次请求时,将last-Modified赋值给if-modified-since参数发起,服务端对比返回304使用缓存,或者返回200和新的资源及新的last-modified

缺点:只能精确到秒,如果一秒内发生了变化,是检测不到的。如果改变后又恢复,此时还是会发送请求。有些服务端不能及时最新的修改信息。所以为了解决这三个问题,出现了Etag

Etag

为了更加的精确,我们采用哈希值作为标识,以解决last-modefied的缺点。

第一次请求,服务端返回一个Etag,第二次发起请求的时候,我们将etag赋值给if-none-match去发起请求,如果返回304则表示资源没有更新,使用缓存,如果不相同则返回200和新的资源及新的etag

缺点:这个方法也有缺点,就是每个文件生成哈希值,对于大文件来说,开销很大。不同服务器间的哈希值不一样,所以对于服务器群处理请求来说缓存命中率会降低。

注意:Etag优先级高于last-Modified

2.3用户操作方面

用户回车、前进后退、跳转、打开新窗口强缓存和协商缓存都有效。

刷新时候,强缓存会无效,协商缓存有效。

强制刷新时,强缓存和协商缓存都无效。

2.4如何解决缓存问题

服务端禁用缓存:

Cache-Control: max-age=0, must-revalidate

Cache-Control: no-cache

Cache-Control: no-store

浏览器禁用缓存:

在url后面加个时间戳,如:https://192.168.33:443/xxx

五、浏览器从url输入到页面完全展示都经历了什么?

1.浏览器解析url

2.解析DNS域名

3.浏览器发起请求

4.服务端处理请求

5.浏览器根据返回资源渲染页面

六、介绍npm模块安装机制,为什么输入npm就能自动安装对应模块?

1.npm安装机制

  1. 发出npm install 安装命令
  2. 检查node_modules是否有该模块
  •         有。不再安装
  •         无。首先,npm向register查询模块压缩地址。其次,将压缩包下载并存放在根目录下的.npm(缓存)文件中。最后,将模块 解压至node_moudles文件夹。

2.为什么输入npm就能自动安装对应模块?

当我们输入npm install 命令的时候,会经历7个阶段,分别是:执行工程自身preinstall、确定首层依赖模块、获取模块、模块扁平化(dedupe)、执行模块生命周期、执行工程生命周期、更新package-lock.json。

2.1 执行工程自身preinstall

当npm工程定义定义了preinstall的时候就会执行

2.2 确定首层依赖模块

这里先说一下,npm会生成两个文件:package.json和package-lock.json。

package.json包含项目基本信息、以及第三方依赖等

package-lock.json包含具体版本信息

这一部分可以看这篇:详解package.json和package-lock.json

首层依赖模块就是package.json里面的devDependencies(开发依赖)和dependencies(生产依赖)中指定的模块。

我们获取的流程如下:

工程(根节点)> 首层依赖模块(子节点)> 更深层次的节点

2.3 获取模块

 获取模块分三步:获取模块信息、获取模块实体、查找该模块依赖        

第一步:查看package.json中的版本信息,如果和package-lock.json中的一致就使用package-lock.json的文件(版本不一致就取兼容版本)。如果package-lock.json中没有就从仓库中获取对应的版本。

第二步:上一步会获取到压缩包地址,npm此时查看本地是否有此缓存,有就用缓存,没有就去仓库下载。

第三步:此时再查找是否有该模块,如果有依赖就执行1,没有就停止。        

2.4模块扁平化(dedupe)

以上三步生成了完整的依赖树,但我们很可能存在多个不同版本的依赖,我们为了优化他,需要执行dedupe的过程。

他会遍历完所有节点,将依赖放在node_modules,当有重复依赖的时候,如果有兼容版本就是使用兼容版本。如果不能兼容,则后面一个版本留在依赖树中。

2.5安装模块

到这里会在更新一次node_modules,并执行模块生命周期(preinstall、install、postInstall)

2.6工程自身生命周期

模块更新完了,工程也需要更新。

如果npm定义了钩子,此时就会执行(install、postinstall、prepublish、prepare)

2.7.生成版本描述文件

一切完成,生成或更新package-lock.json文件

七、写一个正则精确判断邮箱?

八、为什么会有AJAX跨域?如何解决?

跨域的原因就是浏览器的同源策略,协议端口域名不同我们称之为跨域。

跨域的解决方式有以下几种:

1.jsonp

利用标签属性不受同源策略约束解决跨域,缺点是只能发送get请求,容易收到xss攻击,不安全。

2.cors

属于后端解决跨域的方法,服务端设置Access-Control-Allow-Origin,可再响应请求中看到

3.vue代理devserver

vue中可以设置devserver中的地址

4.nginx反向代理

在nginx.conf配置监听域名、端口以及实际要访问的地址

还可以使用node解决

5.websokect

建立双向通行协议,就可以访问啦

九、如何取消AJAX跨域?

我们知道跨域是浏览器机制限制,如何取消肯定是从浏览器上入手。

我们右击浏览器图标,重新设置目标参数就可以啦

chrome 浏览器
–args --disable-web-security --user-data-dir=D:\chrome


safari 浏览器
open -a ‘/Applications/Safari.app’ --args --disable-web-security --user-data-dir
 

十、vue中哪些情况会导致数据更新页面不更新?

1.data未定义变量

vue初始化的时候会对data中定义的数据setter/getter,此时才会监听到数据变化。

解决方案:data中定义变量

2.通过索引修改数组的项

如:this.array[2] = "新的值" 

由于js的限制,不能直接修改数组数据。性能代价和用户体验不成正比。

解决方案:

使用vue官方推荐$set进行设置,this.$set(array,index,value)。

使用array.prototype.splice设置,array.prototype.splice(index,1,value)

3.修改数据长度

如:this.array.length=10

由于js的限制,不能直接修改数组数据。性能代价和用户体验不成正比。

解决方案:使用splice改变,array.splice(newLength)

4.对象的添加或移除

如:   object.name = "添加的值"   

          delete object.id

由于vue2是object.defineprototype(),检测不到对象属性的添加或移除。

解决方案:

        1.添加。使用$set(obj,name,value);使用object.assign(obj,obj1,obj2)添加多个

         2.删除。this.$delete(obj,name)

4.异步更新之前操作dom

vue是异步更新,当数据变化时,vue开启队列并在同一事件循环中发生数据变更。如果同一watcher被多次触发,则会放在同一队列中(此时已去重)。

解决方案:使用this.$nextTick(),表示dom更新之后立即被调用。

5.动态路由共用router-view

当我们配置了动态路由,如:/index/:id,同时多个组件公用一个页面,数据只会渲染第一次路由匹配的参数数据。

解决方案:

        1.使用watch监听$router变量。

        2.为每个组件赋一个key(不建议,不同路由直接也有key)

6.keep-alive

keep-alive可以记录两个路由之间的状态,数据会缓存下来,切换回来时,也会先获取缓存内容,适用于不需要及时更新的数据页面。

解决方案:

        1.keep-alive中created失效,取而代之的是activited中重新获取数据

        2.window.location.reload()重新请求

        3.动态绑定include的值,需要时缓存,不需要时清除缓存

        4.使用组件导航守卫beforeRouterEnter的next( 不能访问this,只能用next来访问啦)里面获 取最新数据并赋值刷新

7.深层嵌套

层次太多导致的问题。

解决方案:使用this.$forceUpdate()强制更新,不太建议用。

十一、组件传值方式?

1.父传子 prop 

<comp-a  :prop="msg" />

2.子传父 $emit

<button  @click="submitForm" />

submitForm(){

this.$emit("change",msg)

}

3.$parent /$children(父子之间传值)

父组件:this.$children[0].msg="msg"

子组件:computed:{

        value(){

                this.$parent.msg

         }

}

4. $ref /$refs(父子传值)

给子组件设置ref属性 ,用$refs获取子组件实例(包括变量、方法等)

<comp-a  ref="child"/> 

获取:this.$refs.child

5.vuex / evenBus(兄弟、跨级传值 )

const evenbus= new Vue()

触发:evenbus.$emit("change",msg)

接收:evenbus.$on("change",value=>{

console.log(value,"接收到得值")

})

6.provide/inject(父子、跨级传值)

可以获取嵌套层级很深得数据

父:provide:{"for":value}

子:inject:["for"]   ,通过this.for获取变量

孙子:inject:["for"]   ,通过this.for获取变量

...获取得方式都一样,使用inject注入后通过this.for调用

7.$attrs / $listeners(父子、跨级传值)

在组件上设置属性,通过调用$attar可获取这个属性对象

父:

<comp-a width="200" height="500"  :name="name">

data(){

        retrun{

                name:"小明"

        }

}

子:

<comp-b  v-bind="$attrs">

设置inheritAttars:false   挂仔除了props中的所有属性

获取:this.$attrs

结果为:{width:"200" , height:"500" ,name:"小明"}

孙子:

<div >  $attrs</div>

inheritAttars:false 

props:{

name:string

}

获取:this.$attrs 

结果为:{width:"200" , height:"500"}

十二、数字精度问题?

导致数字精度问题原因有很多,由于计算机二进制所以对于浮点数加减来说,结果并不是一个整数;也有可能是整数长度超过js长度Math.pow(2,52),数值长度超过16位,此时也会出现精度问题;还有可能是使用了tofixed()喝浏览器不兼容导致得。

不管是那种情况,我们都需要解决这个问题,解决方法有一下几种:

1.将小数位变为整数再除倍数

let num = 0.1+0.2;
console.log(num)

结果:可以看到这种情况,就是因为二进制得缘故

解决:

let num =( 0.1*10 +0.2*10)/10;
console.log(num);

结果:

2.使用插件Decimal或bignumber

十三、es6熟悉哪些特性?

十四、扩展运算符复制一个数组,是深拷贝还是浅拷贝?

分两种情况,数组中如果只是基本数据,则可以理解位深拷贝,若有对象或数组则是浅拷贝。看个栗子:

第一种情况:

    let obj=['1','2']
    let cc=[ ...obj]
    cc[0] = '你好'
    console.log(obj,cc)

结果:

可以看到cc变化了,但obj没有变化,此时为深拷贝。

看第二种情况:

let obj=['1',['2','3']]
let cc=[ ...obj]
cc[1] = '你好'
console.log(obj,cc)

 结果:

 此时因为数组得值也是数组,所以是深拷贝。

第三种情况:

let obj=['1',{a:'1'}]
let cc=[ ...obj]
cc[1].a = '2'
console.log(obj,cc)

结果:

此时,数组里面得值是对象,此时为浅拷贝 

综上所诉,扩展运算符复制对象或数组得时候如果值是基本数据类型,就是深拷贝;如果是数组或象引用类型,就是浅拷贝。

这里说一句我们经常也用object.assign(),这也是一个浅拷贝。

1.如何实现深拷贝?缺陷?

目前有三种方式:

1.对象序列化

使用json.parse()和json.stringify()

缺点:会忽略null undefined symbol得数据 ,不能深拷贝对象中得方法

2.函数库lodash

3.递归遍历

遍历判断该项是否为数组或对象,是则递归,不是则置入。

十五、const定义的数据可以更改值吗?

const定义得基本数据是不可以更改得,但是如果是数组或对象,我们可以更改项得值。如下:

    const bb=['1',"2"]
    bb[2]="你好"
    console.log(bb)

十六、css哪些熟悉可以继承

文字属性:font=family、font-size、font-weigh

文本属性:text-indent、text-align、line-height、direction(文本方向)

其他属性:color、visibility、cursor(光标)

十七、重排和重绘?

重排:当页面结构(dom)发生了变化,需要重新渲染页面结构

重绘:当页面样式发生变化,需要重新渲染样式

十八、代码题目

第一题:输出题

  var  a=[];
    for(var i=0;i<10;i++){
        console.log(i,"1")
         a[i]=function () {
             console.log(i)
         }
    }
    a[6]()  

输出:10

解答:for循环为数组a的每一个数值都添加一个函数,i是var定义的全局变量,所以i是共享的,调用a[6]()的时候,全局函数i以及变成了10,所以输出结果是10。

第二题:输出题

    var b =100;
    function foo() {
        console.log(b)
        
    }
    function bar(fn) {
        var b =200;
       fn()
    }
    bar(foo)

输出:100

解答:调用bar(foo),函数bar里面调用foo函数,此时foo()作用域里面没有变量b,向上级作用域查找,查找到b=100 ,所以输出100.

第三题:手写字符串倒序

 //将"a,b,c,d,e,f" 变成 "f,e,d,c,b,a"
    var str = "a,b,c,d,e,f";
    var newStr=str.split(',').reverse().join(',');
    console.log(newStr)

第四题:手写数组去重

// 去重数组arr
    var arr =[1,2,2,3,3,3,4,8,6]

//1
    console.log([...new Set(arr)])
   var newArr =[]


// 2 includes
   for(var i=0;i<arr.length;i++){
     if( ! newArr.includes(arr[i]) ){
         newArr.push(arr[i])
     }
    }



// 3 indexof
    arr.filter((i,index)=>{
        if(newArr.indexOf(i)==-1){
            newArr.push(i)
        }
    })



// 4 reduce
     arr.reduce((result,item)=>{
         newArr.includes(item)?newArr:newArr.push(item)
            return
        },0)

    console.log(newArr)

第五题:手写递归

// 0 1 1 2 3 5 8 13 ...N 求N
    var per ='0 1 1 2 3 5 8 13 '
    function di(n) {
        var f =[]
        var result;
        for (var i=0;i<n;++i) {
            console.log(i)
             f.push((i<2)?i:f[i-1]+f[i-2])
        }
        result =f.slice(-1).toString()
        console.log(result,"结果")
        // console.log(f.slice(-1),f.pop(),"sss")
    }
    di(9)


总结

以上就是今天要讲的内容,本文仅仅简单记录了一下面试,简单写了一下答案,希望对你有帮助吧。

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐