javascript - detta inuti prototypsfunktion är lika med window istället för objektinstans


I följande kod i HeadDirective.prototype.link är this lika med det globala window-objektet snarare än HeadDirective-instansen. Min förståelse är att värdet av this inuti en prototype-funktion är det innehållande objektet självt.

var HeadDirective = (function () {
    function HeadDirective($rootScope, $compile) {
        this.$rootScope = $rootScope;
        this.$compile = $compile;
        this.restrict = 'E';
    }
    HeadDirective.prototype.link = function (scope, elem) {
        var html = '<link rel="stylesheet" ng-repeat="cssUrl in routeStyles" ng-href="{{cssUrl}}" />';
        elem.append(this.$compile(html)(scope));
        scope.routeStyles = [];
        this.$rootScope.$on('$routeChangeStart', function (e, next, current) {
            if (next && next.$$route && next.$$route.css) {
                if (!Array.isArray(next.$$route.css)) {
                    next.$$route.css = [next.$$route.css];
                }
                angular.forEach(next.$$route.css, function (sheet) {
                    scope.routeStyles.push(sheet);
                });
            }
        });
        this.$rootScope.$on('$routeChangeSuccess', function (e, next, current) {
            if (current && current.$$route && current.$$route.css) {
                if (!Array.isArray(current.$$route.css)) {
                    current.$$route.css = [current.$$route.css];
                }
                angular.forEach(current.$$route.css, function (sheet) {
                    scope.routeStyles.splice(scope.routeStyles.indexOf(sheet), 1);
                });
            }
        });
    };
    return HeadDirective;
})();

directives.directive('head', [
    '$rootScope', '$compile', function ($rootScope, $compile) {
        return new HeadDirective($rootScope, $compile);
    }]);

Koden ovan genererades från följande TypeScript:

class HeadDirective implements ng.IDirective {

    constructor(private $rootScope: ng.IRootScopeService, private $compile: ng.ICompileService) {}

    link(scope: IScope, elem: JQuery): void {
        var html = '<link rel="stylesheet" ng-repeat="cssUrl in routeStyles" ng-href="{{cssUrl}}" />';
        elem.append(this.$compile(html)(scope));
        scope.routeStyles = [];
        this.$rootScope.$on('$routeChangeStart', (e: ng.IAngularEvent, next?: IRoute, current?: IRoute): any => {
            if(next && next.$$route && next.$$route.css){
                if(!Array.isArray(next.$$route.css)){
                    next.$$route.css = [next.$$route.css];
                }
                angular.forEach(next.$$route.css, (sheet: string) => {
                    scope.routeStyles.push(sheet);
                });
            }
        });
        this.$rootScope.$on('$routeChangeSuccess', (e: ng.IAngularEvent, next?: IRoute, current?: IRoute): any => {
            if(current && current.$$route && current.$$route.css){
                if(!Array.isArray(current.$$route.css)){
                    current.$$route.css = [current.$$route.css];
                }
                angular.forEach(current.$$route.css, (sheet) => {
                    scope.routeStyles.splice(scope.routeStyles.indexOf(sheet), 1);
                });
            }
        });
    }

    restrict = 'E';
}

directives.directive('head', ['$rootScope','$compile', ($rootScope: ng.IRootScopeService, $compile: ng.ICompileService): ng.IDirective =>{
    return new HeadDirective($rootScope, $compile);
}]);

Enligt den senaste TypeScript-språkspecifikationen:

Typen av this i ett uttryck beror på den plats där referensen sker:

  • I en konstruktor, instansmedlemsfunktion, instansmedlemsåtkomstmetod eller initiator för instansmedlemsvariabel är this av klassen instanstyp för den innehållande klassen.
  • I en statisk medlemsfunktion eller statisk medlemsåtkomst är typen av this konstruktionsfunktionstypen för den innehållande klassen.
  • I en funktionsdeklaration eller ett standard funktionsuttryck är this av typen Any.
  • I den globala modulen är this av typen Any.

I alla andra sammanhang är det ett kompileringstidfel att referera till detta.

TypeScript-språkspecifikationen är ganska tydlig. Inuti en medlemsfunktion (som kompileras till en prototypfunktion) refererar this till klassinstansen. Detta är uppenbarligen inte vad jag ser.

Några idéer? Kan Browserify störa this?

Svar #1

Nyckelordet this är mycket kontextuellt. Om en metod anropas av en händelse kommer this att vara objektet som är händelsens mål, till exempel.

Du kan kringgå detta problem genom att skjuta this in i en variabel, eller genom att använda JavaScript call (eller apply) metoder för att binda omfattningen av this.

Kort exempel... här är premissen:

class MyClass {
    constructor(private myProp: string) {

    }

    myMethod() {
        alert(this.myProp);
    }
}

var myClass = new MyClass('Test');

// 'Test'
myClass.myMethod();

// undefined
window.setTimeout(myClass.myMethod, 1000);

Lösning Ett - Pilsyntax

I TypeScript kommer pilsyntaxen automatiskt att shimmy this till en variabel som kallas _this åt dig och ersätta användningarna inuti pilfunktionen... Så detta kommer att lösa undefined-problemet ovan och istället visa en alert med Test.

class MyClass {
    constructor(private myProp: string) {

    }

    public myMethod = () => {
        alert(this.myProp);
    }
}

Lösning Två - Anropa Metod

Du kan använda call-metoden för att ersätta det kontextuella this med vilken objekt du vill, i exemplet nedan återställer vi det till att vara myClass-instansen.

Detta fungerar oavsett om du skriver TypeScript eller vanlig JavaScript... medan den första lösningen verkligen är en TypeScript-lösning.

window.setTimeout(function() { myClass.myMethod.call(myClass) }, 1000);

Eller för att vara kortare (för att vara tydlig, användningen av pilfunktionen här har ingenting att göra med scope - det är bara en kortare syntax pilfunktioner påverkar endast scope om du har this inuti dem):

window.setTimeout(() => myClass.myMethod.call(myClass), 1000);

Relaterade artiklar