做为一名有追求的前端攻城狮,掌握几个高级编程技巧是必须的~学会这些技巧,会让咱们的开发工做事半功倍,让咱们在工做中更加游刃有余,本篇文章将介绍三个小技巧:前端
惰性载入函数的应用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前端日记」~你们能够关注一波~