How to proxy JavaScript creation primitive - javascript

I want to do something when creating a string, for example:
String = new Proxy(String, {
construct: (t, a) => {
return { a: 123 }
}
})
console.log(new String('q')) // { a: 123 }
However, if you use primitives, it doesn't work.
String = new Proxy(String, {
construct: (t, a) => {
return { a: 123 }
}
})
console.log('1') // Expected: { a: 123 }, Actual: 1
Is there any way?
then the second question is,
when the runtime converts primitives, can I proxy the process?
var a = '123' // This is a primitive
console.log('123'.substring(0,1)) // Actual: 1
// The runtime wraps the primitive as a String object.
// then uses a substring, and then returns the primitive.
now:
String = new Proxy(String, {
construct: (t, a) => {
return { a: 123 }
},
apply: (target, object, args) => {
return { a: 123 }
}
})
console.log('1'.a) // Expected: 123 , Actual: undefined
I know I can add 'a' to the prototype of String to achieve the expectations.
But I want to be able to proxy access to arbitrary attributes for primitives.
(Is '1'.*, Is not jsut '1'.a)
Is there any way?
Thank you for your answer.

No, this is not possible. Proxies only work on objects, not on primitives. And no, you cannot intercept the internal (and optimised-away) process that converts a primitive to an object to access properties (including methods) on it.
Some of the operations involving primitives do use the methods on String.prototype / Number.prototype / Boolean.prototype, and you can overwrite these methods if you dare, but you cannot replace the entire prototype object for a proxy.

Related

Linter complaining can't use the spread operator on string, which is good, but how do I avoid it in this situation?

When I hover over ...res in VSCode I get the warning from my linter:
Spread types may only be created from object types
When I log res it's either a string or an object. However, I have no idea how to satisfy the linter in this case.
function getCookie(req: Request, key: string): string | undefined {
const {
headers: { cookie },
} = req;
return (
cookie &&
cookie.split(";").reduce<string | undefined>((res, item) => {
const data = item.trim().split("=");
return <string | undefined>{ ...res, [data[0]]: data[1] };
}, "")
);
}
So let's deal with the JavaScript and TypeScript aspects separately. In plain JavaScript terms, your function doesn't do what you've described. When there are cookies defined, it returns a Record<string, string> mapping cookie names to cookie values, not a particular string or false. If you mean for it to do something with key, you haven't included that part in your example.
When no cookies are defined, it ought to return undefined, but because you test the truthiness cookies in your return statement, it could return an empty string instead. That can be fixed by making that test separately and returning undefined explicitly. (Make sure your linter is warning you about using non-booleans in boolean context in TS.)
The reason you're spreading a string is that you're passing an empty string "" as the initial value to Array.prototype.reduce. Since you are accumulating objects, not strings, your initial value should be {} (an empty object) instead.
That helps us see the TypeScript issues. Your string | undefined casts are all unnecessary once the initial value is corrected, and the return type of the function overall becomes Record<string, string> | undefined. In sum:
function getCookie(req: Request, key: string): Record<string, string> | undefined {
const {
headers: { cookie },
} = req;
if (!cookie) {
return undefined;
}
return cookie.split(";").reduce<Record<string, string>>((res, item) => {
const data = item.trim().split("=");
return { ...res, [data[0]]: data[1] };
}, {});
}
However, if you actually want to look for a specific key and return its value, an array reducer isn't the best approach and your function would look rather different.

What properties will rest skip in Object Destructuring?

