VUE响应式原理(1)

Object.defineProperty()

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。

Object.defineProperty方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回该对象。
方法的第一个参数是对象,第二个是对象的属性,第三个参数也是一个对象,用来操作属性。该参数有如下几个常用属性及方法:

  • value:属性值
  • writable:是否可写
  • enumerable:是否可以枚举
  • configurable:是否可以被配置,默认false
  • set():改变属性值时触发,需要参数
  • get():访问属性时触发,不能与value或writable同时出现,需要返回值
var obj={};
Object.defineProperty(obj,'a',{
    // value:3,
    // 是否可写
    // writable:true,
    // 是否可以被枚举
    enumerable:true,
    // 有value或writable没get
    // 外部访问a属性时被执行
    get(){
        console.log('a的get')
        // 要有返回值
        return 1
    },
    // 外部改变a属性时被执行
    set(newValue){
        console.log('a的set',newValue)
    }
});
Object.defineProperty(obj,'b',{
    value:5,
})
Object.defineProperty(obj,'c',{
    value:6,
})
console.log(obj.a)//触发get方法 1
obj.a=10//触发set方法 10 
console.log(obj.a)//触发get方法 1

在上边的代码中,obj.a=10调用set方法给a赋值10,但是再访问a,其值仍为get的返回值1。
我们需要在这两个方法之间借助一个变量实现值的改变。

var obj={};
var tmp;
Object.defineProperty(obj,'a',{
    enumerable:true,
    get(){
        console.log('a的get')
        // 要有返回值
        return tmp;
    },
    // 外部改变a属性时被执行
    set(newValue){
        console.log('a的set',newValue)
        tmp=newValue
    }
});
console.log(obj.a)//触发get方法 undefined
obj.a=10//触发set方法  
console.log(obj.a)

在这里,我们定义了一个全局变量tmp,实现了set与get之间的周转。但是,这并不是一个好的策略,因为出现了全局变量。
利用闭包实现:

var obj = {};
function defineReactive(data, key, val) {
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable:true,
        get() {
            console.log('a的get')
            // 要有返回值
            return val;
        },
        // 外部改变a属性时被执行
        set(newValue) {
            console.log('a的set', newValue)
            val = newValue
        }
    });
}
defineReactive(obj,'a',1)
console.log(obj.a)//触发get方法 undefined
obj.a = 10//触发set方法  
console.log(obj.a)//10

外层函数的val参数就实现了闭包。
如果对象里边嵌套对象,上边的代码做不到让子对象的属性也成为响应式,所以需要实现递归。
index.js

import observe from './observe'
var obj = {
    a:{
        m:{
            n:333
        }
    },
    b:3
};
// 创建observe函数,注意,不是observer
observe(obj);
obj.a.m.n=345

observe.js

import Observer from './Observer'
export default function(value){
    if(typeof value != 'object')return;// m[n]不再是object,返回
    // 定义ob
    var ob;
    if(typeof value.__ob__!=='undefined'){
        ob=value.__ob__;
    }else{
        ob=new Observer(value);
    }
    return ob;
}

Observer.js

import utils, { def } from './utils'
import defineReactive from './defineReactive'
export default class Observer{
    constructor(value){
        console.log('Observer构造器');
        // 给实例(this)添加__ob__属性, 构造函数的this不是类本身,而是实例
        def(value,'__ob__',this,false);// 这样的话,value对象就有了__ob__属性,第一次时obj调用,于是obj有了__ob__属性
        // Observer类的目的是:将一个正常的object转换为每个层级的属性都是响应式的object(可以被侦测的)
        this.walk(value) 
    }
    // 遍历
    walk(value){
        for(let k in value){//obj有了__ob__,调用walk,a和b就
            defineReactive(value,k) // 把value的k属性变成响应式的
        }
    }
}

defineReactive.js

