Frage

In the following code in HeadDirective.prototype.link, this is equal to the global window object rather than the HeadDirective instance. My understanding is that the value of this inside a prototype function is the containing object itself.

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);
    }]);

The above code was generated from the following 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);
}]);

According to the latest TypeScript language specification:

The type of this in an expression depends on the location in which the reference takes place:

  • In a constructor, instance member function, instance member accessor, or instance member variable initializer, this is of the class instance type of the containing class.
  • In a static member function or static member accessor, the type of this is the constructor function type of the containing class.
  • In a function declaration or a standard function expression, this is of type Any.
  • In the global module, this is of type Any.

In all other contexts it is a compile-time error to reference this.

The TypeScript language specification is quite clear. Inside a member function (which is compiled into a prototype function), this refers to the class instance. This is obviously not what I'm seeing.

Any ideas? Could Browserify be interfering with this?

War es hilfreich?

Lösung

The this keyword is highly contextual. If a method is called by an event, this will be the object that is the event target, for example.

You can get around this problem by shimmying this into a variable, or by using the JavaScript call (or apply) methods to bind the scope of this.

Short example... here is the premise:

class MyClass {
    constructor(private myProp: string) {

    }

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

var myClass = new MyClass('Test');

// 'Test'
myClass.myMethod();

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

Solution One - Arrow Syntax

In TypeScript the arrow syntax will shimmy this into a variable called _this automatically for you and substitute usages inside the arrow function... So this will solve the undefined issue above and instead alert Test.

class MyClass {
    constructor(private myProp: string) {

    }

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

Solution Two - Call Method

You can use the call method to replace the contextual this with any object you like, in the example below we reset it to be the myClass instance.

This works whether you are writing TypeScript or plain JavaScript... whereas the first solution is really a TypeScript solution.

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

Or to be shorter (to be clear, the use of the arrow function here has nothing to do with scope - it is just a shorter syntax arrow functions only affect scope if you have this inside of them):

window.setTimeout(() => myClass.myMethod.call(myClass), 1000);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top