前端面试总结(一)

334 阅读26分钟

最近在面试前端的工作,面试了几家,在这里做一个暂时性的总结,后面如果还有面试会持续的更新 内容有点多,可以收藏了慢慢看

一、 js基础

1、说说你对原型和原型链的理解

js里面当函数对象被创建的时候,都会携带一个prototype的属性,这个属性指向一个prototype对象,也就是原型对象;

各级子对象的_proto_属性连线引用形成的结构就叫做原型链

bd1d956d6e5a64c3b6dcc77183cb628.png

2、构造函数是什么

所谓的构造函数,实际上就是通过关键字new来调用的函数:

3、说说你对call(),apply(),bind()的理解

这三个方法都是用来改变this指向的,他们的第一个参数都是 this 的指向对象,第二个参数差别在于:

  • call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面 obj.myFun.call(db,'成都', ... ,'string' )
  • apply 的所有参数都必须放在一个数组里面传进去 obj.myFun.apply(db,['成都', ..., 'string' ])。
  • bind 除了返回是函数以外,它 的参数和 call 一样。bind() 函数会创建一个新函数
//在常见的单体模式中,通常我们会使用 _this , that , self 等保存 this ,这样我们可以在改变了上下文之后继续引用到它
var fn = {
    bar : 1,
    eventBind: function(){
        var _this = this;
        $('.someClass').on('click',function(event) {
            console.log(_this.bar);     //1
        });
    }
}
//当然使用 bind() 可以更加优雅的解决这个问题:
var fn = {
    bar : 1,
    eventBind: function(){
        $('.someClass').on('click',function(event) {
            /* Act on the event */
            console.log(this.bar);      //1
        }.bind(this));
    }
}
//创建了一个新的函数 func,当使用 bind() 创建一个绑定函数之后,它被执行的时候,它的 this 会被设置成 fn , 而不是像我们调用 bar() 时的全局作用域。
var bar = function(){
console.log(this.a);
}
var fn = {
a:3
}
bar(); // undefined
var func = bar.bind(fn);
func(); // 3

4、new一个对象和直接创建一个对象有什么区别

new 关键字是用来实例化一个构造函数的,函数内部的this指向该实例,也就是函数的调用者,直接创建的函数内部的this指向父对象,在浏览器里面是指向window的

5、深浅拷贝的方法有哪些

首先看看什么是堆和栈
栈(stack):栈会自动分配内存空间,会自动释放,存放基本类型,简单的数据段,占据固定大小的空间。(基本类型:String,Number,Boolean,Null,Undefined)
堆(heap):动态分配的内存,大小不定也不会自动释放,存放引用类型,指那些可能由多个值构成的对象,保存在堆内存中,包含引用类型的变量,实际上保存的不是变量本身,而是指向该对象的指针。(引用类型:Function,Array,Object)

所谓深浅拷贝,都是进行复制,那么区别主要在于复制出来的新对象和原来的对象是否会互相影响,改一个,另一个也会变,这就叫浅拷贝,改一个,另一个不变,叫做深拷贝

浅拷贝的方法

  • 循环赋值
  • 用扩展运算符解构赋值
var obj1 = {a: 1, b: 2}
var obj2 = {...obj1}
obj2.a = 4
 
console.log(obj1, obj2)
  • Object.assign(),Object.assign我们经常会用到合并对象,当然利用Object.assign性质我们也可以实现对象的拷贝。这里要注意的是Object.assign第一个参数必须是个空对象
var obj1 = {a: 1, b: 2}
var obj2 = Object.assign({}, obj1)
obj2.a = 4
 
console.log(obj1, obj2)
  • arr.slice()实现数组的浅拷贝, slice不传参即可
  • Object.create()
var obj = {
    name:2,
    say:{
        a: 1,
        b:2
    }
}
var obj1 = Object.create(obj)
obj1.say.a=100
console.log(obj)
console.log(obj1)

