最近在学习three.js,同时也学习一下vue3,然后就出现问题了,报错直接用不了,错误信息如下:
Uncaught TypeError: 'get' on proxy: property 'modelViewMatrix' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '#' but got '[object Object]')
这个是什么鬼???相信大家都把错误信息复制到百度搜了一下解决方案吧?遇到问题的人并不多,解决方案就是把scene
、mesh
啥的放到全局变量中,不放到data
中,好的问题解决了。
我这样写是不是有点水文的嫌疑?作为一个成熟的程序员怎么能水文呢?我得找到为什么会出现这个问题才行,于是我花了两个小时找到问题所在,也是怪自己不够专业,不然应该花不了两个小时。
1. vue的问题?
先思考一个问题,网上的解决方案为什么要将几个场景对象定义在全局中,而不能定义在vue
的data
中呢?全局和data
中的变量有什么区别?应该就是全局的对象不是双向绑定的,data
中的数据可以实现双向绑定,那么为什么双向绑定会出现这个问题呢?带着问题寻找答案
众所周知,vue3
是通过Proxy
实现的数据双向绑定,vue2
是通过defineProperty
实现的数据双向绑定。
尝试的使用了一下vue2
没有发现这个问题,那么问题应该就是出现在vue3
使用Proxy
的问题上。
上面说了一堆,就是为了引出Proxy
的异常情况,好了又可以学习一下Proxy
的知识了
2. Proxy的异常情况
在使用Proxy
时,当属性存在属性特性configurable: false, value: undefined,
时,则取其属性值时会报错:
const handle = {
get() {
return 1;
},
};
const obj = Object.create(null);
Object.defineProperty(obj, 'a', {
configurable: false,
});
Object.defineProperty(obj, 'b', {
value: undefined,
});
Object.defineProperty(obj, 'c', {
configurable: true,
value: 'c',
});
const proxy = new Proxy(obj, handle);
console.log(proxy.a); // 报错TypeError: 'get' on proxy: property 'a' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'undefined' but got '1')
console.log(proxy.b); // 报错Uncaught TypeError: 'get' on proxy: property 'b' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'undefined' but got '1')
console.log(proxy.c); // 1
看看上面的报错,是不是很熟悉?这样看是不是一下就知道是什么问题了?
3. Three.js 的问题
this.scene = new Scene();
this.scene.modelViewMatrix;
直接运行上面的代码,就会看到文首出现的错误,错误原因就是因为configurable
设置为false
,找到问题就要解决问题,是不是觉得直接使用defineProperty
就可以解决了?
很抱歉,这个不行,再来学习一下defineProperty
。
4. defineProperty异常情况
MDN中是这样描述的
如果属性已经存在,
Object.defineProperty()
将尝试根据描述符中的值以及对象当前的配置来修改这个属性。如果旧描述符将其configurable
属性设置为false
,则该属性被认为是“不可配置的”,并且没有属性可以被改变(除了单向改变 writable 为 false)。当属性不可配置时,不能在数据和访问器属性类型之间切换。
当试图改变不可配置属性(除了value
和writable
属性之外)的值时,会抛出TypeError
,除非当前值和新值相同。
也就是说之前定义了configurable
为false
,就不能再将configurable
改为true
了,那怎么办?我说了问题当然是要给你解决的。
4. 解决
之前在网上查了,全局变量来处理,但是我使用的vue
啊,我当然是希望将它定义到data
中的,但是定义到data
中就会自动生成代理,那就只能从源码入手了。
我也就不讲我是怎么去找源码的,我直接上解决之后的吧,在node_modules\three\build\three.module.js
这个文件中,第7392行,里面的代码如下:
Object.defineProperties( this, {
position: {
configurable: true,
enumerable: true,
value: position
},
rotation: {
configurable: true,
enumerable: true,
value: rotation
},
quaternion: {
configurable: true,
enumerable: true,
value: quaternion
},
scale: {
configurable: true,
enumerable: true,
value: scale
},
modelViewMatrix: {
value: new Matrix4()
},
normalMatrix: {
value: new Matrix3()
}
} );
看到了吧,里面有一个modelViewMatrix
属性,它没有设置configurable
属性描述,也就是默认为false
,加上就好了,改好了如下:
Object.defineProperties( this, {
position: {
configurable: true,
enumerable: true,
value: position
},
rotation: {
configurable: true,
enumerable: true,
value: rotation
},
quaternion: {
configurable: true,
enumerable: true,
value: quaternion
},
scale: {
configurable: true,
enumerable: true,
value: scale
},
modelViewMatrix: {
configurable: true,
value: new Matrix4()
},
normalMatrix: {
value: new Matrix3()
}
} );
然后重启服务,就不会报错了,当然这种方式是有缺陷的,因为改了只是你本地的,你其他同事的代码并没有改,如果要升级three.js
也会把改了的代码重新覆盖,同时也不知道为什么three.js
要这样处理这个变量。
那么有没有更好的解决方案呢?答案当然是有的
5. 最佳解决方案
上面说的解决方案其实都不够正常,一般使用第三方库是不建议修改库的源码,因为你不知道会产生什么样的未知问题,同时需要考虑协同的问题,第三方库升级的问题等一系列的问题。
网上说的解决方案就是定义全局变量,全局变量无非就是失去了双向数据绑定,但是我们在使用的vue
的时候,不管数据是不是响应式的,都是希望能用this
给它点出来的,全局变量对应使用vue
的用户来说肯定不是很优雅的,甚至让人感觉到难受,那么怎么样才能用this
能点出来的同时让数据不是响应式的呢?
答案是在data
中直接定义,废话说的有点多,直接上代码:
data() {
// 场景对象
this.scene = null;
// 透视摄像机
this.camera = null;
// 渲染器
this.webGLRender = null;
return {
};
},
好了,问题圆满解决,撒花!!!!