【问题标题】:Avoid targeting the object scope of this inside a function in ES6避免在 ES6 中的函数内定位 this 的对象范围
【发布时间】:2018-10-23 11:53:07
【问题描述】:

例如,我正在使用 D3.js 运行一个项目,导入特定模块并调用它们的功能。

设置:

  • TypeScript/ES6
  • 导入特定的 D3 组件
  • 角度 6

我有一个对象,在这种情况下是一个角度指令,并在 SVG 画布上绘制了一些圆圈,并希望它们在拖动事件上触发一个函数。

缩码sn-p: 请看一下这个sn-p底部的drawPoints()

import { ElementRef, HostListener, Output, EventEmitter, OnInit, Input, OnChanges, SimpleChanges, Directive } from '@angular/core';
import * as Selection from 'd3-selection';
import * as Shape from 'd3-shape';
import * as Random from 'd3-random';
import * as Drag from 'd3-drag';

import { Config } from './../models/config.model';
import { Point } from './../models/point.model';
import { Param } from './../models/param.model';

@Directive({
  selector: '[appCanvas]'
})
export class CanvasDirective implements OnInit, OnChanges {
  private canvas: any;
  private defs: any;
  private gradient: any;
  private svg: any;
  private expandedPoints: Point[];
  private drag: Point;
  public config: Config;

  @Input()
  private param: Param;

  @Output()
  private emitConfig: EventEmitter<Config>;

  @HostListener('window:resize', ['$event'])
  private onResize(event) {
    this.init();
  }

  constructor(
    private el: ElementRef
  ) {
    this.canvas = el.nativeElement;
    this.emitConfig = new EventEmitter();
  }

  ngOnInit() {
    intSvg();
    // ..
  }

  private initSvg() {
    if (!this.svg) {
      this.svg = Selection.select(this.canvas).append('svg');
    }
    this.svg
      .attr('width', this.config.width)
      .attr('height', this.config.height);
  }

  private drawPoints(points: Point[]) {
    points.forEach(point => {
      this.svg.append('circle')
        .attr('r', point.color ? 20 : 10)
        .attr('cx', point.x)
        .attr('cy', point.y)
        .attr('fill', point.color ? point.color : 'lightgray')
        .attr('stroke-width', !point.color ? 2 : 0)
        .attr('stroke', !point.color ? 'gray' : '')
        .call(Drag.drag()
          .on('drag', () => {
            // What to call here?
            // Selection.select(this) will not work
            // So how to target the correct „this“?
          }));
    });
  }
  // ...
}

出现的问题是无法在附加圆圈的拖动功能内到达正确的this

有多个示例,但它们在类中不起作用,因为 this 参数受到保护。

感谢 Mike Bostock 的示例https://bl.ocks.org/mbostock/22994cc97fefaeede0d861e6815a847e

【问题讨论】:

    标签: javascript typescript d3.js ecmascript-6 arrow-functions


    【解决方案1】:

    像 D3 这样的旧库依赖于动态 this 上下文,而不是将所有必要的数据作为参数传递,并且需要使用 const self = this 技巧在回调中达到词法 this。这个技巧在 ES6 中被认为是过时的,但在这种情况下是必要的。为了获得动态上下文,需要使用常规函数而不是箭头:

    private drawPoints(points: Point[]) {
      const self = this;
      ...
        .call(Drag.drag()
          .on('drag', function (this: ProperContextTypeIfNecessary) {
            Selection.select(this);
            // class instance can be referred as `self`
          }));
    });
    

    在一个地方应该将类实例称为 this 而在另一个地方引用 self 似乎不一致(这在 ES5 中不是问题,因为应该在这种情况下彻底使用 self一致性)。

    正如this related answer 中所解释的,另一种选择是包装函数,它将提供回调函数 D3 上下文作为参数,而this 可能仍然引用类实例。在这种情况下可以使用箭头函数:

    function contextWrapper(fn) {
        const self = this;
    
        return function (...args) {
            return fn.call(self, this, ...args);
        }
    }
    
    ...
    
    private drawPoints(points: Point[]) {
      const self = this;
      ...
        .call(Drag.drag()
          .on('drag', contextWrapper((d3Context: ProperContextTypeIfNecessary) => {
            Selection.select(d3Context);
            // class instance can be referred as `this`
          }));
    });
    

    【讨论】:

      【解决方案2】:

      通过避免 ES6 组件中流行的箭头函数语法并改用旧的 function() {} 语法,问题得到了解决。箭头函数改变了游戏规则,因为在这些被调用函数中,this 并没有被简化为函数的被调用上下文,而是上下文全局扩展到类级别。

      解决方案:

      private drawPoints(points: Point[]) {
        points.forEach(point => {
          this.svg.append('circle')
            .attr('r', point.color ? 20 : 10)
            .attr('cx', point.x)
            .attr('cy', point.y)
            .attr('fill', point.color ? point.color : 'lightgray')
            .attr('stroke-width', !point.color ? 2 : 0)
            .attr('stroke', !point.color ? 'gray' : '')
            .call(Drag.drag()
              .on('drag', function() {
                console.log(this);
              }));
        });
      }
      

      延伸阅读:

      [...] 和 ES2015 [ES6] 引入了不提供自己的 this 绑定的箭头函数(它保留了封闭词法上下文的 this 值)。 rwaldron, abasao, martian2049 et. al, this, MDN web docs

      【讨论】:

        猜你喜欢
        • 2014-02-25
        • 2021-02-01
        • 2019-11-13
        • 2016-01-08
        • 2018-04-07
        • 2018-10-22
        • 2016-12-22
        • 1970-01-01
        相关资源
        最近更新 更多