深拷贝的方法

  • 使用递归的方式实现深拷贝
 function deepClone(obj, endObj) {
    var newObj = endObj || {};
    for (var i in obj) {
            if (typeof obj[i] === 'object') {
        newObj[i] = obj[i].constructor === Array ? [] : {},
        deepClone(obj[i], newObj[i])
      } else {
        newObj[i] = obj[i]
      }
    }
    return newObj
  }
  • 通过 JSON 对象实现深拷贝
objClone = JSON.parse(JSON.stringify(obj));
  • Object.assign()拷贝 当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。
  • lodash函数库实现深拷贝 lodash很热门的函数库,提供了 lodash.cloneDeep()实现深拷贝

6、判断对象类型的方法有哪些

我们知道,JavaScript中检测对象类型的运算符有:typeof、instanceof,还有对象的constructor属性:
typeof 运算符 typeof 是一元运算符,返回结果是一个说明运算数类型的字符串。如:"number","string","boolean","object","function","undefined"(可用于判断变量是否存在)。 但 typeof 的能力有限,其对于Date、RegExp类型返回的都是"object"。如:

`typeof` `{}; ``// "object"`
`typeof` `[]; ``// "object"`
`typeof` `new` `Date(); ``// "object"`

instanceof 运算符要求其左边的运算数是一个对象,右边的运算数是对象类的名字或构造函数。如果 object 是 class 或构造函数的实例,则 instanceof 运算符返回 true。如果 object 不是指定类或函数的实例,或者 object 为 null,则返回 false。如:

[] instanceof Array// true
[] instanceof Object// true
[] instanceof RegExp// false

new Date instanceof Date// true

JavaScript中,对象有一个constructor属性,它引用了初始化该对象的构造函数,常用于判断未知对象的类型。如给定一个未知的值 通过typeof运算符来判断它是原始的值还是对象。如果是对象,就可以使用constructor属性来判断其类型。所以判断数组的函数也可以这样写:

function isArray(arr){
     return typeof arr == "object" && arr.constructor == Array;
}

但是这种检测在跨框架(cross-frame)页面中的数组时,会失败。原因就是在不同框架(iframe)中创建的数组不会相互共享其prototype属性。 Object.prototype.toString()可以解决上面的跨框架问题,我们就可以写一个健壮的判断对象是否为数组的函数

function isArray(arr){
     return Object.prototype.toString.call(arr) === "[object Array]" ;
}

7、怎么做到字符串的随机排序

  • sort随机排序(Array.prototype.sort 随机排序,结果并不均匀,并不完全随机)
function randomsort(a, b) {
  return Math.random()>.5 ? -1 : 1; //通过随机产生0到1的数,然后判断是否大于0.5从而影响排序,产生随机性的效果。
}
var arr = [1, 2, 3, 4, 5];
arr.sort(randomsort);
  • 经典随机排序
function shuffle(arr){
  var len = arr.length;
  for(var i = 0; i < len - 1; i++){
    var idx = Math.floor(Math.random() * (len - i));
    // var idx = parseInt(Math.random()*len);

    var temp = arr[idx];
    arr[idx] = arr[len - i - 1];
    arr[len - i -1] = temp;
    // 或者用splice替换
  }
  return arr;
}

8、为什么{}==={}输出的是false

===比较两个同样创建的对象将始终返回false,除非手动改变两个对象的this指向

var a = function() {console.log(11)};
 
var b = function() {console.log(11)};
 
console.log( a==b ); //false

当我们需要访问引用数据类型 (对象 / 数组 / 函数) 的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

变量a实际保存的是指向堆内存中对象的一个指针,而b保存的是指向堆内存中另一个对象的一个指针;虽然这两个对象的值是一样的,但它们是独立的2个对象,占了2份内存空间;所以 a==b 为 false。

基本类型与引用类型最大的区别实际就是 传值与传址的区别。

题目当中的===两侧的对象指向的是两个不同的地址,因此输出false

9、两个不同的域名下,怎么互相访问localstorage

两个不同的域名的localStorage不能直接互相访问。那么如何在aaa.com中如何调用bbb.com的localStorage?

