纯函数:
对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。
var arr = [1,2,3,4,5];//纯函数arr.slice(0,3); //=> [1,2,3]arr.slice(0,3); //=> [1,2,3]//非纯函数arr.splice(0,3); //=> [1,2,3]arr.splice(0,3); //=> [4,5]
定定义
函数式编程为何排斥不纯的函数:
非纯函数中,函数的行为需要由外部的系统环境决定。也就是说此函数行为不仅取决于输入的参数 age,还取决于一个外部的变量 timeOfLife。这种对于外部状态的依赖,是造成系统复杂性大大提高的主要原因。
var timeOfLife = 20;//纯函数function test(age){return age > 20;}//非纯函数function test(age){return age > timeOfLife;}
函数柯里化(Currying)
向函数传递一部分参数来调用它,让它返回一个函数去处理剩下的参数。
事实上柯里化是一种“预加载”函数的方法,通过传递较少的参数,得到一个已经记住了这些参数的新函数,某种意义上讲,这是一种对参数的“缓存”,是一种非常高效的编写函数的方法。
var timeOfLife = 20;function test(timeOfLife){return function(age){return age > timeOfLife;}}var testing = test(20);testing(18); // => false
函数组合
为避免写出不优雅的包菜式代码 h ( g ( f ( x ) ) ) ?我们需要用到函数组合。
我们定义的compose就像双面胶一样,可以把任何两个纯函数结合到一起,也可以扩展出组合N个函数的N面胶。
这种灵活的组合,让我们可以像拼积木一样优雅地组合函数式代码。
//两个函数的组合var compose = function(f, g){return function(x){return f(g(x));};};var mult = function(x){return x*5;}var add = function(x){return x+1;}compose(mult, add)(2); //=> 15
命令式代码:
通过编写一条又一条指令,让计算机执行一些动作,其中一般会涉及许多繁杂的细节。
声明式代码:
通过写表达式的方式,声明我们想干什么,而不是通过一步一步的指示。
声明式代码,是函数式编程的一个明显好处——编写、优化代码时能更专注。
//命令式var rest = [];var arr = [4,9,16,25,4,16];for(var i = 0; i < arr.length; i++){if(rest.indexOf(arr[i]) === -1){rest.push(arr[i]);}}//声明式var rest = arr.map(Math.sqrt);
函数式编程的一个明显的好处就是这种声明式的代码,对于无副作用的纯函数,我们完全可以不考虑函数内部是如何实现的,专注于编写业务代码。优化代码时,目光只需要集中在这些稳定坚固的函数内部即可。
相反,不纯的不函数式的代码会产生副作用或者依赖外部系统环境,使用它们的时候总是要考虑这些副作用。
总结:
- 函数对于外部状态的依赖,是造成系统复杂性大大提高的主要原因。
- 代码书写中让函数尽可能地纯净。
- 函数式编程不是万能的,它与OOP 一样,只是一种编程范式。
- 为降低软件复杂度,OOP 的方式是靠良好的封装、继承、多态以及接口定义。函数式编程则是靠纯函数以及它们的组合、柯里化等技术。
underscore整体结构:
通过对下面文章的学习可以对underscore有一个初步的学习和认识。
https://www.html.cn/archives/4728
- underscore结构
面向对象风格支持
虽然underscore推崇函数式编程,但也支持面向对象风格的函数调用,仅需要通过_()来包裹对象。
mixin
mixin(混入)模式也是增加代码复用度的一个广泛使用的设计模式。
链接式调用
var data = {name: 'Max',age: 21,change: function(){}};// var d1 = _.unique([21, 12, 10, 21, 'a', 'A'], function(value) {// return typeof value === 'string' ? value.toLowerCase() : value;// });// console.log(d1);var d2 = _([1,2,1,2,3,6,5,'A',6,'a']).chain().unique().map().value();console.log(d2);
(function(root) {var push = Array.prototype.push;var _ = function(obj) {if(obj instanceof _) {return obj;}// 第一次调用时,this指向window,所以会执行new _(obj)// 第二次调用时,this指向当前underscore对象,所以会跳过这一步执行if(!(this instanceof _)) {return new _(obj);}// 将对象引用封装在_wrapped属性中this._wrapped = obj;};_.unique = function(arr, callback) {var ret = [];var i = 0, target;for(; i < arr.length; i++) {target = callback ? callback(arr[i]) : arr[i];if(ret.indexOf(target) === -1) {// push arr[i]比target 更好ret.push(arr[i]);}}return ret;}//开启链式调用_.chain = function(obj){var instance = _(obj);instance._chain = true;return instance;};//辅助函数var result = function(instance, obj) {return instance._chain ? _(obj).chain() : obj;};_.prototype.value = function() {return this._wrapped;}_.functions = function (obj) {var result = [];var key;for(key in obj) {result.push(key);}return result;};_.map = function(args){args.push('David');return args;};//类型检测_.isArray = function(obj){return toString.call(obj) === '[object Array]';};_.each = function(target, callback){var key,i=0;if(_.isArray(target)) {var length = target.length;for(; i < length; i++) {callback.call(target, target[i], i);}}else {for(key in target) {callback.call(target, key, target[key]);}}};_.mixin = function(obj) {_.each(_.functions(obj), function(name) {var func = obj[name];_.prototype[name] = function() {var args = [this._wrapped];push.apply(args, arguments);return result(this, func.apply(this, args));};});}_.mixin(_);root._ = _;})(this);
undefined 的处理和 iteratee
var obj = {name: 'Max', age: 21};console.log(_.map([1, 2, 3], function(value, index, object){return value * 3;}, obj));
//默认迭代器_.identity = function(value) {return value;}function cb(iteratee, context) {if(!iteratee) {return _.identity;}if(_.isFunction(iteratee)) {console.log(2222);return optimizeCb(iteratee, context);}}//optimizeCb 优化迭代器var optimizeCb = function(func, context, count) {if(context == void 0) {return func;}// count 为 undefined,则默认值为3switch(count == null ? 3 : count) {case 1:return function(value) {return func.call(context, value);};case 3:return function(value, index, obj) {return func.call(context, value, index, obj);};}}_.map = function(obj, iteratee, context){//生成不同功能的迭代器var iteratee = cb(iteratee, context);// 区分数组和对象var keys = !_.isArray(obj) && Object.keys(obj);var length = (keys || obj).length;var i = 0;var result = Array(length);for(i; i< length;i++) {var currentKey = keys ? keys[i] : i;result[i] = iteratee(obj[currentKey], i, obj);}return result;};
rest参数:
function test(count1, count2, rest){console.log(count1);console.log(count2);console.log(rest);}var myTest = _.restAuguments(test);myTest(1, 2, 3, 4, 5, 6, 7);
_.restAuguments = function(func) {// rest参数位置var startIndex = func.length - 1;return function() {var index = 0;var length = arguments.length - startIndex;var rest = Array(length);for(; index < length; index++) {rest[index] = arguments[index + startIndex];}var args = Array(func.length);for(index=0; index < startIndex; index++) {args[index] = arguments[index];}args[startIndex] = rest;return func.apply(this, args);};}
Object.create **polyfill:**
polyfill(聚酯填充物),这里可以理解为Object.create 的补丁
// Object.create polyfillvar baseCreate = function(prototype){if(!_.isObject(prototype)) return {};if(Object.create) {return Object.create(prototype);}Ctor.prototype = prototype;var res = new Ctor;Ctor.prototype = null;return res;};
reduce和真值检测函数:
// reducevar createReduce = function(dir){var reduce = function(obj, iteratee, memo, init) {// 判断obj为数组或对象var keys = !_.isArray(obj) && Object.keys(obj);var length = (keys || obj).length;var index = dir > 0 ? 0 : length-1;// 如果没传memo的默认值,则从第一个元素开始if(!init) {memo = obj[keys ? keys[index] : index];index += dir;}// dir为1,表示从头到尾;尾-1则表示从尾到头for(; index >= 0 && index < length; index += dir) {var currentKey = keys ? keys[index] : index;memo = iteratee(memo, obj[currentKey], currentKey, obj);}return memo;};//返回函数return function(obj, iteratee, memo, context) {var init = arguments.length >= 3;return reduce(obj, optimizeCb(iteratee, context, 4), memo, init);};};_.reduce = createReduce(1); // 1 or -1,1表示从头开始,-1表示从尾开始_.filter = _.select = function(obj, predict, context){var results = [];predict = cb(predict, context);_.each(obj, function(value, index){if(predict(value, index)) {results.push(value);}});return results;};
index和lastIndexOf:
_.sortedIndex = function(array, obj, iteratee, context) {// 如果iteratee是null, 则cb返回 function(value){ return value;}iteratee = cb(iteratee, context, 1);value = iteratee(obj);var low = 0;var high = array.length-1; // 长度减一while(low < high) {var mid = Math.floor((low+high)/2);if(array[mid] < value) {low = mid + 1;}else {high = mid;}}// 返回low或high都可return low;}// 1:正向查找,-1:反向查找function createPredicateIndexFinder(dir) {return function(array, predicate, context) {predicate = cb(predicate, context);var length = array.length;var index = dir > 0 ? 0 : length-1;for(; index>=0 && index < length; index+=dir) {if(predicate(array[index], index, array)) {return index;}}return -1;};}// 1为正向查找,-1为反向查找_.findIndex = createPredicateIndexFinder(1);_.findLastIndex = createPredicateIndexFinder(-1);function createIndexFinder(dir, predicateFind, sortedIndex) {// 调用形式:array, item, idx,idx表示是否已经为有序return function(array, item, idx){var i = 0;var length = array.length;//如果已经为正序,则调用折半查找if(idx && _.isBoolean(idx) && length) {idx = sortedIndex(array, item);return array[idx] === item ? idx : -1;}// 特殊情况,要查找的元素是NaNif(item !== item) {idx = predicateFind(slice.call(array, 0, length), _.isNaN);return idx >=0 ? idx : -1;}//正常遍历for(idx = (dir > 0 ? i : length-1); idx >= 0 && idx < length; idx +=dir) {if(array[idx] === item) {return idx;}}return -1;};}_.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);_.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
洗牌算法:
console.log(_.sample([1,2,3,4,5,6,7,8,9,10],5));console.log(_.shuffle([1,2,3,4,5,6,7,8,9,10]));
_.clone = function(obj) {return _.isArray(obj) ? obj.slice() : _.extend({}, obj);};//随机数函数_.random = function(min, max) {// 如果最大值不传,则取0到最小值之间的数if(max == null) {max = min;min = 0;}// Math.random()为取0到1之间的随机数,可以等于0,但是一定小于1// 返回的随机数可能等于最大值或最小值return min + Math.floor((Math.random() * (max - min + 1)));};_.shuffle = function(array) {return _.sample(array, Infinity);};_.sample = function(array, n) {// 判断是否是Arrayif(!_.isArray(array)) {return array;}//判断n的合法性if(n == null) {return array[_.random(array.length-1)];}var length = array.length;var tempArray = _.clone(array);var last = array.length -1;// 获取n和length的最小值,为什么不这样:n = n>length ? length : n;n = Math.max(Math.min(n, length),0);for(var index = 0; index < n; index++) {var ran = _.random(index, last);var temp = tempArray[index];tempArray[index] = tempArray[ran];tempArray[ran] = temp;}return tempArray.slice(0, n);};
非纯函数中,函
数的行为需要由外部的系统环境决定。也就是说此函数行为不仅取决于输入的参
数
age,还取决于一个外部的变量 timeOfLife。
::
对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。
义:
对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。
