How do I change the "this" pointer in JavaScript? - javascript

I made an object like this:
var MyObj = function(arg)
{
var PublicMethods = {
SomeMethod: function(someArg)
{
if(SomeCheck(arg)
{
PublicMethods.SomeFunc2 = somethingElse;
}
}
};
return PublicMethods;
};
However MyObj doesn't seem to be persistent and between calls, PublicMethods doesnt preserve the new methods added to it, so I tried moving it to global scope, however then it doesn't recognize the 'args' passed from MyObj anymore.
If I make MyObj like this:
var MyObj = (function()
{
//..
}());
Then it becomes a persistent object, but I'm not sure - can I call it like a function again?
jQuery seems to have a persistent object and at the same time it can be called like a function, how do they achieve that?
I want to be able to do this:
MyObj("Something Here").SomeMethod("Something Else");
and thus to be able to create a SomeFunc2 method that I can later call too:
MyObj("Something Here").SomeFunc2("Something Else");

Simply store the result of the initial MyObj call in a local variable:
var obj = MyObj("Something Here");
obj.SomeMethod("Something Else");
obj.SomeFunc2("Something else");
The PublicMethods variable is specific to each call of MyObj, so when you call MyObj for the second time, you get a different instance of PublicMethods. By using a variable to store the result of the first MyObj call, you can use the same instance of PublicMethods for both the SomeMethod and SomeFunc2 functions.
As a side note, you may want to look into constructor functions which would allow you to define functions more simply, instead of returning an object. For example:
function Example() {
this.a = function() {
return "a";
};
this.b = function() {
this.a = function() {
return "b";
}
}
}
var example = new Example();
example.a(); // => "a"
example.b();
example.a(); // => "b"

You can create a jQuery method which extends jQuery or jQuery.fn and can also set this context within the method.
(function($) {
jQuery.addMethod = function addMethod({methodName, method, type}) {
let bool = {
[type]: false
};
let _jQuery_jQueryFn_ = Object.keys(bool).pop();
if (type === "jQuery") {
for (let prop in jQuery) {
if (prop === methodName
|| prop.toUpperCase() === methodName.toUpperCase()) {
bool[type] = true;
break;
}
}
}
if (type === "fn") {
for (let prop in jQuery.fn) {
if (prop === methodName
|| prop.toUpperCase() === methodName.toUpperCase()) {
bool[type] = true;
break;
}
}
}
if (type === "jQuery" && bool[_jQuery_jQueryFn_] === false) {
jQuery[methodName] = method;
}
if (type === "fn" && bool[_jQuery_jQueryFn_] === false) {
jQuery[type][methodName] = method;
}
if (bool[_jQuery_jQueryFn_] === true) {
return Promise.reject(
new ReferenceError(
methodName
+ " previously defined at "
+ _jQuery_jQueryFn_
));
} else {
console.log(methodName + " defined at " + _jQuery_jQueryFn_);
}
return {methodName:methodName, type};
}
})(jQuery);
$(function() {
Promise.resolve($.addMethod({
methodName: "add",
method: function add(a, b, context) {
console.log(a + b);
return (context || this)
},
type: "jQuery"
}))
.then(function({methodName, type}) {
if (type === "jQuery" && methodName in window[type]) {
jQuery[methodName](10, 10)
} else {
if (methodName in window["jQuery"][type]) {
jQuery[type][methodName](10, 10);
}
}
})
.catch(function(err) {
console.error(err)
});
});
$(function() {
Promise.resolve($.addMethod({
methodName: "add",
method: function add(a, b, context) {
console.log(a + b);
return (context || this)
},
type: "fn"
}))
.then(function({methodName, type}) {
if (methodName === "jQuery" && methodName in window[type]) {
jQuery[methodName](10, 10)
} else {
if (methodName in window["jQuery"][type]) {
jQuery("span")[methodName](10, 10);
}
}
})
.catch(function(err) {
console.error(err)
});
});
$(function() {
Promise.resolve(
$.addMethod({
methodName: "reverseText",
method: function reverseText(_text, context) {
let text = [...(_text || this.text())].reverse().join("");
(context || this).text(text);
return (context || this)
},
type: "fn"
}))
.then(function({methodName, type}) {
if (type === "jQuery" && methodName in window[type]) {
jQuery[methodName]()
} else {
if (methodName in window["jQuery"][type]) {
// set context `this` to `span`
let span = jQuery("section")[methodName]("321", $("span"))
.css("color", "sienna");
console.log(
span.is(document.querySelector("span"))
);
jQuery("section")[methodName]()
.css("color", "green");
}
}
})
.catch(function(err) {
console.error(err)
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js">
</script>
<section>section</section>
<span>span</span>

Related

Convert javascript class instance to plain object preserving methods

I want to convert an instance class to plain object, without losing methods and/or inherited properties. So for example:
class Human {
height: number;
weight: number;
constructor() {
this.height = 180;
this.weight = 180;
}
getWeight() { return this.weight; }
// I want this function to convert the child instance
// accordingly
toJSON() {
// ???
return {};
}
}
class Person extends Human {
public name: string;
constructor() {
super();
this.name = 'Doe';
}
public getName() {
return this.name;
}
}
class PersonWorker extends Person {
constructor() {
super();
}
public report() {
console.log('I am Working');
}
public test() {
console.log('something');
}
}
let p = new PersonWorker;
let jsoned = p.toJSON();
jsoned should look like this:
{
// from Human class
height: 180,
weight: 180,
// when called should return this object's value of weight property
getWeight: function() {return this.weight},
// from Person class
name: 'Doe'
getName(): function() {return this.name},
// and from PersonWorker class
report: function() { console.log('I am Working'); },
test: function() { console.log('something'); }
}
Is this possible to achieve, and if so, how?
In case you're wondering, I need this because I am using a framework that, unfortunately, accepts as input only an object, whereas I am trying to use TypeScript and class inheritance.
Also, I am doing the above conversion once so performance isn't an issue to consider.
The solutions consisting of iterating through object properties will not work if the compiler's target option is set to es6. On es5, the existing implementations by iterating through object properties (using Object.keys(instance)) will work.
So far, I have this implementation:
toJSON(proto?: any) {
// ???
let jsoned: any = {};
let toConvert = <any>proto || this;
Object.getOwnPropertyNames(toConvert).forEach((prop) => {
const val = toConvert[prop];
// don't include those
if (prop === 'toJSON' || prop === 'constructor') {
return;
}
if (typeof val === 'function') {
jsoned[prop] = val.bind(this);
return;
}
jsoned[prop] = val;
const proto = Object.getPrototypeOf(toConvert);
if (proto !== null) {
Object.keys(this.toJSON(proto)).forEach(key => {
if (!!jsoned[key] || key === 'constructor' || key === 'toJSON') return;
if (typeof proto[key] === 'function') {
jsoned[key] = proto[key].bind(this);
return;
}
jsoned[key] = proto[key];
});
}
});
return jsoned;
}
But this is still not working. The resulted object includes only all the properties from all classes but only methods from PersonWorker.
What am I missing here?
Lots of answers already, but this is the simplest yet by using the spread syntax and de-structuring the object:
const {...object} = classInstance
This is what's working for me
const classToObject = theClass => {
const originalClass = theClass || {}
const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(originalClass))
return keys.reduce((classAsObj, key) => {
classAsObj[key] = originalClass[key]
return classAsObj
}, {})
}
Ok, so the implementation in my OP was wrong, and the mistake was simply stupid.
The correct implementation when using es6 is:
toJSON(proto) {
let jsoned = {};
let toConvert = proto || this;
Object.getOwnPropertyNames(toConvert).forEach((prop) => {
const val = toConvert[prop];
// don't include those
if (prop === 'toJSON' || prop === 'constructor') {
return;
}
if (typeof val === 'function') {
jsoned[prop] = val.bind(jsoned);
return;
}
jsoned[prop] = val;
});
const inherited = Object.getPrototypeOf(toConvert);
if (inherited !== null) {
Object.keys(this.toJSON(inherited)).forEach(key => {
if (!!jsoned[key] || key === 'constructor' || key === 'toJSON')
return;
if (typeof inherited[key] === 'function') {
jsoned[key] = inherited[key].bind(jsoned);
return;
}
jsoned[key] = inherited[key];
});
}
return jsoned;
}
Here is the implementation for the toJSON() method. We are copying over the properties & methods from the current instance to a new object and excluding the unwanted methods i.e. toJSON and constructor.
toJSON() {
var jsonedObject = {};
for (var x in this) {
if (x === "toJSON" || x === "constructor") {
continue;
}
jsonedObject[x] = this[x];
}
return jsonedObject;
}
I have tested the object returned by toJSON() in Chrome and I see the object behaving the same way as you are expecting.
I'm riffing on Alex Cory's solution a lot, but this is what I came up with. It expects to be assigned to a class as a Function with a corresponding bind on this.
const toObject = function() {
const original = this || {};
const keys = Object.keys(this);
return keys.reduce((classAsObj, key) => {
if (typeof original[key] === 'object' && original[key].hasOwnProperty('toObject') )
classAsObj[key] = original[key].toObject();
else if (typeof original[key] === 'object' && original[key].hasOwnProperty('length')) {
classAsObj[key] = [];
for (var i = 0; i < original[key].length; i++) {
if (typeof original[key][i] === 'object' && original[key][i].hasOwnProperty('toObject')) {
classAsObj[key].push(original[key][i].toObject());
} else {
classAsObj[key].push(original[key][i]);
}
}
}
else if (typeof original[key] === 'function') { } //do nothing
else
classAsObj[key] = original[key];
return classAsObj;
}, {})
}
then if you're using TypeScript you can put this interface on any class that should be converted to an object:
export interface ToObject {
toObject: Function;
}
and then in your classes, don't forget to bind this
class TestClass implements ToObject {
toObject = toObject.bind(this);
}
This solution will lose methods, but it is a very simple solution to convert a class instance to an object.
obj = JSON.parse(JSON.stringify(classInstance))
using Lodash
This method isn't recursive.
toPlainObject() {
return _.pickBy(this, item => {
return (
!item ||
_.isString(item) ||
_.isArray(item) ||
_.isNumber(item) ||
_.isPlainObject(item)
);
});
}

Extending Object prototype by adding a function in a "namespace"

The below code (also in Plunker) is adapted from this SO post.
I am trying to implement the same logic, only add the uniqueId function in a special property of the Object.prototype to keep things clear.
The below code works when run using nodejs (also the HTML-ized Plunker example works as well). I.e. the console prints three unique object identifiers: 0, 1 and 2.
However, when the test switch variable is set to 1, console prints 0, 0 and 0.
What should I do to add the uniqueId function in the foo namespace?
function addUniqueId1(){
if (Object.prototype.foo===undefined) {
Object.prototype.foo = {};
console.log('foo "namespace" added');
}
if (Object.prototype.foo.uniqueId===undefined) {
var i = 0;
Object.prototype.foo.uniqueId = function() {
console.log('uniqueId function called');
if (this.___uniqueId === undefined) {
this.___uniqueId = i++;
}
return this.___uniqueId;
};
console.log('function defined');
}
}
function addUniqueId2() {
if (Object.prototype.uniqueId === undefined) {
var i = 0;
Object.prototype.uniqueId = function() {
if (this.___uniqueId === undefined) {
this.___uniqueId = i++;
}
return this.___uniqueId;
};
};
};
var test=2; // if you set this to 1 it stops working
if (test==1)
addUniqueId1();
else
addUniqueId2();
var xs = [{}, {}, {}];
for (var i = 0 ; i < xs.length ; i++) {
if (test==1)
console.log('object id is: ['+xs[i].foo.uniqueId()+']');
else
console.log('object id is: ['+xs[i].uniqueId()+']');
}
You need to define foo with a getter, to allow it to access this for use within the hash of sub-method(s):
function defineFoo() {
if (Object.prototype.foo) return;
var i = 0;
Object.defineProperty(Object.prototype, 'foo', {
get: function() {
var self = this;
return {
uniqueId: function() {
return self.__uniqueId = self.uniqueId || ++i;
}
};
}
});
}
Now
defineFoo();
obj.foo.uniqueId()
If you prefer, an alternative to using self:
Object.defineProperty(Object.prototype, 'foo', {
get: function() {
return {
uniqueId: function() {
return this.__uniqueId = this.uniqueId || ++i;
}.bind(this)
};
}
});

apply parameter into function within parameter array

http://jsbin.com/ukizof/1/
how do you call a function which is part of a array and set a paramater to it , as in the example below i want the script to return a function in order to call a function parameter which as below in the example is set below.
var bQuery = {
one: function(elem, options) {
options = this.extend({
method: 'html',
event: 'test2',
func:null
}, options || {});
var element = elem;
if (options.method == 'html') {
element.innerHTML = options.event;
} else if (options.method == 'append') {
element.innerHTML = element.innerHTML + options.event;
} else if (options.method == 'prepend') {
element.innerHTML = options.event + element.innerHTML;
}
return // return method to apply string to func: parameter function as below is "e"
},
extend: function(a, b) {
for (var prop in b) {
a[prop] = b[prop];
}
return a;
}
};
$ = bQuery;
$.one(document.getElementById("log"), {
method: 'append',
event: 'rjfjfjjffj',
func: function(e){
alert(e);
}
});
If I understood you right, you want
var options, element;
…
return function() {
if (typeof options.func == 'function')
return options.func(element.innerHTML);
};

Why is this prototype failing?

function _(e) {
if (typeof e == 'string') {
if (e.charAt(0) == '#') {
return document.getElementById(e.slice(1));
} else if (e.charAt(0) == '.') {
var c = document.getElementsByClassName(e.slice(1));
return (c.length==1)?c[0]:c;
} else {
var t = document.getElementsByTagName(e);
return (t.length==1)?t[0]:t;
}
} else {
console.log('Error. Not a valid string in _.');
}
}
_.prototype.hide = function() {
//testing
console.log(this)
}
The function works fine, but when i try to add the method hide and try and call it like _('#menu').hide();, it throws the error: TypeError: Object #<HTMLDivElement> has no method 'hide' What have I misunderstood?
And yes, I did google this problem, I just don't get it. A hint would be much appreciated.
The constructor function needs to return itself (return this;). Currently it returns a DOM object, or undefined if no string is passed.
Try this:
function _(e) {
if (!(this instanceof _)){
return new _(e);
}
if (typeof e == 'string') {
if (e.charAt(0) == '#') {
this.el = document.getElementById(e.slice(1));
} else if (e.charAt(0) == '.') {
var c = document.getElementsByClassName(e.slice(1));
this.el = (c.length==1)?c[0]:c;
} else {
var t = document.getElementsByTagName(e);
this.el = (t.length==1)?t[0]:t;
}
return this;
} else {
console.log('Error. Not a valid string in _.');
throw e + ' is not a valid string';
}
}
_.prototype.hide = function() {
console.log(this);
}
You can invoke the constructor like so:
e = _('#myDiv');
e.hide();
You use a constructor function as a regular function, so it won't create an object, it will just return what you specified.
You can use it as a regular function, but then you need to call itself as a constructor to create the object to return, and handle when it's used as a constructor:
function _(e) {
if (!this instanceof _) {
if (typeof e == 'string') {
if (e.charAt(0) == '#') {
return new _(document.getElementById(e.slice(1)));
} else if (e.charAt(0) == '.') {
var c = document.getElementsByClassName(e.slice(1));
return new _((c.length==1)?c[0]:c);
} else {
var t = document.getElementsByTagName(e);
return new _((t.length==1)?t[0]:t);
}
} else {
console.log('Error. Not a valid string in _.');
}
} else {
this.elements = e;
}
}
You might consider to always use an array for the elements, even when it's a single element. Right now the elements property will either be an element or an array of elements, so you have to check for this every time you use it...
Try to define your function like this:
var _ = function (e) { };
EDIT
And yes don't forget to return this of course.

What is the best practice to make an object in JS like this: T('isArray')([]) == T.run('isArray')([]) == T().run('isArray')?

What is the best practice to make an object in JavaScript like this, knowing T is the main object:
T('isArray')([])
T.run('isArray')([])
T().run('isArray')([])
T('isArray', [])
T.run('isArray', [])
T().run('isArray', [])
They all must use the same function.
Since the main object can be called it must be a function. The function should decide what to return based on the arguments:
var T = (function() {
var functions = { // define functions that can be run like isArray
isArray: function(a) {
return Array.isArray(a);
},
log: function(a, b) {
console.log(a + b);
}
};
var noop = function() {}; // function doing nothing (no operation)
var T = function(f) {
if(arguments.length >= 2) { // function + args provided
return (functions[f] || noop) // call it
.apply(this, [].slice.call(arguments, 1));
} else if(arguments.length === 1) { // only function provided
return function() { // return function that can be called with args
return (functions[f] || noop)
.apply(this, arguments);
}
} else { // nothing provided, return T itself (so that e.g. T.run === T().run)
return T;
}
}
T.run = function() { // run function
return T.apply(this, arguments);
};
T.getState = function() { // another function
console.log("Not implemented");
};
return T; // actually return T so that it gets stored in 'var T'
})();
// tests
console.log(
T('isArray')([]),
T.run('isArray')([]),
T().run('isArray')([]),
T('isArray', []),
T.run('isArray', []),
T().run('isArray', [])
);
T('log')(1, 2);
T.getState();

Categories

Resources