web前端面试题整理
1、link和@import的区别
本质上,这两种方式都是为了加载css文件,但还是存在细微的差别。
差别1:老祖宗的差别,link属于XHTML标签,而@import完全是css提供的一种方式。
link标签除了可以加载css外,还可以做很多其他的事情,比如定义RSS,定义rel连接属性等,@import只能加载CSS。
差别2:加载顺序的差别:当一个页面被夹在的时候(就是被浏览者浏览的时候),link引用的CSS会同时被加载,而@import引用的CSS会等到页面全部被下载完再加载。所以有时候浏览@import加载CSS的页面时会没有样式(就是闪烁),网速慢的时候还挺明显。
差别3:兼容性的差别。由于@import是CSS2.1提出的所以老的浏览器不支持,@import只有在IE5以上的才能识别,而link标签无此问题,完全兼容。
差别4:使用dom控制样式时的差别。当时用JavaScript控制dom去改变样式的时候,只能使用link标签,因为@import不是dom可以控制的(不支持)。
差别5(不推荐):@import可以在css中再次引入其他样式表,比如创建一个主样式表,在主样式表中再引入其他的样式表,如:
@import “sub1.css”; @import “sub2.css”; sub1.css ———————- p {color:red;} sub2.css ———————- .myclass {color:blue}
这样有利于修改和扩展。
但是:这样做有一个缺点,会对网站服务器产生过多的HTTP请求,以前是一个文件,而现在确实两个或更多的文件了,服务器压力增大,浏览量大的网站还是谨慎使用。
@import的书写方式
@import 'style.css' //Windows IE4/ NS4, Mac OS X IE5, Macintosh IE4/IE5/NS4不识别 @import "style.css" //Windows IE4/ NS4, Macintosh IE4/NS4不识别 @import url(style.css) //Windows NS4, Macintosh NS4不识别 @import url('style.css') //Windows NS4, Mac OS X IE5, Macintosh IE4/IE5/NS4不识别 @import url("style.css") //Windows NS4, Macintosh NS4不识别
由上分析知道,@import url(style.css)和@import url("style.css")是最优的选择,兼容的浏览器最多。从字节优化的角度来看@import url(style.css)最值得推荐。
2、HTML5有哪些新特性,移除了那些元素?如何处理HTML5新标签的浏览器兼容问题?如何区分HTML和HTML5
新增加了图像、位置、存储、多任务等功能。
新增元素:
canvas
用于媒介回放的video和audio元素
本地离线存储。localStorage长期存储数据,浏览器关闭后数据不丢失;sessionStorage的数据在浏览器关闭后自动删除
语意化更好的内容元素,比如article footer header nav section
位置API:Geolocation
表单控件,calendar date time email url search
新的技术:web worker(web worker是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。您可以继续做任何愿意做的事情:点击、选取内容等等,而此时 web worker 在后台运行) web socket
拖放API:drag、drop
移除的元素:
纯表现的元素:basefont big center font s strike tt u
性能较差元素:frame frameset noframes
区分:
DOCTYPE声明的方式是区分重要因素
根据新增加的结构、功能来区分
随着Web App的发展,越来越多的移动端App使用HTML5的方式来开发,除了一些HybridApp以外,其他一部分Web App还是通过浏览器来访问的,通过浏览器访问就需要联网发送请求,这样就使得用户在离线的状态下无法使用App,同时Web App中一部分资源并不是经常改变,并不需要每次都向服务器发出请求,出于这些原因,HTML5提出的一个新的特性:离线存储。通过离线存储,我们可以通过把需要离线存储在本地的文件列在一个manifest配置文件中,这样即使在离线的情况下,用户也可以正常使用App。
首先来讲解下离线存储的使用方法,说起来也很简单。只要在你的页面头部像下面一样加入一个manifest的属性就可以了。
<!DOCTYPE HTML><html manifest = "cache.manifest">
...</html>
然后cache.manifest文件的书写方式,就像下面这样:
CACHE MANIFEST
#v0.11
CACHE:
js/app.js
css/style.css
NETWORK:
resourse/logo.png
FALLBACK:/ /offline.html
离线存储的manifest一般由三个部分组成: 1.CACHE:表示需要离线存储的资源列表,由于包含manifest文件的页面将被自动离线存储,所以不需要把页面自身也列出来。 2.NETWORK:表示在它下面列出来的资源只有在在线的情况下才能访问,他们不会被离线存储,所以在离线情况下无法使用这些资源。不过,如果在CACHE和NETWORK中有一个相同的资源,那么这个资源还是会被离线存储,也就是说CACHE的优先级更高。 3.FALLBACK:表示如果访问第一个资源失败,那么就使用第二个资源来替换他,比如上面这个文件表示的就是如果访问根目录下任何一个资源失败了,那么就去访问offline.html。
4、flex实际应用常见面试题
1、flex-内容宽度等分
//css
.box {
display: flex;
}
.box div {
flex: 1;
border: 1px solid red;
}//html
<div class="box">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
2、左右布局,一侧定宽,一侧自适应撑满
//css
html,
body {
height: 100%
}
.main {
display: flex;
height: 100%;
}
.left,
.right {
height: 100%;
border: 1px solid red;
box-sizing: border-box;
}
.left {
width: 300px;
}
.right {
width: 100%;
}
//html
<div class="main">
固定宽度300px</div>
自适应宽度</div>
</div>
3、未知高宽上下左右居中
//css
html,
body {
height: 100%
}
.main {
display: flex;
height: 100%;
justify-content: center;
align-items: center
}
.box {
width: 300px;
border: 1px solid red;
}
//html
<div class="main">
未知高度上下左右居中</div>
</div>
5、闭包的实际应用
一.什么是闭包
高级程序设计三中:闭包是指有权访问另外一个函数作用域中的变量的函数.可以理解为(能够读取其他函数内部变量的函数)
闭包的作用: 正常函数执行完毕后,里面声明的变量被垃圾回收处理掉,但是闭包可以让作用域里的 变量,在函数执行完之后依旧保持没有被垃圾回收处理掉
闭包的实例
比较经典的应用是防抖和节流的操作,详细看11
6、vue无法更新视图的问题
Vue之所以能够监听Model状态的变化,是因为JavaScript语言本身提供了Proxy或者Object.observe()机制来监听对象状态的变化。但是,对于数组元素的赋值,却没有办法直接监听。
一、数组
因此,如果我们直接对数组元素赋值
<ul>
<li v-for="(item,index) in arrInfo">{{item.name}}--{{item.age}}</li>
</ul>
data(){
return {
arrInfo:[
{'name':'zuobaiquan','age':22},
{'name':'zhangsan','age':20}
]
}
},// created(){// this.arrInfo[0]={'name':'zuobaiquan01','age':22}// },mounted(){
this.arrInfo[0]={'name':'zuobaiquan02','age':22}
}
在mounted阶段,直接对数组元素 arrInfo 赋值会导致Vue无法更新View
说明:在created 视图未渲染时 直接对数组元素 arrInfo 赋值 data里面的初值会改变的。
此时在mounted阶段,简单的做法是不要对数组元素赋值,而是更新
mounted(){
this.arrInfo[0]={'name':'zuobaiquan02','age':22}
//正确做法
//this.arrInfo[0].name='zuobaiquan02'
//this.arrInfo[0].age=23
}
另一种做法:通过splice()方法,删除某个元素后,再添加一个元素,达到“赋值”的效果:
//正确做法二 通过splice()方法,删除某个元素后,再添加一个元素,达到“赋值”的效果:
var newArrItem={'name':'zuobaiquan02','age':24}
this.arrInfo.splice(0, 1, newArrItem);
//正确做法三 利用vue内置属性 set
var newArrItem={'name':'zuobaiquan02','age':24}
this.$set(this.arrInfo,0,newArrItem)
总结
由于JavaScript 的限制,Vue 不能检测以下变动的数组:
第一类问题:当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
第二类问题:当你修改数组的长度时,例如:vm.items.length = newLength
为了解决第一类问题,以下两种方式都可以实现和vm.items[indexOfItem] = newValue 相同的效果,同时也将触发状态更新:
// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
或者
// Array.prototype.splice
example1.items.splice(indexOfItem, 1, newValue)
为了解决第二类问题,你可以使用splice:
example1.items.splice(newLength)
二、对象更改检测注意事项
还是由于JavaScript 的限制,Vue 不能检测对象属性的添加或删除:
var vm = new Vue({
data: {
a: 1
}
})// `vm.a`现在是响应式的
vm.b = 2// `vm.b`不是响应式的
对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。但是,可以使用Vue.set(object, key, value) 方法向嵌套对象添加响应式属性。例如,对于:
var vm = new Vue({
data: {
userProfile: {
name: 'Anika'
}
}
})
你可以添加一个新的age 属性到嵌套的 userProfile 对象:
Vue.set(vm.userProfile, 'age', 27)
时你可能需要为已有对象赋予多个新属性,比如使用Object.assign() 或 _.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。所以,如果你想添加新的响应式属性,不要像这样:
Object.assign(this.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
你应该这样做:
创建一个新的对象与原对象混合,来达到视图更新的目的
this.userProfile = Object.assign({}, this.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
7、vue的watch的深度监听和首次赋值立即执行
watch: {
obj: {
handler(newName, oldName) {
console.log('obj.a changed');
},
immediate: true, //首次赋值立即执行
deep: true //深度监听
}
}
7、算法:实现36进制转换?
以下是一个36进制,转为10进制和2进制的方法。
const num = '1023456789abcdefghijklmnopqrstuvwxyz'; //这是36进制数字
const b = parseInt(num, 36) //其中,string为必需。要被解析的字符串。radix为可选。表示要解析的数字的基 数。该值介于 2 ~ 36 之间。如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。如果该参数小于 2 或者大于 36,则parseInt() 将返回 NaN。
const c = b.toString(2)
console.log(c) //2.959962226643665e+54
但是面试官肯定不是想听到这个,于是我们需要自己写一个方法。
我想的方法是,将一个36进制数字先取一个倒置的数组,然后每位数字按照index乘以36相加取到10位数,然后10位数再转2位数就简单了。一直除以2就行了。
我自己想到的方法如下,36转10;
function switchBinary(num) {
if (isNaN(num)) {
switch (num) {
case 'a': return 10; break;
case 'b': return 11; break;
case 'c': return 12; break;
case 'd': return 13; break;
case 'e': return 14; break;
case 'f': return 15; break;
case 'g': return 16; break;
case 'h': return 17; break;
case 'i': return 18; break;
case 'j': return 19; break;
case 'k': return 20; break;
case 'l': return 21; break;
case 'm': return 22; break;
case 'n': return 23; break;
case 'o': return 24; break;
case 'p': return 25; break;
case 'q': return 26; break;
case 'r': return 27; break;
case 's': return 28; break;
case 't': return 29; break;
case 'u': return 30; break;
case 'v': return 31; break;
case 'w': return 32; break;
case 'x': return 33; break;
case 'y': return 34; break;
case 'z': return 35; break;
}
} else {
return num
}
}
function binaryTen (num = '1023456789abcdefghijklmnopqrstuvwxyz') {
const b = num.split('').reverse()
var c = 0
for (var i = 0; i < b.length; i++) {
c += (switchBinary(b[i])) * Math.pow(36, i)
}
return c
}
但是看起来很繁琐,能不能将函数switchBinary简化呢?
我又这样写了函数
function switchBinary (num) {
const a = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
const b = Array.from(new Array(36), (item, index) => {
return index
})
for (var i = 0; i < a.length; i++) {
if (num === a[i])
return b[i]
}
}
但是呢,还是感觉哪里不对。又说不上来,算了先不管了,看下一题。
8、简述https原理,以及与http的区别
首先要知道http是明文传输,而https是密文传输。它的加密方式是SSL/TLS
即HTTPS协议 = HTTP协议 + SSL/TLS协议
HTTPS在传输的过程中会涉及到三个密钥:
服务器端的公钥和私钥,用来进行非对称加密
客户端生成的随机密钥,用来进行对称加密
一个HTTPS请求实际上包含了两次HTTP传输,可以细分为8步。
1.客户端向服务器发起HTTPS请求,连接到服务器的443端口
2.服务器端有一个密钥对,即公钥和私钥,是用来进行非对称加密使用的,服务器端保存着私钥,不能将其泄露,公钥可以发送给任何人。
3.服务器将自己的公钥发送给客户端。
4.客户端收到服务器端的公钥之后,会对公钥进行检查,验证其合法性,如果发现发现公钥有问题,那么HTTPS传输就无法继续。严格的说,这里应该是验证服务器发送的数字证书的合法性,关于客户端如何验证数字证书的合法性,下文会进行说明。如果公钥合格,那么客户端会生成一个随机值,这个随机值就是用于进行对称加密的密钥,我们将该密钥称之为client key,即客户端密钥,这样在概念上和服务器端的密钥容易进行区分。然后用服务器的公钥对客户端密钥进行非对称加密,这样客户端密钥就变成密文了,至此,HTTPS中的第一次HTTP请求结束。
5.客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端密钥发送给服务器。
6.服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
7.然后服务器将加密后的密文发送给客户端。
8.客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成。
以上是我从网上摘抄的,有点晦涩难懂懂。
作者:Leon_hy链接:https://www.jianshu.com/p/14cd2c9d2cd2
9、原生实现ajax
function ajax(options){
options = options ||{}; //调用函数时如果options没有指定,就给它赋值{},一个空的Object
options.type=(options.type || "GET").toUpperCase();/// 请求格式GET、POST,默认为GET
options.dataType=options.dataType || "json"; //响应数据格式,默认json
var params=formatParams(options.data);//options.data请求的数据
var xhr;
//考虑兼容性
if(window.XMLHttpRequest){
xhr=new XMLHttpRequest();
}else if(window.ActiveObject){//兼容IE6以下版本
xhr=new ActiveXobject('Microsoft.XMLHTTP');
}
//启动并发送一个请求
if(options.type=="GET"){
xhr.open("GET",options.url+"?"+params,true);
xhr.send(null);
}else if(options.type=="POST"){
xhr.open("post",options.url,true);
//设置表单提交时的内容类型
//Content-type数据请求的格式
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr.send(params);
}
// 设置有效时间
setTimeout(function(){
if(xhr.readySate!=4){
xhr.abort();
}
},options.timeout)
// 接收
// options.success成功之后的回调函数 options.error失败后的回调函数
//xhr.responseText,xhr.responseXML 获得字符串形式的响应数据或者XML形式的响应数据
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
var status=xhr.status;
if(status>=200&& status<300 || status==304){
options.success&&options.success(xhr.responseText,xhr.responseXML);
}else{
options.error&&options.error(status);
}
}
}
}
//格式化请求参数
function formatParams(data){
var arr=[];
for(var name in data){
arr.push(encodeURIComponent(name)+"="+encodeURIComponent(data[name]));
}
arr.push(("v="+Math.random()).replace(".",""));
return arr.join("&");
}
//基本的使用实例
ajax({
url:"http://server-name/login",
type:'post',
data:{
username:'username',
password:'password'
},
dataType:'json',
timeout:10000,
contentType:"application/json",
success:function(data){
。。。。。。//服务器返回响应,根据响应结果,分析是否登录成功
},
//异常处理
error:function(e){
console.log(e);
}
})
10、Object.defineProperty与proxy比较
11、节流和防抖
js的防抖和节流主要用在监听输入框、监听页面滚动等一些高频触发事件,让这些事件既能保持高频触发,但又不浪费性能。
参考链接:https://segmentfault.com/a/1190000018428170
//防抖
//对于短时间内连续触发的事件(按钮、表单提交、搜索、滚动事件等),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。
export const debounce = function debounce (callback, wait, immediate = false) {
var timeout
return function () {
const callNow = immediate && !timeout
const next = () => callback.apply(this, arguments)
clearTimeout(timeout)
timeout = setTimeout(next, wait)
if (callNow) {
next()
}
}
}
简化写法
function debounce(fn,delay){
let timer = null //借助闭包
return function() {
if(timer){
clearTimeout(timer)
}
timer = setTimeout(fn,delay) // 简化写法
}
}
//节流
效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。
export const throttle = function throttle (fn, delay) {
let last, deferTimer
return function (args) {
const that = this
const _args = arguments
const now = +new Date()
if (last && now < last + delay) {
clearTimeout(deferTimer)
deferTimer = setTimeout(function () {
last = now
fn.apply(that, _args)
}, delay)
} else {
last = now
fn.apply(that, _args)
}
}
}
简化写法:
function throttle(fn,delay){
let valid = true
return function() {
if(!valid){
//休息时间 暂不接客
return false
}
// 工作时间,执行函数并且在间隔期内把状态位设为无效
valid = false
setTimeout(() => {
fn()
valid = true;
}, delay)
}
}
12、事件委托
一、什么是事件委托?
事件委托是利用事件冒泡,只指定一个事件处理程序来管理某一类型的所有事件,即利用冒泡的原理,把事件加到父级上,触发执行效果。
二、为什么要用事件委托
1.在JavaScript中添加到页面上的事件处理程序的个数直接关系到页面的整体运行性能。为什么呢?因为,每个事件处理函数都是对象,对象会占用内存,内存中的对象越多,性能就越差。此外,必须事先指定所有的事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。
2.对有很多个数据的表格以及很长的列表逐个添加事件,简直就是噩梦。所以事件委托,能极大地提高页面的运行性能,减少开发人员的工作量。
三、事件委托好处是:
1. 只在内存中开辟了一块空间,节省资源同时减少了dom操作,提高性能
2. 对于新添加的元素也会有之前的事件
四、代码示例
<ul id = "lists">
<li>列表1</li>
<li>列表2</li>
<li>列表3</li>
<li>列表4</li>
<li>列表5</li>
<li>列表6</li>
</ul>
lists.addEventListener("click",function(event){
var target = event.target;
if(target.nodeName === "LI"){
target.style.backgroundColor = "red";
}
})