javascript --- > vue2.x中原型的使用(拦截数组方法) && 响应式原理(部分)

说明

在Vue2.x中,利用了对原型链的理解,巧妙的利用JavaScript中的原型链,实现了数组的pop、push、shift、unshift、reverse、sort、splice等的拦截.

你可能需要的知识

原型链

JavaScript常被描述为一种基于原型的语言(prototype-based language),每个对象拥有一个原型.
数组类型也不例外.验证如下:

let arr = [];
console.log(arr)
/*
   {
   	length: 0
   	__proto__: {
   		length: 0
   		constructor: f Array()
   		...
   		__proto__: Object
   	}
   }
*/

可见数组的原型是继承于Object。

响应式

参考 - MDN
响应式的核心是使用Object.defineProperty在对数据进行读取或者写入时进行劫持操作.

let o = {}
,_gender
Object.defineProperty(o, gender, {
	get(){
		return _gender
	},
	set(newVal){
		_gender = newVal
	}	
})

对一个属性,同时使用get和set方法时,需要一个中间变量取存储,否则会造成循环使用.

Vue 2.x中的响应式

  • Vue在使用过程中,可能会用到很多的变量,而每把一个数据进行响应式化,就需要一个变量去存储.这样有可能会污染全局作用域.
  • Vue中采取的方法是使用函数的形参,来实现响应式,实现如下
function defineReactive(target, key, value, enumerable){
   // 注意: 此处的value与上文的_gender类型
   Object.defineProperty(target, key, {
   	configurable: true,
   	enumerable: !!enumerable,
   	get(){
   		console.log(`读取${value}`)
   		return value
   	},
   	set(newVal){
   		console.log(`写入: ${value} --> ${newVal}`)
   		value = newVal
   	}
   })
}
let o = {
   name: 'marron',
   age: 26,
   remark: 'hunt for job'
}
Object.keys(o).forEach(k => {
   defineReactive(o,k,o[k],true)
})

在这里插入图片描述
以上实现了对数据的拦截: 即对数据进行 写入/读取 操作时,会按照一定规则优先执行某些步骤.
但是以上代码还存在一些小小的瑕疵

对象深层次

以上代码不对对象的深层次进行响应式化,如下面数据

let o = {
	list: [ 
		{ 
			person1: {
				name:'Marron',
				age: 18
		}},
		{
			person2: {
				name:'Mar',
				age: 25
			}
		}
	]
}

在这里插入图片描述
此时,需要考虑数组,和对象的子元素问题.
对于数组问题,我们修改遍历,如果是数组,则取出数组中的每个元素,进行添加响应式处理

- Object.keys(o).forEach(k =>{
- 	defineReactive(o, k, o[k], true)
- })
+ function reactify(o){
+ 	Object.keys(o).forEach(k => {
+ 		if(Array.isArray(o[k])){
+ 			o[k].forEach(val => reactive(val))
+ 		} else {
+ 			defineReactive(o, k, o[k], true)
+ 		}
+ })}

对于深层次对象问题,我们对defineReactive进行修改

function defineReactive(o, key, value, enumerable){
	if(typeof value =='object' && value !== null && !Array.isArray(value)){
		// 此处可以认为是对象
		reactify(value)
	}
	// 此处是最后一层,添加响应式
	Object.defineProperty(o, key, {
		configurable: true,
		enumerable: !!enumerable,
		get(){
			console.log(`读取${key}`)
			return value
		},
		set(newVal){
			console.log(`写入${key} => ${newVal}`)
			value = newVal
		}
	})
}

在这里插入图片描述

Vue2.x对数组部分方法的拦截

上面的响应式无法对数组的pop、push等方法进行响应
在这里插入图片描述
在Vue2.x中,使用了修改原型链的结构的方式来对数组的变化进行拦截.
先看下面的关系

arr
Array.prototype
Object.prototype
  • 原本的关系图示已经描述的很清楚了
  • 我们对pop和push的拦截的原理
  • 实际上是对Array原型上的pop、push方法进行重写
  • 但是我们不可能直接在这个原型上重写(因为有些数组的实例,并不需要响应式).
  • 因此我们在arrArray.prototype之间添加一层arr_methods,改进后的关系如下
arr
arr_methods
Array.prototype
Object.prototype

【具体的实现思路】:
先创建一个arr_methods对象其原型是Array.prototype.然后修改arr_methods上需要拦截的方法(存储在数组ARRAY_METHOD中)

const ARRAY_METHOD = [
	'push',
	'pop',
	'shift',
	'unshift',
	'reverse',
	'sort',
	'splice'
]
let arr_methods = Object.create(Array.prototype)

ARRAY_METHOD.forEach(method=>{
	arr_methods[method] = function(){
		// 拦截的函数
		console.log(`调用${method}方法`)	
		return Array.prototype[method].apply(this, arguments)
	}
})
arr.__proto__ = arr_methods

在这里插入图片描述
此时既不影响原生的Array.prototype,又实现了对pop、push...方法的拦截,完成之后只需要修改前面的方法.即可完成对数组pop、push方法的拦截

 function reactify(o){
 	Object.keys(o).forEach(k => {
 		if(Array.isArray(o[k])){
 			// 数组方法的响应式
 			o[k].__proto__ = array_method
. 			o[k].forEach(val => reactive(val))
 		} else {
 			defineReactive(o, k, o[k], true)
 		}
 })}

最后,此时只是拦截,还差一步形成响应式

ARRAY_METHOD.forEach(method=>{
	arr_methods[method] = function(){
		// 拦截的函数
		console.log(`调用${method}方法`)
		for(let i =0, len = arugments.length; i < len; i++){
			reactify(arguments[i])
		}
		return Array.prototype[method].apply(this, arguments)
	}
})
发布了228 篇原创文章 · 获赞 41 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/piano9425/article/details/104628990