Vue數(shù)據(jù)雙向綁定原理(vue2向vue3的過渡)

眾所周知,Vue的兩大重要概念:

    數(shù)據(jù)驅(qū)動
    組件系統(tǒng)

接下來我們淺析數(shù)據(jù)雙向綁定的原理
一、vue2
1、認(rèn)識defineProperty

vue2中的雙向綁定是基于defineProperty的get操作與set操作,那么我們簡單認(rèn)識下defineProperty,
作用: 就是直接在一個(gè)對象上定義一個(gè)新屬性,或者修改一個(gè)已經(jīng)存在的屬性。
那么我們先來看下Object.getOwnPropertyDescriptor(),有定義方法就會有獲取方法,對這就是與defineProperty相對的方法,它可以獲取屬性值。

var a ={
    b:1,
    c:2
}
console.log(Object.getOwnPropertyDescriptor(a,'b')); //查看獲取b屬性




這就是打印出來的結(jié)果,configurable的意思是可便利,enumerable的意思是可枚舉,writable的意思是可寫。
2、使用defineProperty實(shí)現(xiàn)簡單的私有變量

知道了屬性內(nèi)部的屬性值,我們可以使用defineProperty來實(shí)現(xiàn)一個(gè)私有變量。

var a ={
    b:1,
    c:2
}
Object.defineProperty(a,'b',{
    writable:false  //不可寫
})
console.log(Object.getOwnPropertyDescriptor(a,'b')); //查看獲取b屬性




發(fā)現(xiàn),改變不了a.b的值。

另外,還可以使用一個(gè)很快捷的方法實(shí)現(xiàn)私有變量

var a ={
    b:1,
    c:2
}
Object.freeze(a,'b'); //凍結(jié),可理解為變?yōu)樗接袑傩?br>// Object.seal(a,'b'); //configurable置為false
console.log(Object.getOwnPropertyDescriptor(a,'b')); //查看獲取b屬性




同樣,writable為false。如果使用seal()只能使configurable為false。
3、簡單認(rèn)識defineProperty的get、set的方法

我們先不要?jiǎng)?chuàng)建全局變量,跟get方法return值,你會發(fā)現(xiàn)值為undefined
















于是我們改了改

var a ={
    b:1,
    c:2
}
 var _value=a.b; //b值賦值需要先賦給全局變量,不然使用不了
 Object.defineProperty(a,'b',{
    get:function(){
        console.log('get');
        return _value; // get方法必須ruturn值,值為全局變量。
    },
    set:function(newValue){
        console.log(newValue)
        _value=newValue; // 賦值
    }
 })



取到了。
4、使用defineProperty簡單實(shí)現(xiàn)Vue雙向綁定

test.js

// 使用defineProperty
function vue () {
    this.$data={
        a:1 // 數(shù)組的話不會更新
    };
    this.el=document.getElementById('app');
    this._html="";
    this.observe(this.$data);
    this.render();
};
vue.prototype.observe=function(obj){
    var value;
    var self=this;
    for(var key in obj){
        value =obj[key];
        if(typeof value === 'object'){
            this.observe(value)
        }else{
            Object.defineProperty(this.$data,key,{
                get:function(){
                    return value;
                },
                set:function(newvalue){
                    value=newvalue;
                    self.render();
                }
            })
        }
    }
}
vue.prototype.render=function(){
    this._html='I am '+ this.$data.a;
    this.el.innerHTML=this._html;
}


// ***************************************
//數(shù)組改變更新,裝飾者模式
// var arraypro=Array.prototype;
// var arrayob=Object.create(arraypro);
// var arr = ["push","pop","shift"];
// // arr里的方法,既能保持原有的方法,又能觸發(fā)更新。
// arr.forEach(function(method,index){
//     arrayob[method]=function(){
//         var ret=arraypro[method].apply(this,arguments);
//         console.log('更新');
//         return ret;
//     }
// })
// var arr=[];
// arr.__proto__=arrayob;
// arr.push(1);


test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>vue雙向綁定原理</title>
</head>
<body>
    <div id="app"></div>
</body>
<script src="test.js"></script>
<script>
    var vm = new vue();
    setTimeout(function(){
        console.log('changes');
        console.log(vm.$data);
        vm.$data.a=222;
    },2000)
