高級前端軟件工程師知識整理之基礎篇(一)

1. 說出幾點前端優化的方法?

(1)減少網絡請求次數。優化情形:客戶端向服務端請求大量資源,如圖片、js文件等。網絡請求遵循HTTP協議,每一次請求都是與服務端的獨立通信,在建立通信過程需要時間,如果請求次數過多會造成頁面反應遲鈍,遇到這種情形,可以將資源文件合併壓縮成一個文件來加載,通過實現減少與服務端資源請求次數達到優化效果。

(2)數據增量加載。優化情形:客戶端向服務端API接口請求大量數據,數據量甚至達到幾兆。增量加載,是指先請求部分頁面數據,足夠把一屏頁面顯示出來,再分批量去請求其它數據,不必一次就要把所有數據都請求進來。

(3)靜態資源使用CDN服務。優化情形與第一點相似,有些頁面需要大量的圖片資源,這類資源很少需要去更新但又無法再壓縮,這種情形可以使用CDN服務,如阿里雲的CDN服務。

(4)代碼優化。代碼優化可以是算法優化、路由懶加載、組件緩存、打包時刪除不必要的註釋和腳本等。

2. 閉包是什麼?介紹閉包的使用場景以及爲什麼閉包沒有清除?

閉包就是能夠讀取其他函數內部變量的函數,也可以理解爲定義在一個函數內部的函數,匿名函數也是閉包的一種。

請看這段代碼:

function f1() {
	var n = 1;
	return function f2() {      
	    alert(n); 
	} 
}

代碼中f2函數就是閉包。要理解閉包,首先要理解javascript的特殊的變量作用域,變量的作用域無非就兩種:全局變量和局部變量。函數內部可以直接讀取全局變量,但是在函數外部無法讀取函數內部的局部變量。閉包的作用就是實現從函數的外部也可以讀取到函數內部的變量。閉包除了上例寫法,還可以由外部傳入函數,返回函數執行結果:

function f1(f2) {
	var n = 1;
	return f2(n);
}

閉包的使用場景很多,最常用的有兩種:一個是前面提到的可以讀取函數內部的變量,另一個就是讓父函數(如 f1)的局部變量始終保持在內存中,不會在調用後被自動清除。爲什麼閉包沒有清除,隨意使用會造成內存泄漏?原因就在於f1函數的執行結果是返回f2,把f2變成了一個window下的全局變量,即f2始終在內存中,而f2的存在依賴於f1,因此f1也始終在內存中,不會在調用結束後,被垃圾回收機制回收。因此,在使用閉包時要注意兩點:

  • 由於閉包會使得父函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露。解決方法:在退出函數之前,將不使用的局部變量全部刪除,如 var fn = f1();  fn=null。
  • 當閉包格式爲爲自執行函數時,如下面這段代碼,n值將會作爲f1函數作用域中的全局變量只初始化一次,這裏不建議外部函數對父函數裏的變量進行修改(下面例子中的n值),外部函數僅使用數據而不操作。
function todo(n) {
	console.log(n);
}

var test = (function(fn) {
	var n = 0;
	return function(){
		n++;
		fn(n);
	};
})(todo);

test(); // 1
test(); // 2
test(); // 3

3. jsonp實現的原理是什麼?

瀏覽器是不允許跨域請求服務端數據的,但是允許跨域請求js格式文件和帶src屬性標籤指向的資源,jsonp就是利用了這個特性實現跨域請求。它的原理是:通過帶src的標籤向服務端發起GET請求,請求結果是由服務端包裝好的一個js格式文件,當該文件加載到瀏覽器後會觸發客戶端事先定義好的一個全局回調函數,其入參即爲服務端響應的數據。

我們來看代碼,由前端定義一個處理數據的回調函數cbHandler並開始發送請求:

<script type="text/javascript">
    // 回調函數
    var cbHandler = function(json){
        console.log(json);
    };
    var url = "url?param=data&callback=cbHandler"; // 向服務端發送get請求,參數在問號後面傳遞
    var script = document.createElement('script'); // 創建script標籤,設置其屬性
    script.setAttribute('src', url);
    document.getElementsByTagName('head')[0].appendChild(script); // 把script標籤加入head,此時調用開始
 </script>

