深度剖析手写 vue 响应式源码 Vue 响应式原理

深度剖析手写 vue 响应式源码 Vue 响应式原理

  • 简单的来说 vue 响应式就是当数据改变时,页面就被重新渲染
  • 在剖析 vue 源码之前 我们需要先了解到 Object.defineProperty

defineProperty 是 object 原型上的一个方法
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty()(obj, prop, descriptor)
参数:obj 需要定义属性的当前对象,即要被监听的对象
prop 当前监听对象的属性名,
descriptor:对这个对象的配置,比如属性是否可读 是否可枚举,我们这次会用到它中的 get(),set()方法

let data = {
  name: "zhangxiaozhuo",
  age: 18,
};
let value = data.name;
Object.defineProperty(data, "name", {
  get() {
    // get()方法为获取,当我们读取data对象中的属性时就会执行get()方法
    console.log("读");
    return value;
    //return 的值是什么,那么我们获取的值就是什么
  },
  set(newVal) {
    //参数newVal:被重新赋予的值
    //set()方法为设置方法,当我们改变data对象中的属性时就会执行get()方法
    console.log("写");
    value = newVal;
  },
});
data.name = "zhangshuai";
console.log(data.name);
// 写
// 读
// zhangshuai

我们可以把它放在一个函数里

let data = {
  name: "zhangxiaozhuo",
  age: 18,
};

function defineReactive(data, key, value) {
  Object.defineProperty(data, key, {
    get() {
      // get()方法为获取,当我们读取data对象中的属性时就会执行get()方法
      console.log("读");
      return value;
      //return 的值是什么,那么我们获取的值就是什么
    },
    set(newVal) {
      //参数newVal:被重新赋予的值
      //set()方法为设置方法,当我们改变data对象中的属性时就会执行get()方法
      console.log("写");
      value = newVal;
      render(); //
    },
  });
}
//data数据会有多个属性 所以我们需要遍历它
for (const key in data) {
  defineReactive(data, key, data[key]);
}
//当我们执行更改data的属性时 函数就会被渲染
function render() {
  console.log("函数渲染了");
}
data.name = 1234;

当 data 对象里面中还有对象时,我们可以进行递归操作,观察 value 是否为对象,如果为对象时,那么会继续递归执行

let data = {
  name: "zhangxiaozhuo",
  age: 18,
  love: {
    he: "hah",
  },
};

function defineReactive(data, key, value) {
  observe(value);
  //当data对象里面中还有对象时,我们可以进行递归操作,观察value是否为对象,如果为对象时,那么会继续递归执行
  Object.defineProperty(data, key, {
    get() {
      // get()方法为获取,当我们读取data对象中的属性时就会执行get()方法
      console.log("读");
      return value;
      //return 的值是什么,那么我们获取的值就是什么
    },
    set(newVal) {
      //参数newVal:被重新赋予的值
      //set()方法为设置方法,当我们改变data对象中的属性时就会执行get()方法
      console.log("写");

      if (value === newVal) {
        //如果我们设置的是属性和原来相同,那么不会让render()函数渲染
        return;
      }
      value = newVal;
      render(); //
    },
  });
}
//data数据会有多个属性 所以我们需要遍历它
function observe(data) {
  if (typeof data === "object") {
    for (const key in data) {
      defineReactive(data, key, data[key]);
    }
  }
}
//当我们执行更改data的属性时 函数就会被渲染
function render() {
  console.log("函数渲染了");
}
observe(data);
// data.name = "zhangxiaozhuo"
data.love.he = "124";
// console.log(data.love.he);
//我们不能给对象增加和修改值

data.arr[0]=100;

//在 vue、中不会观察数组变化 执行 data.arr[100] =100 监听不到 因为数组也是一个对象,这是是增加行为,那么就不会监听 这就是为什么 vue 不能根据索引增加数组的

在我们写项目时会有大量的数据存在数组中,如果根据索引遍历数组中的每一项,会比较浪费性能,所以 vue 中不会用 defineReactive 观察数组的变化
但是它会利用数组的变异方法实现页面渲染,改写了数组方法
在源码中改写了数组方法,让它的方法在数组里面可以执行 render 函数,在执行 render 函数之前会执行他的原型的功能 也就是执行原型链数组的方法

let data = {
  name: "zhangxiaozhuo",
  age: 18,
  love: {
    he: "hah",
  },
  arr: [1, 2],
};

function defineReactive(data, key, value) {
  observe(value);
  Object.defineProperty(data, key, {
    get() {
      // get()方法为获取,当我们读取data对象中的属性时就会执行get()方法
      console.log("读");
      return value;
      //return 的值是什么,那么我们获取的值就是什么
    },
    set(newVal) {
      //参数newVal:被重新赋予的值
      //set()方法为设置方法,当我们改变data对象中的属性时就会执行get()方法
      console.log("写");

      if (value === newVal) {
        //如果我们设置的是属性和原来相同,那么不会让render()函数渲染
        return;
      }
      value = newVal;
      render(); //
    },
  });
}
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
["push", "pop", "shift", "unshift", "sort", "splice", "reverse"].forEach(
  (method) => {
    arrayMethods[method] = function () {
      arrayProto[method].call(this, ...arguments);
      render();
    };
  }
);
function observe(data) {
  if (typeof data === "Array") {
    data.__proto_ = arrayMethods;
    return;
  }
  if (typeof data === "object") {
    for (const key in data) {
      defineReactive(data, key, data[key]);
    }
  }
}

//当我们执行更改data的属性时 函数就会被渲染
function render() {
  console.log("函数渲染了");
}
observe(data);

总结代码


const data = {
  name: 'shanshan',
  age: 18,
  shan: {
    name: 'shanshan',
    age: 18,
    obj: {}
  },
  arr: [1, 2, 3]
}

const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
['push', 'pop', 'shift', 'unshift' ,'sort', 'splice', 'reverse'].forEach(method => {
  arrayMethods[method] = function () {
    arrayProto[method].call(this, ...arguments);
    render();
  }
})

function defineReactive (data, key, value) {
  observer(value);
  Object.defineProperty(data, key, {
    get () {
      return value;
    },
    set (newVal) {
      if(value === newVal) {
        return;
      }
      value = newVal;
      render();
    }
  })
}

function observer (data) {
  if(Array.isArray(data)) {
    data.__proto__ = arrayMethods;
    return;
  }

  if(typeof data === 'object') {
    for(let key in data) {
      defineReactive(data, key, data[key])
    }
  }
}

function render () {
  console.log('页面渲染啦');
}

function $set (data, key, value) {
  if(Array.isArray(data)) {
    data.splice(key, 1, value);
    return value;
  }
  defineReactive(data, key, value);
  render();
  return value;
}

function $delete(data, key) {
  if(Array.isArray(data)) {
    data.splice(key, 1);
    return;
  }
  delete data[key];
  render();
}

observer(data);

利用Object.defineProperty实现响应式的劣势

天生就需要进行递归
监听不到数组不存在的索引的改变
监听不到数组长度的改变
监听不到对象的增删

你可能感兴趣的:(vue,js)