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 = 127.0.0.1:8000?;

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表單?

TAG:互聯網 | web前端設計師 | 前端開發 |