给vue提个PR(一个数组引发的血案)

3,510 阅读3分钟

给vue提个PR,没想到回复的这么快,赞赞赞,但关键是mobx修复这个bug呢,还是vue来修复呢?还是我们开发者使用的姿势不对?

avatar

最近在开发一个项目,使用到了mobx+react,然后我们需要接入一个第三方的sdk擎天柱,这个sdk是使用vue开发的,然后就悲剧了,一直报错

Uncaught TypeError: 'set' on proxy: trap returned falsish for property '__proto__'

弄了一上午,终于定位到了这个问题,下面是我的debug的整个过程,大概的代码如下所示(公司项目代码就不放了,代码类似于这样)

const data = mobx.observable({
            name: 'like',
            age: 1,
            role: [{
                title: 'student',
                name: "111"
            }, {
                title: 'student2',
                name: "222"
            }]
        })
        var app = new Vue({
            el: '#app',
            data:data
        })

其中data是一个Proxy对象,把他赋值给vue中的data就会报错,然后,我想是不是data的问题,我试着把data数据toJS还原成一个js对象,发现就不报错了,代码如下

 const data = mobx.observable({
            name: 'like',
            age: 1,
            role: [{
                title: 'student',
                name: "111"
            }, {
                title: 'student2',
                name: "222"
            }]
        })
        
        var app = new Vue({
            el: '#app',
            data: mobx.toJS(data)
        })

顺着这个报错信息 ,我们一步一步来查看到底是为什么会报错,首先new Vue的时候会调用initState方法,如果传入的data存在,就用调用initData,如图所示

avatar

在initData方法中又调用了observe方法,继续往下看(object's property keys into getter/setters,给data的属性转换成vue定制的seteter/getter方法) 调用walk方法,walk就是递归的给每个对象的属性添加方法(set/get)

avatar
avatar
avatar
avatar
avatar
avatar

到这里发现只要mobx属性中有数组,并且这个对象有__proto__,就会执行这个方法,对target.__proto__进行赋值就报错了。

hasProto = '__proto__' in {} 
 // 来判断浏览器是不是ie
 //参考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
var noIe = ('__proto__' in {});
alert(noIe);
//或者
if('__proto__' in {}){非ie}
else{ie}

其中src为 var arrayProto = Array.prototype上的方法

 var arrayProto = Array.prototype;
 var arrayMethods = Object.create(arrayProto);

why?这时候,我反过来查了下proxy的一些信息如下 developer.mozilla.org/zh-CN/docs/… (

在开发者工具中复制下面代码试了下,retturn false 果然报错了

'use strict'
    const obj2 = {};
    const p2 = new Proxy(obj, {
        set: function(target, prop, value, receiver) {
            console.log("called: " + prop + " = " + value);
            return false;
        }
    });
    
p2.__proto__ = Array.prototype

VM765:10 Uncaught TypeError: 'set' on proxy: trap returned falsish for property '__proto__'
    at <anonymous>:10:18

试试return true 看看会发生什么情况,哎,果然没报错

'use strict'
    const obj3 = {};
    const p3 = new Proxy(obj, {
        set: function(target, prop, value, receiver) {
            console.log("called: " + prop + " = " + value);
            return true;
        }
    });
    
    p3.__proto__ = Array.prototype
    called: __proto__ = [constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ, …]

那么mobx对这个proxy对象做了些什么,我们回过头在mobx源码里面看看,mobx确实是在严格模式下

'use strict';

/*! *****************************************************************************
    Copyright (c) Microsoft Corporation. All rights reserved.
    Licensed under the Apache License, Version 2.0 (the "License"); you may not use
    this file except in compliance with the License. You may obtain a copy of the
    License at http://www.apache.org/licenses/LICENSE-2.0

    THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
    WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
    MERCHANTABLITY OR NON-INFRINGEMENT.

    See the Apache Version 2.0 License for specific language governing permissions
    and limitations under the License.
    ***************************************************************************** */
    /* global Reflect, Promise */

在看看他的set方法的处理,还记得开始我们对data进行操作的时候调用了observable, 接下来遍历observableFactories,把observableFactories的方法对复制到obserale中, 我们上文中知道我们是在处理数组的时候发生了错误,所以我们来看看array方法中到底做了什么,终于找到罪魁祸首了,原来mobx对array对象进行proxy处理的时候,不允许对__proto__进行改写,但是vue对其进行了改写,原来都是数组惹的祸,那么我们把数组去掉,看看会发生什么,惊奇的发现他不报错了,这么看来这个问题就得到了答案

不报错的代码(toJS或者data里面没有数组就可以)
 const data = mobx.observable({
            name: 'like',
            age: 1,
            role: [{
                title: 'student',
                name: "111"
            }, {
                title: 'student2',
                name: "222"
            }]
        })
        
        var app = new Vue({
            el: '#app',
            data: mobx.toJS(data)
        })

最后,给vue提个PR,vue的posva回复真快。。。 github:github.com/daydream-li…