Web前端面試題總結
### 1.1 常見元素
塊級元素
- h1-h6 標題
- table cation tbody thead tfoot tr 表格的元素
- ul ol li dt dd dl
- form
- p
- div
行內元素
- a
- b em i 廢棄標籤
- code
- label
- span
- th td
行內塊級元素
- img
- input
- textarea
h5
- header
- footer
- nav
- article
- aside
- datalist
- section
- video
- audio
## 2 CSS
### 2.1 @import 和 link的區別
- @import 引入的樣式會面頁面載入完成後才會載入,而link會和頁面同時載入
- link是標籤,可以通過DOM去操作,而@import不可以
### 2.2 實現一個左側固定寬度200px,右邊自適應布局
```html
// html
<div class="left"></div>
<div class="right"></div>
```
第一種方法:浮動
```css
.left {
float: left;
width: 200px;
height: 500px;
background: darkcyan;
}
.right {
height: 500px;
background: deepskyblue;
}
```
第二種方法:絕對定位
```css
body {
position: relative;
}
.left {
position: absolute;
left: 0;
top: 0;
width: 200px;
height: 500px;
background: darkcyan;
}
.right {
height: 500px;
background: deepskyblue;
}
```
第三種方法:flex
```css
.parent {
display: flex;
}
.left {
width: 200px;
height: 500px;
background: darkcyan;
}
.right {
height: 500px;
background: deepskyblue;
flex-grow: 1;
}
```
### 2.3 less定義一個圓角函數
```css
.radius(@r:50%) {
-webkit-border-radius: @r;
-moz-border-radius: @r;
border-radius: @r;
}
```
### 2.4 w3c標準盒模型佔位寬度是如何計算的? 它與IE盒模型有什麼不同?
IE67的混雜模式,寬度是算上了border + padding
```css
width = border-left + padding-left + con_width + padding-right + border-right
```
標準盒模型是分開來計算的
### 2.5 box-sizing的屬性值content-box 和 border-box的計算方式?
content-box 是w3c標準盒模型,其中的margin border padding width都是分別計算的,其中的width屬性是只針對content的寬度。而border-box是IE67的混雜模型的盒模型計算方式,其中的width屬性是盒模型的border + padding + content
### 2.6 常見移動端適配不同屏幕
- REM布局
- 流式布局
- flex布局
### 2.7 引入css的方式
- 行內式
- 內嵌式
- 外鏈式,使用link標籤
- 使用@import引入css
### 2.8 寫出position的屬性值
- static
- relative
- absolute
- fixed
### 2.9 用box-shadow 寫出單邊上陰影效果(陰影半徑與模糊半徑不限)
```css
div {
-webkit-box-shadow: inset 5px 0 3px deepskyblue;
-moz-box-shadow: inset 5px 0 3px deepskyblue;
box-shadow: inset 5px 0 3px deepskyblue;
}
```
box-shadow: inset h v blur spreed color
內陰影 豎直 水平 模糊 半徑 顏色
### 2.10 常見清除浮動的方式
- 給父級指定高度
- 父級最後加空元素
- 父級overflow: hidden
- 父級也一起浮動
### 2.11 css3新增特性
- border-radius
- text-shadow
- box-shadow
- linear-gradient
- box-sizing
- word-warp
- column-count
- display: box
### 2.12 a標籤偽類的順序
link -> visited -> hover -> active
原因如下:link和visited是常態,hover和active是即時狀態,即時狀態想要覆蓋常態就必須要放到後面,否則無效。
同樣,在常態里,visited是訪問後的狀態,想要覆蓋未訪問狀態就要寫到其後面;
即時態里,active想要覆蓋hover就要寫到後面
### 2.13 樣式權重的排序
- !import 1000
- id 100
- class 10
- tag 1
### 2.14 display有哪些屬性值,有什麼作用
- none:元素不會顯示,而且改元素現實的空間也不會保留
- inline: 將元素顯示為內聯元素,元素前後沒有換行符
- block:將元素顯示為塊級元素,元素前後會帶有換行符
- inline-block:行內塊級元素
- list-item:此元素會作為列表顯示
- table:此元素會作為塊級表格來顯示,表格前後帶有換行符
- inherit:規定從父元素繼承display屬性的值
- box:彈性盒模型,c3新增
### 2.15 Flex
#### 2.15.1 作用在父元素上的屬性
- `display: flex`
設置這個屬性後就會成為flex怪異盒模型
- `flex-direction`
設置flex主軸的方向,屬性值有:`row/row-reverse/colum/colum-reverse`
- `flex-wrap`
設置換行的方式,屬性值有:`nowrap/wrap/wrap-reverse`
- `justify-content`
設置項目在主軸上的排列方式:屬性值有
1. flex-start
2. flex-end
3. center
4. space-between 兩端對齊,中間均分
5. space-around 兩側有間隙,這個間隙是項目之間間隙的一半
- `align-items`
項目在交叉軸上的對齊方式:屬性值有
1. flex-start
2. flex-end
3. center
4. baseline
5. stretch 默認值,佔滿空間
- `align-content`多根軸的對齊方式,如果只有一根軸就不生效
#### 2.15.2 作用在項目上的屬性
- `order` 數值越小越靠前,默認為0
- `flex-grow` 放大比例,如何去分配默認空間,0不放大,1均分,如果按其他比例,可以單獨給項目設置
- `flex-shrink` 縮小比例,0不縮小,1等比例縮小,默認值是1
複合寫法是flex
- `align-self` 單獨設置對齊方式
## 3 JavaScript
### 3.1 AJAX
相關知識點:
- 跨域(80%以上會問到)
- fetch(替代xhr對象的東西)
- socket
#### 3.1.1 JSONP
跨域是**瀏覽器**的行為
跨域需要服務端的配合,如果不能修改服務端,可以自己後台轉發一下請求,畢竟後台請求是沒有跨域限制的
跨域的方式有幾種,面試官要是問跨域的方式的話,一般就是在問JSONP。和面試官講請JSONP的原理也算一點加分項。
相關知識點:
- 瀏覽器的同源,協議、域名和埠號
- script標籤的src屬性沒有跨域的限制
- 需要服務端配合,前端需要給後台傳回調名稱
總而言之,就是使用script標籤把數據請求回來。
一般印象中,script標籤就是外鏈一段js代碼。現在寫一個服務,當瀏覽器訪問`/js`時,就`send(alert("js"))`,發送一段js代碼。現在看是否會執行
![image][img1]
![image][img2]
![image][img3]
可以看到js代碼正常執行了。這個就等於直接往全局作用域里直接寫了一行`alert(js)`
這個時候我們再返回一個JSON串回去
![image][img4]
可以看到,直接報錯了。這個就好比,直接往全局作用域里直接赤裸裸的扔了一個對象,沒有任務操作。
假如我們在全局作用域里已經定義好了一個函數
```javascript
function handle(data) {
console.log(data);
}
```
如果用script標籤請求回來的數據,能直接用這個函數處理多好。所以後台給我們處理的時候
```javascript
res.send(handle({name: "chang"}))
```
這個返回的數據和上面一樣,也相當於直接在全局作用域里寫了個
```javascript
handle({name: "chang"});
```
這個時候我們早已經定義好了處理函數,那麼這個函數就可以直接處理數據了
所以不管哪個工具封裝的JSONP都是這個操作,也就是為什麼要和後台定義回調函數的名稱,因為後台不知道你在JS定義什麼名稱的函數來處理數據,一般這個回調函數名稱是需要放在`queryString`中的。後台需要截取,再拼接
以下就是簡單的JSONP代碼
```javascript
function xxxx(content) {
console.log(content);
}
let script = document.createElement(script);
script.src = http://127.0.0.1:8000?cb=xxxx;
document.body.appendChild(script);
```
所以綜上,jsonp只能是get請求
#### 3.1.2 fetch
fetch是之前瀏覽器的`xhr`對象的替代方案,現在使用的話,有瀏覽器兼容問題,畢竟有些瀏覽器是不支持的,可以使用`whatwg-fetch`這個包來解決一下。
和原生的xhr一樣,都不會直接使用原生,而是使用封裝好的庫。
和原生的`xhr`相比,優點:
- 使用promise
- 進而可以使用`async/await`,寫的就像同步代碼一樣爽
```javascript
try {
let response = await fetch(url);
let data = await response.json();
console.log(data);
} catch(e) {
console.log("error:", e);
}
```
使用的坑:
- 默認請求是不帶cookie的,需要設置
- `fetch(url, {credentials: same-origin})`同域下發送cookie
- `fetch(url, {credentials: include})`跨域下發送cookie
- 服務返回的400和500錯誤,是不會觸發reject的,只有網路錯誤導致請求不能完成時,fetch才會觸發reject
#### 3.1.3 socket
一種前後台通信的方案,後台可以主動給前台推送消息,而取代之前的短輪循和長輪循
### 3.2 JS輸出問題
#### 3.2.1 對JS非同步的理解
說出下面數字的輸出順序:
```javascript
console.time(setTimeout);
console.time(setImmediate);
setImmediate(() => {
console.log(1);
console.timeEnd(setImmediate);
});
setTimeout(() => {
console.log(2);
console.timeEnd(setTimeout);
}, 0);
process.nextTick(() => console.log(3));
new Promise(function(resolve){
console.log(4);
resolve();
console.log(5);
}).then(function(){
console.log(6);
});
(() => console.log(7))();
```
答案:
```
4 5 7 3 6 2 1
```
解析:同步代碼先順序執行,然後是本輪循環的非同步任務和次輪循環的非同步任務。
`process.nextTick`和`Promise`就屬於本輪循環的,而且`process.nextTick`最先執行
其他兩個屬性次輪循環的非同步任務,在`setTimeout`的延遲時間為4ms及以下時,`setTimeout`會先執行
#### 3.2.2 JS的逗號語句
```javascript
var k;
for (var i = 0, j = 0; i < 7, j < 10; i++, j++) {
k = i + j;
}
console.log(k);
var l;
for (var i = 0, j = 0; i < 10, j < 7; i++, j++) {
l = i + j;
}
console.log(l);
```
#### 3.2.3 alert()
alert的時候會自動調用`toString()`方法
```javascript
alert([1, 3]); // -> 1, 3
alert({name: chang}); // -> [object Object]
```
#### 3.2.4 非同步
```javascript
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 1000*i);
}
```
結果是連續輸出5個5。setTimeout執行時,i已經是5了,全部輸出5很正常,現在的問題是時間間隔是多少。正確的姿勢是,間隔時間是不相同的。(實驗得知)
#### 3.2.5 一些和預解釋相關的東西
```javascript
var obj = {name: Joo, age: 30};
~function (n) {
console.log(n);
n.name = 中國;
n = {};
n.age = 5000;
console.log(n);
}(obj);
console.log(obj);// {name: Joo, age: 30}
// 形參 形式上的一個代表值
// 解耦
function add(a, b) {
console.log(arguments.callee.caller);
}
// 實參 實際傳入的參數
function a() {
add(1, 4);
}
a();
```
### 3.3 數組
#### 3.3.1 數組的方法
|方法|參數|返回值|是否影響原數組|發布版本|
|:-|:-|:-|:-|:-|
|push||length|y||
|pop||item|y||
|shift||item|y||
|unshift||length|y||
|splice|index, length, item|array|y||
|slice|index, index|array|n||
|concat|item/array|array|n||
|sort|function(){}|array|y||
|toString|no|string|n||
|join|符號|string|n||
|reverse|||y||
|indexOf/lastIndexOf|||||
|forEach|||||
|every|||||
|some|||||
|filter|||||
|map|||||
|reduce/reduceRight|||||
|find/findIndex|||||
|includes|||||
|fill|||||
|entries/keys|||||
|Array.from|||||
|Array.of|||||
#### 3.3.2 多維數組轉一維數組
第一種方法:使用遞歸
```javascript
var arr = [1, 2, [3, 4, [5, 6], 7], 8, [9, 10, [11]], 12];
var spreadArr = [];
function spread(arr) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] instanceof Array) {
spread(arr[i]);
} else {
spreadArr.push(arr[i]);
}
}
}
spread(arr);
console.log(spreadArr);
```
#### 3.3.3 數組相關的演算法
- 冒泡排序
- 快速排序
- 插入排序
- 去重
```javascript
function bubbleSort(arr) {
let isCompleted = true;
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
isCompleted = false;
}
}
if (isCompleted) {
console.log(提前完成);
return arr;
} else {
isCompleted = true;
}
}
isCompleted = null;
return arr;
}
```
```javascript
function insertSort(arr) {
let newArr = [arr[0]];
for (let i = 1; i < arr.length; i++) {
let cur = arr[i];
for (let j = 0; j < newArr.length;) {
if (cur > newArr[j]) {
j++;
if (j === newArr.length) {
newArr.push(cur);
break;
}
} else {
newArr.splice(j, 0, cur);
break;
}
}
}
return newArr;
}
```
```javascript
function quickSort(arr) {
if (arr.length <= 1) {
return arr;
}
let midIndex = Math.ceil(arr.length / 2);
let midVal = arr.splice(midIndex, 1)[0];
let left = [], right = [];
for (let i = 0; i < arr.length; i++) {
let cur = arr[i];
cur < midVal ? left.push(cur) : right.push(cur);
}
return quickSort(left).concat(midVal, quickSort(right));
}
```
```javascript
function unique2(arr) {
let obj = {};
for (let i = 0; i < arr.length; i++) {
let cur = arr[i];
if (obj[cur] === cur) {
arr.splice(i, 1);
i--;
} else {
obj[cur] = cur;
}
}
return arr;
}
```
使用數組的filter進行去重
```
function unique(arr) {
return arr.filter(function(item, idx, array) {
return array.indexOf(item) === idx;
})
}
```
### 3.4 繼承
#### 3.4.1 原型式繼承
```javascript
function A(name) {
this.name = name;
this.sex = 1;
}
A.prototype.say = function () {
console.log(hello world);
};
function B(age) {
this.age = age;
}
let a = new A();
B.prototype = a;
let b = new B(18);
console.log(b);//{ age: 18 }
console.log(b.name, b.sex);//undefined 1
b.say();//hello world
/*
* 原型鏈繼承:子類的原型指定父類的實例
*
* extend:繼承了父類的私有和公有屬性,在此處name和sex是私有屬性,say是公有屬性
* - 私有屬性是從父類的實例上得到的
* - 公的屬性是沿著作用域鏈b.__proto__ --> a (沒有) --> a.__proto__ --> A.prototype (保存在此處)
*
* 原型鏈繼承的問題:
* - 繼承父類的私有屬性時,無法向父類型中傳參,所以在輸出b.name的時候是undefined
* - 在繼承引用數據類型的值時,就會有問題,請看下例
* */
function SuperType() {
this.color = [red, blue, yellow];
}
function SubType() {
}
SubType.prototype = new SuperType();
let instance1 = new SubType();
let instance2 = new SubType();
instance1.color.push(black);
console.log(instance2.color);//[ red, blue, yellow, black ]
//修改了instance1的屬性的時候instance2的屬性也會跟著變化,因為這個屬性都是來自於它們的原型
```
#### 3.4.2 call繼承
```javascript
function SuperType(age) {
this.age = age;
}
SuperType.prototype.say = function () {
console.log(hello world);
};
function SubType(age) {
SuperType.call(this, age);
}
let sub = new SubType(18);
console.log(sub);//SubType { age: 18 }
console.log(sub.say);//undefined
/*
* call繼承:讓父類的構造函數在子類的構造函數里執行,並使用call改變裡面的this
*
* extend:因為只是讓父類的構造函數在子類里執行,所以只繼承了私有屬性。
*
* 優點:和原型鏈繼承相比,解決了無法傳參的問題,這個參數可以寫在call的第二個參數位置
*
* 缺點:
* - 因為只有私有屬性繼承,定義在父類原型上的方法對子類的實例都是不可見的
* */
```
#### 3.4.3 組合繼承
```javascript
function SuperType() {
this.name = chang;
}
SuperType.prototype.say = function () {
console.log(hello);
};
function SubType() {
SuperType.call(this);
}
SubType.prototype = new SuperType();
let sub = new SubType();
console.log(sub);//{ name: chang }
sub.say();//hello
/*
* 組合式繼承:將原型繼承和call繼承組合使用
*
* extend:使用call繼承私有屬性,使用原型鏈繼承繼承公有屬性
*
* 優點:解決了原型繼承和call繼承的問題
*
* 缺點:父類的構造函數調用了兩次
*
* */
```
#### 3.4.4 原型式繼承
```javascript
/*
* 這種方法並沒有使用嚴格意義上的構造函數,藉助原型可以基於已有的對象創新新的對象,同時還不必創建自定義的類型
* */
function object(o) {
function F() {
}
F.prototype = o;
return new F();
}
let obj1 = {name: chang, friend: [a, b, c]};
let obj2 = object(obj1);
console.log(obj2.__proto__);//{ name: chang, friend: [ a, b, c ] }
/*
* 在es5中規範了這個繼承的方法:Object.create(),這個方法接收兩個參數,一個用作新對象原型的對象和(可選的)一個為新對象定義額外屬性的對象
* */
let obj3 = Object.create(obj1);
console.log(obj3.__proto__);//{ name: chang, friend: [ a, b, c ] }
//添加額外屬性時,必須使用這種格式
let obj4 = Object.create(obj1, {
like: {
value: code
}
});
console.log(obj4.like);//code
```
#### 3.4.5 `__proto__`
```javascript
function SuperType() {
this.name = chang;
}
SuperType.prototype.say = function () {
console.log(ok);
};
function SubType() {
}
SubType.prototype.__proto__ = SuperType.prototype;
let sub = new SubType();
console.log(sub.name);//undefined
sub.say();//ok
/*
* 不知道名字的繼承:改變子類的原型的__proto__,指向父類的原型
* 只繼承公有
* */
```
#### 3.4.6 `setProtoOf`
```javascript
/*
* Object.setPrototypeOf方法的作用與__proto__相同,用來設置一個對象的prototype對象,返回參數對象本身。它是 ES6 正式推薦的設置原型對象的方法。
*
*
* */
let obj1 = {name: chang};
let obj2 = {};
Object.setPrototypeOf(obj2, obj1);
console.log(obj2.__proto__ === obj1);
```
### 3.5 其他
#### 3.5.1 兩個頁面的通訊方式
- url
- localStorage
- cookie
#### 3.5.2 統計頁面中類名的個數
```javascript
var os = document.getElementsByTagName(*);
var cObj = {};
var max = 0;
function sta(cName) {
var cNameArr = cName.split(/ +/);
for (var i = 0; i < cNameArr.length; i++) {
var cur = cNameArr[i];
if (cObj[cur]) {
cObj[cur]++;
} else {
cObj[cur] = 1;
}
}
}
for (var i = 0; i < os.length; i++) {
if (os[i].className) {
sta(os[i].className);
}
}
for (var attr in cObj) {
if (cObj.hasOwnProperty(attr)) {
if (cObj[attr] > max) {
max = cObj[attr];
}
}
}
console.log(max);
```
### 3.6 ES6 +
- `let/const`
- 解構賦值
- 字元串
- 模板字元串
- `includes/startsWith/endsWith`
- `repeat`
- 函數
- 箭頭函數
- 參數默認值
- 剩餘參數
- **雙冒號**
- 數組
- 擴展運算符
- `entries/keys/values`
- `Array.from`將類數組轉化成數組
- `Array.of`將一級值轉成數組
- `includes`
- 對象的擴展
- 簡潔表達形式
- 屬性表達式
- 函數的name
- `Object.assign`合併對象,淺拷貝
- `Object.keys()/Object.values()/Object.entries()`
- 擴展運算符
- 解構
- `Symbol`第6種基本數據類型的值 `let s = Symbol() // 不需要new`
- `Set` 可用於數組去重
```javascript
var arr = [1, 1, 1, 1];
var s = new Set(arr);
var newArr = [...s];
```
- `Map`和`Object`相比,key值可以是任何值
- `proxy`做目標對象的讀寫操作的攔截
- `Promise`
- `async/await`
- `Class/extends`
- `Module`
## 4 代碼管理工具
### 4.1 GIT
常用命令(結合自己常用)
#### 4.1.1 提交4部曲
- `git add`
- `git commit`
- `git pull`
- `git push`在多人協作開發的時候,如果不`pull`是絕對`push`不上去的
#### 4.1.2 查看提交記錄
- `git log`
如果想查看簡單點就是
- `git log --pretty=oneline`
想看是某個人的提交歷史
- `git log --author=changzhn`
想查看某個文件的提交歷史
- `git log -- filename`
想在芒芒提交中搜索自己印象關鍵字
- `git log --grep=xxx`
終於找到那個提交的記錄了,想看看提交了啥子內容,提交的ID截取前幾位就可以了
- `git show hash`
#### 4.1.3 分支管理
查看當前的分支
- `git branch`
新建個分支並且切換過去
- `git checkout -b develop`
切換分支
- `git checkout develop/release/master`
注意:切換分支的時候,在當前分支修改的內容也是帶過去,這一點很噁心,所以切換分支前,先使用下面這個命令,來看當前分支的狀態
- `git status`
如果有修改(紅色)或者添加(綠色)的內容,就要使用提交4部曲的`add`和`commit`來產生版本號後,才能切換分支
或者,使用下面這個命令,將這個分支上的所有修改放入一個異次元...
- `git stash`
再切換回來想恢復修改的時候可以使用
- `git stash list`
想看,往異次元中放入了幾次,如果只想恢復最近的一次的話
- `git stash pop stash@{0}`
![image][img5]
比如切到`release`分支上想合併`develop`分支
- `git merge develop`
在合併的時候,很不幸運的是衝突了
這個時候就需要解決衝突,在當前分支上,使用提交4部曲,為這次解決衝突產生一個版本號
#### 4.1.4 版本管理
找到版本號的時候想回退代碼
- `git reset --HARD hash`
比如現在將代碼推到過程,後來發現這真是一次垃圾的提交,在本地使用代碼回退上一個版本的時候,想再`push`上去,這個時候發現是推不上去的。
- `git push origin master -f`
可以使用`-f`來暴力提交
推薦閱讀:
※結合源碼分析 Node.js 模塊載入與運行原理
※2018年各大互聯網前端面試題三(阿里)
※gulp配置browserSync,搭建開發環境,代理ajax請求
※怎樣設計以用戶為中心的WEB表單?