import observe from './observe'
export default
    function defineReactive(data, key, val) {
        console.log('defineReactive',data,key)
    if (arguments.length == 2) {
        val = data[key]//当传进来(obj,a)时,val=obj[a]   最后一次递归调用是传的m[n]
    }
    // 子元素要进行observe,至此形成递归,这个递归不是函数自己调用自己,而是多个函数循环调用
    let childOb = observe(val);//obj[a]也有了__ob__属性  传m[n]时,由于不是object,observe直接返回了,所以n是没有__ob__属性的,b也没有,因为b也不是对象
    Object.defineProperty(data, key, {//第一次data是obj,key是a,于是a属性变成了响应式的,之后是(a,m),m响应式,(m,n),n响应式
        enumerable: true,
        configurable: true,
        get() {
            console.log('你试图访问'+key+'属性')
            // 要有返回值
            return val;
        },
        // 外部改变a属性时被执行
        set(newValue) {
            console.log('你试图改变'+key+'属性', newValue)
            val = newValue
            // 当设置了新值,也要observe,防止新值也是个对象
            childOb=observe(newValue)
        }
    });
}

utils.js

export const def = function(obj,key,value,enumerable){
    Object.defineProperty(obj,key,{
        value,
        enumerable,//可枚举
        writable:true,
        configurable:true
    });
}

这个递归并不是函数自己调用自己的那种递归,而是几个函数循环调用,当最后一次传入的不再是对象时,return空,此时,结束递归。
流程是这样的:
在这里插入图片描述observe(obj),进入observe函数,我们知道,obj对象是没有__ob__属性的,所以会new一个Observer对象,并把这个对象设置为__ob__属性,给obj添加上。(def(value,'__ob__',this,false);,这里的def方法就是给对象添加__ob__属性,它是在utils.js中定义的)。紧接着,让obj调用walk方法,遍历obj的所有属性,调用defineReactive方法,而在defineReactive中,让val = data[key],然后val调用observe,这个val就是obj的属性,于是,完成了递归调用。我们可以理解为,从observe出发,又回到了observe。一直循环到m的属性n调用observe时,由于n不是一个对象,所以observe返回空,递归结束。回到了defineReactive上,接着执行后边的Object.defineProperty,将属性变为响应式的。

数组的响应式处理

当数据是一个数组时,应该怎么对它进行处理才能成为响应式的呢?
数组也是对象,所以如果obj还有一个属性c,c:[1,2,3,4,5],使用上边的代码,c也会被添加上__ob__属性,但是如果要改变c数组,并不会触发子元素的get或者set方法。
首先是,数组的七个方法被改写了:

  • push
  • pop
  • shift
  • unshift
  • splice
  • sort
  • reverse
    数组的响应式原理就是,以Array.prototype为原型重新定义一个对象,并且将改写的7个方法加到这个对象上,然后强制改变数组的原型链,将数组的原型改为我们新定义的这个对象。这样,当数组再调用七个方法的时候,调用的就是我们修改过的方法了。
import { def } from './utils'
// 得到Array.prototype
const arrayPrototype = Array.prototype;
// 我们要以arrayPrototype为原型创建arrayMethods对象 暴露出去
export const arrayMethods = Object.create(arrayPrototype);
// 要被改写的7个方法
const methodsNeedChange = ['push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
];
methodsNeedChange.forEach(methodName => {
    // 备份原来的方法--需要保留原来的功能
    const original = arrayPrototype[methodName];
    // 定义新的方法 methodName,把它加到arrayMethods对象上,7个方法重新在arrayMethods上定义
    def(arrayMethods, methodName, function () {
        // 把数组的__ob__取出来
        const ob = this.__ob__;
        // push unshift splice方法能够插入新值,现在要把插入的新值也要变成observe的
        let inserted = [];
        switch (methodName) {
            case 'push':
            case 'unshift':
                inserted=arguments;
                break;
            case 'splice'://splice(下标,数量,插入的新项) splice() 方法可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素
                // inserted=Array.prototype.slice.apply(arguments).slice(2);//选择 插入的新项   类数组转数组
                inserted=[...arguments].slice(2)
                break;
        }
        // 判断有没有要插入的新项,让新项也变为响应式的
        if(inserted){
            ob.observeArray(inserted)//ob是一个Observer
        }
        const result=original.apply(this, arguments)//这里如果直接调用origin,那是window在调用,所以需要apply或者call
        console.log(123)
        return result
    }, false)

})