請求結果爲服務端包裝好的一個js格式文件,它的數據結構如下:

cbHandler(JSON.stringify(Object));

服務端響應結果將會作爲入參,在請求成功時等於瀏覽器解析了該js格式文件,最終實現調用客戶端的cbHandler函數。

通常我們不用那麼麻煩,可以直接使用ajax封裝好的jsonp請求功能,實現代碼如下:

<script>
	jQuery(document).ready(function() {
		$.ajax({
			type: "get",
			async: false,
			url: "",
			dataType: "jsonp",
			jsonp: "callback", //傳遞給請求處理程序或頁面的,用以獲得jsonp回調函數名的參數名(一般默認爲:callback)
			jsonpCallback: "cbHandler", //自定義的jsonp回調函數名稱,默認爲jQuery自動生成的隨機函數名,也可以寫"?",jQuery會自動爲你處理數據
			success: function(json) {
				console.log('success');
			},
			error: function() {
				console.log('fail');
			}
		});
	});
</script>

4. new函數是怎麼實現的?

new函數的實現是一個原型指向和數據劫持的過程,如:

function Person(sex) {
	this.sex = sex;
}
Person.prototype.name = 'John';
Person.prototype.age = '30';
Person.prototype.getName = function() {
	return this.name;
}

var obj = new Object();
obj.__proto__ = Person.prototype; // 新對象原型指向Person原型
Person.call(obj, '男'); // 數據劫持,劫持Person中this內容
console.log(obj);

代碼中obj對象等同於new Person()對象。

5. 數組的操作有哪些?

push(value) 方法用於在數組的末端添加一個或多個元素(用逗號隔開),並返回添加新元素後的數組長度。該方法會改變原數組。

pop() 方法用於刪除數組的最後一個元素,並返回該元素。該方法會改變原數組。

join(value) 方法以參數value作爲分隔符分隔數組,返回一個字符串。如果不提供參數,默認用逗號分隔。該方法不會改變原數組。

concat(value) 方法用於數組合並。將新數組value添加到原數組後面,返回一個合併後的新數組,兩個原數組均不變。

另外,concat 方法也可以用於將多個對象合併爲數組,入參的對象間用逗號隔開。如:

[].concat({a: 1}, {b: 2}) // [{ a: 1 }, { b: 2 }]

shift() 方法用於刪除數組的第一個元素,並返回該元素。該方法會改變原數組。

unshift(value) 方法用於在數組的第一個位置添加一個或多個元素(用逗號隔開),並返回添加新元素後的數組長度。該方法會改變原數組。

reverse() 方法用於顛倒數組中元素的順序,返回改變後的數組。該方法將改變原數組。

slice(startindex, endindex) 方法用於提取原數組的一部分,返回提取出來的新數組。索引值從0開始計數。該方法不會改變原數組。

var a = ['a', 'b', 'c'];
a.slice(0) // ["a", "b", "c"]
a.slice(1) // ["b", "c"]
a.slice(1, 2) // ["b"]
a.slice(2, 6) // ["c"]
a.slice() // ["a", "b", "c"] 相當於複製數組

如果slice方法的參數是負數,則表示倒數計算的位置。

var a = ['a', 'b', 'c'];
a.slice(-1); // ["c"]
a.slice(-2) // ["b", "c"]
a.slice(-2, -1) // ["b"]

如果參數值大於數組成員的個數,或者第二個參數小於第一個參數,則返回空數組。

var a = ['a', 'b', 'c'];
a.slice(4) // []
a.slice(2, 1) // []

splice(index, num_to_remove, addElement1, addElement2, ...) 方法用於刪除原數組的一部分成員,並可以在被刪除的位置添加入新的數組成員,返回值是被刪除的元素。索引值從0開始計數。如果只是單純地插入元素,splice方法的第二個參數可以設爲0。該方法會改變原數組。

var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2) // ["e", "f"]
a // ["a", "b", "c", "d"]

var b = [1, 1];
b.splice(1, 0, 2) // []
b // [1, 2, 1]

sort(fun) 方法對數組成員進行排序,排序後,原數組將被改變。當沒有參數時按升序排列,sort也可以接受一個函數作爲參數,以便我們指定哪個值位於哪個值的前面。比較函數接收兩個參數,如果第一個參數應該位於第二個之前則返回一個負數,如果兩個參數相等則返回 0,如果第一個參數應該位於第二個之後則返回一個正數。該方法會改變原數組。

