最近在面试前端的工作,面试了几家,在这里做一个暂时性的总结,后面如果还有面试会持续的更新 内容有点多,可以收藏了慢慢看
一、 js基础
1、说说你对原型和原型链的理解
js里面当函数对象被创建的时候,都会携带一个prototype的属性,这个属性指向一个prototype对象,也就是原型对象;
各级子对象的_proto_属性连线引用形成的结构就叫做原型链
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、从地址栏输入一个网址开始,浏览器都做了什么事情
- 网络 从网络方面的角度来看,以较为简单的流程来说,如下:
- [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 :
- 支持缓存,只有依赖数据发生改变,才会重新进行计算
- 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
- computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
- 如果一个属性依赖其他属性,是一个多对一或者一对一,一般用computed
- 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
侦听属性watch:
- 不支持缓存,数据变,直接会触发相应的操作;
- watch支持异步;
- 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
- 当一个属性发生变化时,需要执行对应的操作;一对多;
- 监听数据必须是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可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
- 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。
开发中如何选择key?:
- 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用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中数据通信和事件通信有哪些方法
- props / $emit
- parent
- provide/ inject
- ref / refs
- eventBus 事件总线
- Vuex状态管理
- state:用于数据的存储,是store中的唯一数据源
- getters:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算
- mutations:类似函数,改变state数据的唯一途径,且不能用于处理异步事件
- actions:类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作
- modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护
- localStorage / sessionStorage
- listeners
总结
常见使用场景可以分为三类:
- 父子组件通信: props; children; provide / inject ; ref ; listeners
- 兄弟组件通信: eventBus ; vuex
- 跨级通信: eventBus;Vuex;provide / inject 、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、有多少种方法实现水平居中垂直
水平居中
- 若是行内元素, 给其父元素设置 text-align:center,即可实现行内元素水平居中.
- 若是块级元素, 该元素设置 margin:0 auto即可.
- 若子元素包含 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浏览器.
- 使用justify-content: center;可以轻松的实现水平居中。
- 绝对定位+transform:translate(-50%,0);
- 使用绝对定位方式, 以及负值的margin-left
- 用绝对定位方式, 以及left:0;right:0;margin:0 auto;
- 盒模型
display: box;
box-orient: horizontal;
box-pack: center;
垂直居中
- 若元素是单行文本, 则可设置 line-height 等于父元素高度
- 若元素是行内块级元素, 基本思想是使用display: inline-block, vertical-align: middle和一个伪元素让内容块处于容器中央.
- 设置父元素display:table, 子元素 display:table-cell;vertical-align:middle;
- align-items: center;
- 绝对定位+transform:translate(-50%,0);
- 使用绝对定位方式, 以及负值的margin-top
- 绝对定位,left,top,right,bottom设置为0,margin:0 auto
- 盒模型
display: box;
box-orient: vertical;
box-pack: center;
2、并列的三个div怎么实现品字布局
<!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像素边框(移动端)
- 通过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;
}
- 通过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----------
以上是我通过自己理解来整理的面试知识点,希望对你们有帮助,如有补充或者修正的地方,欢迎评论!