一、html&css
1.html5新增的标签属性
2.css3新增的属性
3.position定位
4.浮动
5.盒子模型
6.margin问题
7.display的属性值
8.background属性值
9.px、rem、em的区别
10. css动画和js动画的区别
1.js是帧动画,当js在浏览器主线程运行时,主线程还有其他需要运行的js脚本、样式、计算、布局、交互等一系列任务,对其干扰线程可能出现阻塞,造成丢帧的情况。
2. js动画通过js引擎解析,css通过 GUI渲染,效果更流畅
二、js相关问题
1.js的基本数据类型和引用数据类型
2.const,let,val的区别
3.说说作用域
在es6的let和const推出之前,js并没有块作用域的概念,只有全局作用域和函数作用域。
4.instanceof和typeof 的区别
5.隐式转换有哪些
6.闭包
7.原型和原型链
(1)、原型
任何对象都有一个原型对象除了null,这个原型对象由对象的内置属性_proto_指向它的构造函数的prototype
即任何对象都是由一个构造函数创建的,但是,不是每一个对象都有prototype,只有方法才有prototype。
(2)、什么是原型链?
原型链的核心就是依赖对象的_proto_的指向,当查找自身不存在的属性时,就一层层的扒出创建对象的构造函数,直至到Object时,就没有_proto_指向了。
因为_proto_实质找的是prototype,所以我们只要找这个链条上的构造函数的prototype。其中Object.prototype是没有_proto_属性的,它==null。
(3)、原型链查找分析:
当访问一个对象的成员的时候,会先在自身找有没有,如果找到直接使用。
如果没有找到,则去原型链指向的对象的构造函数的prototype中找,找到直接使用,没找到就返回undifined或报错。
function Person(name){
this.name = name;
}
var p = new Person();
p ---> Person.prototype --->Object.prototype---->null
8.call和apply的区别
9.new干了什么
10.this的指向
11.箭头函数
相对于普通函数的区别,新的书写方式 ,this 的改变,不能当构造函数,没有 prototype 属性,没有 arguments 对象
讨论箭头函数的 this 之前,不得不再熟悉一下 执行上下文(Execution Context),因为 this 指针(this value) 就存储在执行上下文中。
执行上下文保存着函数执行所需的重要信息,其中有三个属性:变量对象(variable object),作用域链(scope chain),this指针(this value),它们影响着变量的解析、变量作用域、函数 this 的指向。执行上下文分为 全局执行上下文 和 函数执行上下文。
函数预编译的 this 分下面四种情况:
第一种: 函数自主调用 如 fun() 或者是 普通的立即执行函数(区别于箭头函数方式的立即执行函数), this 指向 window;
第二种: 函数被调用,当函数被某个对象调用时,函数产生的执行上下文中的 this 指向 该对象;
第三种: 通过 call() apply() bind() 方法改变 this,this 指向被传入的 第一个参数;
第四种: 在构造函数中,this 指向被创建的 实例
箭头函数不会创建自己的 this,它只会从自己的作用域链上找父级执行上下文的 this
*由于箭头函数是不能通过 call() apply() bind() 方法改变 this,也不能当做构造函数
12.setTimeout、Promise、async/await 三者之间异步解决方案的区别?
13.实现节流和防抖
function throttle(fn){
let can = true;
return function(){
if(!can)return;
can = false;
setTimeout(() => {
fn.apply(this,arguments);
can = true
}, 2000);
}
}
function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn.apply(this, arguments);
}, 500);
};
}
14.对象深拷贝
function deepClone(source){
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for(let keys in source){ // 遍历目标
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
}else{ // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
15.setTimeout在跳转页面之后是否还会继续执行
16.多页面还单页面的区别
17.说一下设计模式有哪些
19.http缓存原理
20.常见的状态码有哪些
21.js事件流
22.get,post的区别
1.post更安全(不会作为url的一部分,不会被缓存、保存在服务器日志、以及浏览器浏览记录中)
2.post发送的数据量更大(get有url长度限制)
3.post能发送更多的数据类型(get只能发送ASCII字符)
4.post支持4种提交格式
application/x-www-form-urlencoded,multipart/form-data,application/json,text/xml
最重要的一条,post在真正接受数据之前会先将请求头发送给服务器进行确认,然后才真正发送数据
post请求的过程:
1.浏览器请求tcp连接(第一次握手)
2.服务器答应进行tcp连接(第二次握手)
3.浏览器确认,并发送post请求头(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)
4.服务器返回100 continue响应
5.浏览器开始发送数据
6.服务器返回200 ok响应
get请求的过程
1.浏览器请求tcp连接(第一次握手)
2.服务器答应进行tcp连接(第二次握手)
3.浏览器确认,并发送get请求头和数据(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)
4.服务器返回200 ok响应
23.js事件循环机制,宏任务与微任务有哪些
常见的微任务有Promise、process.nextTick、MutationObserver
常见的宏任务:script、setTimeout、setInterval、setImmediate
发出疑问:到底是宏任务优先还是微任务优先?(个人观点:微任务优于宏任务)
Event Loop执行顺序为:
先执行宏任务script,并执行里面的同步任务;执行栈为空后查询是否存在微任务,存在就立即执行,然后开始下一轮的事件循环
24.实现响应式数据
var oldArrayProto = Array.prototype;
var arrProto = Object.create(oldArrayProto);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
arrProto[methodName] = function () {
console.log("进来额")
update() // 触发视图更新
oldArrayProto[methodName].call(this, ...arguments)
}
})
function update(val) {
console.log("数据发生了变化",obj.nums);
}
function define(target,key,value){
observer(value)
Object.definePrototype(target,key,{
get(){
return value;
},
set(val){
if(val !== value){
value = val;
observer(value);
update()
}
}
})
}
function observer(obj){
if(typeof obj !== 'object' || obj === null) {
reutrn obj;
}
if(Array.isArray(obj)){
obj._proto_ = arrProto;
}
for(let key in obj){
define(obj,key,obj[key]);
}
}
25. 0.1+0.2 = ? js为什么会有精度问题?
JS 数字类型只有number类型,number类型相当于其他强类型语言中的double类型(双精度浮点型),不区分浮点型和整数型。
number 有四种进制表示方法,十进制,二进制,八进制和十六进制
由于Js的所有数字类型都是双精度浮点型(64位)采用 IEEE754 标准
计算机中的数字都是以二进制存储的,如果要计算 0.1 + 0.2 的结果,计算机会先把 0.1 和 0.2 分别转化成二进制,然后相加,最后再把相加得到的结果转为十进制,0.1 和 0.2 在转换为二进制时就发生了一次精度丢失,而对于计算后的二进制又有一次精度丢失 。
26. try catch的作用域
27.获取url ?后的参数
28.jquery的链式调用和promise的链式调用
jquery的方法都是挂在在原型上的,每次调用内部方法会返回this,返回当前的实例对象
promise的then,每次都会返回一个新的promise
29.async 和defer的区别
<script>标签打开defer或async属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。
defer与async的区别是:defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。
30.前端性能优化:
从2个方面来说 1.资源加载优化 2.页面渲染优化
一:资源加载优化
(1).减少http请求次数
合并请求-http缓存-本地缓存-service worker
(2).减少资源大小
代码压缩-gzip压缩-图片压缩-代码拆分
(3).提高http请求响应速度
cdn,dns,http2
(4).优化资源加载时机
按需加载-懒加载-预加载
(5).优化资源/内容加载方式
客户端内h5离线化包
二: 页面渲染优化
(1).优化html代码
js外链放在底部-css外链放在顶部-减少dom数量
(2).优化js/css代码
长任务分片执行-减少重排重绘-降低css选择器复杂性
(3).优化动画效果
使用css动画代替js动画
31.DNS预解析(dns-prefetch)
通过 DNS 预解析来告诉浏览器未来我们可能从某个特定的 URL 获取资源,当浏览器真正使用到该域中的某个资源时就可以尽快地完成 DNS 解析。
第一步:打开或关闭DNS预解析
你可以通过在服务器端发送 X-DNS-Prefetch-Control 报头。或是在文档中使用值为 http-equiv 的meta标签:
<meta http-equiv="x-dns-prefetch-control" content="on">
需要说明的是,在一些高级浏览器中,页面中所有的超链接(<a>标签),默认打开了DNS预解析。但是,如果页面中采用的https协议,很多浏览器是默认关闭了超链接的DNS预解析。如果加了上面这行代码,则表明强制打开浏览器的预解析。
第二步:对指定的域名进行DNS预解析
如果我们将来可能从 smyhvae.com 获取图片或音频资源,那么可以在文档顶部的 标签中加入以下内容:
<link rel="dns-prefetch" href="http://www.smyhvae.com/">
当我们从该 URL 请求一个资源时,就不再需要等待 DNS 解析的过程。该技术对使用第三方资源特别有用。
32.预加载
1.Prerendering pages
预先渲染页面,这是更牛的预加载方式了,他的作用就类似打开一个隐藏的tab差不多:
<link rel=”prerender” href=”http://css-tricks.com”>
2.Preloading
和prefetching不同的是,preloading会让浏览器无论如何都下载指定的资源:
<link rel=”preload” href=”image.png”>
3.H5音乐预加载 preload=”auto”
<audio src=”music.mp3″ autoplay=”autoplay” loop preload=”auto” id=”sendid2″></audio>
33.JavaScript中使用局部变量是否比使用全局变量高效?
当读取一个变量的时候,就会从当前域中(具体说,是当前执行上下文的活动对象,Active Object)中查找这个变量,查找不到就继续往上找,直到全局。查找层级少,所以快。
34.位与位或
先把10进制数转成二进制,然后二进制从个位开始依次比对 位与& 都对true为1 不对false为0;位或|如果不相等取后面的数,如果相等取1
10 & 15
1010 & 1111
比对后得1010
10 | 15
1010 | 1111
比对后得1111
35. 离线功能对比:service worker和applicationCache
appCache仅仅在离线的时候才能发挥用处(无法解决网络慢的用户体验问题),而SW不是,可以通过拦截请求,并且返回合适的数据,如果发现网络较慢。
36. 收到的 HTML 如果包含几十个图片标签,这些图片是以什么方式、什么顺序、建立了多少连接、使用什么协议被下载下来的呢?
如果图片都是 HTTPS 连接并且在同一个域名下,那么浏览器在 SSL 握手之后会和服务器商量能不能用 HTTP2,如果能的话就使用 Multiplexing 功能在这个连接上进行多路传输。不过也未必会所有挂在这个域名的资源都会使用一个 TCP 连接去获取,但是可以确定的是 Multiplexing 很可能会被用到。
如果发现用不了 HTTP2 呢?或者用不了 HTTPS(现实中的 HTTP2 都是在 HTTPS 上实现的,所以也就是只能使用 HTTP/1.1)。那浏览器就会在一个 HOST 上建立多个 TCP 连接,连接数量的最大限制取决于浏览器设置,这些连接会在空闲的时候被浏览器用来发送新的请求,如果所有的连接都正在发送请求呢?那其他的请求就只能等等了。
37.前端如何解决跨域的?
1. 跨域资源共享cors
服务端设置Access-Control-Allow-Origin:浏览器请求头的orgin信息/ * ,代表允许指定的请求来访问或者允许所有请求都可访问
2. jsonp
jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。
缺点:不安全,只支持get
3.nginx反向代理
通过Nginx配置一个代理服务器域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域访问
4.vue proxy
利用webpack-dev-serve,webpack-dev-server是一个采用Node.js Express实现的微型服务器, 利用他来做代理在本地开启一个服务器,同源策略仅是针对浏览器的安全策略。因此服务器之间也就不存在跨域问题。
5.postMessage跨域
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题: 页面和其打开的新窗口的数据传递、多窗口之间消息传递、页面与嵌套的iframe消息传递、上面三个场景的跨域数据传递
6. WebSocket协议跨域
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
38. 小程序如何在app里运行的,如何在多端app里运行?
App 里包含 javascript 运行引擎,比如用户点击打开一个小程序,微信App从微信服务器下载这个小程序,分析 app.json 得到应用程序的配置信息(导航栏,窗口样式,包含的页面列表等)
加载并运行 app.js,展示在 app.json 里配置的第一个页面,会调用app原生的接口
多端运行,通过嵌套webview来实现。
39. 为什么javascript是解释型语言
解释型语言:不需要事先编译,通过解释器一边解释一边执行,启动快,但执行慢。
因为javascript是弱类型语言,我们不需要关心它的类型,并且可以随意的修改,无法像c++那样可以事先提供足够的信息供编译器编译出更加低级的机器代码,它只能在运行阶段收集类型信息,然后根据这些信息进行编译再执行,所以javascript是解释型语言。
三、vue相关
1.vue的nextTick方法的实现原理:
首先修改数据,这是同步任务。同一事件循环的所有的同步任务都在主线程上执行,形成一个执行栈,此时还未涉及 DOM 。
同步任务执行完毕,开始执行异步 watcher 队列的任务,更新 DOM 。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel 方法, 如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。
2.Vue.use() 有什么作用
Vue.use(),会调用install方法,让它里面被注册的组件能够被全局使用。
3.vue组件中的data为什么是函数
为了保证组件的独立性 和 可 复用性,data 是一个函数,组件实例化的时候这个函数将会被调用,返回一个对象,计算机会给这个对象分配一个内存地址,你实例化几次,就分配几个内存地址,他们的地址都不一样,所以每个组件中的数据不会相互干扰,改变其中一个组件的状态,其它组件不变。
4.vue-router的两种模式区别
hash模式的工作原理是hashchange事件,可以在window监听hash的变化
window.onhashchange = function(event){
console.log(event);
}
history模式,这种模式充分利用 history.pushState API 来完成 通过pushState和replaceState修改浏览器的历史状态,
区别: hash模式需要加#,history需要nginx配置,如果history刷新找不到路径会出错,hash模式不会
5. 子组件为什么不可以修改父组件传递的Prop
Vue是单向数据流,这样来防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解,如果破坏了单向数据流,当应用复杂时,debug 的成本会非常高
6. vue 是如何监控到上述情况并给出警告的
在vue 底层,做了一个类似全局标记Flag;它的实现原理,还是Object.defineProperty()API,window.isUpdatingChildComponent = false; 相当于一个Flag;只有当在父组件中修改传递给子组件的Prop值的时候,才会被赋值为True; 在子组件Proxy.vue 中代理父组件传递的Prop值; 使用 this.$forceUpdate(); 强制更新; 这时候,触发代理中的setter;提示不可以在子组件中直接修改父组件传递的Prop值的警告;
7.Vuex 的 mutation 中为什么 不能做异步操作?
Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样使得我们可以方便地跟踪每一个状态的变化,每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。
8. vue编译原理:
整体逻辑分为三个过程:
1.将模板字符串转换成element ASTs(解析器)
2.对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
3.使用element ASTs生成render函数代码字符串(代码生成器)
Vue源码中虚拟DOM构建经历 template编译成AST语法树 -> 再转换为render函数 最终返回一个VNode(VNode就是Vue的虚拟DOM节点)
9. Vue父子组件,哪个生命周期是子组件先执行的父组件后执行的?
mounted,因为只有所有的子组件全部加载完毕,父组件才能触发mounted
四、webpack
webpack的loader顺序,为什么?
webpack选择了函数式编程的方式,所以loader的顺序编程了从右往左
webpack loader和plugin的区别
loader: 更像是转换器 像babel-loader,scss-loader style-loader file-loader,
plugin: 插件压缩文件,分割文件等改变输出的结果
五、项目亮点
一、抢券兜底策略:
1.首先是当巨大流量进入到这个页面,会不会引发cdn等资源/接口加载失败?
2.抢的策略,倒计时结束点击按钮发送接口
针对1的解决方法:1.利用缓存策略减少http请求 2.在每台机器上配置静态资源当cdn资源挂掉的时候访问切换域名路径成当前机器的,
SSI就是在HTML文件中,可以通过注释行调用的命令或指针。
针对2的解决方案:用户疯狂点击按钮,为了避免服务器挂掉可以采用防抖策略 1.比如一段时间内只允许发起一次请求,2.设置开关,在上一次请求有所返回时才进行下一次请求
3.永远只发送一次请求
如何防止脚本恶意刷抢,可以弹起验证框校验
六、算法
1.二分查找
function binary_search(arr, key) {
var left = 0,
right = arr.length - 1;
while(left <= right){
var mid = parseInt((right + left) / 2);
if(key == arr[mid]){
return mid;
}else if(key > arr[mid]){
left = mid + 1;
}else if(key < arr[mid]){
right = mid -1;
}else{
return -1;
}
}
};
var arr = [1,2,3,4,5,6,7,8,9,10,11,23,44,86];
var result = binary_search(arr,10);