力扣 30 天 JavaScript 挑战 第36天 第8题笔记 深入了解reduce,this
开始做题
版本一:
/*** @param {Function[]} functions* @return {Function}*/
var compose = function(functions) {return function(x) {let result = x;for(let i=functions.length-1;i>=0;i--){console.log(x);x = functions[i](x);console.log(functions[i]);console.log(x);console.log('--------')}return result;}
};const fn = compose([x => x + 1, x => 2 * x])console.log(fn(4))
控制台输出
我也不知道为什么错 问了ai 知道哪里错了 我犯的错太愚蠢了 在for循环里面是对x进行更改 返回的却是result
版本二
/*** @param {Function[]} functions* @return {Function}*/
var compose = function(functions) {return function(x) {let result = x;for(let i=functions.length-1;i>=0;i--){result = functions[i](result);}return result;}
};
这次对了
不理解的地方
- 为什么不能输出具体的函数只能输出[Function (anonymous)]
解答:
在js中函数也是一个对象,console.log会输出函数对象,在加上这个函数是匿名函数,所以会输出[Function (anonymous)],如果要是命名函数就会输出[Function: add1]之类的。
如果要是想输出函数的具体实现,比如x=>x*2之类的,要这样输出 console.log(functions[i].toString());加上toString()
学习题解里面的知识点
看题解的时候,发现自己忽略了一种情况就是,当 @param {Function[]} functions为空数组的时候,应该返回原来的x,我写代码的时候,没有考虑这种情况,但是代码却是可以解决这种情况。当 @param {Function[]} functions为空数组的时候,i=functions.length-1 为-1,不符合i>=0,会直接返回本来的x。
方法 1:使用迭代的函数组合
就是我解决这道题的方法。官方的写法明显更优雅,而且使用了两种遍历的方法。
var compose = function (functions) {return function (x) {if (functions.length === 0) return x;let input = x;for (let i = functions.length - 1; i >= 0; i--) {const currFunc = functions[i];input = currFunc(input);}return input;};
};
var compose = function (functions) {return function (x) {if (functions.length === 0) return x;let input = x;for (const func of functions.reverse()) {input = func(input);}return input;};
};
方法二:使用js数组自带方法Array.reduceRight
/*** @param {Function[]} functions* @return {Function}*/
var compose = function(functions) {return function(x) {return functions.reduceRight((taget,fn)=>fn(taget),x) }
};
之前的题目中对reduce有了更详细的了解,reduceRight跟rudece的使用语法相同,唯一的区别是reduce从左到右累加,reduceRight从右到左累加。reduceRight接受两个参数,一个是函数callbackFn,另一个是初始值initialValue。
函数callbackFn接受四个参数
- accumulator
上一次调用 callbackFn 的结果。在第一次调用时,如果指定了 initialValue 则为指定的值,否则为数组最后一个元素的值。 - currentValue
数组中当前正在处理的元素。 - index
正在处理的元素在数组中的索引。 - array
调用了 reduceRight() 的数组本身。
上面题目的解法中函数callbackFn就是(taget,fn)=>fn(taget)。其中taget的就是accumulator即累加值,它的初始值为 initialValue。fn就是函数数组中正在被遍历的函数。这个解法中callbackFn没有index,array参数。
方法三:调用第三方库
import { flowRight } from 'lodash';const composedFn = flowRight(...functions);
额外关于this的思考 其实这里我还是不懂
var compose = function(functions) {return function(x) {let result = x;for (let i = functions.length - 1; i >= 0; i--) {result = functions[i](result);}return result;};
};
这里 functionsi 是直接调用函数,所以 调用方式是普通函数调用,那么 this 就会变成 undefined(严格模式) 或 全局对象(非严格模式下的 window/global)。
如果这些函数只是单纯的 x => x + 1 这种纯函数,没问题。
但如果这些函数是对象的方法,依赖 this,就会出错。
下面的函数事对象的方法,依赖 this
const obj = {value: 1,increment: function() { this.value++; return this.value; },double: function() { this.value *= 2; return this.value; },
};
这里 increment 和 double 需要通过 this.value 访问对象里的值。
如果直接 compose:
const badComposedFn = compose([obj.increment, obj.double]);
console.log(badComposedFn(1));
执行时 this 已经丢失了(不再指向 obj),所以 this.value 是 undefined,结果就会得到 NaN。
要想在组合时保留原本的 this,就不能直接调用函数,而是要用 .call 或 .apply 来 显式指定调用时的上下文。
const goodCompose = function(functions, context) {return function(x) {let result = x;for (let i = functions.length - 1; i >= 0; i--) {result = functions[i].call(context, result); // 显式绑定 this}return result;};
};
然后使用时传入 obj:
const goodComposedFn = goodCompose([obj.increment, obj.double], obj);
console.log(goodComposedFn(1)); // ✅ 正确输出
这时 this 永远指向 obj,所以函数里访问 this.value 没问题。