在Observer.js中,我们需要加一个判断,判断传进来的是不是数组,如果是的话,要修改它的原型。

import utils, { def } from './utils'
import defineReactive from './defineReactive'
import arrayPrototype from './array'
import {arrayMethods} from './array'
import observe from './observe';
export default class Observer{
    constructor(value){
        console.log('Observer构造器');
        // 给实例(this)添加__ob__属性, 构造函数的this不是类本身,而是实例
        def(value,'__ob__',this,false);// 这样的话,value对象就有了__ob__属性,第一次时obj调用,于是obj有了__ob__属性
        // Observer类的目的是:将一个正常的object转换为每个层级的属性都是响应式的object(可以被侦测的)
        // 检查是数组还是对象
        if(Array.isArray(value)){
            // 如果是数组,将原型指向arrayMethods
            Object.setPrototypeOf(value,arrayMethods)
        }
        this.walk(value) 
    }
    // 遍历
    walk(value){
        for(let k in value){//obj有了__ob__,调用walk,a和b就
            defineReactive(value,k) // 把value的k属性变成响应式的
        }
    }
    // 数组的特殊遍历
    observeArray(arr){
        for(let i=0,l=arr.length;i<l;i++){
            // 逐项observe
            observe(arr[i])
        }
    }
}

七个方法中的push、unshift、splice由于可以向数组中插入新的元素,所以需要单独判断,我们要保证新插入的值也得是响应式的。

AI实战-学生生活方式模式数据集分析预测实例(含24个源代码+69.54 KB完整的数据集) 代码手工整理,无语法错误,可运行。 包括:24个代码,共149.89 KB;数据大小:1个文件共69.54 KB。 使用到的模块: pandas os matplotlib.pyplot seaborn plotly.express warnings sklearn.model_selection.StratifiedShuffleSplit sklearn.pipeline.Pipeline sklearn.compose.ColumnTransformer sklearn.impute.SimpleImputer sklearn.preprocessing.OrdinalEncoder numpy sklearn.model_selection.cross_val_score sklearn.linear_model.LinearRegression sklearn.metrics.mean_squared_error sklearn.tree.DecisionTreeRegressor sklearn.ensemble.RandomForestRegressor sklearn.model_selection.train_test_split sklearn.preprocessing.PowerTransformer imblearn.pipeline.Pipeline imblearn.over_sampling.SMOTE sklearn.ensemble.AdaBoostClassifier sklearn.metrics.accuracy_score sklearn.metrics.precision_score sklearn.metrics.recall_score sklearn.metrics.f1_score optuna scipy.stats torch torch.nn torchvision.transforms torchvision.models torch.optim cv2 glob glob.glob torch.utils.data.DataLoader torch.utils.data.Dataset random.shuffle torch.utils.data.random_split torchsummary.summary matplotlib.ticker pyspark.sql.SparkSession pyspark.sql.functions.count pyspark.sql.functions.max pyspark.sql.functions.min pyspark.sql.functions.avg pyspark.sql.functions.stddev_samp pyspark.sql.functions.skewness pyspark.sql.functions.kurtosis pyspark.sql.functions pyspark.ml.feature.Tokenizer pyspark.ml.feature.VectorAssembler sklearn.preprocessing.LabelEncoder keras.models.Sequential keras.layers.Dense keras.utils.to_categorical ptitprince statsmodels.distributions.empirical_distribution.ECDF statsmodels.stats.outliers_influence.variance_inflation_factor ppscore sklearn.feature_selection.mutual_info_classif sklearn.decomposition.PCA sklearn.model_selection.StratifiedKFold sklearn.tree.DecisionTreeClassifier sklearn.metrics.balanced_accuracy_score sklearn.metrics.confusion_matrix mlxtend.plotting.plot_confusion_matrix scipy.stats.pearsonr scipy.stats.f_oneway sklearn.feature_selection.mutual_info_regression sklearn.feature_selecti
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值