JS的平凡之路--模仿Vue写个数组监听

2,102 阅读4分钟

接着上一篇,本篇内容将介绍如何监听数组

一、分析

  相比较对象,数组就有点复杂了,为什么要这样说?对于对象我们多是通过key操作的,但是数组的操作就多了:

  • 通过length对数组的修改;
  • 通过Index访问和修改;
  • 通过Array.prototype上的方法修改。

1、length的监听

            const b = [1,2,3];

            console.log(Object.getOwnPropertyDescriptor(b,'length'));

数组length的描述器属性的赋值
数组length的描述器属性的赋值

  一看到configuarable的值为false,我们就很绝望了,通过get,set方法来监听它的路子是走不通了。(方法后面会讲。)

2、index的监听

  对于数组的下标是可以采用defineProperty定义set和get方法,但是我们要注意一个问题:

            const arr = [];
            arr[3] = 1;

  对于上面这段代码,我们虽然只设置了index=3的数据,但是下标0~2会被自动填充为undefined,它的length也变成了4,所以这里存在的问题很隐晦。

  当我们将一个数组的下标通过defineProperty完成监听的时候是没什么问题的,但是这时我们执行上面那样的操作时,和之前的对象一样要采用set方法添加新的下标,但是前面未定义的下标就失控了。但是下面这种方法就可以规避这种风险:

            const arr = [];
            arr.splice(3,1,10);

数组的splice方法并不会产生尴尬的undefined
数组的splice方法并不会产生尴尬的undefined

  这也就是为什么在Vue中要通过下标访问修改数组时要采用set方法,而Vue的set方法的内部正是采用了splice解决这种问题。

3、数组原型方法的监听

  首先我们可以通过继承Array对象,重写原型方法实现我们的需求,那么我们先来实现一个继承重写的例子:

            function Shape() {
                this.x = 0;
                this.y = 0;
            }
            Shape.prototype.move = function (x,y) {
                this.x += x;
                this.y += y;
                console.log('shape move');
            }
            function Rectangle() {
                Shape.call(this);
            }
            Rectangle.prototype = Object.create(Shape.prototype);
            Rectangle.prototype.constructor = Rectangle;
            Rectangle.prototype.move = function (x,y) {
                console.log('监听父类的move方法');
                Shape.prototype.move.call(this,x,y);
            }
            const rect = new Rectangle();
            rect.move(20,10);

  这里我们通过子类Rectangle重写父类Shape的move方法,达到监听的效果。是不是我们将这里的Shape换成Array不就行了吗?上代码:这里暂且只监听push、pop和splice,其他的基本一样。

            function OBArray() {
                Array.apply(this, arguments);
            }
            OBArray.prototype = Object.create(Array.prototype);
            OBArray.prototype.constructor = OBArray;
            OBArray.prototype.push = function () {
                const len = Array.prototype.push.apply(this,arguments);
                console.log('push: ' + arguments[0] + ' 新的数组长度为:' + len);
                return len;
            }
            OBArray.prototype.pop = function () {
                const temp = Array.prototype.pop.call(this);
                console.log('pop: ' + temp);
                return temp;
            }
            OBArray.prototype.splice = function () {
                console.log('采用了splice方法:');
                return Array.prototype.splice.apply(this,arguments);
            }

            const arr = new OBArray(1,2,3,4,5);

  这时问题就来了,你会发现只能的arr是一个空对象。但是Array原型链上的方法是继承了。而这里主要的原因就在于:

            Array.apply(this, arguments);

  ES6之前,JS内置对象的构造函数多不会去处理这里的this。所以我们这里虽然执行了Array的构造函数,但是并没有绑定到我们此刻的this上。这里Vue就采用了一个相当好的方法,__proto__属性。关于__proto__和prototype的区别,所以这里我们可以这样改写:

            function OBArray() {
                const arr = Array.apply(null,arguments);
                arr.__proto__ = OBArray.prototype;
                arr.constructor = OBArray;
                return arr;
            }

  到这里基本就是实现数组监听的需求了,虽然源码也是这样的思想,不过人家代码写的优雅啊Vue数组监听的代码部分,很值得学习呀。

  人生不能留下遗憾,编代码也一样。所以我们来看ES6给我们带来的便利:

1、class-extends

  这里我们可以拜读一下阮老师的Class的继承。文中清楚的解释到ES5和ES6继承的差别,下面看一下实现的代码:

            class OBArray extends Array {
                constructor (...args) {
                    super(...args);
                }
                push (...args) {
                    const len = super.push(...args);
                    console.log('push: ' + args + ' 新的数组长度为:' + len);
                    return len;
                }
            }
            const a = new OBArray(...[1,2]);
            a.push(20); // "push: 20 新的数组长度为:3"

  是不是非常的方便。

2、Proxy

  对于Proxy入门,还是推荐阮老师的教程吧Proxy,代码如下:

            let a = new Proxy([], {
                get: function (target, key, receiver) {
                    console.log("读取数组");
                    return Reflect.get(target, key, receiver);
                },
                set: function (target, key, receiver) {
                    console.log('设置数组');
                    return Reflect.set(target, key, receiver);
                }
            })

            a.splice(3,1,10);

  这里控制台输出很有趣,有兴趣的可以研究研究。也正是通过Proxy我们可以实现对length的监听,不过兼容性是硬伤啊。。。。

二、总结

  到这里基本上数组的监听的学习就完成啦。无论你用古老的"__proto__",还是用es6的extends和Proxy实现,我们最主要的还是要理解Array这个对象的特点,哎,基础并不是我们想象的那么基础呀!

参考资料
知乎proto和prototype
阮老师的Class-extends
阮老师的Proxy
Vue core array.js源码

您的关注是我学习的最大动力^_^