做爲一名有追求的前端攻城獅,掌握幾個高級編程技巧是必須的~學會這些技巧,會讓咱們的開發工做事半功倍,讓咱們在工做中更加遊刃有餘,本篇文章將介紹三個小技巧:前端
惰性載入函數的應用web
函數柯里化的應用面試
compose調用函數扁平化編程
在咱們的代碼中,必定包含了大量的if
語句,這些if
語句的執行要花費一些時間。有一種狀況是這樣的,咱們第一次進入到if
分支中,執行了這部分代碼,而後第二次一樣執行進入了同一分支,因此會再次執行此部分代碼。這樣的狀況下,代碼執行確定會慢一些,那麼若是咱們只讓代碼有一個if
分支,代碼就會執行的快一些。若是讓代碼執行的更快一些,這就是惰性載入函數的應用,接下來,看一個DOM
事件綁定的例子:redux
function emit(element, type, func) {
if(element.addEventListener) {
element.addEventListener(type, func, false)
} else if(element.attachEvent) { // IE六、七、8
element.attachEvent("on" + type, func)
} else {
element["on" + type] = func;
}
}
複製代碼
上面的例子中,判斷瀏覽器是否支持每一個方法,進入到不一樣分支,從而用不一樣的方式綁定事件。若是屢次綁定就會屢次進入同一if
分支執行代碼,其實在同一瀏覽器環境下,屢次綁定,只須要判斷一次就能夠完成目的。因此,這時須要應用惰性載入函數數組
function emit(element, type, func) {
if(element.addEventListener) {
emit = function(element, type, func) {
element.addEventListener(type, func, false)
}
} else if(element.attachEvent) {
emit = function(element, type, func) {
element.attachEvent("on" + type, func)
}
} else {
emit = function(element, type, func) {
element["on" + type] = func;
}
}
emit(element, type, func);
}
複製代碼
優化後的代碼中,第一次執行進入到一個分支執行此部分代碼後,函數emit
會被從新賦值,這樣就保證了屢次調用時只有一次if
執行,代碼執行的就變得快了一些。瀏覽器
對於函數柯里化,最通俗的理解是:一個大函數返回一個小函數在《JavaScript高級程序設計》中,這樣解釋函數柯里化:與函數綁定緊密相關的主題時函數柯里化,它用於建立已經設置好了一個或多個參數的函數。函數柯里化的基本方法和函數綁定是同樣的:使用一個閉包返回一個函數。 咱們先來看一個例子:bash
function add(num1, num2) {
return num1 + num2;
}
function curried(num2) {
return add(5, num2);
}
console.log(curried(2)); // 7
複製代碼
這段代碼有兩個函數,add
函數返回了兩個參數的和,curried
函數返回了調用add
函數後5
和接收的參數的和。這個curried
函數就是我們所說的一個大函數返回了一個小函數,可是它並非一個柯里化函數。閉包
柯里化函數一般動態建立:調用另外一個函數併爲它傳入要柯里化的函數和必要參數。 下面,寫一個柯里化函數的通用方式app
function curry(fn) {
var args = Array.prototype.slice.call(arguments, 1); // 獲取第一個參數以後的參數
return function() {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(null, finalArgs); // 執行傳入函數fn
}
}
function add(num1, num2) {
return num1 + num2;
}
console.log(curry(add, 5, 12)()); // 17
console.log(curry(add, 6, 7)()); // 13
複製代碼
柯里化函數也可應用於構造出bind()
函數中
/**
* @params
* fn: 要執行的函數
* context: 須要改變的this指向
*/
function bind(fn, context) {
context = context || window; // 若是沒傳context參數,就讓其爲window
var args = Array.prototype.slice.call(arguments, 2); // 獲取第二個參數以後的參數
return function() {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(context, finalArgs);
}
}
複製代碼
來看一下效果:
var obj = {
x: 1
}
function fn() {
return this.x + 2;
}
btn.onclick = function() {
console.log(bind(fn, obj)()) // 3
}
複製代碼
bind
函數成功將this
指向修改成了obj
。可是咱們爲了調用時和Function
原型上的bind
同樣,咱們將本身寫的bind
寫在原型裏
(function(proto) {
function bind(context) {
context = context || window; // 若是沒傳context參數,就讓其爲window
var args = Array.prototype.slice.call(arguments, 1); // 獲取第二個參數以後的參數
return function() {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(context, finalArgs);
}
}
proto.bind = bind;
})(Function.prototype)
複製代碼
再來看一下效果
var obj = {
x: 1
}
function fn(y) {
return this.x + y;
}
btn.onclick = function() {
console.log(fn.bind(obj, 3)()) // 4
}
複製代碼
以上,就是應用函數柯里化手寫實現的bind
。另外,有一些源碼也運用了函數柯里化,好比redux
等。
下面,咱們來看一道面試題:實現add
函數
add(1); //1
add(1)(2); //3
add(1)(2)(3); //6
add(1)(2,3); //6
add(1,2)(3); //6
add(1,2,3); //6
複製代碼
function currying(anonymous, length) {
return function add(...args) { // 返回add,接收參數...args
// 若是接收的參數長度大於函數參數的個數,則直接執行接收函數
if (args.length >= length) {
return anonymous(...args);
}
// 不然,遞歸調用currying,返回新的anonymous函數和新的length
return currying(anonymous.bind(null, ...args), length - args.length);
}
}
// count參數爲傳進參數的總個數,每次調用此函數時都須要修改
let add = currying(function anonymous(...args) {
return args.reduce((x, y) => x + y);
}, count);
複製代碼
咱們來看一下效果:
console.log(add(1)); // 修改count參數爲1, 輸出結果爲1
console.log(add(1)(2)); // 修改count參數爲2, 輸出結果爲3
console.log(add(1)(2)(3)); // 修改count參數爲3, 輸出結果爲6
console.log(add(1)(2,3)); // 修改count參數爲3, 輸出結果爲6
console.log(add(1,2)(3)); // 修改count參數爲3, 輸出結果爲6
console.log(add(1,2,3)); // 修改count參數爲3, 輸出結果爲6
console.log(add(5, 6, 7, 8)) // 修改count參數爲4, 輸出結果爲26
複製代碼
綜上,咱們完成了add
函數的編寫,一道題的解法不必定只有一種,這道題也是同樣,還有不少解法,歡迎你們在評論區討論~
在以前,咱們據說過數組扁平化,數組扁平化就是將多層次的數組變爲一層,那麼一樣的,調用函數扁平化就是將深層次的調用函數變爲一層,咱們來看一個例子:
var fn1 = function(x) {
return x + 5;
}
var fn2 = function(x) {
return x + 6;
}
var fn3 = function(x) {
return x + 7;
}
console.log(fn3(fn2(fn1(5)))); // 23
複製代碼
上面的例子中,將函數fn1
的返回結果傳給fn2
,再將fn2
的返回結果傳給fn3
,最終輸出fn3
的結果。這樣層層嵌套的調用,看起來不是那麼舒服,用起來也沒有那麼方便,因此咱們想要實現這樣的compose
函數:
compose(fn1, fn2, fn3)(5) // 等價於fn3(fn2(fn1(5)))
複製代碼
下面開始實現:
function compose() {
var funcs = Array.prototype.slice.call(arguments);
return function() {
var args = Array.prototype.slice.call(arguments);
var len = funcs.length;
if(len === 0){
return args[0];
}
if(len === 1) {
return funcs[0](...args)
}
return funcs.reduce(function(x, y) {
return typeof x === "function" ? y(x(...args)) : y(x)
})
}
}
複製代碼
來看一下效果:
console.log(compose(fn1, fn2, fn3)(5)); // 23
console.log(compose(fn1)(5)); // 10
console.log(compose()(5)); // 5
複製代碼
輸出的結果與咱們預期的結果是一致的,說明咱們封裝的compose
函數是正確的。可是compose
函數封裝的方式並非只有這一種,咱們來看一下redux
中的compose
函數
export default function compose(...funcs) {
if (funcs.length === 0) { // 當傳入的函數個數爲0時,直接返回參數
return arg => arg
}
if (funcs.length === 1) { // 當傳入函數個數爲1時,直接執行函數
return funcs[0]
}
// 當傳入函數個數不爲0和1時,按函數傳入從後向前的順序依次執行函數
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製代碼
這兩種方式均可以實現compose
函數,第一種方式看起來很好理解~第二種方式看起來更簡單一些,這也須要咱們認真的思考一下它的執行邏輯,加強本身的思惟能力,讓本身也能夠寫出更好的代碼~
高級編程技巧是開發中的必備,熟練掌握更是好處多多~以爲文章對你有幫助,能夠給本篇文章點個贊呀~若是文章有不正確的地方,還但願你們指出~咱們共同窗習,共同進步~
最後,分享一下個人公衆號「web前端日記」~你們能夠關注一波~