纯函数:
对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。
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,则默认值为3
switch(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 polyfill
var 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和真值检测函数:
// reduce
var 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;
}
// 特殊情况,要查找的元素是NaN
if(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) {
// 判断是否是Array
if(!_.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。
::
对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。
义:
对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。