vuejs / Discussion

Vue.js discussion
167 stars 17 forks source link

TypeError: can't redefine non-configurable property "toJSON" #365

Closed Haixing-Hu closed 9 years ago

Haixing-Hu commented 9 years ago

Hi, I get a type error when loading pages with VueJS.

TypeError: can't redefine non-configurable property "toJSON"

The error occurs at the line 130 of the file src/observer/index.js.

p.convert = function (key, val) {
  var ob = this
  var childOb = ob.observe(val)
  var dep = new Dep()
  Object.defineProperty(ob.value, key, {        // HERE the error occurs
    enumerable: true,
    configurable: true,
    get: function () {
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
        }
        if (_.isArray(val)) {
          for (var e, i = 0, l = val.length; i < l; i++) {
            e = val[i]
            e && e.__ob__ && e.__ob__.dep.depend()
          }
        }
      }
      return val
    },
    set: function (newVal) {
      if (newVal === val) return
      val = newVal
      childOb = ob.observe(newVal)
      dep.notify()
    }
  })
}

After carefully debuging, I found that the Vue walks through all properties of an object and define the getter and setter for the property (in the function p.walk in src/observer/index.js).

My component has a object of the type Location, which is the build-in DOM object (in fact I don't know where did it come from; I haven't define a Location object in the data property of the component).

The Location object has properties named toJSON and toString. It seems that the Vue tries to redefine getter and setter for the toJSON and toString properties of the Location object, which causes the type error.

Haixing-Hu commented 9 years ago

Finally, I found the bug!

I define a variable table in the data property of the component. The variable table is used to store the DataTables object (see http://datatables.net) created in the ready function, since I need the object to control the data tables.

But unfortunately the DataTable object contains a property called context, which contains a property of the type HTMLDocument; and the HTMLDocument object contains a property of the type Location --- That's the cause of the bug!!

Now I still have a question: how do I specify a variable in the data property which should not be observed by the VM?

yyx990803 commented 9 years ago

I've made the observer only observe object if it is a plain object, so it should automatically skip DOM objects in the next version.

Mat-Moo commented 9 years ago

@yyx990803 This now means that where I have a plain javascript class that I can no longer access functions within them e.g.

var colourClass = function(){
  return {
    r : 1,
    g : 2,
    b : 0,
    asRGB : function() {
        return 'rgba(' + this.r + ', ' + this.g + ', ' + this.b + ')';
  }
};

So this works fine in 0.12, but now in 1 alpha asRGB is an unknown. I have a several widgets that all have multiple colour classes in them that all use asRGB (and other computed, above is a simplistic version) as computed styles. Could this be done via a mixin?

yyx990803 commented 9 years ago

@Mat-Moo it only skips native objects, e.g. window, DOM nodes, etc. Any user defined class instances are also "plain objects", but Vue will not observe any prototype properties.

Mat-Moo commented 9 years ago

I think it maybe a code bug, wrapping my head around all the changes and tiredness not helping.

Haixing-Hu commented 9 years ago

@yyx990803 I think it's better to let user specify which data variables should not be observed. For example, we may have a new property of an Vue object, say, "unobservable", and specify the name of the unobservable data variables in that property.

var vm = new Vue({
   ...
   data: {
     name: "",
     age: 0,
     table: null,
     dom: null
   },
  unobservable: ["table", "dom"],
  ...
});

The above code indicate that the Vue should not observe the "table" and "dom" variable, since they could be a complex object, and they are only used to store the internal state of the VM.

Another way is as follows:

var vm = new Vue({
   ...
  data: { 
     name: "",
     aga: 20
  },
  unobservable: {
     table: null,
     dom: null
  },
  ...
});

Seems that the second way is better.

yyx990803 commented 9 years ago

@Haixing-Hu why do you even need that if you don't want to observe it? Just do this:

var vm = new Vue({
   ...
  data: { 
     name: "",
     aga: 20
  },
  created: function () {
    this.table = ...
    this.dom = ...
  }
  ...
})
Haixing-Hu commented 9 years ago

@yyx990803 yes you are right 😓

saul commented 8 years ago

@yyx990803 your solution does not consider the case where you don't want props to be observable. While passing three.js objects around in component props, I'm getting a similiar 'cannot redefine property' error.

Obviously I want to know when the property is changed, but only as a 'shallow' watch, not deep. There's currently no way for me to do this.