前端面试题整理一

JS相关

1、ES6新特性
  • Default Parameters(默认参数)

ES6中,我们可以直接把默认值放在函数申明里:

var link = function(height = 50, color = 'red', url = 'http://azat.co') {
 
...
 
}
  • Template Literals(模板对象)

在ES6中,我们可以使用新的语法$ {NAME},并把它放在反引号里:

var name = `Your name is ${first} ${last}. `;
var url = `http://localhost:3000/api/messages/${id}`;
  • Multi-line Strings (多行字符串)

在ES6中,仅仅用反引号就可以实现多行字符串:

var fourAgreements = `You have the right to be you.
 
You can only be you when you do your best.`;
  • Destructuring Assignment (解构赋值)

解构对象:

var { house, mouse } = $('body').data();
 
var { jsonMiddleware } = require('body-parser');
 
var { username, password } = req.body;

同样也适用于数组:

var [col1, col2] = $('.column');
 
var [line1, line2, line3, , line5] = file.split('n');
  • Arrow Functions(箭头函数)

主要解决this改变的问题

$('.btn').click((event) => {
 
this.sendData();
 
});
  • Promises 可用于异步加载等,之后单独详细列出相关方法

  • Block-Scoped Constructs Let and Const (块级作用域声明方式)

let和const均为ES6新引申的块级作用域,由一对花括号{}中的语句集都属于一个块,在这个{}里面包含的块内定义的所有变量在代码块外都是不可见的,因此称为块级作用域。
而以前的var为函数作用域。let和const的区别为const声明的是不变量

2、Promise介绍
  • promise的生命周期

    每个 Promise都会经历一个短暂的生命周期,初始为挂起态( pending state),这表示异步操作尚未结束。一个挂起的 Promise 也被认为是未决的( unsettled )。一旦异步操作结束, Promise就会被认为是已决的( settled ),并进入两种可能状态之一:
    1. 已完成(fulfilled ): Promise 的异步操作已成功结束;
    2. 已拒绝(rejected ): Promise 的异步操作未成功结束,可能是一个错误,或由其他原因导致。

    一旦状态改变,就「凝固」了,会一直保持这个状态,不会再发生变化。当状态发生变化,promise.then绑定的函数就会被调用。注意:Promise一旦新建就会「立即执行」,无法取消。这也是它的缺点之一

  • promise的方法
    then()方法: then()方法在所有的 Promise上都存在,并且接受两个参数。第一个参数是 Promise 被完成时要调用的函数,与异步操作关联的任何附加数据都会被传入这个完成函数。第二个参数则是 Promise 被拒绝时要调用的函数,与完成函数相似,拒绝函数会被传入与拒绝相关联的任何附加数据。
    catch()方法:其行为等同于只传递拒绝处理函数给 then() 。
    resolve()方法:这段代码会让这个Promise对象立即进入resolved状态,并将结果success传递给then指定的onFulfilled回调函数。由于Promise.resolve()也是返回Promise对象,因此可以用.then()处理其返回值。
    reject()方法:这段代码会让这个Promise对象立即进入rejected状态,并将错误对象传递给then指定的onRejected回调函数。
    all()方法:传入Promise对象组成的数组,然后同时执行,并且返回对应返回值的数组。
    race()方法:与all方法调用方式一致,只是返回值只返回第一个Promise结果。

3、闭包

定义:
闭包就是指有权访问另一个函数作用域中的变量的函数。

例子:

function func() {
  var a = 1, b= 2;
  function closure () { // 闭包
    return a + b; // 返回a,b的值
  }
  return closure;  // 返回闭包函数 
}

运用闭包的关键:
闭包引用外部函数变量对象中的值;在外部函数的外部调用闭包。

闭包的缺陷:
闭包的缺点就是常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露。
如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。