// 無參數時
var arr1 = ["a", "d", "c", "b"];
console.log(arr1.sort()); // ["a", "b", "c", "d"]
var arr2 = [13, 24, 51, 3];
console.log(arr2.sort()); // [13, 24, 3, 51]
console.log(arr2); // [13, 24, 3, 51](原數組被改變)

// 有參數時
var a = [3, 4, 1, 2, 5];
var b = a.sort(function(v1, v2){
	return v1-v2;
});
console.log(a); // [1, 2, 3, 4, 5]
console.log(b); // [1, 2, 3, 4, 5] 

map(callback) 方法對數組的所有成員依次遍歷執行回調函數,根據函數結果返回一個新數組。其回調函數有三個入參,分別是當前成員、當前位置和數組本身。該方法不會改變原數組。

var a = [1, 2, 3];
var b = a.map(function(elem, index, arr) {
  return elem * index;
});
console.log(a); // [1, 2, 3]
console.log(b); // [0, 2, 6]

forEach(callback) 方法與map方法很相似,也是遍歷數組的所有成員,執行某種操作,但是forEach方法一般不返回值,只用來讀取數據。如果需要有返回值,一般使用map方法。其回調函數與map一樣有三個入參,分別是當前成員、當前位置和數組本身。forEach方法無法中斷執行,總是會將所有成員遍歷完。該方法不會改變原數組。

var a = [1, 2, 3];
var b = a.forEach(function(elem, index, arr) {
  return elem * index;
});
console.log(a); // [1, 2, 3]
console.log(b); // undefined

forEach方法也可以接受第二個參數,用來綁定回調函數的this關鍵字。

var obj = {
  name: '張三',
  times: [1, 2, 3],
  print: function () {
    this.times.forEach(function (n) {
      console.log(this.name);
    }, this);
  }
};

obj.print()
// 張三
// 張三
// 張三

filter(callback) 方法用於數組過濾,所有數組成員依次執行回調函數,返回結果爲true的成員組成一個新數組返回。該方法不會改變原數組。

[1, 2, 3, 4, 5].filter(function (elem) {
  return (elem > 3);
})
// [4, 5]

some(callback),every(callback) 兩個方法類似“斷言”(assert),用來判斷數組成員是否符合某種條件。它們接受一個函數作爲參數,所有數組成員依次執行該函數,返回一個布爾值。該函數接受三個參數,依次是當前位置的成員、當前位置的序號和整個數組。some方法是隻要有一個數組成員的返回值是true,則整個some方法的返回值就是true,否則false

var arr = [1, 2, 3, 4, 5];
arr.some(function (elem, index, arr) {
  return elem >= 3;
});
// true

every方法則是所有數組成員的返回值都是true,才返回true,否則false

var arr = [1, 2, 3, 4, 5];
arr.every(function (elem, index, arr) {
  return elem >= 3;
});
// false

reduce()方法,調用如下:

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

依次處理數組的每個成員,最終累計爲一個值。執行函數中有4個參數,其中前兩個是必選參數,後兩個是可選參數:total累計變量,currentValue當前數組元素,currentIndex當前數組索引,arr原數組。initialValue爲累計的初始值。 

var a = [1, 2, 3, 4, 5].reduce(function(x, y){
  console.log(x, y)
  return x + y;
}, 10);
console.log(a);
// 最後結果:25
// 累加過程如下:
// 10 1
// 11 2
// 13 3
// 16 4
// 20 5

indexOf(),lastIndexOf() indexOf 方法返回給定元素在數組中第一次出現的位置,如果沒有出現則返回-1。lastIndexOf 方法返回給定元素在數組中最後一次出現的位置,如果沒有出現則返回-1

var a = 'hello';
console.log(a.indexOf('l')); // 2

var a = ['a', 'b', 'c'];
console.log(a.indexOf('b')); // 1

Array.isArray()  方法用來判斷一個值是否爲數組。

var a = [1, 2, 3];
Array.isArray(a) // true

技巧:上面數組操作中,改變原數組的操作有:push、pop、shift、unshift、reverse、splice和sort。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章