前端面试题-基础

1.移动端适配1px的问题

解答:css

(1)缘由:html

css中的1px并不等于移动设备的1px,这些因为不一样的手机有不一样的像素密度。在window对象中有一个devicePixelRatio属性,他能够反应css中的像素与设备的像素比。前端

devicePixelRatio的官方的定义为:设备物理像素和设备独立像素的比例,也就是 devicePixelRatio = 物理像素 / 独立像素。vue

关于devicePixelRatio的详细介绍能够参考张鑫旭的这篇博文,http://www.zhangxinxu.com/wordpress/2012/08/window-devicepixelratio/webpack

(2)解决方法:nginx

a:0.5px边框

IOS8下已经支持带小数的px值, media query 对应 devicePixelRatio 有个查询值 -webkit-min-device-pixel-ratio, css能够写成这样
经过-webkit-min-device-pixel-ratio设置。web

.border { border: 1px solid #999 }
@media screen and (-webkit-min-device-pixel-ratio: 2) {
    .border { border: 0.5px solid #999 }
}
@media screen and (-webkit-min-device-pixel-ratio: 3) {
    .border { border: 0.333333px solid #999 }
}

 

若是使用less/sass的话只是加了1句mixin
缺点: 安卓与低版本IOS不适用, 这个或许是将来的标准写法, 如今不作期望vue-router

b.viewport + rem 实现

同时经过设置对应viewport的rem基准值,这种方式就能够像之前同样轻松愉快的写1px了。
在devicePixelRatio = 2 时,输出viewport:vue-cli

<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">

 

在devicePixelRatio = 3 时,输出viewport:
<meta name="viewport" content="initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no">

 

c.使用border-image实现

准备一张符合你要求的border-image:npm

.border-bottom-1px {
    border-width: 0 0 1px 0;
    -webkit-border-image: url(linenew.png) 0 0 2 0 stretch;
    border-image: url(linenew.png) 0 0 2 0 stretch;
}

 

上文是把border设置在边框的底部,因此使用的图片是2px高,上部的1px颜色为透明,下部的1px使用视觉规定的border的颜色。若是边框底部和顶部同时须要border,可使用下面的border-image:

.border-image-1px {
    border-width: 1px 0;
    -webkit-border-image: url(linenew.png) 2 0 stretch;
    border-image: url(linenew.png) 2 0 stretch;
}

 

d.使用background-image实现

background-image 跟border-image的方法同样,你要先准备一张符合你要求的图片。而后将边框模拟在背景上。
样式设置:

.background-image-1px {
    background: url(../img/line.png) repeat-x left bottom;
    -webkit-background-size: 100% 1px;
    background-size: 100% 1px;
}

 

e.使用box-shadow模拟边框

利用css 对阴影处理的方式实现0.5px的效果

.box-shadow-1px {
    box-shadow: inset 0px -1px 1px -1px #c8c7cc;
}

 

f.伪类 + transform 实现

对于老项目,有没有什么办法能兼容1px的尴尬问题了,我的认为伪类+transform是比较完美的方法了。
原理是把原先元素的 border 去掉,而后利用 :before 或者 :after 重作 border ,并 transform 的 scale 缩小一半,原先的元素相对定位,新作的 border 绝对定位。
单条border样式设置:

.scale-1px{
    position: relative;
    border:none;
}
.scale-1px:after{
    content: '';
    position: absolute;
    bottom: 0;
    background: #000;
    width: 100%;
    height: 1px;
    -webkit-transform: scaleY(0.5);
    transform: scaleY(0.5);
    -webkit-transform-origin: 0 0;
    transform-origin: 0 0;
}

 

四条boder样式设置:

.scale-1px{
    position: relative;
    margin-bottom: 20px;
    border:none;
}
.scale-1px:after{
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    border: 1px solid #000;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    width: 200%;
    height: 200%;
    -webkit-transform: scale(0.5);
    transform: scale(0.5);
    -webkit-transform-origin: left top;
    transform-origin: left top;
}

 

最好在使用前也判断一下,结合 JS 代码,判断是否 Retina 屏:

if(window.devicePixelRatio && devicePixelRatio >= 2){
    document.querySelector('ul').className = 'scale-1px';
}

 

2.如何解决跨域的问题

1. JSONP 

利用 <script> 标签没有跨域限制的漏洞,网页能够获得从其余来源动态产生的 JSON 数据。JSONP 请求必定须要对方的服务器作支持才能够。

JSONP 优势是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持 get 方法具备局限性,不安全可能会遭受 XSS 攻击。

2.cors

CORS 须要浏览器和后端同时支持。IE 8 和 9 须要经过 XDomainRequest 来实现。

浏览器会自动进行 CORS 通讯,实现 CORS 通讯的关键是后端。只要后端实现了 CORS,就实现了跨域。

服务端设置 Access-Control-Allow-Origin 就能够开启 CORS。 该属性表示哪些域名能够访问资源,若是设置通配符则表示全部网站均可以访问资源。

虽然设置 CORS 和前端没什么关系,可是经过这种方式解决跨域问题的话,会在发送请求时出现两种状况,分别为简单请求和复杂请求。

3.postMessage 

postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,且是为数很少能够跨域操做的 window 属性之一,它可用于解决如下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的 iframe 消息传递
  • 上面三个场景的跨域数据传递

postMessage()方法容许来自不一样源的脚本采用异步方式进行有限的通讯,能够实现跨文本档、多窗口、跨域消息传递。

otherWindow.postMessage(message, targetOrigin, [transfer]);

    • message: 将要发送到其余 window 的数据。
    • targetOrigin:经过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值能够是字符串"*"(表示无限制)或者一个 URI。在发送消息的时候,若是目标窗口的协议、主机地址或端口这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会被发送;只有三者彻底匹配,消息才会被发送。
    • transfer(可选):是一串和 message 同时传递的 Transferable 对象. 这些对象的全部权将被转移给消息的接收方,而发送一方将再也不保有全部权。

4.Websocket 

Websocket 是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通讯,同时也是跨域的一种解决方案。WebSocket 和 HTTP 都是应用层协议,都基于 TCP 协议。可是 WebSocket 是一种双向通讯协议,在创建链接以后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在创建链接时须要借助 HTTP 协议,链接创建好了以后 client 与 server 之间的双向通讯就与 HTTP 无关了。 

原生 WebSocket API 使用起来不太方便,咱们使用Socket.io,它很好地封装了 WebSocket 接口,提供了更简单、灵活的接口,也对不支持 WebSocket 的浏览器提供了向下兼容。

5. Node 中间件代理(两次跨域) 

实现原理:同源策略是浏览器须要遵循的标准,而若是是服务器向服务器请求就无需遵循同源策略。

代理服务器,须要作如下几个步骤:

  • 接受客户端请求 。
  • 将请求转发给服务器。
  • 拿到服务器响应数据。
  • 将响应转发给客户端。 

6.nginx 反向代理

实现原理相似于 Node 中间件代理,须要你搭建一个中转 nginx 服务器,用于转发请求。

使用 nginx 反向代理实现跨域,是最简单的跨域方式。只须要修改 nginx 的配置便可解决跨域问题,支持全部浏览器,支持 session,不须要修改任何代码,而且不会影响服务器性能。

实现思路:经过 nginx 配置一个代理服务器(域名与 domain1 相同,端口不一样)作跳板机,反向代理访问 domain2 接口,而且能够顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域登陆。

7.window.name + iframe

window.name属性的独特之处:name 值在不一样的页面(甚至不一样域名)加载后依旧存在,而且能够支持很是长的 name 值(2MB)。

经过 iframe 的 src 属性由外域转向本地域,跨域数据即由 iframe 的 window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操做。

8.location.hash + iframe

实现原理: a.html 欲与 c.html 跨域相互通讯,经过中间页 b.html 来实现。 三个页面,不一样域之间利用 iframe 的 location.hash 传值,相同域之间直接 js 访问来通讯。

具体实现步骤:一开始 a.html 给 c.html 传一个 hash 值,而后 c.html 收到 hash 值后,再把 hash 值传递给 b.html,最后 b.html 将结果放到 a.html 的 hash 值中。

9.document.domain + iframe

该方式只能用于二级域名相同的状况下,好比 a.test.com 和 b.test.com 适用于该方式。

只须要给页面添加 document.domain ='test.com' 表示二级域名都相同就能够实现跨域。

实现原理:两个页面都经过 js 强制设置 document.domain 为基础主域,就实现了同域。

3.路由的动态加载

在Vue项目中,通常使用vue-cli构建项目后,咱们会在Router文件夹下面的index.js里面引入相关的路由组件,如:

import Hello from '@/components/Hello'
import Boy from '@/components/Boy'
import Girl from '@/components/Girl'

这样作的结果就是webpack在npm run build的时候会打包成一个整个的js文件,若是页面一多,会致使这个文件很是大,加载缓慢,为了解决这个问题,须要将他分红多个小文件,并且还要实现异步按需加载,即用到了再加载,而不用一股脑所有加载。

1.webpack提供的require.ensure(),这样能够实现按需加载,而且你能够将多个相同类的组件打包成一个文件,只要给他们指定相同的chunkName便可,如示例中的demo将会打包成一个文件。

{
     path:  '/promisedemo' ,
     name:  'PromiseDemo' ,
     component: r => require.ensure([], () => r(require( '../components/PromiseDemo' )),  'demo' )
},
{
     path:  '/hello' ,
     name:  'Hello' ,
     // component: Hello
     component: r => require.ensure([], () => r(require( '../components/Hello' )),  'demo' )
}

2.Vue的异步组件技术,这种方法能够实现按需加载,而且一个组件会打包成一个js文件

{
            path:  '/promisedemo' ,
            name:  'PromiseDemo' ,
            component: resolve => require([ '../components/PromiseDemo' ], resolve)
        }


3.es提案的import(),也是我推荐的方法

首先,能够将异步组件定义为返回一个 Promise 的工厂函数 (该函数返回的 Promise 应该 resolve 组件自己):

const Foo = () => Promise.resolve({  /* 组件定义对象 */  })

第二,在 Webpack 2 中,咱们可使用动态 import语法来定义代码分块点 (split point):

import( './Foo.vue' // 返回 Promise

注意:若是您使用的是 Babel,你将须要添加 syntax-dynamic-import 插件,才能使 Babel 能够正确地解析语法。

结合这二者,这就是如何定义一个可以被 Webpack 自动代码分割的异步组件。

const Foo = () => import( './Foo.vue' )
这样作的结果是每一个组件都会打包成一个js文件,有时候咱们想把某个路由下的全部组件都打包在同个异步块 (chunk) 中。只须要使用 命名 chunk,一个特殊的注释语法来提供 chunk name
const Foo = () => import( /* webpackChunkName: "group-foo" */  './Foo.vue' )

 

4.怎么实现对象的深拷贝

/*
  this的使用:在预编译的过程this指向是window
  在全局做用域里this指向是window
  call/apply 改变this的指向
  obj.function();function()里面的this指向的是obj
  */
 
var  obj = {
     a:  function  () {
         console.log( this .name)
 
     },
     name:  '123'
}
obj.a();   //谁调用这个方法this指向谁,没用调用就是预编译
 
var  foo = 123;
function  print() {
     this .foo = 234;
     console.log(foo);
 
}
 
/*print();*/  //234
new  print();  //123
 
 
/*
  arguments.callee:指向函数的引用,也就是它自己
  */
 
var  num = ( function  (n) {
 
     if  (n == 1) {
         return  1;
     else  {
         return  n * arguments.callee(n - 1);
     }
 
}(10))
 
 
/*深层拷贝*/
/*
  遍历对象 for (var prop in obj)
  1:判断是否是原始值  typeof()
  2:判断是数组仍是对象
  3:判断相应的数组和对象
  */
 
var  obj = {
     name:  'tom' ,
     age: 23,
     son: {},
     wife: [ 'sss' 'ddd' ]
}
 
 
function  deepClone(origin, target) {
     //容错
     var  target = target || {},
         toStr = Object.prototype.toString(),
         arrStr =  "[Object Array]" ;
 
 
     for  ( var  prop  in  origin){
         if  (origin.hasOwnProperty(prop)){
             if  (origin[prop] !==  'null'  &&  typeof  (origin[prop]) ==  'object' ){
                if  ( toStr.call(origin[prop]) == arrStr){
                    target[prop]= [];
                } else  {
                    target[prop] ={};
                }
                deepClone(origin[prop],target[prop]);
             } else  {
                 target[prop] == origin[prop];
             }
         }
     }
 
}

5.什么叫优雅降级和渐进加强

优雅降级:

Web站点在全部新式浏览器中都能正常工做,若是用户使用的是老式浏览器,则代码会检查以确认它们是否能正常工做。因为IE独特的盒模型布局问题,针对不一样版本的IE的hack实践过优雅降级了,为那些没法支持功能的浏览器增长候选方案,使之在旧式浏览器上以某种形式降级体验却不至于彻底失效。

渐进加强:

从被全部浏览器支持的基本功能开始,逐步地添加那些只有新式浏览器才支持的功能,向页面增长无害于基础浏览器的额外样式和功能的。当浏览器支持时,它们会自动地呈现出来并发挥做用。

.transition {  /*渐进加强写法*/
   -webkit-transition: all .5s;
      -moz-transition: all .5s;
        -o-transition: all .5s;
           transition: all .5s;
}
.transition {  /*优雅降级写法*/
           transition: all .5s;
        -o-transition: all .5s;
      -moz-transition: all .5s;
   -webkit-transition: all .5s;
}

  前缀CSS3(-webkit- / -moz- / -o-*)和正常的 CSS3 在浏览器中的支持状况是这样的:

  1. 好久之前:浏览器前缀CSS3和正常的CSS3都不支持。
  2. 不久之前:浏览器只支持前缀CSS3,不支持正常CSS3。
  3. 如今:浏览器既支持前缀CSS3,又支持CSS3。
  4. 将来:浏览器不支持前缀CSS3,仅支持正常CSS3。

 注意: css中须要知道的是 - 若是属性不可用,则不发挥任何做用,无影响;若是属性是相同的做用,则后者会覆盖前者。

      渐进加强的写法,优先考虑老版本浏览器的可用性,最后才考虑新版本浏览器的可用性。

    而优雅降级的写法,优先考虑新版本浏览器的可用性,最后才考虑浏览器的可用性。 

   就CSS3来讲,咱们更加推荐渐进加强的写法。  

6.promise、async有什么区别(对async、await的理解,内部原理、介绍下Promise,内部实现)

理解 async/await

7.介绍AST(Abstract Syntax Tree)抽象语法树

AST抽象语法树

8.什么是防抖和节流?有什么区别?如何实现?

防抖

触发高频事件后n秒内函数只会执行一次,若是n秒内高频事件再次被触发,则从新计算时间

思路:

每次触发事件时都取消以前的延时调用方法

function  debounce(fn) {
       let timeout =  null // 建立一个标记用来存放定时器的返回值
       return  function  () {
         clearTimeout(timeout);  // 每当用户输入的时候把前一个 setTimeout clear 掉
         timeout = setTimeout(() => {  // 而后又建立一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内若是还有字符输入的话,就不会执行 fn 函数
           fn.apply( this , arguments);
         }, 500);
       };
     }
     function  sayHi() {
       console.log( '防抖成功' );
     }
 
     var  inp = document.getElementById( 'inp' );
     inp.addEventListener( 'input' , debounce(sayHi));  // 防抖

 

节流

高频事件触发,但在n秒内只会执行一次,因此节流会稀释函数的执行频率

 思路: 

每次触发事件时都判断当前是否有等待执行的延时函数

function  throttle(fn) {
       let canRun =  true // 经过闭包保存一个标记
       return  function  () {
         if  (!canRun)  return // 在函数开头判断标记是否为true,不为true则return
         canRun =  false // 当即设置为false
         setTimeout(() => {  // 将外部传入的函数的执行放在setTimeout中
           fn.apply( this , arguments);
           // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示能够执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
           canRun =  true ;
         }, 500);
       };
     }
     function  sayHi(e) {
       console.log(e.target.innerWidth, e.target.innerHeight);
     }
     window.addEventListener( 'resize' , throttle(sayHi));

9.vue-router有哪几种导航钩子

  • 全局导航钩子
  • router.beforeEach(to, from, next),
  • router.beforeResolve(to, from, next),
  • router.afterEach(to, from ,next)
  • 组件内钩子
  • beforeRouteEnter,
  • beforeRouteUpdate,
  • beforeRouteLeave
  • 单独路由独享组件
  • beforeEnter

10.Vue的双向数据绑定原理是什么?

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,经过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变更时发布消息给订阅者,触发相应的监听回调。

具体步骤:
第一步:须要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化

第二步:compile 解析模板指令,将模板中的变量替换成数据,而后初始化渲染页面视图,并将每一个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变更,收到通知,更新视图

第三步:Watcher 订阅者是 Observer 和 Compile 之间通讯的桥梁,主要作的事情是:

  • 在自身实例化时往属性订阅器(dep)里面添加本身
  • 自身必须有一个 update()方法
  • 待属性变更 dep.notice()通知时,能调用自身的 update() 方法,并触发 Compile 中绑定的回调,则功成身退。

第四步:MVVM 做为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,经过 Observer 来监听本身的 model 数据变化,经过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通讯桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变动的双向绑定效果。

11.请详细说下你对vue生命周期的理解?

总共分为 8 个阶段建立前/后,载入前/后,更新前/后,销毁前/后。

  • 建立前/后: 在 beforeCreate 阶段,vue 实例的挂载元素 el 尚未。
  • 载入前/后:在 beforeMount 阶段,vue 实例的$el 和 data 都初始化了,但仍是挂载以前为虚拟的 dom 节点,data.message 还未替换。在 mounted 阶段,vue 实例挂载完成,data.message 成功渲染。
  • 更新前/后:当 data 变化时,会触发 beforeUpdate 和 updated 方法。
  • 销毁前/后:在执行 destroy 方法后,对 data 的改变不会再触发周期函数,说明此时 vue 实例已经解除了事件监听以及和 dom 的绑定,可是 dom 结构依然存在

12.客户端存储

13.路由的history模式

14.flex布局

15.原型链

16.ES6数组操做:some、every、find、filter、map、forEach有什么区别

17.数组去重(ES5/ES6)

18.项目如何管理模块

相关文章
相关标签/搜索