admin管理员组

文章数量:1297086

I'm trying to understand TypeScript decorators (specifically for properties), and I came up with the following code based on some examples I've seen:

decorator.ts

export function logProperty(target: any, key: string) {

  let val = this[key];

  const getter = () => {
    console.log(`Get: ${key} => ${val}`);
    return val;
  };

  const setter = (newVal) => {
    console.log(`Set: ${key} => ${newVal}`);
    val = newVal;
  };

  if (delete this[key]) {
    Object.defineProperty(target, key, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });
  }
}

main.ts

import { logProperty } from './decorators';

class Person {
  @logProperty
  firstName: string;

  @logProperty
  lastName: string;

  constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

const foo = new Person('Foo', 'Bar');

My problem is that when I try to run this, I get:

TypeError: Cannot read property 'firstName' of undefined

It seems that the value of this is undefined. What am I missing?

For reference, my tsconfig.json has:

"target": "es5"
"experimentalDecorators": true
"strict": false

UPDATE 8/27 It seems that this issue only arises when the decorator is in a different .ts file. If you place the decorator in a different file and import it from another file, then the error occurs. However, placing them all in the same file doesn't cause the issue. Am I simply misunderstanding how this gets interpreted?

I'm trying to understand TypeScript decorators (specifically for properties), and I came up with the following code based on some examples I've seen:

decorator.ts

export function logProperty(target: any, key: string) {

  let val = this[key];

  const getter = () => {
    console.log(`Get: ${key} => ${val}`);
    return val;
  };

  const setter = (newVal) => {
    console.log(`Set: ${key} => ${newVal}`);
    val = newVal;
  };

  if (delete this[key]) {
    Object.defineProperty(target, key, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });
  }
}

main.ts

import { logProperty } from './decorators';

class Person {
  @logProperty
  firstName: string;

  @logProperty
  lastName: string;

  constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

const foo = new Person('Foo', 'Bar');

My problem is that when I try to run this, I get:

TypeError: Cannot read property 'firstName' of undefined

It seems that the value of this is undefined. What am I missing?

For reference, my tsconfig.json has:

"target": "es5"
"experimentalDecorators": true
"strict": false

UPDATE 8/27 It seems that this issue only arises when the decorator is in a different .ts file. If you place the decorator in a different file and import it from another file, then the error occurs. However, placing them all in the same file doesn't cause the issue. Am I simply misunderstanding how this gets interpreted?

Share edited Aug 28, 2017 at 3:09 Andrew M. asked Aug 23, 2017 at 4:53 Andrew M.Andrew M. 8521 gold badge9 silver badges18 bronze badges 10
  • In what line do you get the error? – Aleksey L. Commented Aug 23, 2017 at 6:03
  • The first time this is referenced, so the third line down. – Andrew M. Commented Aug 23, 2017 at 11:52
  • Strange, works fine on my side and also in playground – Aleksey L. Commented Aug 23, 2017 at 13:24
  • Thanks @AlekseyL.. It seems to be working in the playground for me too. The problem is that I'm running my code in a project setup as a node module, so there's something that is different between the two, just can't figure out what... You can try pasting the code into a file (or create a new file) from this repo to reproduce the error. – Andrew M. Commented Aug 24, 2017 at 17:25
  • 2 I am running into this exact same issue, however my logProperty decorator is defined in the same file. this is undefined however. Any help would be appreciated. – Maurits Moeys Commented Mar 21, 2018 at 10:24
 |  Show 5 more ments

5 Answers 5

Reset to default 1

tl;dr: I'm not sure why OP's config didn't work; it seems to work beautifully now. See below for some brute-force testing.

Guess

I'm wondering if you were somehow picking up the wrong tsconfig. I've looked at your repo's tsconfig and it looks correct. Is there any chance another config file was infecting those runs? I see there were no automated tests there.

Testing

I ran into a similar issue today and threw together a quick test using OP as a blueprint. I pulled piler options from the official docs.

decorators.ts

export function logProperty(target: any, key: string) {

    let val = this[key];

    const getter = () => {
        console.log(`Get: ${key} => ${val}`);
        return val;
    };

    const setter = (newVal) => {
        console.log(`Set: ${key} => ${newVal}`);
        val = newVal;
    };

    if (delete this[key]) {
        Object.defineProperty(target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    }
}

main.ts

import { logProperty } from './decorators';

class Person {
    @logProperty
    firstName: string;

    @logProperty
    lastName: string;

    constructor(firstName: string, lastName: string) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

const foo = new Person('Foo', 'Bar');

function logProperty2(target: any, key: string) {

    let val = this[key];

    const getter = () => {
        console.log(`Get: ${key} => ${val}`);
        return val;
    };

    const setter = (newVal) => {
        console.log(`Set: ${key} => ${newVal}`);
        val = newVal;
    };

    if (delete this[key]) {
        Object.defineProperty(target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    }
}

class Person2 {
    @logProperty2
    firstName: string;

    @logProperty2
    lastName: string;

    constructor(firstName: string, lastName: string) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

const foo2 = new Person2('Foo', 'Bar');

index.ts

import * as assert from "assert";
import * as shelljs from "shelljs";

const MODULE_GENERATION = [
    "CommonJS",
    "AMD",
    "System",
    "UMD",
    "ES6",
    "ES2015",
    "ESNext",
];

const TARGETS = [
    "ES5",
    "ES2015",
    "ES2016",
    "ES2017"
]

shelljs.exec("tsc --target 'ES5' --module 'None' --strict main.ts", { silent: true });
assert.ok(shelljs.error());
shelljs.exec("tsc --target 'ES5' --module 'None' main.ts", { silent: true });
assert.ok(shelljs.error());

for (const moduleGeneration of MODULE_GENERATION) {
    console.log(`Testing module generation: ${moduleGeneration}`);
    for (const target of TARGETS) {
        console.log(`  Building for ${target}`);
        for (const strict of [true, false]) {
            console.log(`    Strict mode: ${strict ? 'en' : 'dis'}abled`)
            const mand = (
                `tsc` +
                ` --module '${moduleGeneration}'` +
                ` --experimentalDecorators` +
                ` --target '${target}'` +
                ` ${strict ? "--strict" : ""}` +
                ` main.ts`
            );
            const output = shelljs.exec(
                mand,
                { silent: true },
            );
            let symbol;
            if (strict) {
                assert.ok(shelljs.error());
                symbol = '✖'
            } else {
                assert.strictEqual(0, output.code);
                symbol = '✓'
            }
            console.log(`      ${symbol} ${mand}`);
        }
    }
}

Results

You can see the full build on Travis.

Testing module generation: CommonJS
  Building for ES5
    Strict mode: enabled
      ✖ tsc --module 'CommonJS' --experimentalDecorators --target 'ES5' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'CommonJS' --experimentalDecorators --target 'ES5'  main.ts
  Building for ES2015
    Strict mode: enabled
      ✖ tsc --module 'CommonJS' --experimentalDecorators --target 'ES2015' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'CommonJS' --experimentalDecorators --target 'ES2015'  main.ts
  Building for ES2016
    Strict mode: enabled
      ✖ tsc --module 'CommonJS' --experimentalDecorators --target 'ES2016' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'CommonJS' --experimentalDecorators --target 'ES2016'  main.ts
  Building for ES2017
    Strict mode: enabled
      ✖ tsc --module 'CommonJS' --experimentalDecorators --target 'ES2017' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'CommonJS' --experimentalDecorators --target 'ES2017'  main.ts
Testing module generation: AMD
  Building for ES5
    Strict mode: enabled
      ✖ tsc --module 'AMD' --experimentalDecorators --target 'ES5' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'AMD' --experimentalDecorators --target 'ES5'  main.ts
  Building for ES2015
    Strict mode: enabled
      ✖ tsc --module 'AMD' --experimentalDecorators --target 'ES2015' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'AMD' --experimentalDecorators --target 'ES2015'  main.ts
  Building for ES2016
    Strict mode: enabled
      ✖ tsc --module 'AMD' --experimentalDecorators --target 'ES2016' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'AMD' --experimentalDecorators --target 'ES2016'  main.ts
  Building for ES2017
    Strict mode: enabled
      ✖ tsc --module 'AMD' --experimentalDecorators --target 'ES2017' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'AMD' --experimentalDecorators --target 'ES2017'  main.ts
Testing module generation: System
  Building for ES5
    Strict mode: enabled
      ✖ tsc --module 'System' --experimentalDecorators --target 'ES5' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'System' --experimentalDecorators --target 'ES5'  main.ts
  Building for ES2015
    Strict mode: enabled
      ✖ tsc --module 'System' --experimentalDecorators --target 'ES2015' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'System' --experimentalDecorators --target 'ES2015'  main.ts
  Building for ES2016
    Strict mode: enabled
      ✖ tsc --module 'System' --experimentalDecorators --target 'ES2016' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'System' --experimentalDecorators --target 'ES2016'  main.ts
  Building for ES2017
    Strict mode: enabled
      ✖ tsc --module 'System' --experimentalDecorators --target 'ES2017' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'System' --experimentalDecorators --target 'ES2017'  main.ts
Testing module generation: UMD
  Building for ES5
    Strict mode: enabled
      ✖ tsc --module 'UMD' --experimentalDecorators --target 'ES5' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'UMD' --experimentalDecorators --target 'ES5'  main.ts
  Building for ES2015
    Strict mode: enabled
      ✖ tsc --module 'UMD' --experimentalDecorators --target 'ES2015' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'UMD' --experimentalDecorators --target 'ES2015'  main.ts
  Building for ES2016
    Strict mode: enabled
      ✖ tsc --module 'UMD' --experimentalDecorators --target 'ES2016' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'UMD' --experimentalDecorators --target 'ES2016'  main.ts
  Building for ES2017
    Strict mode: enabled
      ✖ tsc --module 'UMD' --experimentalDecorators --target 'ES2017' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'UMD' --experimentalDecorators --target 'ES2017'  main.ts
Testing module generation: ES6
  Building for ES5
    Strict mode: enabled
      ✖ tsc --module 'ES6' --experimentalDecorators --target 'ES5' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'ES6' --experimentalDecorators --target 'ES5'  main.ts
  Building for ES2015
    Strict mode: enabled
      ✖ tsc --module 'ES6' --experimentalDecorators --target 'ES2015' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'ES6' --experimentalDecorators --target 'ES2015'  main.ts
  Building for ES2016
    Strict mode: enabled
      ✖ tsc --module 'ES6' --experimentalDecorators --target 'ES2016' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'ES6' --experimentalDecorators --target 'ES2016'  main.ts
  Building for ES2017
    Strict mode: enabled
      ✖ tsc --module 'ES6' --experimentalDecorators --target 'ES2017' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'ES6' --experimentalDecorators --target 'ES2017'  main.ts
Testing module generation: ES2015
  Building for ES5
    Strict mode: enabled
      ✖ tsc --module 'ES2015' --experimentalDecorators --target 'ES5' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'ES2015' --experimentalDecorators --target 'ES5'  main.ts
  Building for ES2015
    Strict mode: enabled
      ✖ tsc --module 'ES2015' --experimentalDecorators --target 'ES2015' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'ES2015' --experimentalDecorators --target 'ES2015'  main.ts
  Building for ES2016
    Strict mode: enabled
      ✖ tsc --module 'ES2015' --experimentalDecorators --target 'ES2016' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'ES2015' --experimentalDecorators --target 'ES2016'  main.ts
  Building for ES2017
    Strict mode: enabled
      ✖ tsc --module 'ES2015' --experimentalDecorators --target 'ES2017' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'ES2015' --experimentalDecorators --target 'ES2017'  main.ts
Testing module generation: ESNext
  Building for ES5
    Strict mode: enabled
      ✖ tsc --module 'ESNext' --experimentalDecorators --target 'ES5' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'ESNext' --experimentalDecorators --target 'ES5'  main.ts
  Building for ES2015
    Strict mode: enabled
      ✖ tsc --module 'ESNext' --experimentalDecorators --target 'ES2015' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'ESNext' --experimentalDecorators --target 'ES2015'  main.ts
  Building for ES2016
    Strict mode: enabled
      ✖ tsc --module 'ESNext' --experimentalDecorators --target 'ES2016' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'ESNext' --experimentalDecorators --target 'ES2016'  main.ts
  Building for ES2017
    Strict mode: enabled
      ✖ tsc --module 'ESNext' --experimentalDecorators --target 'ES2017' --strict main.ts
    Strict mode: disabled
      ✓ tsc --module 'ESNext' --experimentalDecorators --target 'ES2017'  main.ts

Solid tsconfig

Based on those results, it looks like this is an okay tsconfig.

{
  "pilerOptions": {
    "target": "es5",
    "module": "<not None>",
    "experimentalDecorators": true,
    "strict": false
  }
}

Final Notes

  • I didn't test as many piler options as I could have. If there's interest, I can update the repo later.
  • I still don't have a good answer for OP's issue and that bothers me.
  • I did check TS version as well. OP was using 2.4.2 and I'm using 2.7.2. Just to make sure that wasn't the issue, I bumped my version down too.

'this' is undefined because you are not properly configuring the property descriptor.

Instead of:

Object.defineProperty(target, key, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });

Do something like:

Object.defineProperty(target, key, {
      get() {
         // this is defined
      },
      set(value: any) {
         // this is defined
      },
      enumerable: true,
      configurable: true
    });

I had the same issue and the solution above solves it.

you can try this:

instead of

function logProperty(target: any, key: string) {

 ...
}

using:

const logProperty = (target: any, key: string) => {
 ...
}

because => 's this just outside, so it can get it. hope it's helpful!

I used allen wang post to build from and came up with this

export const logProperty = (target: any, key: string) => {
    let val = this?[key]: '';

    const getter = () => {
        console.log(`Get: ${key} => ${val}`);
        return val;
    };

    const setter = (newVal) => {
        console.log(`Set: ${key} => ${newVal}`);
        val = newVal;
    };

    if (val) {
        Object.defineProperty(target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    }
}

I met the similar issue but w/ a serial strict conditions.

My code was like below:

namespace StringUtil {
  export function validate(...) {...} 
}

export function mv_string(minLen: number, maxLen: number, allowNull?: boolean, errCode?: string, errMsg?: string) {
    return function (target: any, propertyKey: string) {
        let cls = target.constructor as Function;
        createValidator(cls, propertyKey, StringUtil.validate, [minLen, maxLen, allowNull], errCode, errMsg);
    };
}

class MyCls {
    @mv_string
    myProp: string;
}

Previously I was using typescript 3.7.0+ w/ a lower ts-node version, all goes fine no matter running via ts-node or via webpack && node xxx.

Later I upgrade to typescript 4.7.0+ w/ a ts-node 10.0.0+ version, the webpack & node run goes fine as well, however ts-node | ts-node-dev give a runtime issue:

/Users/xxx/xxx/node_modules/reflect-metadata/Reflect.js:553
                var decorated = decorator(target, propertyKey, descriptor);
                                ^
TypeError: Cannot read properties of undefined (reading 'validate')
    at /Users/xxx/xxx/src/xxx/my-xxx-validator.ts:55:54
    at DecorateProperty (/Users/xxx/xxx/node_modules/reflect-metadata/Reflect.js:553:33)
    at Reflect.decorate (/Users/xxx/xxx/node_modules/reflect-metadata/Reflect.js:123:24)
    at __decorate (/Users/xxx/xxx/src/my-xxx-tscode.ts:4:92)
    at Object.<anonymous> (/Users/xxx/xxx/src/my-xxx-tscode.ts:6:5)
    at Module._pile (node:internal/modules/cjs/loader:1105:14)
    at Module.m._pile (/Users/xxx/xxx/node_modules/ts-node/src/index.ts:839:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
    at Object.require.extensions.<puted> [as .ts] (/Users/xxx/xxx/node_modules/ts-node/src/index.ts:842:12)
    at Module.load (node:internal/modules/cjs/loader:981:32)

It major said StringUtil is undefined when using StringUtil.validate as I could see.

I doubt the reason might like @CJ Harries mentioned above but I'm not sure, playing w/ typescript it looks namespace would be piled as an object so it's wired... Maybe the upgrade caused any break change when on decorator while via ts-node?

本文标签: javascript39this39 is Undefined in TypeScript Property DecoratorStack Overflow