preface

The object.defineproperty () method is the key to understanding data hijacking in vue2.0. If you don’t already know this method, you can learn it first and then read the article or source code. To learn.

This article has written the data hijacking demo, the main reference vue2.0 source code. For simplicity, only the core functionality has been removed and a small change has been made. Hopefully this article has helped you read the data hijacking section of the vue2.0 source code. Finally, the case source code, so that we learn to understand.

Environment set up

Note: Make sure the Node environment is installed

  1. Create a new folder: XXX

  2. In the current folder, open a PowerShell or DOS window

  3. Execute NPM init for initialization (keep pressing Enter)

  4. Use NPM to install the required dependency packages

npm webpack webpack-cli webpack-dev-server html-webpack-plugin -D
Copy the code
  1. Open the package.json file, modify scripts, and add two lines of command. Other basic attributes, modify.
"scripts": {
    "dev": "webpack-dev-server".// Run NPM run dev
    "build": "webpack" // Package the NPM run build
  },
Copy the code

Final result (package.json) :

{
  "name": "vue-demo"."version": "1.0.0"."description": "Vue-data Hijacking"."main": "index.js"."scripts": {
    "dev": "webpack-dev-server"."build": "webpack"
  },
  "keywords": []."author": ""."license": "ISC"."devDependencies": {
    "html-webpack-plugin": "^ 4.4.1." "."webpack": "^ 4.44.1"."webpack-cli": "^" 3.3.12."webpack-dev-server": "^ 3.11.0"}}Copy the code
  1. Create webpack.config.js and perform basic configuration.
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js'.// Import file
  
  // Output tells webPack how and where to output your "bundle, asset, and
  // Anything else you pack or load with Webpack.
  output: { 
    filename: 'js/bundle.js'.path: path.resolve(__dirname, 'dist')},devtool: 'source-map'.// Generate the source map
  
  resolve: {
    // With modules configured as follows, you can use import Vue from 'Vue' in the import file;
    // This is introduced in the same way as in the vue project main.js
    modules: [path.resolve(__dirname, ' '), path.resolve(__dirname, 'node_modules')]},devServer: {
    host: '127.0.0.1'.// Local services
    port: 8088 // Default port
  },
  
  plugins: [
  // The plugin will generate an HTML5 file for you, introduced in the body using the script tag
  // All of your WebPack-generated bundles.
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'public/index.html')]}})Copy the code

Project structure Preview

  • SRC /index.js Project entry js file
  • The files under SRC/Vue are the core of data hijacking
  • Public /index.html Page entry file

Code parsing

SRC /index.js to create an instance

Create a Vue instance using the new keyword.

import Vue from 'vue';

const vm = new Vue({
  el: '#app',
  data () {
    return {
      studentNum: 1.subject: ['history'.'culture'].bookInfo: {
        name: Romance of The Three Kingdoms.author: {
         name: 'Luo Guanzhong'.age: 18}},studentList: [{ id: 1.name: 'Ming'}}}});console.log('vm instances', vm);
vm.studentList.push({id: 2.name: 'grapes'});
console.log('A few people', vm.studentList);
Copy the code

Vue/index. Js entrance

Define and export Vue functions for instantiation with the new keyword. At the same time, execute initMixin(Vue) to initialize the _init method and mount it to Vue. Prototype.

import { initMixin } from './init';

function Vue (options) {
  // When a Vue instance is created with the keyword new, the Vue prototype method _init is called to initialize the data
  this._init(options); 
}

// Executing initMixin will mount _init on vue. prototype (Vue prototype)
initMixin(Vue); 

export default Vue;
Copy the code

Vue/init. Js initialization

Call the initState(VM) function to initialize the data.

import { initState } from './state';

function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    const vm = this; // Store this (Vue instance)
    vm.$options = options; // Mount options to the VM for later use

    // Data, props, methods, computed, and watch in Vue instances are all in the initState function
    // Initialize. Since we mainly explain: Vue data hijacking, only data will be processed.initState(vm); }}export {
  initMixin
}
Copy the code

Vue /state.js initializes data

If data exists, the initData(VM) function is called to initialize data.

import proxy from './proxy';
import observe from './observe/index';

