I am getting a Maximum call stack size exceeded error whenever I try to use Object.observe to observe changes in an object that I defined properties for through Object.defineProperty.
What is the correct way to get around throwing this error while still being able to use both of these methods?
Note: Object.observe is only available in Chrome and Opera
var TestModule = (function () {
"use strict";
function TestClass() {
this.testValue = 0;
Object.defineProperty(this, "testValue", {
get: function () {
return this.testValue;
},
set: function (value) {
this.testValue = value;
},
enumerable: true,
configurable: false
});
}
return {
TestClass: TestClass
};
}());
<!DOCTYPE html>
<head>
<title>Stack Exceed Test</title>
<script src="../js/TestModule.js"></script>
</head>
<body>
<main>
<div id="logger" role="log"></div>
</main>
<script>
document.addEventListener("DOMContentLoaded", function () {
var logger = document.getElementById("logger"),
tc = new TestModule.TestClass();
function log(message) {
if (logger) {
logger.innerHTML = message;
} else {
console.error(message);
}
}
if (typeof Object.observe === "function") {
Object.observe(tc, function (changes) {
console.log("Change");
});
try {
tc.testValue = 5;
} catch (e) {
log(e);
}
} else {
log("Object.observe is unsupported in your browser");
}
});
</script>
</body>
You are reading and writing to the same variable over and over again in Object.defineProperty...
You should change the name of this.testValue in the first line of TestClass. I would suggest renaming it to this._testValue which is a convention for naming variables to indict they are "private".
Note, you can also keep this.testValue and completely remove the Object.defineProperty... section, because all you're doing is reading and writing the value, which is default.
var TestModule = (function () {
"use strict";
function TestClass() {
this._testValue = 0;
Object.defineProperty(this, "testValue", {
get: function () {
return this._testValue;
},
set: function (value) {
this._testValue = value;
},
enumerable: true,
configurable: false
});
}
return {
TestClass: TestClass
};
}());
<!DOCTYPE html>
<head>
<title>Stack Exceed Test</title>
<script src="../js/TestModule.js"></script>
</head>
<body>
<main>
<div id="logger" role="log"></div>
</main>
<script>
document.addEventListener("DOMContentLoaded", function () {
var logger = document.getElementById("logger"),
tc = new TestModule.TestClass();
function log(message) {
if (logger) {
logger.innerHTML = message;
} else {
console.error(message);
}
}
if (typeof Object.observe === "function") {
Object.observe(tc, function (changes) {
console.log("Change");
});
try {
tc.testValue = 5;
} catch (e) {
log(e);
}
} else {
log("Object.observe is unsupported in your browser");
}
});
</script>
</body>
Another way to solve this issue, if you don't want to sort of "wrap" your value with indirection, is to use Object.getNotifier() which allows you to emit notifications manually and keep a local variable that isn't a member of your object.
If you use the notifier you can get around having to have object properties that won't actually be used. If you use the wrapping method, you will have both _testValue and testValue on the object. Using the notifier, you will only have testValue.
Consider the code change:
function TestClass() {
var testValue, notifier;
/*
* The new locally scoped varible which will
* be captured by the getter/setter closure
*/
testValue = 0;
/*
* Create a notifier for the object
* which we can use to trigger
* Object.observe events manually
*/
notifier = Object.getNotifier(this);
Object.defineProperty(this, "testValue", {
get: function () {
return testValue;
},
set: function (value) {
/*
* Use the notifier to trigger
* the observe()
*/
notifier.notify({
type: "update",
name: "testValue",
oldValue: testValue
});
testValue = value;
},
enumerable: true,
configurable: false
});
}
Related
This is an evolution of a question I asked more than a year ago: How to create methods with a loop in jquery/javascript
I've a code that is shared with other co-workers so it's better if it changes not much. It goes like this:
var scriptList = {
components : [
'all'
],
modules : [
'one',
'two',
'three'
]
}
function core() {
var scope = this;
var promises = [];
jQuery.each(scriptList, function(key, value) {
jQuery.each(value, function (index, name) {
var hookValue = 'hook_'+name,
stringValue = 'string_'+name,
argsValue = 'args_'+name;
scope[name] = function(callback){
window[hookValue] = jQuery('.js-'+name),
window[stringValue] = 'js-'+name;
window[argsValue] = arguments;
loadAndUse(window[hookValue],key+'/'+name,callback);
}
if(key === 'modules'){
scope[name]();
}
});
});
jQuery.when.apply(jQuery, promises).then(function() {
window.executeReady = true;
});
}
ui = new core();
ui.exec = methodLoader;
ui.exec();
This code works fine, because I can use the various method I added with ui.one - ui.two and so on and is also logged in the console if I do console.log(ui).
Before this code gets fired tho, I have now another block of code inside the HTML page, which create a method (always of the ui object) called exec:
window.executeReady = false;
var ui = {},
scriptToBeLoaded = [];
var methodLoader = function(){
var scope = this;
this.exec = function(module, callback){
scriptToBeLoaded.push({
'module' : module,
'callback' : callback
});
if(module === undefined){
console.warn('This module does not exists. Please check the scriptList.');
} else {
function waitForList($context, $variable, $callback) {
if ($context[$variable]) {
$callback();
} else {
Object.defineProperty($context, $variable, {
configurable: true,
enumerable: true,
writeable: true,
get: function() {
return this['_' + $variable];
},
set: function(val) {
this['_' + $variable] = val;
$callback();
}
});
}
}
waitForList(window, 'executeReady', function(){
for (var i = 0; i < scriptToBeLoaded.length; i++) {
ui[scriptToBeLoaded[i].module](scriptToBeLoaded[i].callback);
}
scriptToBeLoaded = [];
});
}
};
};
ui = new methodLoader();
Because of this block of code, when I console.log(ui); I see only the exec method and all of the other methods are gone. Although, the method I create in the core() function are executed correctly, but not present in the ui object.
I would like to edit the code in the HTML Page to have the ui object with exec (which is create on the html side) and the other method (that are created in the js file) all inside the ui object.
How can I achieve that?
You can add new methods to existing object like this. Or you can use jQuery.extend() to merge two object.
var ui = ui || {},
scriptToBeLoaded = [];
ui.exec = function(module, callback){
scriptToBeLoaded.push({
'module' : module,
'callback' : callback
});
if(module === undefined){
console.warn('This module does not exists. Please check the scriptList.');
} else {
function waitForList($context, $variable, $callback) {
if ($context[$variable]) {
$callback();
} else {
Object.defineProperty($context, $variable, {
configurable: true,
enumerable: true,
writeable: true,
get: function() {
return this['_' + $variable];
},
set: function(val) {
this['_' + $variable] = val;
$callback();
}
});
}
}
waitForList(window, 'executeReady', function(){
for (var i = 0; i < scriptToBeLoaded.length; i++) {
ui[scriptToBeLoaded[i].module](scriptToBeLoaded[i].callback);
}
scriptToBeLoaded = [];
});
}
};
I have a plugin called "myPlugin" which uses the commonly used design pattern of default values which can be overridden upon initialization.
Instead of passing the overriding values upon initialization, I would like to extend the plugin as "myPlugin2" and change the default values so that when I initiate the extended plugin, it already has the desired new default values.
I've played around with adding new methods to the extended plugin, but I can't figure out how to change the default values.
In other words, I want the two lines of code to provide identical results.
$("body").myPlugin({'prop1':'prop1 modified','default_func4':function () {console.log('default_func4 modified')}});
$("body").myPlugin2();
How can I extend a jQuery plugin and change the default values?
http://jsfiddle.net/L1ng37wL/2/
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Testing</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.js" type="text/javascript"></script>
<style type="text/css">
</style>
<script type="text/javascript">
(function ($) {
var defaults = {
'prop1' : 'prop1',
'default_func4' : function () {console.log('default_func4');},
'default_func5' : function () {console.log('default_func5');}
};
var methods = {
init: function (options) {
console.log("init");
console.log('defaults',defaults);
console.log('options',options);
var settings = $.extend({}, defaults, options);
console.log('settings',settings);
console.log('The value of "prop1" is '+settings.prop1);
settings.default_func4.call()
},
func1: function () {console.log("func1");},
func2: function () {console.log("func2");}
};
$.fn.myPlugin = function (method) {
// Method calling logic
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist');
}
};
})(jQuery);
(function ($) {
var methods = {
'func1': function () {console.log("myPlugin2: func1");},
'func3': function () {console.log("myPlugin2: func3");}
}
$.fn.myPlugin2 = function (method) {
//HOW DO I CHANGE defaults.prop1 and defaults.default_func5?????
// Method calling logic
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if ((typeof method === 'object' || !method) && methods.init) {
return methods.init.apply(this, arguments);
} else {
try {
return $.fn.myPlugin.apply(this, arguments);
} catch (e) {
$.error(e);
}
}
}
})(jQuery);
$(function(){
$("body").myPlugin({
'prop1':'prop1 modified',
'default_func4':function () {console.log('default_func4 modified')}
});
$("body").myPlugin2();
//$("body").myPlugin2('func1');
//$("body").myPlugin2('func2');
//$("body").myPlugin2('func3');
});
</script>
</head>
<body>
</body>
</html>
The double check on the method parameter feels a bit weird to me, but if I replace your single line in the try-block to the code below, the thing works like it should, while still allowing you to supply an object with even different options.
var args = arguments;
if (typeof method === 'object' || !method) {
// Fill args with the new defaults.
args = {
'prop1': 'prop1 modified',
'default_func4': function () {
console.log('default_func4 modified')
}
};
$.extend(args, method);
args = [args];
}
return $.fn.myPlugin.apply(this, args);
I have a javascript object containing many functions like this
function obj()
{
this.test1_func = function()
{
return "This is function 1";
}
this.test2_func = function()
{
return "This is function 2";
}
this.test3_func = function()
{
return "This is function 3";
}
// and many other functions like test"x"_func ...
}
No inside the definition of the object, I define properties like this
Object.defineProperty(this, 'test1',
{
get : function()
{
return this.test1_func();
}
});
Object.defineProperty(this, 'test2',
{
get : function()
{
return this.test2_func();
}
});
Object.defineProperty(this, 'test3',
{
get : function()
{
return this.test3_func();
}
});
is there a way you can have the properties names in an array and a function that defines all of them ?? I made a function that works fine, but I used eval and I want to know if is there a way you can do it without eval
this.defineProp = function (prop)
{
for(key in prop)
{
Object.defineProperty(this,prop[key],{ get : function() { return eval("this." + prop[key] + "_func();"); } });
}
}
You should be able to use the square-bracket notation:
Object.defineProperty(this,prop[key],{ get : function() { return this[prop[key] + "_func"](); } });
Or more concisely:
Object.defineProperty(this,prop[key],{ get: this[prop[key] + "_func"]; } });
Try this:
this.defineProp = function (prop)
{
for(key in prop)
{
Object.defineProperty(this,prop[key],{ get : function() { return this[prop[key]+"_func"](); } });
}
}
Why not simply expose the functions via another function?
obj.prototype.get = function(testName) {
return this[testName + _func];
};
So instead of:
obj.test1();
You need:
obj.get('test1')();
I have this code which is working with the localStorage html5 calls. However it has to be rewritten for a Chrome Desktop app and I can't figure out how to port it over.
window.fakeStorage = {
_data: {},
setItem: function (id, val) {
return this._data[id] = String(val);
},
getItem: function (id) {
return this._data.hasOwnProperty(id) ? this._data[id] : undefined;
},
removeItem: function (id) {
return delete this._data[id];
},
clear: function () {
return this._data = {};
}
};
function LocalScoreManager() {
this.key = "bestScore";
var supported = this.localStorageSupported();
this.storage = supported ? window.localStorage : window.fakeStorage;
}
LocalScoreManager.prototype.localStorageSupported = function () {
var testKey = "test";
var storage = window.localStorage;
try {
storage.setItem(testKey, "1");
storage.removeItem(testKey);
return true;
} catch (error) {
return false;
}
};
LocalScoreManager.prototype.get = function () {
return this.storage.getItem(this.key) || 0;
};
LocalScoreManager.prototype.set = function (score) {
this.storage.setItem(this.key, score);
};
The error I get says "window.localStorage is not available in packaged apps. Use chrome.storage.local instead."
My attempt to rewrite it was this so far.. but it is breaking somewhere along the way.
$(document).ready(function() {
$("body").bind('keyup', function() {
var number = $(".best-container").val();
if(number == 'undefined'){
var number = "0";
}
chrome.storage.local.set({'bestScore': number});
});
chrome.storage.local.get('bestScore', function (result) {
hello= result.bestScore || 0;
$(".best-container").val(hello);
});
});
Porting localStorage to chrome.storage has one important pitfall: chrome.storage methods are asynchronous whereas localStorage access is synchronous.
That means: If you try to get a value from chrome.storage before the callback of the set method has been called, the value will still be undefined
Wrong way:
chrome.storage.local.set({'key': value});
...
chrome.storage.local.get('key', function(items) {
if(items.key) // won't be able to find the key
alert(items.key);
});
Correct way:
chrome.storage.local.set({'key': value}, function() {
...
chrome.storage.local.get('key', function(items) {
if(items.key)
alert(items.key); // will be "value"
});
});
or rather:
chrome.storage.local.set({'key': value}, function() {
doFurtherStuff();
});
function doFurtherStuff() {
...
chrome.storage.local.get('key', function(items) {
if(items.key)
alert(items.key); // will be "value"
});
}
I'm creating a small tooltip application and I'm having trouble. I'm trying to add an event to the document, but am having trouble referencing the function that needs to be executed. Here is the code:
var Note, note;
(function () {
'use strict';
// Helper functions
function addEvent(to, type, fn) {
if (to.addEventListener) {
to.addEventListener(type, fn, false);
} else if (to.attachEvent) {
to.attachEvent('on' + type, fn);
} else {
to['on' + type] = fn;
}
}
// Temporary constructor
function Temp() {
this.dragging = false;
return this;
}
Temp.prototype = {
listen: function () {
this.dragging = true;
},
drag: function () {
alert('hi 1');
if (!this.dragging) return;
alert('hi 2');
},
create: function () {
// unimportant code ...
addEvent(document, 'mousedown', this.drag);
// unimportant code ...
}
};
window.Note = Temp;
}());
note = new Note();
note.create(); // the note is created as planned
note.listen(); // alert(note.dragging) yields true
If there are small mistakes in the code I don't think those are the problem, the code on my system passes JSLint (I know that doesn't guarantee correctness). Neither of the alerts alert their arguments; I suspect, though, that the problem is assigning 'this.drag' as the function reference to the event handler. Are there any workarounds for this?
Thank you all for your time!
Try next:
(function () {
'use strict';
// Helper functions
function addEvent(to, type, fn) {
if (to.addEventListener) to.addEventListener(type, fn, false);
else if (to.attachEvent) to.attachEvent('on' + type, fn);
else to['on' + type] = fn; // this is bad. this do not support multiple handlers
}
// Temporary constructor
function Temp() {
this.dragging = false;
}
Temp.prototype = {
constructor: Temp, // needed because it is removed when used Temp.prototype={...}
listen: function () {
this.dragging = true;
},
drag: function () {
alert('hi 1');
if (!this.dragging) return;
alert('hi 2');
},
create: function () {
//addEvent(document, 'mousedown', this.drag);
addEvent(document, 'mousedown', this.drag.bind(this));
// or in older maner (when .bind() not implemented):
//var self=this;
//addEvent(document, 'mousedown', function(){self.drag();});
}
};
window.Note = Temp;
})();
var note = new Note();
note.create(); // the note is created as planned
note.listen(); // alert(note.dragging) yields true