力扣 30 天 JavaScript 挑战 第36天 第8题笔记 深入了解reduce,this

开始做题

版本一:

js 复制代码
/**
 * @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

版本二

js 复制代码
/**
 * @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;
    }
};

这次对了

不理解的地方

  1. 为什么不能输出具体的函数只能输出[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:使用迭代的函数组合

就是我解决这道题的方法。官方的写法明显更优雅,而且使用了两种遍历的方法。

js 复制代码
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;
  };
};
js 复制代码
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

js 复制代码
/**
 * @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接受四个参数

  1. accumulator
    上一次调用 callbackFn 的结果。在第一次调用时,如果指定了 initialValue 则为指定的值,否则为数组最后一个元素的值。
  2. currentValue
    数组中当前正在处理的元素。
  3. index
    正在处理的元素在数组中的索引。
  4. array
    调用了 reduceRight() 的数组本身。
    上面题目的解法中函数callbackFn就是(taget,fn)=>fn(taget)。其中taget的就是accumulator即累加值,它的初始值为 initialValue。fn就是函数数组中正在被遍历的函数。这个解法中callbackFn没有index,array参数。

方法三:调用第三方库

js 复制代码
import { flowRight } from 'lodash';

const composedFn = flowRight(...functions);

额外关于this的思考 其实这里我还是不懂

js 复制代码
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

js 复制代码
const obj = {
  value: 1,
  increment: function() { this.value++; return this.value; },
  double: function() { this.value *= 2; return this.value; },
};

这里 increment 和 double 需要通过 this.value 访问对象里的值。

如果直接 compose:

js 复制代码
const badComposedFn = compose([obj.increment, obj.double]);
console.log(badComposedFn(1)); 

执行时 this 已经丢失了(不再指向 obj),所以 this.value 是 undefined,结果就会得到 NaN。

要想在组合时保留原本的 this,就不能直接调用函数,而是要用 .call 或 .apply 来 显式指定调用时的上下文。

js 复制代码
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:

js 复制代码
const goodComposedFn = goodCompose([obj.increment, obj.double], obj);
console.log(goodComposedFn(1));  // ✅ 正确输出

这时 this 永远指向 obj,所以函数里访问 this.value 没问题。