前端常见面试题---1
# 1. 说一下对HTML语义化的理解?
语义化就是选择与语义相符合的标签
html语义化让页面的内容结构化,结构更清晰,便于对浏览器、搜索引擎解析;
搜索引擎的爬虫也依赖于HTML标记来确定上下文和各个关键字的权重,利于SEO;
使阅读源代码的人对网站更容易将网站分块,便于阅读维护理解。
通俗的讲语义化就是让正确的标签做正确的事情,比如段落用p标签,头部用header标签,主要内容用main标签,侧边栏用aside标签等等。
# 2. meta viewport 是做什么用的
将视口大小设置为可视区域的大小。
什么是视口
视口简单理解就是可视区域大小。 在PC端,视口大小就是浏览器窗口可视区域的大小 在移动端, 视口大小并不等于窗口大小, 移动端视口宽度被人为定义为了980
为什么移动端视口宽度是980而不是其他的值
因为过去网页的版心都是980的,为了能够让网页在移动端完美的展示, 所以将手机视口的大小定义为了980。
移动端自动将视口宽度设置为980带来的问题
虽然移动端自动将视口宽度设置为980之后让我们可以很完美的看到整个网页,但是由于移动端的物理尺寸(设备宽度)是远远小于视口宽度的,所以为了能够在较小的范围内看到视口中所有的内容, 那么就必须将内容缩小。 但是缩小后用户看到的是一个缩小版的整个页面,字体、图标和内容等等都非常小,想要点击或者查看都需要去放大页面进行操作,放大页面之后就会出现横向滚动条,这对用户体验来说是非常不好的。
如何保证在移动端不自动缩放网页的尺寸
通过meta设置视口大小
<meta name="viewport" content="width=device-width, initial-scale=1.0">
viewport 是指 web 页面上用户的可视区域。 meta标签的属性:
width=device-width 设置视口宽度等于设备的宽度
initial-scale=1.0 初始缩放比例, 1不缩放
maximum-scale:允许用户缩放到的最大比例
minimum-scale:允许用户缩放到的最小比例
user-scalable:用户是否可以手动缩放
2
3
4
5
# 3.在浏览器输入一个URL的过程是怎么样的
浏览器解析URL
DNS 域名解析
TCP 建立连接
发送HTTP请求
服务器处理请求
返回响应结果
接受响应
浏览器解析渲染页面
断开连接
每个过程细节参考各流程详解 (opens new window)
# 4.请描述一下 cookies,sessionStorage 和 localStorage 的区别?
cookie是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。
- cookie数据始终在同源的http请求中携带(即使不需要),记会在浏览器和服务器间来回传递。
- sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。
存储大小:
- cookie数据大小不能超过4k。
- sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
有效期时间:
localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;
sessionStorage 数据在当前浏览器窗口关闭后自动删除。
cookie 设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
# 5.html5有哪些新特性、移除了那些元素?如何处理HTML5新标签的浏览器兼容问题?如何区分 HTML 和 HTML5?
实现上:h5不再是SGML的子集。
新特性:主要是关于图像,位置,存储,多任务等功能的增加。
如:
绘画canvas
用于媒介回放的video和audio元素
本地离线存储localStorage,长期存储,浏览器关闭之后数据不丢失
sessionStorage的数据在浏览器关闭后自动删除
语意化更好的内容元素,比如 article、footer、header、nav、section
表单控件,calendar、date、time、email、url、search;
新的技术webworker, websocket, Geolocation;
移除的元素:
纯表现的元素:basefont,big,center,font, s,strike,tt,u;
对可用性产生负面影响的元素:frame,frameset,noframes;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
处理兼容性: IE8/IE7/IE6支持通过document.createElement方法产生的标签,可以利用这一特性让这些浏览器支持HTML5新标签,浏览器支持新标签后,还需要添加标签默认的样式。
也可以使用html5shim,可以让IE9或更低版本能支持html5
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
如何区分HTML5: DOCTYPE声明\新增的结构元素\功能元素
# 6.常见的浏览器内核有哪些
浏览器 | 内 核 |
---|---|
IE | Trident内核 |
FireFox | Gecko内核 |
Opera | Presto内核 |
Safari,Chrome | Webkit内核 |
# 7.介绍一下你对浏览器内核的理解?
主要分为两部分:
- 渲染引擎
- JS引擎
渲染引擎:取得网页的内容(html、xml、图片)、构造cssom树、计算网页的显示方式,比如各元素宽高,然后输出至显示器或打印机。
js引擎:解析和执行javascript来实现网页的动态效果
# 8.什么是闭包?
闭包的实质是因为函数嵌套而形成的作用域链 比如说:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包
用途:使用闭包主要是为了设计私有的方法和变量 优点:可以避免变量被全局变量污染 缺点:函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包 解决方法:在退出函数之前,将不使用的局部变量全部删除
# 9.手写选择排序和冒泡排序
选择排序:
let arr = [3, 4, 1, 2];
let len = arr.length;
// 这里之所以是len-1,是因为到最后两个元素,交换位置,整个数组就已经排好序了。
for(let i = 0; i < len - 1; i++){
let min = arr[i];
// j = i+1是把与自己比较的情况给省略掉
for(let j = i+1; j < len; j++){
if(arr[j] < min){
// 利用ES6数组的解构赋值交换数据
[arr[j], min] = [min, arr[j]];
}
}
arr[i] = min;
}
console.log(arr);//[1, 2, 3, 4]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
冒泡排序:
let arr = [3, 4, 1, 2];
let max = arr.length - 1;
for(let i = 0; i < max; i++){
// 声明一个变量,作为标志位
// 如果某次循环完后,没有任何两数进行交换,就将标志位设置为 true,表示排序完成
let flag = true;
for(let j = 0; j < max - i; j++){
if(arr[j] > arr[j + 1]){
// 利用ES6数组的解构赋值交换数据
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
flag = false;
}
}
if(flag){
break;
}
}
console.log(arr);//[1, 2, 3, 4]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 10.数组去重和反转数组
数组去重 方法 1: 扩展运算符和 Set 结构相结合,就可以去除数组的重复成员
// 去除数组的重复成员
var a=[1, 2, 2, 3, 4, 5, 5]
[...new Set(a)]
// [1, 2, 3, 4, 5]
2
3
4
方法 2: Array.from()能把set结构转换为数组
Array.from(new Set([1, 2, 2, 3, 4, 5, 5]));
// [1, 2, 3, 4, 5]
2
方法 3(ES5)
function unique(arr) {
let temp = [];
arr.forEach(e => {
if (temp.indexOf(e) == -1) {
temp.push(e);
}
});
return temp;
}
2
3
4
5
6
7
8
9
反转数组
要求 输入: I am a student 输出: student a am I 输入是数组 输出也是数组 不允许用 split splice reverse
解法一
function reverseArray(arr) {
let result = []
let distance = arr.length - 1
for (let i = 0; i <= distance; i++) {
result[i] = arr[distance - i]
}
return result
}
console.log(reverseArray(['I', 'am', 'a', 'student']))
// ["student", "a", "am", "I"]
2
3
4
5
6
7
8
9
10
解法二
function reverseArray(arr) {
let str = arr.join(' ')
let result = []
let word = ''
for (let i = 0; i < str.length; i++) {
if (str[i] != ' ') {
word += str[i]
} else {
result.unshift(word)
word = ''
}
}
result.unshift(word)
return result
}
console.log(reverseArray(['I', 'am', 'a', 'student']))
// ["student", "a", "am", "I"]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 11. new一个对象的时候发生了什么?
function Person(name, age) {
this.name = name;
this.age = age;
}
let p = new Person("Alice", 23);
2
3
4
5
new一个对象的四个过程:
1、创建一个空对象
let obj = {};
2、让构造函数中的this指向新对象,并执行构造函数的函数体
let result = Person.call(obj);
3、设置新对象的__proto__属性指向构造函数的原型对象
obj.__proto__ = Person.prototype;
4、判断构造函数的返回值类型,如果是值类型,则返回新对象。如果是引用类型,就返回这个引用类型的对象。
if (typeof(result) == "object")
p = result;
else
p = obj;
2
3
4
# 12.前端对后端返回的数据如何处理(JSON/XML)
前端通过javascript对后端返回的json或者xml进行格式化处理
JSON:
var jsonObj = JSON.parse(后端返回的json字符串);
var result = JSON.stringify(jsonObj, null, 2);//格式化
2
XML: 先使用 xml2json转化为JSON格式,然后再JSON.parse再JSON.stringfy进行格式化
//待转换的xml字符串
var xmlText = "<data id='123'><test>success</test><result>hangge.com</result></data>";
//创建一个x2js对象进行转换
var x2js = new X2JS();
var jsonObj = x2js.xml_str2json( xmlText );
2
3
4
5
6
# 13.什么是MVVM,MVVM和MVC以及MVP有什么区别?
MVVM最早由微软提出来,它借鉴了桌面应用程序的MVC思想,在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离,把Model和View关联起来的就是ViewModel。 ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model View 和 Model 之间的同步工作完全是自动的,无需人为干涉(由viewModel完成,在这里指VUE) 因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理
MVVM的设计思想:关注Model的变化,让MVVM框架去自动更新DOM的状态,从而把开发者从操作DOM的繁琐步骤中解脱出来!
MVC和MVP的关系 MVP是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数 据,View负责显示。作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过 Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller。
MVVM和MVP的关系 而 MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。 唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。这样开发者就不用处理接收事件和View更新的工作,框架已经帮你做好了。
# 14.说一下Vue的优点
体积小
更高的运行效率
基于虚拟DOM, 一种可以预选通过JavaScript进行各种计算, 把最终的DOM操作计算出来并优化的技术,由于这个DOM操作属于预处理操作, 并没有真正地操作DOM, 所以叫虚拟DOM。
双向数据绑定
让开发者不用再去操作DOM对象, 把更多的精力投入到业务逻辑上。
生态丰富、学习成本低
# 15.Vue的生命周期
通俗说就是 Vue 实例从创建到销毁的过程,就是生命周期。
Vue生命周期可以分为四个阶段:创建、挂载、更新、销毁
8个钩子函数:
beforeCreate(){ ... }
1. 创建(create)
created(){ ... }
beforeMount(){ ... }
2. 挂载(mount)
mounted(){ ... }
beforeUpdate(){ ... }
3. 更新(update)
updated(){ ... }
beforeDestroy(){ ... }
4. 销毁(destroy)
destroyed(){ ... }
2
3
4
5
6
7
8
9
10
11
12
# 16.Vue是如何实现双向数据绑定的(Vue双向数据绑定原理)
什么是双向数据绑定?Vue是一个MVVM框架,数据绑定简单来说,就是当数据发生变化时,相应的视图会进行更新,当视图更新时,数据也会跟着变化。
MVVM 双向绑定,采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()
来劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
Vue主要通过以下4个步骤实现数据双向绑定: 1、实现一个数据监听器 Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者 2、实现一个指令解析器 Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数 3、实现一个订阅者 Watcher,作为连接 Observer 和 Compile 的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图 4、最后实现 MVVM 作为数据绑定的入口,整合监听器、解析器和订阅者
流程图:
# 17.你对Vue项目进行了哪些优化
不在模板里面写过多表达式
循环调用子组件时添加key
频繁切换的使用v-show,不频繁切换的使用v-if
尽量少用float,可以用flex
按需加载,可以用require或者import()按需加载需要的组件
路由懒加载
# 18.为什么 0.1 + 0.2 != 0.3,请详述理由
0.1 + 0.2 = 0.30000000000000004
因为 JS 采用 IEEE 754 双精度版本(64位),并且只要采用 IEEE 754 的语言都有该问题。
出现这个问题的原因,其实是因为数值的表示在计算机内部是用二进制的
十进制0.1
=> 二进制0.00011001100110011…(循环0011)
=>尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-4(二进制移码为00000000010),符号位为0
=> 计算机存储为:0 00000000100 10011001100110011…11001
=> 因为尾数最多52位,所以实际存储的值为0.00011001100110011001100110011001100110011001100110011001
而十进制0.2
=> 二进制0.0011001100110011…(循环0011)
=>尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-3(二进制移码为00000000011),符号位为0
=> 存储为:0 00000000011 10011001100110011…11001
因为尾数最多52位,所以实际存储的值为0.00110011001100110011001100110011001100110011001100110011
那么两者相加得:
0.00011001100110011001100110011001100110011001100110011001
+ 0.00110011001100110011001100110011001100110011001100110011 (确认??)
= 0.01001100110011001100110011001100110011001100110011001100
转换成10进制之后得到:0.30000000000000004
2
3
4
5
6
7
8
9
10
11
12
13
14
15
解决
parseFloat((0.1 + 0.2).toFixed(10))
# 19.盒子模型你是怎么理解的?
盒子模型有两种,W3C和IE盒子模型
(1)w3c的盒模型width=content,不包括margin,padding,border
(2)IE盒模型width=content+padding+border
(3)box-sizng border-box在已设定的宽度和高度之内去设定padding和border
content-box在已设定的高度和宽度之外设置padding和border
2
3
4
5
# 20.什么是跨域,以及如何解决跨域问题?
跨域是相对于同源策略而言的。 同源策略是一种约定,它是浏览器最核心也最基本的安全功能 所谓同源是指: 协议,域名,端口都相同,就是同源, 否则就是跨域
同源策略带来的影响 在同源策略下, 浏览器只允许Ajax请求同源的数据, 不允许请求不同源的数据 但在企业开发中, 一般情况下为了提升网页的性能, 网页和数据都是单独存储在不同服务器上的 这时如果再通过Ajax请求数据就会拿不到跨域数据
跨域解决方案 现在主流跨域解决方案是jsonp JSONP让网页从跨域的地址那获取资料,即跨域读取数据
JSONP实现跨域访问的原理
1.在同一界面中可以定义多个script标签
2.同一个界面中多个script标签中的数据可以相互访问
3.可以通过script的src属性导入其它资源, 通过src属性导入其它资源的本质就是将资源拷贝到script标签中
script的src属性不仅能导入本地资源, 还能导入远程资源
4.由于script的src属性没有同源限制, 所以可以通过script的src属性来请求跨域数据
更多方法
JSONP封装(参考jQuery)
//myJsonp.js
function obj2str(obj){
// 生成随机因子
obj.t = (Math.random()).replace(".", "");
let arr = [];
for(let key in obj){
arr.push(key + "=" + encodeURI(obj[key]));
}
let str = arr.join("&");
return str;
}
function myJsonp(options){
options = optionns || {};
// 1. 生成url地址
let url = options.url;
// 判断是否指定回调函数名称的key
if(options.jsonp){
url += "?" + options.jsonp + "=";
}else{
url += "?callback=";
}
// 判断是否指定回调函数名称
let callbackName = ("jQuery" + Math.random()).replace(".", "");
if(options.jsonpCallback){
callbackName = options.jsonpCallback;
url += options.jsonpCallback;
}else{
url += callbackName;
}
// 判断是否传递数据
if(options.data){
let str = obj2str(options.data);
url += "&" + str;
}
// 2. 获取跨域的回调函数
let oScript = document.createElement("script");;
oScript.src = url;
document.body.appentChild(oScript);
// 3.定义回调函数
window[callbackName] = function(data){
// 删除已经获取了数据的script标签
document.body.removeChild(oScript);
// 将获取到的数据返回给外界
options.success(data);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
JSONP封装.html
...
<script src="myJsonp.js"></script>
<script>
myJSONP({
url: "http://127.0.0.1:80/jQuery/Ajax/jsonp.php",
data:{
"name": "zs",
"age": 22
},
jsonp: "cb", // 告诉jQuery服务器在获取回调函数名称的时候需要用什么key来获取
jsonpCallback: "zs", // 告诉jQuery服务器在获取回调函数名称的时候回调函数的名称是什么
success: function (msg) {
console.log(msg);
}
});
</script>
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
jsonp.php(后台)
<?php
// 1.拿到传递过来的数据
$name = $_GET["name"];
$age = $_GET["age"];
$arr = array("name"=>$name, "age"=>$age);
$data = json_encode($arr); // 将数组转换为json
// 2.拿到回调函数的名称
$cb = $_GET["cb"];
// 3.返回数据
echo $cb."(".$data.");";
?>
2
3
4
5
6
7
8
9
10
11
12