</script>
</html>



這樣我們就簡單實(shí)現(xiàn)了數(shù)據(jù)雙向綁定。
二、vue3
1、簡單認(rèn)識Proxy

就是一種機(jī)制,用來攔截外界對目標(biāo)對象的訪問,可以對這些訪問進(jìn)行過濾或者改寫,所以Proxy更像是目標(biāo)對象的代理器。

var ob= {
    a:1,
    b:2
}
// 新創(chuàng)建一個(gè)Proxy對象直接替換ob對象
ob=new Proxy(ob,{
    get:function(target,key,receive){   // target指的是原始對象,key指的是屬性值,receive指的是這個(gè)Proxy對象
        console.log(target,key,receive)
        return target[key]; //get方法 依然需要return
    },
    set:function(target,key,newvalue,receive){
        console.log(target,key,newvalue,receive) // newvalue指新創(chuàng)建的值
        target[key]=newvalue;
    }
});



這里需要注意的是:第一次使用proxy時(shí),在new的時(shí)候把原對象替換掉。就會觸發(fā)get方法。

看到上方的代碼,我們可以總結(jié)Proxy的幾個(gè)優(yōu)點(diǎn):

    defineProperty只能監(jiān)聽某個(gè)屬性,不能對全對象監(jiān)聽;
    所以可以省去for in 提升效率;
    可以監(jiān)聽數(shù)組,不用再去單獨(dú)的對數(shù)組做異性操作。

2、使用Proxy簡單實(shí)現(xiàn)數(shù)據(jù)雙向綁定

test1.js

// 使用Proxy
function vue () {
    this.$data={
        a:1
    };
    this.el=document.getElementById('app');
    this._html="";
    this.observe(this.$data);
    this.render();
};
vue.prototype.observe=function(obj){
    var self=this;
    this.$data=new Proxy(this.$data,{
        get:function(target,key){
            return target[key];
        },
        set:function(target,key,newvalue){
            target[key]=newvalue;
            self.render();
        }
    })
}
vue.prototype.render=function(){
    this._html='I am '+ this.$data.a;
    this.el.innerHTML=this._html;
}



test.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>vue雙向綁定原理</title>
</head>
<body>
    <div id="app"></div>
</body>
<script src="test1.js"></script>
<script>
    var vm = new vue();
    setTimeout(function(){
        console.log('changes');
        console.log(vm.$data);
        vm.$data.a=222;
    },2000)
</script>
</html>



3、Proxy還可以做什么呢?

(1)、校驗(yàn)類型

function createValidator(target,validator){
    return new Proxy(target,{
        _validator:validator,
        set:function(target,key,value,proxy){
            if (target.hasOwnProperty(key)) {
                var validator1=this._validator[key];
                if(validator1(value)){
                    return Reflect.set(target,key,value,proxy)
                }
                else{
                    throw Error('type error')
                }
            }
        }
    })
}
var personvalidator={
    name(val){
        return typeof val==='string'
    },
    age(val){
        return typeof val==='number'&&val>18
    }
}

class Person {
    constructor(name,age) {
        this.name=name;
        this.age=age;
        return createValidator(this,personvalidator);
    }
}
var tss=new Person('maomin',22);



(2)、真正私有變量

var api = {
    _secret: 'xxxx',
    _otherSec: 'bbb',
    ver: 'v0.0.1'
};

api = new Proxy(api, {
    get: function (target, key) {
        // 以 _ 下劃線開頭的都認(rèn)為是 私有的
        if (key.startsWith('_')) {
            console.log('私有變量不能被訪問');
            return false;
        }
        return target[key];
    },
    set: function (target, key, value) {
        if (key.startsWith('_')) {
            console.log('私有變量不能被修改');
            return false;
        }
        target[key] = value;
    },
    has: function (target, key) {
        return key.startsWith('_') ? false : (key in target);
    }
});

api._secret; // 私有變量不能被訪問
console.log(api.ver); // v0.0.1
api._otherSec = 3; // 私有變量不能被修改
console.log('_secret' in api); //false
console.log('ver' in api); //true


作者:Vam的金豆之路

主要領(lǐng)域:前端開發(fā)

我的微信:maomin9761

微信公眾號:前端歷劫之路