I am using rest parameter to do some operations, but got some properties disappeared in result.
The data format of this.props looks like this:
```
this.props = {
data: [{...}, ...],
...
}
```
And I tried to rest it in this way:
```
let {showWaveAnimation, ...otherProps} = this.props;
console.log('data' in otherProps); // false
console.log('data' in this.props); //true
```
Why would 'data' property lost after I tried rest operation?
According to MDN, I found the description:
rest parameters are only the ones that haven't been given a separate name (i.e. formally defined in function expression), while the arguments object contains all arguments passed to the function;
What does separate name here means? Is it means properties that extends from its prototype would not be rest? But after I tried the following statements, I got the same result, I am confusing.
```
class Parent {
constructor() {
this.props = {
data: '123',
};
}
}
class Child extends Parent {
constructor() {
super();
this.props.childProp = 'test';
let {test, ...otherProps} = this.props;
console.log('data' in this.props, 'data' in otherProps);
// true true
}
}
new Child();
```
The former code behaviors abnormal after Babel's transpiling, could it be Babel's plugin problem?
ADD: I found this concept may be more precise in describing my rest operations. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Rest_in_Object_Destructuring
I figured out why 'data' disappeared in my rest result, it's the enumerable property of 'data' field!
Once, the property is set as enumerable: false, we can't get it with the operation of rest in object destructuring.
According to MDN
The Rest/Spread Properties for ECMAScript proposal (stage 3) adds the rest syntax to destructuring. Rest properties collect the remaining own enumerable property keys that are not already picked off by the destructuring pattern.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Rest_in_Object_Destructuring
I think this example can explain my ideas more precisely:
const person = {
name: 'Dave',
surname: 'Bowman'
};
Object.defineProperty(person, 'age', {
enumerable: false, // Make the property non-enumerable
value: 25
});
console.log(person['age']); // => 25
const clone = {
...person
};
console.log(clone); // => { name: 'Dave', surname: 'Bowman' }
The rest parameter allows you to get all the remaining attributes not already selected by identifier preceding it in the assignment.
Let's take a simple example:
const obj = {
a: 1,
b: 2,
c: 3
}
const { a, ...rest } = obj;
console.log(a);
console.log(rest);
Above obj has a property named a so it is destructured from obj and assigned to the variable a, then all other properties are assigned in an object to rest.
However, what you have is more like:
const obj = {
a: 1,
b: 2,
c: 3
}
const { d, ...rest } = obj;
console.log(d);
console.log(rest);
Here obj doesn't have a property named d. So it is destructured from obj as undefined and assgined to the variable a. Then all other properties not already assigned, i.e. a, b and c are given to rest.
In your code otherProps has the same properties as this.props, because object this.props doesn't have a property named test. In other words, in order to destructure an object you have to know its structure.
rest parameters are only the ones that haven't been given a separate name (i.e. formally defined in function expression), while the arguments object contains all arguments passed to the function;
What does separate name here means? Is it means properties that extends from its prototype would not be rest? But after I tried the following statements, I got the same result, I am confusing.
Given a function definition function foo(a, b, ...c) {}, arguments a, and b are the named parameters, whereas the rest of the passed arguments are spread into the last variable c. Now, c is a named variable, but any parameters passed from the third on will just be accessible in c, i.e. c[0].
Now, your console.log error is stemming from your use of the in operator. You defined an object const foo = {'data': 'bar'}. 'data' in foo is true, but 'blarg' in foo is false since foo doesn't contain a key/property named 'blarg'.
You're actually using the spread operator to destructure your props object into several named variables.
const object = {
data: 'foo',
other: 'bar',
moreData: [1,2,3],
};
// Example usage of the spread operator
const { data, ...allTheRest} = object; // data is destructured, other and moreData into allTheRest
console.log(data);
console.log(allTheRest);
// in operator
console.log('data' in object); // true
console.log('data' in allTheRest); // false
console.log('moreData' in allTheRest); // true
// This is an example of the rest operator
const foo = (...allTheArgs) => {
allTheArgs.forEach(e => console.log(e));
}
foo(1,2,3); // all the args are collected into a single array variable in the function
const bar = (a, b, ...c) => {
console.log(`a: ${a}`);
console.log(`b: ${b}`);
console.log(`c: ${c}`);
};
bar('this is first param', 42, 'foo', 'bar', '1', 2, 'stackoverflow');

Flow: Object type incompatible with Array<mixed>

I don’t understand the flow error I’m currently getting. I have a Javascript object of objects (dataObject) that I want to convert to an array of objects, so I do so using Object.values(dataObject). Then, I iterate through each object in the array with the following:
const dataObjectArray = Object.values(dataObject);
return dataObjectArray((data: DataObject) => {
const { typeA, typeB } = data;
return {
TYPE_A: typeA,
TYPE_B: typeB,
};
});
But I get the following flowtype error:
I’m not sure how to match up the types. Currently my DataObject flow type is
type DataObject = {
typeA: string,
typeB: string,
};
Any help would be appreciated. Thanks!
The type definition for the Object.values function has no way to know that the argument passed to it is an object where the values are all the same type. You could just as easily be doing Object.values({foo: 4, bar: "str"}). The type definition is
(any) => Array<mixed>
meaning that you are doing .map on a value of type Array<mixed>.
That means if you want to use it as object, your method will not work. Assuming your "object of objects" is typed as
type DataObjects = {
[string]: DataObject,
}
You'd likely be better off doing
function values(objs: DataObjects): Array<DataObject> {
return Object.keys(objs).map(key => objs[key]);
}
If you prefer to use Object.values() (probably more efficient) and have typing right, you can use a helper function like this:
function objectToValues<A, B>(obj:{[key: A]: B} ): Array<B> {
return ((Object.values(obj): any): Array<B>)
}