function initState (vm) {
  const options = vm.$options;

  if (options.data) {
    initData(vm); // Initialize data}}function initData (vm) {
  let data = vm.$options.data;

  // Data in Vue can be functions (it is recommended to use data as a function in Vue)
  // Can also be Object --> {}
  data = vm.$data = typeof data === 'function' ? data.call(vm) : data || {};
  
  for (var key in data) {
    // proxy implements the data proxy, vm.name --> vm.$data
    proxy(vm, '$data', key);
  }

  // Observe data in order to respond when it changes.
  observe(vm.$data); 
}

export {
    initState
}
Copy the code

Vue /proxy.js Data proxy

The key to the proxy() function implementing the data proxy is the use of the object.defineProperty () method.

function proxy (vm, target, key) {
  // Object.defineProperty() directly defines a new property on an Object,
  // Or modify an existing property of an object and return the object.
  
  // Mount the properties to the VM (Vue instance) and set the getters/setters for the properties,
  // To implement the data broker: vm.name --> vm.$data.name
  
  Object.defineProperty(vm, key, {
    get () {
      return vm[target][key]; // vm[target][key] --> vm.$data.name}, set (newValue) { vm[target][key] = newValue; }}); }export default proxy;
Copy the code

Vue /observe/index.js Observe data

Observe (val) checks whether val is an object (array is also an object). If not, block the run. If so, call new Observer(val), which calls different methods to observe the data object depending on whether val is an Array ([object Array]) or an object ([object object]).

import observeArray from './observeArray';
import { defineReactive } from './reactive';
import { arrayMethods } from './array';
import { isObject } from '.. /shared/util';

function observe (val) {
  // Check if val is an object. (Note: in JS, arrays are also objects. IsObject does not exclude arrays.)
  if(! isObject(val))return;
  return new Observer(val);
}

function Observer (val) {
  if (Array.isArray(val)) {
    // arrayMethods stores overridden arrayMethods, such as push, unshift, etc.
    // Overwrite to do more when changing the data in the array. For example, using the push method
    // When new data is added to the array, it is necessary to hijack the new data and set the getter/setter, otherwise
    // They will not be able to react to subsequent changes. In fact, overridden array methods are still used internally
    // Array native methods to add and delete data.

    val.__proto__ = arrayMethods; // Use __proto__ to intercept the prototype chain to increment the target object
    observeArray(val); // Observe each item in the Array
  } else {
    this.walk(val); // Observe Object (Object --> {})}}// Iterate over all properties and convert them to getters/setters. This method should be called only if the value type is Object
Observer.prototype.walk = function (data) {
  // the object.keys () method returns an array of the given Object's own enumerable properties,
  // The order of the property names in the array is the same as the order returned by a normal loop through the object.
  const keys = Object.keys(data);
   
  for (let i = 0; i < keys.length; i ++) {
    const key = keys[i]; / / property
    const value = data[key]; / / property values
    // Define a reactivity property on the objectdefineReactive(data, key, value); }}export default observe;
Copy the code

vue/shared/util.js

// Object detection
export function isObject (obj) {
    returnobj ! = =null && typeof obj === 'object'
}
Copy the code

Vue /observe/array.js array method overridden

Overwrite array methods (push, unshift, etc.) not to replace them, but to do more when changing the data in the array. For example, when new data is added to an array using the push method, the new data needs to be hijacked so that it will not react to subsequent changes.

import observeArray from './observeArray';

// Store array methods
const methodsToPatch = [
  'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
];

const slice = Array.prototype.slice;
const arrayProto = Array.prototype; // Store the array prototype
const arrayMethods = Object.create(arrayProto); // Create a new array prototype object

methodsToPatch.forEach(function (method) {
  
  const original = arrayProto[method]; // The original method of caching the array
  
  arrayMethods[method] = function () {

    let inserted; // Store new values in the array, undefined by default
    let args = slice.call(arguments); // Convert arguments to a new array and return

    // Instead of returning a value, write original.apply(this, args)
    const result = original.apply(this, args); // Call the native methods of the array to add and delete the array.

    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        The splice() method is used to add or remove elements from an array
        Splice (0, 1) --> args = [0, 1]
        // add: splice(1, 0, 'add ') --> args = 1, 0,' add ']

        The slice() method returns selected elements from an existing array
        // args.slice(2), fixed subscript 2 because splice is used:
        Args. Slice (2) returns an empty array
        Args. Slice (2) returns a new array containing all the new data
        inserted = args.slice(2);
        break;
      default:
        break;
    }

    Inserted is true (empty array --> [], also true), then the observeArray() method is called
    inserted && observeArray(inserted);

    returnresult; }});export {
  arrayMethods
}
Copy the code

Vue/observe/observeArray. Js recursive observe each item of the array

import observe from './index';

function observeArray (arr) {
    // Iterate over the group ARR, recursively observing each of its terms
  for (let i = 0; i < arr.length; i ++) { observe(arr[i]); }}export default observeArray;
Copy the code

Vue /observe/ react.js hijacked data

import observe from "./index";

function defineReactive (data, key, value) {
  
  // Recursively observe value, which may be an object
  observe(value); 

  // Object.defineProperty() directly defines a new property on an Object,
  // Or modify an existing property of an object and return the object. It is the key to data hijacking.
  Object.defineProperty(data, key, {
    get: function reactiveGetter () {
      return value;
    },
    set: function reactiveSetter (newValue) {
      if (newValue === value) return; // Attributes of the same name, no reassignment or observation required
      observe(value); // Recursively observe value, which may be an objectvalue = newValue; }}); }export {
  defineReactive
;
Copy the code

conclusion

You can’t just read without coding. Remember to knock again, knock while debugging, only so, in order to deeply understand.