最近在学习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 {
};
},
好了,问题圆满解决,撒花!!!!