Typeof arguments object in TypeScript

I basically have this:
function foo(){
// literally pass the arguments object into the pragmatik.parse method
// the purpose of pragmatik.parse is to handle more complex variadic functions
const [a,b,c,d,e] = pragmatik.parse(arguments);
// now we have the correct arguments in the expected location, using array destructuring
}
so we have the pragmatik.parse method:
function parse(args){
// return parsed arguments
}
now I want to use TypeScript to define types, all I know is that arguments is an Object:
function parse(args: Object){
}
so my question is: does TypeScript give a definition or type for an arguments object in JS? Sorry this is a bit meta, but please bear with me, what I am asking about is sane.
My Webstorm IDE suggests that this might be IArguments, which is provided by: lib/es6/d.ts, which is somewhere out there. Maybe someone can verify this is correct, but I am fairly certain.
So the answer would be:
function parse(args: IArguments){
}
and the full signature would be:
function parse(args: IArguments) : Array<any> {
}
since the parse method returns a generic array
You can pick exact type of arguments with tsargs package from npm
Eg:
import { ArgsN } from 'tsargs';
function foo(a: boolean, b: number, c: string) {}
const argsABC: ArgsN<typeof foo> = [ true, 123, 'Hello' ];
In your case:
import { ArgsN } from 'tsargs';
function parse<T extends (...args: any[]) => any>(args: IArguments): ArgsN<T> {
// return parsed arguments
}
// ...
const args = parse<typeof foo>(arguments);
// args -> [ boolean, number, string ];

JavaScript to TypeScript: Intellisense and dynamic members

I have a JavaScript object which dynamically allows members to be bound as accessor properties to instances of the object:
Source
function DynamicObject(obj) {
for (var prop in obj) {
Object.defineProperty(this, prop, {
get: function () { return obj[prop]; },
set: function (value) { obj[prop] = value; },
enumerable: true,
configurable: false
});
}
}
Usage
var obj = new DynamicObject({
name: "John Smith",
email: "john.smith#test.net",
id: 1
});
When obj is created, the members of the constructor parameter are bound to obj as accessor properties. These show up in intellisense
I would like to know if it is possible to model this sort of behavior (including having intellisense) in TypeScript?
Notes
When you run this code in TypeScript, there is no intellisense becuase everything is any, so TypeScript doesn't really know what's going on.
You can't. These are completely dynamic properties, added at runtime, so you can't possibly know what they are at compile-time. I would also argue that you don't want to know what they are that early; if you have constraints to enforce, they should just be stated on their own (first example below).
If your code depends on a set of accessors, you should put those in the interface or contract directly, because you know ahead of time that you expect them and should advertise that. You can use optional properties (with the accessor defined lower) to make that simpler:
interface HasSomeProps {
foo: string;
bar?: string;
}
class DoesTheProps implements HasSomeProps {
set foo(value) {
// ...
}
}
If you have a bunch of consistent (or semi-consistent) accessors, you can define an indexer on your type like:
interface AccessStrings {
[key: string]: string;
}
That does not allow you to restrict the keys. If you want that, you should explicitly list the properties.
I would like to know if it is possible to model this sort of behavior (including having intellisense) in TypeScript?
Yes.
You can assign a generic call signature to DynamicObject. You'll need to declare it as a variable:
var DynamicObject: new <T>(obj: T) => T = function (obj)
{
for (var prop in obj)
{
Object.defineProperty(this, prop, {
get: function () { return obj[prop]; },
set: function (value) { obj[prop] = value; },
enumerable: true,
configurable: false
});
}
} as any;
This way, IntelliSense will treat the value returned from new DynamicObject as having the same type as the value passed in. Same property names, same property types. You'll get full autocomplete and type-safety.
Live demo on TypeScript Playground
If you have trouble wrapping your head around that part in the first line, it's the same as writing the following:
// Declare type (only exists during compile-time)
var DynamicObject: new <T>(obj: T) => T;
// Assign value (during runtime)
DynamicObject = function (obj)
{
...
} as any;

Categories

Resources