I think the code will be a best explanation of my issue:
function getMyConstructor(){
...
}
const MyConstructor = getMyConstructor({
x: 0,
getX: function(){ return this.x; }
},{
x: 1,
getX: function(){ return this.__getX() + ' first'; }
},{
x: 2,
getX: function(){ return this.__getX() + ' second'; }
},{
x: 3,
getX: function(){ return this.__getX() + ' third'; }
});
const myInstance = new MyConstructor;
console.log( myInstance.getX() ) // 3 first second third
Does someone know how to implement the __getX method?
UPDATE:
I've made the code but I think that it's not so pretty
function getMyConstructor() {
return Array.prototype.reduce.call(arguments, (proto, obj, index, args) => {
function C() {
}
C.prototype = Object.create(proto);
for (let p in obj) {
if (!obj.hasOwnProperty(p)) {
continue;
}
C.prototype[p] = obj[p];
if (index > 0 && typeof obj[p] === 'function') {
C.prototype['__' + p] = setBaseMethod(p, proto);
}
}
return index === args.length - 1 ? C : C.prototype;
});
/**
* #param {string} method
* #param {Object} proto
* #returns Function
*/
function setBaseMethod(method, proto) {
let obj = {};
obj[method] = proto[method];
obj['__' + method] = proto['__' + method];
return function () {
let context = {};
for (let p in proto) {
if (this[p] && typeof this[p] !== 'function') {
Object.defineProperty(context, p, {
get: () => this[p],
set: value => this[p] = value,
enumerable: true,
configurable: true
});
}
}
return Object.assign({}, context, obj)[method]();
}
}
}
I hope the code suggest how to resolve the problem
Just for fun, since you asked for a prototype chain:
function chainPrototypes(...objs) {
return objs.reduce((p, o) => Object.setPrototypeOf(o, p));
}
const myInstance = chainPrototypes({
x: 0,
getX() { return this.x; }
}, {
x: 1,
getX() { return super.getX() + ' first'; }
}, {
x: 2,
getX() { return super.getX() + ' second'; }
}, {
x: 3,
getX() { return super.getX() + ' third'; }
});
console.log(myInstance.getX()); // 3 first second third
Just use method definitions with the super keyword.
Related
I have a serializable JavaScript object such as
{
a: 'hello ',
b: [],
c: 5,
d: {
e: ' world ';
},
}
and I would like to create a JavaScript object similar to the original object except that every string value has leading and trailing whitespace removed:
{
a: 'hello',
b: [],
c: 5,
d: {
e: 'world';
},
}
How can I do this?
JSON.stringify has a replacer parameter that we can use
const replacer = (key, value) => {
if (typeof value === 'string') {
return value.trim();
}
return value;
};
const trimmedObj = JSON.parse(JSON.stringify(obj, replacer));
Note that JSON does not have undefined values, so any keys with undefined values will be stripped out of the resulting object. You may use null in place of undefined if you wish to preserve these values and if null is suitable for your purposes.
const replacer = (key, value) => {
if (typeof value === 'string') {
return value.trim();
}
if (value === undefined) {
// JSON doesn't have undefined
return null;
}
return value;
};
You can recursively trim the object like so (note that my solution will mutate your initial object):
let obj = {
a: 'hello ',
b: [" foo ", " bar"],
c: 5,
d: {
e: ' world '
}
};
trimAll = obj => {
Object.keys(obj).map(key => {
if (typeof obj[key] === 'string') {
obj[key] = obj[key].trim();
} else {
trimAll(obj[key]);
}
});
}
trimAll(obj);
console.log(obj);
Try this:
function trimAll(obj) {
for (let k in obj) {
if (typeof obj[k] === 'string') obj[k] = obj[k].trim();
else if (typeof obj[k] === 'object' && !(obj[k] instanceof Array)) trimAll(obj[k]);
}
}
Here is my recursive function to handle this:
let x = {
a: 'hello ',
b: [' hi ', 'we '],
c: 5,
d: {
e: ' world '
},
};
function trimRecursively(value) {
if (Array.isArray(value)) {
return value.reduce((all, item) => {
all.push(trimRecursively(item));
return all;
}, []);
} else if (typeof value === 'object') {
return Object.keys(value).reduce((all, key) => {
all[key] = trimRecursively(value[key]);
return all;
}, {});
} else if (typeof value === 'string') {
return value.trim();
} else {
return value;
}
}
console.log(trimRecursively(x));
console.log(x);
My solution works for any type of value, and it also does not mutate the original object!
Let's consider the code below:
// Let's create a constructor "Point".
let Point = function(x,y) { this.x = x; this.y = y; };
Point.prototype = { x:null, y:null, print: function() { console.log("(" + this.x + "," + this.y + ")")} };
let p = new Point(1,2);
p.print();
console.log(Object.getPrototypeOf(p)); // => { x: null, y: null, print: [Function: print] }
console.log(Point.prototype === Object.getPrototypeOf(p)); // => true
console.log(p.constructor); // => Object
console.log(p.constructor.name); // => Object
console.log(p.constructor === Point); // => false
console.log(p.constructor === Object); // => true
console.log(p.prototype); // undefined
console.log(p instanceof Point); // => true
console.log(typeof p); // => Object
console.log();
// Let's create the constructor "Pigment" that derives from "Point".
let Pigment = function(x,y,c) {
Point.call(this, x, y);
this.color = c;
};
Pigment.prototype = new Point;
Pigment.prototype.paint = function () {
console.log(this.color);
};
let pi = new Pigment(1,2,'red');
console.log(pi);
pi.print();
pi.paint();
console.log(Object.getPrototypeOf(pi)); // => (Pigment) { x: undefined, y: undefined, paint: [Function] }
console.log(pi instanceof Object); // => true
console.log(pi instanceof Point); // => true
console.log(pi instanceof Pigment); // => true
console.log(pi.constructor === Object); // => true
console.log(pi.__proto__); // => (Pigment) { x: undefined, y: undefined, paint: [Function] }
console.log(pi.__proto__.__proto__); // => (Point) { x: null, y: null, print: [Function: print] }
console.log(pi.print); // => [Function: print]
console.log(pi.__proto__ === Object.getPrototypeOf(pi)); // => true
console.log(pi.__proto__ == Pigment.prototype); // => true
console.log(pi.__proto__.__proto__ === Point.prototype); // => true
The prototype of the constructor "Point" is :
{
x:null,
y:null,
print: function() { console.log("(" + this.x + "," + this.y + ")")}
}
The prototype of the constructor "Pigment" is an instance of "Point" :
Pigment.prototype = new Point;
Therefore I expect the prototype associated to the constructor "Pigment" to include the property "print". However, it is not the case. Indeed:
console.log(pi.__proto__);
Prints:
{ x: undefined, y: undefined, paint: [Function] }
Why is the property "print" missing?
--EDIT--
The example below works as expected :
var util = require("util");
function Person(age) {
this.age = age;
}
Person.prototype = {
age: null,
print : function(){
console.log("This person is " + this.age + " years old.");
}
};
function Student(age, level) {
Person.call(this, age);
this.level = level;
}
Student.prototype = new Person;
Student.prototype.level = null;
Student.prototype.talk = function () {
console.log("I talk");
};
Student.prototype.constructor = Student;
var b = new Student(10, 5);
b.print();
console.log(b.constructor);
console.log("b.proto:\n" + util.inspect(b.__proto__) + "\n");
console.log("b.proto.proto:\n" + util.inspect(b.__proto__.__proto__) + "\n");
Please note the added line:
Student.prototype.constructor = Student;
The output is:
This person is 10 years old.
[Function: Student]
b.proto:
Student {
age: undefined,
level: null,
talk: [Function],
constructor: [Function: Student] }
b.proto.proto:
{ age: null, print: [Function: print] }
This is correct.
Now, let's do what MeteorZero suggested: let's replace
Student.prototype = new Person;
By:
Student.prototype = Person.prototype;
Then, the result is:
This person is 10 years old.
[Function: Student]
b.proto:
Student {
age: null,
print: [Function: print],
level: null,
talk: [Function],
constructor: [Function: Student] }
b.proto.proto:
{}
This is not what we expect.
Your assumption here is wrong Pigment.prototype = new Point;. You have assigned new Point object for Pigment.prototype not Point.prototype object. I have modified your assignment so print function is available now
// Let's create a constructor "Point".
let Point = function(x,y) { this.x = x; this.y = y; };
Point.prototype = { x:null, y:null, print: function() { console.log("(" + this.x + "," + this.y + ")")} };
let p = new Point(1,2);
p.print();
console.log(Object.getPrototypeOf(p)); // => { x: null, y: null, print: [Function: print] }
console.log(Point.prototype === Object.getPrototypeOf(p)); // => true
console.log(p.constructor); // => Object
console.log(p.constructor.name); // => Object
console.log(p.constructor === Point); // => false
console.log(p.constructor === Object); // => true
console.log(p.prototype); // undefined
console.log(p instanceof Point); // => true
console.log(typeof p); // => Object
console.log();
// Let's create the constructor "Pigment" that derives from "Point".
let Pigment = function(x,y,c) {
Point.call(this, x, y);
this.color = c;
};
// Pigment.prototype = new Point;
Pigment.prototype = Point.prototype;
Pigment.prototype.paint = function () {
console.log(this.color);
};
let pi = new Pigment(1,2,'red');
console.log(pi);
pi.print();
pi.paint();
console.log(Object.getPrototypeOf(pi)); // => (Pigment) { x: undefined, y: undefined, paint: [Function] }
console.log(pi instanceof Object); // => true
console.log(pi instanceof Point); // => true
console.log(pi instanceof Pigment); // => true
console.log(pi.constructor === Object); // => true
console.log("pi.__proto__::", pi.__proto__); // => (Pigment) { x: undefined, y: undefined, paint: [Function] }
console.log(pi.__proto__.__proto__); // => (Point) { x: null, y: null, print: [Function: print] }
console.log(pi.print); // => [Function: print]
console.log(pi.__proto__ === Object.getPrototypeOf(pi)); // => true
console.log(pi.__proto__ === Pigment.prototype); // => true
console.log(pi.__proto__ === Point.prototype); // => true
I'm writing a simple EventEmitter is ES5.
The objective is to ensure that all properties on EventEmitter instances are
non-writable and non-configurable."
After 6 hours of racking my brain I still can't figure out how to, increase the listenerCount, for example if the configurable descriptor is set to false.
Here's an example of what I have:
var eventEmitter = function(){
var listeners = listeners || 0;
var events = events || {};
Object.defineProperties(this, {
listeners: {
value : 0,
configurable: false,
writable: false
},
events: {
value: {},
configurable : false,
writable: false
}
});
return this;
};
eventEmmitter.prototype.on = function(ev, cb) {
if (typeof ev !== 'string') throw new TypeError("Event should be type string", "index.js", 6);
if (typeof cb !== 'function' || cb === null || cb === undefined) throw new TypeError("callback should be type function", "index.js", 7);
if (this.events[ev]){
this.events[ev].push(cb);
} else {
this.events[ev] = [cb];
}
this.listeners ++;
return this;
};
I would recommend the use of an IIFE (immediatly invoked function expression):
var coolObj=(function(){
var public={};
var nonpublic={};
nonpublic.a=0;
public.getA=function(){nonpublic.a++;return nonpublic.a;};
return public;
})();
Now you can do:
coolObj.getA();//1
coolObj.getA();//2
coolObj.a;//undefined
coolObj.nonpublic;//undefined
coolObj.nonpublic.a;//undefined
I know this is not the answer youve expected, but i think its the easiest way of doing sth like that.
You can use a proxy which requires a key in order to define properties:
function createObject() {
var key = {configurable: true};
return [new Proxy({}, {
defineProperty(target, prop, desc) {
if (desc.value === key) {
return Reflect.defineProperty(target, prop, key);
}
}
}), key];
}
function func() {
var [obj, key] = createObject();
key.value = 0;
Reflect.defineProperty(obj, "value", {value: key});
key.value = function() {
key.value = obj.value + 1;
Reflect.defineProperty(obj, "value", {value: key});
};
Reflect.defineProperty(obj, "increase", {value: key});
return obj;
}
var obj = func();
console.log(obj.value); // 0
try { obj.value = 123; } catch(err) {}
try { Object.defineProperty(obj, "value", {value: 123}); } catch(err) {}
console.log(obj.value); // 0
obj.increase();
console.log(obj.value); // 1
I'm trying to rock a more functional style, and would like to set all properties of an object (and if possible sub-objects) to a specific value, e.g. false inplace. Is there a shortcut or do I have to iterate over the properties?
var obj = {
a: true,
b: true,
c: true,
...
z: true
}
Transforms into:
var obj = {
a: false,
b: false,
c: false,
...
z: false
}
You can use underscore for the more functional style.
You can iterate over your object if missing you can change or if it sub-object reiterate and change every missing sub-object properties.
function remap(object, missingValue, suppliedValue){
var keys= _.keys(object);
return _.reduce(keys, function(memo, key){
memo[key] = object[key];
if(memo[key] === missingValue){
memo[key] = suppliedValue;
}
if(_.isObject(memo[key])){
memo[key] = remap(memo[key],missingValue,suppliedValue);
}
return memo;
}, {});
}
var h = {val : 3, b : undefined, d : undefined , k : {
a: false, b: undefined
}, c: function(){ console.log(a);}};
console.log(remap(h,undefined,false));
If you need more complex check for comparing values then use the below function.
function remap(object, complexCheck){
var keys= _.keys(object);
return _.reduce(keys, function(memo, key){
memo[key] = object[key];
memo[key] = complexCheck(memo[key]);
if(_.isObject(memo[key])){
memo[key] = remap(memo[key],complexCheck);
}
return memo;
}, {});
}
I've written something similar for performing a regex replace on fields nested within my object which matched a given pattern. I then mixed it into the underscore/lodash object so I could use it like you're wanting to do.
Modified for your purposes it could look something like this:
function(obj) {
var $this = this,
checkField = function(field) {
if (typeof field === "undefined" || field === null || typeof field === "boolean") {
return false;
} else {
return field;
}
},
checkObject = function(obj) {
if (obj instanceof Object) {
Object.getOwnPropertyNames(obj).forEach(function (val) {
if (obj[val] instanceof Array) {
obj[val] = checkArray(obj[val]);
} else if (obj[val] instanceof Object) {
obj[val] = checkObject(obj[val]);
} else {
obj[val] = checkField(obj[val]);
}
});
return obj;
} else if (obj instanceof Array) {
return checkArray(obj);
} else {
return checkField(obj);
}
},
checkArray = function(arr) {
if (arr instanceof Array) {
arr.forEach(function(val) {
if (val instanceof Object) {
obj[val] = checkObject(val);
} else {
obj[val] = checkField(val);
}
});
return arr;
} else {
return arr;
}
};
obj = checkObject(obj);
}
To add it as a mixin:
window._.mixin({
setBoolsAndSuchToFalse: function(obj) {
. . . . // The contents of the function from above
}
});
Apple changed their home page with a fade in effect that loads fast. Is this HTML 5 or jQuery?
Does anyone know how they did this?
It is JavaScript. HTML5 is a markup language - it doesn't do dynamic things. But the way people are throwing around the term, you'd think it could cure world hunger.
It looks like they use the Prototype library - probably for legacy reasons, now that jQuery has gained more traction. Or maybe they just prefer that library.
In plain JavaScript, you can fade a div with window.setInterval() and animating style.opacity.
With this bit of javascript --> http://images.apple.com/global/scripts/ac_blackout.js.
Here it is after a run through http://jsbeautifier.org/:
AC.Blackout = Class.create({
defaultOptions: {
duration: 1.25,
delay: 2,
showOnce: false,
showWhenReferredByApple: true
},
initialize: function (c, a, b) {
this.uniqueIdentifier = a || Math.floor(Math.random() * 1000000);
this.options = Object.extend(Object.clone(this.defaultOptions), b);
if (!this.shouldShow()) {
return false
}
this._addBodyClass();
Event.onDOMReady(function () {
this.og = {};
this.og.element = $(c);
this.bo = {};
this.bo.offsets = this.og.element ? this.og.element.cumulativeOffset() : [0, 0];
this.images = [];
if (this.options.showOnce) {
this._setHasShown()
}
this._create();
this.fade.bind(this).delay(this.options.delay)
}.bind(this))
},
addImage: function (b, a) {
this.preloadImage(b);
if ( !! this._acceptImages) {
this._addImage(false, b, a)
} else {
this._boundAddImage = this._addImage.bindAsEventListener(this, b, a);
Event.observe(document.body, "ACBlackout:acceptImages", this._boundAddImage)
}
},
preloadImage: function (c) {
var b = function (d) {
delete a
};
var a = new Image();
a.onload = b;
a.src = c
},
_addImage: function (a, c, b) {
if (typeof this.images == "undefined") {
return false
}
this.images.push(new AC.Blackout.Image(this.bo, c, b))
},
wasReferredByApple: function () {
if (typeof this._wasReferredByApple !== "undefined") {
return this._wasReferredByApple
}
this._wasReferredByApple = document.referrer.match(/^\w*:\/\/[^\.]*.apple.com/);
if ( !! document.referrer.match(/\/home/)) {
return false
}
return this._wasReferredByApple
},
shouldShow: function () {
if (typeof this._shouldShow !== "undefined") {
return this._shouldShow
}
if (/msie|MSIE 6/.test(navigator.userAgent)) {
return this._shouldShow = false
}
this._shouldShow = true;
if (this.options.showOnce) {
if (!this.options.showWhenReferredByApple) {
if (!this.wasReferredByApple()) {
return this._shouldShow = true
}
}
try {
typeof localStorage
} catch (b) {
return this._shouldShow = false
}
if (typeof localStorage !== "undefined") {
try {
var a = localStorage.getItem("ACBlackout-" + this.uniqueIdentifier);
this._shouldShow = (a == null) ? true : false
} catch (b) {
return this._shouldShow = false
}
} else {
if ("addBehavior" in document.body) {
document.body.addBehavior("#default#userData");
document.body.load("ACBlackout");
this._shouldShow = document.body.getAttribute("ACBlackout-" + this.uniqueIdentifier) == null ? true : false
}
}
} else {
if (!this.options.showWhenReferredByApple) {
if ( !! this.wasReferredByApple()) {
this._shouldShow = false
}
}
}
return this._shouldShow
},
_addBodyClass: function () {
document.body.className += " ACBlackoutBody"
},
_setHasShown: function () {
var a = new Date;
a = a.getTime();
try {
typeof localStorage
} catch (b) {
return true
}
if (typeof localStorage !== "undefined") {
try {
localStorage.setItem("ACBlackout-" + this.uniqueIdentifier, a)
} catch (b) {
return true
}
} else {
if ("addBehavior" in document.body) {
document.body.addBehavior("#default#userData");
document.body.setAttribute("ACBlackout-" + this.uniqueIdentifier, a);
document.body.save("ACBlackout");
return true
} else {
return true
}
}
},
_create: function () {
this.bo.height = document.documentElement.clientHeight > document.body.scrollHeight ? document.documentElement.clientHeight : document.body.scrollHeight;
if ($("ACBlackout")) {
this.bo.element = $("ACBlackout")
} else {
this.bo.element = new Element("div", {
id: "ACBlackout",
"class": "ACBlackout",
style: "height: " + this.bo.height + "px;"
})
}
this._acceptImages = true;
Event.fire(document.body, "ACBlackout:acceptImages", true);
if (AC.Detector.isCSSAvailable("transition")) {
this.bo.element.setVendorPrefixStyle("transition", this.options.duration + "s opacity ease-in")
}
if (AC.Detector.isIE()) {
Element.insert(document.body, {
bottom: this.bo.element
})
} else {
Element.insert(document.body, {
top: this.bo.element
})
}
Element.removeClassName(document.body, "ACBlackoutBody")
},
fade: function () {
if (AC.Detector.isCSSAvailable("transition")) {
var b = function (c) {
c.target.hide();
c.target.removeVendorEventListener("transitionEnd", a)
};
var a = b.bindAsEventListener(this);
this.bo.element.addVendorEventListener("transitionEnd", a);
this.bo.element.setStyle("opacity: 0;")
} else {
this.bo.element.fade({
duration: this.options.duration
})
}
}
});
AC.Blackout.Image = Class.create({
defaultOptions: {
offsets: [0, 0],
dimensions: false,
duration: 0.75,
delay: 0
},
initialize: function (b, c, a) {
this.options = Object.extend(Object.clone(this.defaultOptions), a);
this.bo = b;
this.src = c;
this._create();
this.fadeIn.bind(this).delay(this.options.delay)
},
_create: function () {
this.left = this.options.offsets[0];
this.top = this.bo.offsets[1] + this.options.offsets[1];
this.main = new Element("div", {
"class": "ACBlackoutMain"
});
this.img = new Element("img", {
src: this.src,
"class": "ACBlackoutImg",
style: "top: " + this.top + "px; left: " + this.left + "px;"
});
if (this.options.dimensions) {
this.img.setStyle("width: " + this.options.dimensions[0] + "px; height: " + this.options.dimensions[1] + "px;")
}
if (AC.Detector.isCSSAvailable("transition")) {
this.img.setStyle("opacity: 0");
this.img.setVendorPrefixStyle("transition", this.options.duration + "s opacity ease-in")
} else {
this.img.hide()
}
this.bo.element.insert(this.main);
this.main.insert(this.img)
},
fadeIn: function () {
if (AC.Detector.isCSSAvailable("transition")) {
this.img.setStyle("opacity: 1;")
} else {
this.img.appear({
duration: this.options.duration
})
}
}
});