4、跨域
  • 何为跨域
    通常域名是由以下几部分组成:
    http:// www.baidu.com : 8080 / #abc
    协议 域名 端口号 hash、查询字符串、文件名等
    同源策略会限制以下的内容:
    1,cookie、localstorage和indexDB无法读取
    2,DOM无法获得
    3,ajax发送后会被浏览器拦截
    不过有些标签是可以允许跨域加载的:
    1,img 标签
    2,link 标签
    3,script 文件
    当协议、域名、端口号任意一个不相同时,都算作不同域。不同域之间请求资源,就是跨域。
    如果是协议和端口号造成的跨域,前端是无法解决的。
    在跨域的问题上,是通过 ‘url首部’来识别而不会根据域名对应的ip地址是否相同来判断。 url首部可以理解为‘协议、域名和端口必须匹配’。
    跨域并不是http请求发送不出去,请求可以发出去,服务端也能接受并正常返回结果,只是结果被浏览器拦截了。
    因为跨域本身是为了阻止用户获取不同域下的内容。所以表单提交这种方式是可以发起跨域请求的,因为表单只会提交不会获取新内容。

  • 解决跨域的方法
    1,jsonp
    因为在页面上调用script标签没有跨域的限制(不仅如此,凡是拥有src属性的标签都具有跨域的能力)。
    首先在本页面声明一个用来获取数据的回调函数, 创建一个script标签,将要跨域的地址加上该回调函数赋值给script的src属性(通过 ?callback= fn)
    服务器接收到该请求后,会在返回的文件中 将该回调函数名和数据拼接起来,返回给客户端。客户端执行该回调函数,就可以获取到服务端传过来的数据。
    jsonp的兼容性很好,但是仅仅只支持get请求。
    延伸:为什么不支持post请求
    (JSONP的最基本的原理是:动态添加一个是一致的,因为他的原理实际上就是 使用js的script标签 进行传参,那么必然是get方式的了,和浏览器中敲入一个url一样)
    2,cors
    cors需要浏览器和后台同时支持。 ie8和ie9需要通过XDomainRequest实现浏览器会自动进行cors通信,所以只要后台实现了cros,就实现了跨域。
    服务端设置Access-Control-Allow-Origin就可以开启cors,该属性表示哪些域名可以访问资源。
    在使用cors发送请求时,会出现两种情况:
    简单请求:
    只要同时满足以下两种条件就属于简单请求
    条件1: 使用get 、head、post方法
    条件2: content-type的值为 text/plain、 multipart/form-data、 application/x-www-form-urlencoded 三者之一。
    复杂请求: 
    不符合简单请求的就属于复杂请求。 复杂请求的cors会在正式请求之前增加一次http查询请求(预检请求),该请求是option方法,通过该请求来知道服务端是否允许跨域请求。
    3,postMessage
    XMLHttpRequest Level 2的api,可以解决以下的问题:
    页面和其打开的新窗口的数据传递
    多窗口之间消息传递
    页面和嵌套的iframe消息传递
    以上三个场景的跨域数据传递
    postMessage方法允许来自不同源的脚本采用异步方式进行有限的通信。
    otherWindow.postMessage(message,targetOrigin,[transfer]);
    message: 要发送的数据
    targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息,只有目标窗口的协议、域名和端口都和提供的targetOrigin匹配的时候,才会被发送。
    transfer: 和message同时传递的Transferable对象,这些对象的所有权将被转移给消息的接收方,发送方不再保有所有权。      
    4,websocket
    h5的一个持久化协议。实现了浏览器和服务器的全双工通信,同时也是跨域的一种解决方案。
    websocket和http都是应用层协议,都基于TCP。但是websocket是一种双向通信协议,一旦连接建立,服务端和客户端都可以向对方发送或接收数据。
    websocket建立的时候需要用到http协议,建立好连接之后就与http无关了。
    5,Node js中间件代理
    同源策略是浏览器需要遵循的标准,而如果服务端向服务端请求就无需遵循同源策略。
    代理服务器接收浏览器的请求,处理后转发给服务器,收到服务器的响应后再转发给浏览器。
    需要注意的是代理服务器和浏览器之间也遵循同源策略。
    6,nginx反向代理
    类似node中间件代理,需要搭建一个中转nginx服务器,用于转发请求。
    最简单的跨域方式。只要修改nginx的配置即可解决跨域问题。

5、Cookie、LocalStorage 与 SessionStorage的区别在哪里?
  • Cookie
    一般由服务器生成,可设置失效时间。如果在浏览器端生成Cookie,默认是关闭浏览器后失效;
    只能储存4kb左右;
    每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题
    需要程序员自己封装,源生的Cookie接口不友好
  • LocalStorage
    除非被清除,否则永久保存
    一般为5MB
    仅在客户端(即浏览器)中保存,不参与和服务器的通信
    源生接口可以接受,亦可再次封装来对Object和Array有更好的支持
  • SessionStorage
    仅在当前会话下有效,关闭页面或浏览器后被清除
    其余和LocalStorage一样
6、关于异步解决方案
  • 使用Promise来处理,上面有Promise的详细介绍
  • Async/Await
    async/await是写异步代码的新方式,优于回调函数和Promise。
    async/await是基于Promise实现的,它不能用于普通的回调函数。
    async/await与Promise一样,是非阻塞的。
    async/await使得异步代码看起来像同步代码,再也没有回调函数。但是改变不了JS单线程、异步的本质。
    Async/Await比起Promise的好处:
    1、简洁
    使用Async/Await明显节约了不少代码。我们不需要写.then,不需要写匿名函数处理Promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码。
    2、中间值使用简单
