编写高质量代码:改善JavaScript程序建议--函数式编程
2016-10-16 21:19
661 查看
函数式编程已经在实际应用中经发挥了巨大作用,更有越来越多的语言不断地加入对诸如闭包、匿名函数等的支持,从某种程度上来讲,函数式编程正在逐步同化命令式编程。
(1)封闭性,外界无法访问闭包内部的数据,如果在闭包内声明变量,外界是无法访问的,除非闭包主动向外界提供访问接口。
(2)持久性,对于一般函数来说,在调用完毕后,系统自动注销函数,而对于闭包来说,在外部函数调用之后,闭包结构依然保存在系统中,闭包中的数据依然存在,从而实现对数据的持久使用。
示例:使用闭包结构能够跟踪动态环境中数据的实时变化,并即时存储
示例:闭包不会因为外部函数环境的注销而消失,会始终存在
调用函数,执行该函数把返回“值”传递给变量,而不是函数的入口指针(地址)。
示例:函数引用
示例:函数调用
示例:常规方式
示例:闭包方式
示例:惰性方式,无需每次都求值
示例:自定义bind
示例:ES5中bind
示例:函数套用
柯里化是利用已有的函数,再创建一个动态的函数,该动态函数内部还是通过已有的函数来发生作用。
示例:柯里化
由于setTimeout性能很差,下述提供一种灵活性方式,但有一定的局限性。
区别于传统节流函数,该简易节流函数调用时立即被执行,且需传递当前毫秒值!
建议1:禁用Function构造函数
使用Function构造函数创建的函数具有顶级作用域。var n = 1; function f(){ var n = 2; var e = new Function("return n;"); return e; } console.log(f()()); // 1
建议2:推荐动态调用函数
使用call和apply方法可以把一个函数转换为方法传递给某个对象。这种行为只是临时的,函数最终并没有作为对象的方法而存在,当函数被调用后,该对象方法会自动被注销。var a = [1, 5, 3]; var m = Math.max.apply(null, a); console.log(m); // 5 m.max(); // Uncaught TypeError: m.max is not a function(…)
建议3:使用闭包跨作用域开发
闭包结构的两个特性:(1)封闭性,外界无法访问闭包内部的数据,如果在闭包内声明变量,外界是无法访问的,除非闭包主动向外界提供访问接口。
(2)持久性,对于一般函数来说,在调用完毕后,系统自动注销函数,而对于闭包来说,在外部函数调用之后,闭包结构依然保存在系统中,闭包中的数据依然存在,从而实现对数据的持久使用。
示例:使用闭包结构能够跟踪动态环境中数据的实时变化,并即时存储
function f(x){ var a = x; var innerFun = function(){ return a; }; a++; return innerFun; } var fn = f(5); console.log(fn()); // 6
示例:闭包不会因为外部函数环境的注销而消失,会始终存在
<!-- 首先需要执行doFn(),形成三个闭包 --> <button onclick="doFn();">doFn()</button> <button onclick="m1();">m1()</button> <button onclick="m2();">m2()</button> <button onclick="m3(100);">m3()</button> var m1, m2, m3; function doFn(){ var t = 1; m1 = function(){ console.log(t); }; m2 = function(){ t++; }; m3 = function(x){ t = x; }; }
建议4:比较函数调用和引用本质
引用函数,多个变量存储的是函数的相同入口指针(地址)。调用函数,执行该函数把返回“值”传递给变量,而不是函数的入口指针(地址)。
示例:函数引用
function f(){ var x = 5; return x; } var f1 = f; var f2 = f; console.log(f1 === f2); // true
示例:函数调用
function f(){ var x = 5; return function(){ // 返回存储在不同变量中,它们的地址指针是完全不同的 return x; } } var f1 = f(); // f1 = function(){ return x; } var f2 = f(); // f2 = function(){ return x; } console.log(f1 === f2); // false
建议5:惰性函数求值
惰性函数模式是一种对函数或请求的处理延迟到真正需要结果时进行的通用概念示例:常规方式
var t; function f(){ t = t ? t : new Date(); return t; } f();
示例:闭包方式
var f = (function(){ var t; return function(){ // 每次调用仍需要求值 t = t ? t : new Date(); return t; }; })(); f();
示例:惰性方式,无需每次都求值
var f = function(){ var t = new Date(); f = function(){ // 再次调用无需求值 return t; }; return f(); }; f();
建议5:惰性载入函数
惰性载入通常解决兼容性问题。要执行的适当代码在实际调用函数时才执行;除第一次调用外,后续调用无需执行判断分支。function fn(index){ switch (index){ case 1: fn = function(){ console.log("Hello"); }; break; case 2: fn = function(){ console.log("안녕하세요"); }; break; default: fn = function(){ console.log("你好"); } } return fn(); } fn(2); // 안녕하세요
建议6:函数绑定
开发中使用回调函数和处理程序经常会遇到this绑定问题。函数绑定可以提供一个可选的执行上下文传递给函数。示例:自定义bind
function bind(fn, context){ return function(){ return fn.apply(context, arguments); } } var handler = { message: "Hello", handlerClick: function(event){ console.log(this.message); } }; document.getElementById("btn").addEventListener("click", bind(handler.handlerClick, handler));
示例:ES5中bind
var handler = { message: "Hello", handlerClick: function(event, currentDom){ console.log(this.message); /** * 注意使用event.currentTarget代替this,有可能会有问题 * 触发a和li会有区别!!! * <li><a></a></li> */ console.log(event.currentTarget); console.log(currentDom === event.currentTarget); } }; // this指向handler,事件处理函数的原this无法传递 document.getElementById("btn").addEventListener("click", handler.handlerClick.bind(handler)); // this指向handler,事件处理函数的原this传递给回调函数 document.getElementById("btn").addEventListener("click", function(event){ handler.handlerClick(event, this); });
建议7:函数套用和柯里化
套用指的是将函数与传递给它的参数相结合,产生一个新的函数。示例:函数套用
// 通过Function扩展函数 Function.prototype.method = function(name, fn){ if(!this.prototype[name]){ this.prototype[name] = fn; return this; } }; Function.method("curry", function(){ var slice = Array.prototype.slice, fn = this, arys = slice.call(arguments); return function(){ return fn.apply(null, arys.concat(slice.call(arguments))); }; }); var add = function(){ var sum = 0; for(var i = 0, len = arguments.length; i < len; i++){ sum += arguments[i]; } return sum; }; var f = add.curry(2); console.log(f(3)); // 5
柯里化是利用已有的函数,再创建一个动态的函数,该动态函数内部还是通过已有的函数来发生作用。
示例:柯里化
function curry(fn){ var slice = Array.prototype.slice, args = slice.call(arguments, 1); return function(){ return fn.apply(null, args.concat(slice.call(arguments))); }; } var add = function(){ var sum = 0; for(var i = 0, len = arguments.length; i < len; i++){ sum += arguments[i]; } return sum; }; var f = curry(add, 2); console.log(f(3)); // 5
建议8:重视函数节流
节流函数的设计思想就是让某些代码可以在间断情况下连续重复执行,实现的方法是使用定时器对函数进行节流。对于resize、mousemove、mouseover、mouseout等事件尤为重要!!
function throttle(method, context){ clearTimeout(method.tId); method.tId = setTimeout(function(){ method.call(context); }, 1000); } function reszie(){ console.log(1); } window.onresize = function(){ throttle(reszie); // 注意,必须是具名函数 };
由于setTimeout性能很差,下述提供一种灵活性方式,但有一定的局限性。
/** * 节流函数(简易) * @param now 当前毫秒值 * @param method 方法 * @param context 上下文 */ function throttle(now, method, context){ var time = +new Date(); throttle = function(now, method, context){ if(now - time > 1000){ time = now; // 更新time method.call(context); } }; throttle(now, method, context); } function reszie(){ console.log(1); } window.onresize = function(){ throttle(+new Date(), reszie, null); }
区别于传统节流函数,该简易节流函数调用时立即被执行,且需传递当前毫秒值!
相关文章推荐
- 编写高质量代码:改善JavaScript程序建议--面向对象编程
- 编写高质量代码:改善JavaScript程序的188个建议
- 编写高质量代码:改善JavaScript程序的188个建议
- 编写高质量代码:改善JavaScript程序的188个建议
- 编写高质量代码:改善JavaScript程序建议--面向对象编程
- 《编写高质量代码改善JavaScript程序的188个建议》读书笔记
- [已读]编写高质量代码 改善JavaScript程序的188个建议
- 编写高质量代码改善java程序的151个建议——导航开篇
- 《编写高质量代码 : 改善C#程序的157个建议》读书笔记 1-10
- 编写高质量代码改善C#程序的157个建议[4-9]
- 编写高质量代码:改善Java程序的151个建议 勘误 [不断更新]
- 编写高质量代码改善C#程序的157个建议[4-9]
- 编写高质量代码改善Java程序的建议
- 编写高质量代码改善C#程序的157个建议——建议18:foreach不能代替for
- 编写高质量代码:改善Java程序的151个建议(第3章:类、对象及方法___建议41~46)
- 编写高质量代码改善C#程序的157个建议——建议33:避免在泛型类型中声明静态成员
- 编写高质量代码改善C#程序的157个建议——建议47:即使提供了显式释放方法,也应该在终结器中提供隐式清理
- 编写高质量代码改善C#程序的157个建议——建议66:正确捕获多线程中的异常
- 编写高质量代码改善C#程序的157个建议——建议72:在线程同步中使用信号量