问题
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
?
回答1:
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);
来源:https://stackoverflow.com/questions/22323585/this-inside-prototype-function-equal-to-window-instead-of-object-instance