第一种:在bbb.com的页面中,嵌入一个src为aaa.com的iframe,此时这个iframe里可以调用aaa.com的localstorage。用postMessage方法实现页面与iframe之间的通信。 我们可以优化下iframe,我们可以在aaa.com中专门写一个负责共享localstorage的页面,这样可以防止无用的资源加载到iframe中,减少HTTP请求,
第二种:可以设置两个域名下设置document.domain为统一的根域

10、sessionStorage和localStorage的区别在哪里,cookie和session的区别

  • cookie和session的区别: 1、cookie数据存放在客户的浏览器上,session数据放在服务器上 
    2、cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session 
    3、session会在一定时间内保存在服务器上,当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie 
    4、单个cookie保存的数
    据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie 
    5、建议将登录信息等重要信息存放为session,其他信息如果需要保留,可以放在cookie中 
    6、session保存在服务器,客户端不知道其中的信息;cookie保存在客户端,服务器能够知道其中的信息 
    7、session中保存的是对象,cookie中保存的是字符串 
    8、session不能区分路径,同一个用户在访问一个网站期间,所有的session在任何一个地方都可以访问到,而cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的
  • sessionStorage、localStorage和cookie的区别 1、cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下 
    2、存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大 
    3、数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭 
    4、作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的 
    5、web Storage支持事件通知机制,可以将数据更新的通知发送给监听者 
    6、web Storage的api接口使用更方便