const makeRequest = async () => {
  const value1 = await promise1()
  const value2 = await promise2(value1)
  return promise3(value1, value2)
}
7、前端性能优化
  • 静态资源优化
    主要是减少静态资源的加载时间,主要包括html、css、js和图片文件,静态资源的加载时间是前端性能最大的瓶颈(特别是图片),现如今优化的手段也很丰富,以下简要列举几种常用的方法。
    1、合并css、js文件,制作雪碧图:减少http的请求次数,节省网络请求时间
    2、静态资源cdn分发:客户端可以通过最佳的网络链路加载静态资源
    3、js、css文件压缩,图片压缩,gzip压缩:减少请求返回的数据量
    4、静态资源缓存机制
    5、权衡dns的查找
  • 接口访问优化
    首屏直出、同构:前者利用ajax加载、后者使用node.js后端
    接口合并
  • 页面渲染速度优化
    1、css放在顶部:优先渲染
    2、js放在底部:避免阻塞
    3、减少DOM元素数量:这个最能体现变成水平了
    4、img标签要设置高宽:减少重绘重排
    另外,新晋前端框架 vue、react,虚拟dom的渲染方案,在内存中进行 dom diff 比较,做到最小化的操作真实的 dom (操作真实的 dom 常常会成为性能瓶颈),能极大的提高渲染速度。

Vue相关

1、关于双向绑定的原理

核心:vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。是通过Object.defineProperty()的get、set方法来实现数据劫持的
实现过程:我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

2、关于v-show和v-if的相同不同

共同点:都能控制元素的显示和隐藏;
不同点:实现本质方法不同,v-show本质就是通过控制css中的display设置为none,控制隐藏,只会编译一次;v-if是动态的向DOM树内添加或者删除DOM元素,若初始值为false,就不会编译了。而且v-if不停的销毁和创建比较消耗性能。
总结:如果要频繁切换某节点,使用v-show(切换开销比较小,初始开销较大)。如果不需要频繁切换某节点使用v-if(初始渲染开销较小,切换开销比较大)。

3、$nextTick

当你修改了data的值然后马上获取这个dom元素的值,是不能获取到更新后的值,
你需要使用$nextTick这个回调,让修改后的data值渲染更新到dom元素之后在获取,才能成功。

new Vue({
  el: '.app',
  data: {
    msg: 'Hello Vue.',
    msg1: '',
    msg2: '',
    msg3: ''
  },
  methods: {
    changeMsg() {
      this.msg = "Hello world."
      this.msg1 = this.$refs.msgDiv.innerHTML
      this.$nextTick(() => {
        this.msg2 = this.$refs.msgDiv.innerHTML
      })
      this.msg3 = this.$refs.msgDiv.innerHTML
    }
  }
})

如上代码:只有this.msg2是显示更新后的值,1和3都依然显示更新前的值

4、单页面应用(SPA)

单页面应用(SPA),通俗一点说就是指只有一个主页面的应用,浏览器一开始要加载所有必须的 html, js, css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。多应用于pc端。

5、Vue常用修饰符

.stop:等同于JavaScript中的event.stopPropagation(),防止事件冒泡;
.prevent:等同于JavaScript中的event.preventDefault(),防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);
.capture:与事件冒泡的方向相反,事件捕获由外到内;
.self:只会触发自己范围内的事件,不包含子元素;
.once:只会触发一次。

6、关于Vue生命周期

beforeCreate:在new一个vue实例后,只有一些默认的生命周期钩子和默认事件,其他的东西都还没创建。在beforeCreate生命周期执行的时候,data和methods中的数据都还没有初始化。不能在这个阶段使用data中的数据和methods中的方法
create:data 和 methods都已经被初始化好了,如果要调用 methods 中的方法,或者操作 data 中的数据,最早可以在这个阶段中操作
beforeMount:执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的
mounted:执行到这个钩子的时候,就表示Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。 如果我们想要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行
beforeUpdate: 当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的, 页面还没有和最新的数据保持同步
updated:页面显示的数据和data中的数据已经保持同步了,都是最新的
beforeDestory:Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于可用状态。还没有真正被销毁
destroyed: 这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于不可用状态。组件已经被销毁了

7、关于深拷贝

对于一个引用类型,如果直接将它赋值给另一个变量,由于这两个引用指向同一个地址,这时改变其中任何一个引用,另一个都会受到影响。当我们想复制一个对象并且切断与这个对象的联系,就要使用深拷贝。对于一个对象来说,由于可能有多层结构,所以我们可以使用递归来解决这个问题
方法1:递归循环,先判断是数组还是对象,使用对应的for循环递归遍历,然后赋值。可解决所有深拷贝问题。
方法2:JSON.stringify()以及JSON.parse()
问题:使用JSON.stringify()以及JSON.parse()它是不可以拷贝 undefined , function, RegExp 等等类型的
方法3: Object.assign(target, source)
问题:无法拷贝多层对象
方法4:concat、slice方法(针对数组)
方法5:ES6扩展运算符

var arr = [1,2,3,4,5]
var [ ...arr2 ] = arr
arr[2] = 5

你可能感兴趣的:(前端面试题整理一)