Working on a performance reviewing tool on wechat mini apps platform (javascript + native hybrid based on wechat app), I am trying to inject codes into its prototypes, for example the wx.request function.
This is how you would use a wx.request function:
wx.request({
url: 'test.php',
data: {
x: '' ,
y: ''
},
header: {
'content-type': 'application/json'
},
success: function(res) {
console.log(res.data)
}
})
So in order to know how long the request has taken without manually writing adding all the anchors, I tried to inject code by:
var owxrequest = wx.request
wx.request = function() {
console.log('test', Date.now())
return owxrequest.apply(owxrequest, arguments)
}
This failed and I got an Cannot set property "prop" of #<Object> which has only a getter error.
So I realized the the object must have been defined similar to:
wx = {
request: get function(){
...
}
...
}
So I tried:
var owxrequest = wx.request
Object.defineProperty(wx, 'request', {
get: function() {
console.log('test', Date.now())
return owxrequest.apply(owxrequest, arguments)
}
})
This failed with an error (request: fail parameter error: parameter.url should be String instead of Undefined). Then I tried:
var owxrequest = wx.request
Object.defineProperty(wx, 'request', {
set: function() {
console.log('test', Date.now())
return owxrequest.apply(owxrequest, arguments)
}
})
This wouldn't throw an error but it also has no effect when calling wx.request()...
You can implement this by re-define the getter. The point is: the re-defined getter should return a function object, as wx.request is a function:
Object.defineProperty(wx, 'request', {
get: function() {
return function() {
//...
};
}
});
Why I get the error: request: fail parameter error: parameter.url should be String instead of Undefined?
You are trying to access the arguments of the getter itself (the arguments of function in get: function(){...}). This arguments is an empty object and it can be verified by console.log() statement. As it is empty, arguments.url is undefined, that's why wx complains about the parameter.
Here is an working example:
let wx = {
get request(){
return function() {
console.log(10);
return 88;
};
}
};
let oldF = wx.request;
Object.defineProperty(wx, 'request', {
get: function() {
return function() {
console.log(new Date());
return oldF.apply(wx, arguments);
};
}
});
console.log(wx.request());
The above code would print:
2017-08-28T06:14:15.583Z // timestamp
10
88
You could just shadowing the request function.
Simple example:
Shadowing the getter:
// original getter latest
let base = {
num: 1,
get latest() {
return this.num;
}
}
console.log(base.latest);
// shadowing getter latest
Object.defineProperty(base, 'latest', {
get: function() {
return this.num + 1;
}
});
console.log(base.latest);
Simple shadowing a object property
// your original object
let base = {
request(){
console.log('request original');
}
};
base.request()
base.request = () => {
console.log('request new implementation');
};
// now shadow the original request implementation
base.request()
Related
I try to define getter and setter in constructor via Object.assign:
function Class() {
Object.assign(this, {
get prop() { console.log('call get') },
set prop(v) { console.log('call set') },
});
}
var c = new Class(); // (1) => 'call get'
console.log(c.prop); // (2) => undefined
c.prop = 'change';
console.log(c.prop); // (3) => 'change'
Questions:
(1) Why getter is called?
(2) Why getter isn't called?
(3) Why setter is ignored?
The answer to all three of your questions is the same: Object.assign reads the value of the property from the source object, it doesn't copy getters/setters.
You can see that if you look at the property descriptor:
var source = {
get prop() { },
set prop(v) { }
};
console.log("descriptor on source", Object.getOwnPropertyDescriptor(source, "prop"));
var target = Object.assign({}, source);
console.log("descriptor on target", Object.getOwnPropertyDescriptor(target, "prop"));
To define that property on this inside Class, use defineProperty:
function Class() {
Object.defineProperty(this, "prop", {
get() { console.log('call get') },
set(v) { console.log('call set') },
});
}
var c = new Class();
console.log(c.prop); // => 'call get', undefined
c.prop = 'change'; // => 'call set'
console.log(c.prop); // => 'call get', undefined
For instance I have YOTI object injected into browser:
When I run this in console:
window.YOTI
I get:
{onShare: ƒ, redirect: ƒ}
Then if I access redirect method I get this:
window.YOTI.redirect
ƒ (callbackUrl, token) {
var currentQuery = window.location.search.replace('?','')
var currentFragment = window.location.hash
var separator = (callbackU…
I want to modify this property in angular onInit() and I tried this from here(https://yoti.zendesk.com/hc/en-us/articles/360008958873-Does-the-Yoti-frontend-client-support-an-Angular-App-):
const ytcontainer = {
};
Object.defineProperty(window, 'YOTI', {
get: () => ytcontainer.YOTI,
set: yoti => {
ytcontainer.YOTI = yoti;
Object.defineProperty(yoti, 'redirect', {
get: () => {
return (callbackUrl, token) => {
console.log(callbackUrl);
console.log(token);
};
},
});
},
});
but after that, when I run window.YOTI I get undefined object.
window.YOTI
undefined
What am I doing wrong here?
I'm fairly new to getters and setters and am looking for a way to listen for changes in an object to store the data immediately, without calling a Save() function everytime a value gets changed. This is how I do it right now:
var myObject = {
Data: {
enabled: true,
show: false
},
Save: function () {
//store myObject.Data to local storage
},
Load: function () {
//load data from local storage and assign it to myObject.Data
},
doSomething: function () {
myObject.Load();
if (myObject.Data.enabled) {
myObject.Data.show = true;
myObject.Save();
}
}
Now I would like to optimize this code so everytime a property in myObject.Data is changed, myObject.Save() is executed. The problem I'm experiencing is that it seems only possible to define a getter for a property that has just one value, but not for a property that is an object itself.
var myObj = {
_Data: {
a: 0,
b: 1,
c: 3
},
set Data (a) {
console.log(a);
}
};
myObj.Data.a = 2;
This obviously doesn't work since myObj.Data is not an object and doesn't have the same properties as myObj._Data.
Thanks in advance for any help.
You are likely interested in the Proxy object.
I used a very simple debounce function callHandler in order to avoid calling the onSet method dozens of times during array modifications. Otherwise, [1, 2, 3].splice(0, 1) would call the set handler once per item in the original array.
'use strict';
var myObject = {
Data: {
a: [1, 2, 3],
b: {c: ['test']}
},
Save: function() {
console.log('Save called');
},
}
function recursiveProxy(target, onSet) {
// For performance reasons, onSet will only be called one millesecond
// after the set handler has last been called.
var timeout;
function callHandler() {
clearTimeout(timeout);
timeout = setTimeout(onSet, 1);
}
var recursiveHandler = {
get: function(target, property) {
// If the property is something that could contain another object,
// we want to proxy it's properties as well.
if (typeof target[property] == 'object' && target[property] != null) {
return new Proxy(target[property], recursiveHandler);
}
return target[property];
},
set: function(target, property, value) {
console.log('Set called - queueing onSet');
callHandler();
target[property] = value;
return true;
}
}
return new Proxy(target, recursiveHandler);
}
myObject.Data = recursiveProxy(myObject.Data, myObject.Save);
myObject.Data.a.splice(0, 1);
myObject.Data.b.c[0] = 'test 2';
I believe you are looking for Defining a getter on existing objects using defineProperty
To append a getter to an existing object later at any time, use
Object.defineProperty().
var o = { a:0 }
Object.defineProperty(o, "b", { get: function () { return this.a + 1; } });
console.log(o.b) // Runs the getter, which yields a + 1 (which is 1)
For e.g:
var Data = {
enable: true,
show: false
};
Object.defineProperty(Data, 'doSomething', {
get: function() {
// get something;
},
set: function(something) {
// set something
}
});
I am trying to create a flux store for a React app I am building. I am using an object-assign polyfill npm package and Facebook's Flux library.
Initially I was getting the error "Cannot read property '_data' of null' error in the console which was refering to var currIds = this._data.map(function(m){return m.id;});. That method is currently the only one being called directly. I then did console.log(this) which returned "null".
I find this strange. What is going on?
My code:
var Assign = require('object-assign');
var EventEmitterProto = require('events').EventEmitter.prototype;
var CHANGE_EVENT = 'CHANGE';
var StoreMethods = {
init: function() {},
set: function (arr) {
console.log(this);
var currIds = this._data.map(function(m){return m.id;});
arr.filter(function (item){
return currIds.indexOf(item.id) === -1;
}).forEach(this.add.bind(this));
},
add: function(item){
console.log(this);
this._data.push(item);
},
all: function() {
return this._data;
},
get: function(id){
return this._data.filter(function(item){
return item.cid === id;
})[0];
},
addChangeListener: function(fn) {
this.on(CHANGE_EVENT, fn);
},
removeChangeListener: function(fn) {
this.removeListener(CHANGE_EVENT, fn);
},
emitChange: function() {
this.emit(CHANGE_EVENT);
},
bind: function(actionType, actionFn) {
if(this.actions[actionType]){
this.actions[actionType].push(actionFn);
} else {
this.actions[actionType] = [actionFn];
}
}
};
exports.extend = function(methods) {
var store = {
_data: [],
actions: {}
};
Assign(store, EventEmitterProto, StoreMethods, methods);
store.init();
require('../dispatcher').register(function(action){
if(store.actions[action.actionType]){
store.actions[action.actionType].forEach(function(fn){
fn.call(null, action.data);
})
}
});
return store;
};
I can't see where set is called, however your this can be null if the function is invoked through call (see here) or apply, and your first argument is null.
This also happens in your require.register callback:
fn.call(null, action.data) //First parameter is your 'this'.
I have the following angularJS service
define(["angular"], function(Angular) {
var dataStorageService = function() {
var serviceConstructor = function() {
var _getColor = function(color) {
return this.config.categoryColorMapping.colors[color];
}
}
var serviceInstance = new serviceConstructor();
angular.extend(serviceInstance.prototype, {
config: {
numberOfMessagesDisplayed: 5,
maxTitleLength: 48,
maxPreambleLength: 140,
categoryColorMapping: {
colors : {
nyheter: '#2B2B2B',
sport: '#F59331',
underholding: '#F9B00D'
},
categories: {
nyheter: _getColor('nyheter'),
sport: _getColor('sport'),
underholding: _getColor('underholding')
}
}
},
get: function(param) {
if(this.config.hasOwnProperty(param)) {
return this.config[param];
} else {
console.warn('Playlist::configService:no "' + param + '" config found');
return false;
}
},
set: function(param, value) {
this.config[param] = value;
}
});
return serviceInstance;
};
return dataStorageService;
});
now my goal is to make public the following methods:
get
set
and I want '_getColor' method private but I want to use it within the JSON object config. When I run the code I have
"ReferenceError: _getColor is not defined"
is it possibie to achievie it this way? (to have _getColor private and use it within the JSON object within angular.extend?)
Functions can be shared and still be private, instance specific private members have to be defined in the constructor though. Since your private function doesn't need to access instance specific private members you can do the following:
define(["angular"], function(Angular) {
var dataStorageService = function() {
var serviceConstructor = function() {
}
var serviceInstance = new serviceConstructor();
//IIFE returning object that will have private members as closure
// privileged methods have to be in the same function body as the
// private fucnction
serviceInstance.prototype = (function() {
var _getColor = function(instance, color) {
return instance.config.categoryColorMapping.colors[color];
};
return {
constructor: serviceConstructor
,config: {
numberOfMessagesDisplayed: 5,
maxTitleLength: 48,
maxPreambleLength: 140,
categoryColorMapping: {
colors : {
nyheter: '#2B2B2B',
sport: '#F59331',
underholding: '#F9B00D'
},
categories: {
//since categories is a sub object of serviceinstance.categorycolormapper
// it is not possible to get the instance of serviceinstance
// at this time unless you set it in the constructor
// solution could be that each serviceinstance has it's own categorycolormaper
// and when categorycolormapper is created pass the serviceinstance instance
nyheter: _getColor(this,'nyheter'),
sport: _getColor(this, 'sport'),
underholding: _getColor(this, 'underholding')
}
}
},
get: function(param) {
if(this.config.hasOwnProperty(param)) {
return this.config[param];
} else {
console.warn('Playlist::configService:no "' + param + '" config found');
return false;
}
},
set: function(param, value) {
this.config[param] = value;
}
}
}());
return serviceInstance;
};
return dataStorageService;
});
More info on constructor functions and prototype can be found here: https://stackoverflow.com/a/16063711/1641941
Functions added to the prototype are defined outside the lexical scope of the constructor, and therefore have no access to "private" methods.
The former are shared between all instances, and the latter are per-instance. The only way to get around this is to explicitly export the (per-instance) function as a property of the instance, making it non-private.
Within the definition of serviceConstructor add following line, after definition of _getColor
serviceConstructor.prototype._getColor = _getColor ;