先看一下响应式的简单例子:
var o = {};
o.name = '张三';
// 等价于
Object.defineProperty( o, 'age', {
configurable: true,
writable: true,
enumerable: !true, // 可枚举
value: 19
} );
Object.defineProperty( o, 'gender', {
configurable: true,
enumerable: true, // 可枚举
get () {
return 'getter被调用';
},
set ( newVal ) {
console.log( '赋值的新值为: ', newVal );
}
} );
复制代码
赋值的时候正常,但是取值的时候发现并没有返回刚才赋值的 ‘nan’
如果同时使用 get 和 set 需要一个中间变量存储真正的数据,修改如下:
var o = {};
// 给 o 提供属性
o.name = '张三';
// 等价于
Object.defineProperty( o, 'age', {
configurable: true,
writable: true,
enumerable: !true, // 可枚举
value: 19
} );
// get 和 set 上
// 要响应式就表示在赋值和读取的时候, 附带的要做一些事情
let _gender;
Object.defineProperty( o, 'gender', {
configurable: true,
enumerable: true, // 可枚举
get () { // 如果使用 o.gender 来访问数据, 就会调用 get 方法 ( getter, 读取器 )
return _gender;
},
set ( newVal ) { // 如果 o.gender = 'xxx', 那么就会调用 这个 set 方法, 设置的值会作为参数传入 set
// console.log( '赋值的新值为: ', newVal );
_gender = newVal;
}
} );
复制代码
这里其实是给_gender赋的值,但是不能将_gender暴露在全局作用域下,这样也不安全,再者,给一个属性设置get/set,就要设置一个中间变量,增加内存消耗。
在Vue中,如何给data里的对象转换为响应式的呢?
在 Vue 使用 defineRective( target, key, value, enumerable )这样一个函数的闭包,那么如何实现呢?
1.先看一个简单的版本:
var o = {
name: 'jim',
age: 19,
gender: '男'
} ;
// 简化后的版本
function defineReactive( target, key, value, enumerable ) {
// 函数内部就是一个局部作用域, 这个 value 就只在函数内使用的变量 ( 闭包 )
Object.defineProperty( target, key, {
configurable: true,
enumerable: !!enumerable,
get () {
console.log( `读取 o 的 ${key} 属性` ); // 额外
return value;
},
set ( newVal ) {
console.log( `设置 o 的 ${key} 属性为: ${newVal}` ); // 额外
value = newVal;
}
} )
}
// 将对象转换为响应式的
let keys = Object.keys( o );
for ( let i = 0; i < keys.length; i++ ) {
defineReactive( o, keys[ i ], o[ keys[ i ] ], true );
}
复制代码
控制台打印结果:
以上是简化版本,可以看到,对象已经是响应式的了!
但是,这只针对对象是一层结构的,例如: data: {name: ‘张三’},但是实际开发中,对象有N多层,对象中有数组,该如何转换成响应式的呢?
let data = {
name: '张三'
, age: 19
, course: [
{ name: '语文' },
{ name: '数学' },
{ name: '英语' },
]
}; // 除了递归还可以使用队列 ( 深度优先转换为广度优先 )
let ARRAY_METHOD = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice',
];
let array_methods = Object.create( Array.prototype );
ARRAY_METHOD.forEach( method => {
array_methods[ method ] = function () {
// 调用原来的方法
console.log( '调用的是拦截的 ' + method + ' 方法' );
// 将数据进行响应式化
for( let i = 0; i < arguments.length; i++ ) {
reactify( arguments[ i ] );
}
let res = Array.prototype[ method ].apply( this, arguments );
// Array.prototype[ method ].call( this, ...arguments ); // 类比
return res;
}
} );
function defineReactive( target, key, value, enumerable ) {
// 函数内部就是一个局部作用域, 这个 value 就只在函数内使用的变量 ( 闭包 )
if ( typeof value === 'object' && value != null && !Array.isArray( value ) ) {
// 是非数组的引用类型
reactify( value ); // 递归
}
Object.defineProperty( target, key, {
configurable: true,
enumerable: !!enumerable,
get () {
console.log( `读取 ${key} 属性` ); // 额外
return value;
},
set ( newVal ) {
console.log( `设置 ${key} 属性为: ${newVal}` ); // 额外
value = newVal;
}
} );
}
// 将对象 o 响应式化
function reactify( o ) {
let keys = Object.keys( o );
for ( let i = 0; i < keys.length; i++ ) {
let key = keys[ i ]; // 属性名
let value = o[ key ];
if ( Array.isArray( value ) ) {
// 数组
value.__proto__ = array_methods; // 数组就响应式了
for ( let j = 0; j < value.length; j++ ) {
reactify( value[ j ] ); // 递归
}
} else {
// 对象或值类型
defineReactive( o, key, value, true );
}
}
}
reactify( data );
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END