写在前面
作为一名有追求的前端攻城狮,掌握几个高级编程技巧是必须的~学会这些技巧,会让我们的开发工作事半功倍,让我们在工作中更加游刃有余,本篇文章将介绍三个小技巧:
惰性载入函数的应用
函数柯里化的应用
compose调用函数扁平化
惰性载入函数
在我们的代码中,一定包含了大量的if
语句,这些if
语句的执行要花费一些时间。有一种情况是这样的,我们第一次进入到if
分支中,执行了这部分代码,然后第二次同样执行进入了同一分支,所以会再次执行此部分代码。这样的情况下,代码执行肯定会慢一些,那么如果我们只让代码有一个if
分支,代码就会执行的快一些。如果让代码执行的更快一些,这就是惰性载入函数的应用,接下来,看一个DOM
事件绑定的例子:
function emit(element, type, func) {
if(element.addEventListener) {
element.addEventListener(type, func, false)
} else if(element.attachEvent) { // IE6、7、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高级程序设计》中,这样解释函数柯里化:与函数绑定紧密相关的主题是函数柯里化,它用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。 我们先来看一个例子:
function add(num1, num2) {
return num1 + num2;
}
function curried(num2) {
return add(5, num2);
}
console.log(curried(2)); // 7
这段代码有两个函数,add
函数返回了两个参数的和,curried
函数返回了调用add
函数后5
和接收的参数的和。这个curried
函数就是咱们所说的一个大函数返回了一个小函数,但是它并不是一个柯里化函数。
柯里化函数通常动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。 下面,写一个柯里化函数的通用方式
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
函数的编写,一道题的解法不一定只有一种,这道题也是一样,还有很多解法,欢迎大家在评论区讨论~
compose调用函数扁平化
在之前,我们听说过数组扁平化,数组扁平化就是将多层次的数组变为一层,那么同样的,调用函数扁平化就是将深层次的调用函数变为一层,我们来看一个例子:
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前端日记」~大家可以关注一波~