11、http状态码2345开头的各表示什么含义

  • 2开头:(请求成功)表示成功处理了请求的状态代码
  • 3开头:(请求被重定向)表示要完成请求,需要进一步操作。通常,这些状态代码用来重定向
  • 4开头:(请求错误)这些状态码表示请求可能出错,妨碍了服务器的处理
  • 5开头:(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错。

12、提升页面性能优化的方法有哪些

页面层面

  • 减少HTTP请求, 合理设置HTTP缓存
  • 使用浏览器缓存
  • 压缩合并文件
  • CSS Sprites(css精灵,多个css所用到图片, 放到一张上面, 然后通过css进行控制)
  • Lazy Load Image(图片懒加载)
  • CSS放到顶部, JS放到底部
  • 异步加载(1、可以在DOMLoaded事件触发时加载;2、通过setTimeout方式来灵活控制)
  • 减少cookie传输(cookie慎用)

JavaScript代码优化

  • 尽量避免DOM查找
  • 尽量避免使用with关键字(with用来改变作用域)
  • 避免使用eval(执行js代码)和Function构造函数
  • 减少作用域链的查找(如果不断需要查找作用域链, 我们需要在遍历之前用局部变量缓存该变量)
  • 减少闭包使用
  • 尽量减少对对象以及数组的深度访问

CSS优化

  • 缩小样式表体积,合并多个css文件
  • 避免使用 table 布局。
  • 尽可能在 DOM 树的最末端改变 class。
  • 避免设置多层内联样式。
  • 将动画效果应用到 position 属性为 absolute 或 fixed 的元素上。
  • 避免使用 CSS 表达式(例如:calc())。

13、从地址栏输入一个网址开始,浏览器都做了什么事情

  1. 网络   从网络方面的角度来看,以较为简单的流程来说,如下:
  • [DNS解析] 输入网址后,DNS域名解析服务器进行解析。
  • [寻址] 将解析得到的IP地址在网络中,向上级进行查找。
  • [建立连接] 若能找到对应的地址,则与其建立连接;否则,向上层继续寻址,直到到NSP骨干网的路由器(拥有最大的路由表),查到地址后与其建立连接。
  • [发起请求] 通过TCP的三次握手机制,建立连接后,将请求的数据包发给服务器;服务器接收到请求后,返回对应的资源给客户端。

2、浏览器

  • [捕捉地址] 输入地址后,浏览器进程的UI线程捕捉输入的地址
  • [发送请求] 网络线程进行DNS解析,并将请求发送到服务端
  • [接收数据] 服务端返回资源(以HTML、JavaScript和CSS为例)
  • [渲染器进程] 浏览器进程将返回的资源通过 IPC 管道传给渲染器进程
  • [DOM解析] 渲染器进程的主线程解析 HTML 文件,生成 DOM 节点树
  • [样式计算] 浏览器解析 CSS文件,进行样式计算,确定每个节点的样式
  • [布局] 根据 DOM 节点树和计算好的样式,生成 LayoutTree,来确定每个节点放在页面上的哪个位置及大小
  • [绘制] 遍历 LayoutTree 生成绘制记录表(Layer Tree),确定以何种顺序来绘制节点(如 z-index
  • [分图层] 合成器线程按规则分图层,并分为更小的图块给栅格线程
  • [栅格化] 将绘制记录表的信息转化成 draw quads 图块信息(记录了图块在内存中的位置、摆放顺序、页面位置等信息,用户可以直接看到的部分),并传回给合成器线程
  • [合成] 合成器线程将图层进行组合,形成合成器帧,并传回给浏览器进程
  • [渲染] 浏览器进程再将帧传回给 GPU 进行渲染展示

注意

  • [JavaScript 会阻塞主线程] DOM 解析过程中遇到CSS、Image 等资源不会阻塞 HTML 加载,因为其不会出现在 DOM 树节点中,而当遇到 script 标签时,会停止解析 DOM,原因在于:浏览器并不知道这段 JavaScript 是否会改变当前的 DOM 树,因此会执行完再继续解析 DOM,这就会引起页面渲染的卡顿。
  • [DOM和Layout树] DOM Tree 和 Layout Tree 不一一对应。如设置了 display: none 的节点会出现在 DOM 树上,而不会出现在 Layout 树上;而设置了伪类如 ::after 的节点只会出现在 Layout 树上,而不会出现在DOM树。原因如下:
    DOM 由HTML 解析获得,不关联样式
    Layout 树是根据 DOM 和计算好的样式来生成

14、解决跨域的方法

  • iframe+document.domain location.hash window.name
  • postMessage
  • proxyTable
  • nodejs中间件
  • cors
  • websocket
  • jsonp
  • nginx反向代理

二、vue.js

1、前端怎么根据接口实现动态路由

关键点是router.addRoute方法 先对接口返回的routesList格式化处理成vue-router配置的路由格式,然后再添加到配置当中

this.routesList.map((item)=>{
  console.log(item)
  this.$router.addRoute(item)
})

2、全局和组件内路由守卫的钩子有哪些

  • 全局守卫:beforeEach(to,from,next) 和 afterEach(to,from)
  • 路由独享守卫:beforeEnter 在单独的路由配置中使用
  • 组件内的守卫:路由进入/更新/离开之前/beforeRouterEnter/update/leave

3、vue组件的生命周期有哪些,分别做了什么事情

  • 1.beforeCreate 在实例初始化之后,数据观测(data observer) 之前被调用。这里不能访问到data数据和$el
  • 2.created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算,watch/event 事件回调。这里没有$el,但是可以访问data
  • 3.beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。
  • 4.mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
  • 5.beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
  • 6.updated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
  • 7.beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。
  • 8.destroyed Vue 实例销毁后调用。调用后, Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用

要掌握每个生命周期内部可以做什么事

  • 1.created 实例已经创建完成,因为它是最早触发的原因可以进行一些数据,资源的请求。
  • 2.mounted 实例已经挂载完成,可以进行一些DOM操作
  • 3.beforeUpdate 可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
  • 4.updated 可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
  • 5.destroyed 可以执行一些优化操作,清空定时器,解除绑定事件

4、computed和watch的区别在哪里

计算属性computed :

  1. 支持缓存,只有依赖数据发生改变,才会重新进行计算
  2. 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
  3. computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
  4. 如果一个属性依赖其他属性,是一个多对一或者一对一,一般用computed
  5. 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。

侦听属性watch:

  1. 不支持缓存,数据变,直接会触发相应的操作;
  2. watch支持异步;
  3. 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
  4. 当一个属性发生变化时,需要执行对应的操作;一对多;
  5. 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,
    immediate:组件加载立即触发回调函数执行,
    deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。

5、template上可以用v-if和v-show吗

v-if 的特点:每次都会重新删除或创建元素
v-show 的特点: 是切换了元素的 display:none 样式
v-if 有更高的切换消耗而 v-show 有更高的初始渲染消耗。因此,如果需要频繁切换 v-show 较好,如果在运行时条件不大可能改变 v-if 较好。

由于v-show是切换display:none,template并不会生成DOM,当v-show和template结合使用,变量是false,内容隐藏功能将失效

6、v-for时使用key关键字的作用,怎么动态渲染列表

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

  • (1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
    若虚拟DOM中内容没变, 直接使用之前的真实DOM!
    若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
  • (2).旧虚拟DOM中未找到与新虚拟DOM相同的key
    创建新的真实DOM,随后渲染到到页面。

用index作为key可能会引发的问题:

  1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
  2. 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。

开发中如何选择key?:

  1. 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
  2. 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

7、vue中seo的方法

1.SSR服务器渲染;
2.静态化打包;
3.预渲染prerender-spa-plugin;
4.使用Phantomjs针对爬虫做处理。原理就是通过Nginx配置, 判断访问的来源UA是否是爬虫访问,如果是则将搜索引擎的爬虫请求转发到一个node server,再通过PhantomJS来解析完整的HTML,返回给爬虫。

总结

  • 如果构建大型网站,如商城类,别犹豫,直接上SSR服务器渲染,当然也有相应的坑等你,社区较成熟,英文好点,一切问题都迎刃而解。
  • 如果只是个人博客、公司官网这类,其余三种都可以。
  • 如果对已用SPA开发完成的项目进行SEO优化,而且支持node服务器,请使用 Phantomjs 。

8、vue中数据通信和事件通信有哪些方法

  1. props / $emit
  2. children/children / parent
  3. provide/ inject
  4. ref / refs
  5. eventBus 事件总线
  6. Vuex状态管理
  • state:用于数据的存储,是store中的唯一数据源
  • getters:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算
  • mutations:类似函数,改变state数据的唯一途径,且不能用于处理异步事件
  • actions:类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作
  • modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护
  1. localStorage / sessionStorage
  2. attrsattrs与 listeners

总结

常见使用场景可以分为三类:

  • 父子组件通信: props; parent/parent / children; provide / inject ; ref ; attrs/attrs / listeners
  • 兄弟组件通信: eventBus ; vuex
  • 跨级通信: eventBus;Vuex;provide / inject 、attrs/attrs / listeners

9、为什么mutation不能做异步操作

在Mutation中使用异步不会对数据造成丢失和其他影响。然而当我们用Vue Devtools查看多次Mutation状态变化时,发现同步的显示Ok,异步的数据显示的和我们预期结果不一致,所以会造成状态改变的不可追踪,所以官方说我们Mutation是同步的!

重点事情
造成状态改变的不可追踪

在actions中就不会出现这种状态改变不可追踪的情况

10、修改了本地依赖包代码,怎么做到npm install之后不覆盖

  • .gitignore忽略修改过的文件
  • 改源码打包使用script引入
  • 发布自己的私包
  • 另外一种思路,不改动源码,用prototype重写或者修改原有api方法

11、vue-router怎么配置404页面

  • path设置为*号通配404默认页面
  • 放置在routes数组中最后面

12、beforeRouterEnter能访问到this吗

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。

不过,你可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。

beforeRouteEnter (to, from, next) {
  next(vm => {
    // beforeRouteEnter不能通过this访问组件实例,但是可以通过 vm 访问组件实例
    console.log(vm.demodata)   //vm.demodata即this.demodata
  })
}

13、document.getElementById()等操作可以访问到vue根节点(app节点)以外的dom元素吗

可以的,

  • 方法一: 直接给相应的元素加id,然后再document.getElementById(“id”);获取,然后设置相应属性或样式
  • 方法二: 使用ref,给相应的元素加ref=“name” 然后再this.$refs.name获取到该元素 注意:在获取相应元素之前,必须在mount钩子进行挂载,否则获取到的值为空,

14、自定义组件实现input输入框的金额显示千分位格式

可以直接在blur方法里面做数据格式化,但是如果想要不改变v-model里面的值,可以用自定义组件来做,如下:

<template>
  <div>
    <div class="el-input el-input--small" v-bind:class="{'is-disabled':disabled}">
      <input class="el-input__inner" v-bind:value="formatValue" v-on:input="updatevalue($event.target.value)" v-on:blur="onBlur" v-on:focus="selectAll" v-bind:disabled="disabled" />
    </div>
  </div>
</template>

<script>
import accounting from "accounting";
export default {
  props: {
    value: {
      type: [String, Number],
      default: 0,
      desc: "数值",
    },
    symbol: {
      type: String,
      default: "",
      desc: "货币标识符",
    },
    decimal: {
      type: Number,
      default: 2,
      desc: "小数位",
    },
    disabled: {
      type: Boolean,
      default: false,
      desc: "是否禁止",
    },
  },
  data() {
    return {
      focused: false,
    };
  },
  computed: {
    formatValue() {
      if (this.focused) {
        return this.value ? accounting.unformat(this.value) : "";
      } else {
        if (this.value === 0) {
          return accounting.formatMoney(this.value, this.symbol, this.decimal);
        } else if (
          this.value === "" ||
          this.value === null ||
          this.value === undefined
        ) {
          return "";
        } else {
          return accounting.formatMoney(this.value, this.symbol, this.decimal);
        }
      }
    },
  },
  watch: {
    value(val) {if (this.validateEvent) {
        this.dispatch("ElFormItem", "el.form.change", [val]);
      }
    },
  },
  methods: {
    updatevalue(value) {
      var formatvalue = !!value ? accounting.unformat(value) : "";
      this.$emit("input", formatvalue);
    },
    onBlur() {
      this.focused = false;
      this.$emit("blur");
      this.dispatch("ElFormItem", "el.form.blur", [this.value]);
    },
    selectAll(event) {
      this.focused = true;
      setTimeout(() => {
        event.target.select();
      }, 0);
    },

    dispatch(componentName, eventName, params) {
      var parent = this.$parent || this.$root;
      var name = parent.$options.componentName;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.componentName;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
  },
};
</script>

15、vue组件里面data为什么要用return返回对象而不是直接定义一个对象

组件是一个可复用的实例,当你引用一个组件的时候,组件里的data是一个普通的对象,所有用到这个组件的都引用的同一个data,就会造成数据污染。

将data封装成函数后,在实例化组件的时候,我们只是调用了data函数生成的数据副本,避免了数据污染。

16、自定义指令的理解

Vue 提供了自定义指令的5个钩子函数:

  • bind():当指令绑定在 HTML 元素上时触发
  • inserted():当指令绑定的元素插入到父节点中的时候触发
  • update():当指令绑定的元素状态/样式、内容(这里指元素绑定的 vue 数据) 发生改变时触发
  • componentUpdated():当 update() 执行完毕之后触发
  • unbind():当指令绑定的元素从 dom 中删除时触发

注意:

  • 除 update 与 componentUpdated 钩子函数之外,每个钩子函数都含有 el、binding、vnode 这三个参数
  • 在每个函数中,第一个参数永远是 el, 表示被绑定了指令的那个 dom 元素,这个el 参数,是一个原生的 JS 对象,所以 Vue 自定义指令可以用来直接和 DOM 打交道
  • binding 是一个对象,它包含以下属性:name、value、oldValue、expression、arg、modifiers
  • oldVnode 只有在 update 与 componentUpdated 钩子中生效
  • 除了 el 之外,binding、vnode 属性都是只读的

举几个应用场景的栗子

(1、输入框自动获取焦点(官方示例)。

(2、点击下拉菜单以外的区域隐藏菜单。

(3、输入的邮箱、电话的校验。

17、vue2.0和vue3.0的区别

vue3的变化可以总结为以下几点:

  • 更小
  • 更快
  • 加强typescript支持
  • Api一致性
  • 提高可维护能力
  • 开放更多底层功能
  • 重构响应式系统,使用Proxy替换Object.defineProperty
  • 新增Composition API,更好的逻辑复用和代码组织
  • 重构 Virtual DOM

18、webpack里面loader和plugin的区别和实现方式

常用配置项简单说明

  • entry:打包的入口文件,它可以是一个字符串或者一个对象。
  • output:配置打包的输出结果,为一个对象。
  • fileName:定义输出文件名,为一个字符串。
  • path:定义输出文件路径,为一个字符串。
  • module:定义对模块的处理逻辑,为一个对象。
  • loaders:定义一系列的加载器,为一个数组。 
  • resolve:影响对模块的解析,为一个对象
  • extensions:自动补全识别后缀,为一个数组
  • plugins:定义插件,为一个数组

webpack是现在前端开发常用的打包工具,webpack本身只能识别js文件,但是前端资源还包含css style 图片等,webpack无法识别和打包,因此需要loader来加载并转换文件,完成文件的编译,打包和压缩。而plugin是为了扩展webpack的功能,作用于webpack的整个生命周期内。

作用点不同

  • loadre作用于文件
  • plugin作用于webpack在loader之后的整个生命周期

作用时间不同

  • 先loader 再用plugin

作用不同

  • 都是为了扩展webpack的功能。
  • 但loader只作用于文件,只是为了文件转换
  • plugin 不会直接作用于文件,而是监听webpack在全周期的事件,有点像切面和不同周期内的拦截器

19、vue中keep-alive的作用

作用以及好处
在做电商有关的项目中,当我们第一次进入列表页需要请求一下数据,当我从列表页进入详情页,详情页不缓存也需要请求下数据,然后返回列表页,这时候我们使用keep-alive来缓存组件,防止二次渲染,这样会大大的节省性能。

缓存所有的页面,直接用<keep-alive>标签包裹,缓存某个组件直接在router中添加

meta:{
    keepAlive: true
}

keep-alive的生命周期

当引入keep-alive的时候,页面第一次进入,钩子的触发顺序created-> mounted-> activated,退出时触发deactivated。当再次进入(前进或者后退)时,只触发activated。

三、uniapp

1、怎么做到多平台的兼容

条件编译
条件编译是用特殊的注释作为标记,在编译时根据这些特殊的注释,将注释里面的代码编译到不同平台

  • #ifdef:if defined 仅在某平台存在
  • #ifndef:if not defined 除了某平台均存在

2、uniapp怎么兼容ios的刘海屏

  • iOS方案一:使用原生占位(仅App端支持)

manifest.json 文件 app-plus 节点下配下 safearea

"safearea": {  
    "background": "#CCCCCC",  
    "bottom": {  
        "offset": "auto"  
    }  
}
  • iOS方案二:不使用原生占位(非App端可以不配置manifest) manifest.json 文件 app-plus 节点下配下 safearea
"safearea": {  
    "bottom": {  
        "offset": "none"  
    }  
}

然后在需要适配的页面内使用 css 常量 constant(safe-area-inset-bottom)、env(safe-area-inset-bottom) 来适配,参考:为iPhoneX设计网站。微信小程序模拟器不支持,以真机为准。

比如为列表底部添加内边距避开安全区,在 iPhoneX 上列表底部会有内边距,在其他设备上没有内边距:

<style>  
.list {  
  padding-bottom: 0;  
  padding-bottom: constant(safe-area-inset-bottom);  
  padding-bottom: env(safe-area-inset-bottom);  
}  
</style>

注意:使用了pages.json里的原生tabbar,不管manifest里安全区设置如何,在tabbar下方都会留出空隙。

判断是否为刘海屏的方法
使用5+ API (plus.navigator.hasNotchInScreen)可查询当前设备是否为刘海屏

3、uniapp怎么跟外部H5通信

可以直接使用vue页面,通过@message方法即可进行通讯

<template>  
    <view><web-view :src="src" @message="handleMsg"></web-view></view>  
</template>  

handleMsg(evt) {  
    console.log('接收到的消息:' + JSON.stringify(evt.detail.data));  
},

网页向应用 postMessage 时,会在特定时机(后退、组件销毁、分享)触发并收到消息。

四、css基础

1、有多少种方法实现水平居中垂直

水平居中

  1. 若是行内元素, 给其父元素设置 text-align:center,即可实现行内元素水平居中.
  2. 若是块级元素, 该元素设置 margin:0 auto即可.
  3. 若子元素包含 float:left 属性, 为了让子元素水平居中, 则可让父元素宽度设置为fit-content,并且配合margin, 作如下设置:
.parent{
    width: -moz-fit-content;
    width: -webkit-fit-content;
    width:fit-content;
    margin:0 auto;
}

fit-content是CSS3中给width属性新加的一个属性值,它配合margin可以轻松实现水平居中, 目前只支持Chrome 和 Firefox浏览器.

  1. 使用justify-content: center;可以轻松的实现水平居中。
  2. 绝对定位+transform:translate(-50%,0);
  3. 使用绝对定位方式, 以及负值的margin-left
  4. 用绝对定位方式, 以及left:0;right:0;margin:0 auto;
  5. 盒模型
display: box;
box-orient: horizontal;
box-pack: center;

垂直居中

  1. 若元素是单行文本, 则可设置 line-height 等于父元素高度
  2. 若元素是行内块级元素, 基本思想是使用display: inline-block, vertical-align: middle和一个伪元素让内容块处于容器中央.
  3. 设置父元素display:table, 子元素 display:table-cell;vertical-align:middle;
  4. align-items: center;
  5. 绝对定位+transform:translate(-50%,0);
  6. 使用绝对定位方式, 以及负值的margin-top
  7. 绝对定位,left,top,right,bottom设置为0,margin:0 auto
  8. 盒模型
display: box;
box-orient: vertical;
box-pack: center;

2、并列的三个div怎么实现品字布局

image.png

<!doctype html>
<html>

<head>
  <meta charset="utf-8">
  <title>品字布局</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
    body {
      overflow: hidden;
    }
    div {
      margin: auto 0;
      width: 100px;
      height: 100px;
      background: red;
      font-size: 40px;
      line-height: 100px;
      color: #fff;
      text-align: center;
    }

    .div1 {
      margin: 100px auto 0;
    }

    .div2 {
      margin-left: 50%;
      background: green;
      float: left;
      transform: translateX(-100%);
    }

    .div3 {
      background: blue;
      float: left;
      transform: translateX(-100%);
    }
  </style>
</head>

<body>
  <div class="div1">1</div>
  <div class="div2">2</div>
  <div class="div3">3</div>
</body>

</html>


3、怎么实现0.5像素边框(移动端)

  1. 通过background-image的渐变效果linear-gradient实现:
.gradient{
    width: 200px;
    height: 20px;
    background-position: left bottom;
    background-image:linear-gradient(to bottom,transparent 50%,#000 50%);
    background-repeat: no-repeat;
    background-size: 100% 1px;
}

  1. 通过border-image的边框背景图实现
.gradient{
    width: 240px;
    height: 20px;
    border:1px solid #ddd;
    border-image: url(data:image/gif;base64,R0lGODlhBQAFAIABAN3d3f///yH5BAEAAAEALAAAAAAFAAUAAAIHhB9pGatnCgA7) 2 stretch;
    -webkit-border-image: url(data:image/gif;base64,R0lGODlhBQAFAIABAN3d3f///yH5BAEAAAEALAAAAAAFAAUAAAIHhB9pGatnCgA7) 2 stretch;
}

----------ending----------

以上是我通过自己理解来整理的面试知识点,希望对你们有帮助,如有补充或者修正的地方,欢迎评论!