欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

源碼揭秘為什么 Vue2 this 能夠直接獲取到 data 和 methods

 更新時間:2021年09月23日 11:28:35   作者:若川  
本篇文章主要介紹的是Vue2 this 能夠直接獲取到 data 和 methods,閱讀本文將能學到如何學習調試 vue2 源碼、data 中的數據為什么可以用 this 直接獲取到、methods 中的方法為什么可以用 this 直接獲取到,需要的朋友可以參考一下

1. 示例:this 能夠直接獲取到 data 和 methods

舉例:

const vm = new Vue({
    data: {
        name: '我是若川',
    },
    methods: {
        sayName(){
            console.log(this.name);
        }
    },
});
console.log(vm.name); // 我是若川
console.log(vm.sayName()); // 我是若川

這樣是可以輸出我是若川的。好奇的人就會思考為啥 this 就能直接訪問到呢。

那么為什么 this.xxx 能獲取到data里的數據,能獲取到 methods 方法。
我們自己構造寫的函數,如何做到類似Vue的效果呢。

function Person(options){

}

const p = new Person({
    data: {
        name: '若川'
    },
    methods: {
        sayName(){
            console.log(this.name);
        }
    }
});

console.log(p.name);
// undefined
console.log(p.sayName());
// Uncaught TypeError: p.sayName is not a function

如果是你,你會怎么去實現(xiàn)呢。帶著問題,我們來調試 Vue2源碼學習。

2. 準備環(huán)境調試源碼一探究竟

可以在本地新建一個文件夾examples,新建文件index.html文件。
<body></body>中加上如下js。

<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
<script>
    const vm = new Vue({
        data: {
            name: '我是若川',
        },
        methods: {
            sayName(){
                console.log(this.name);
            }
        },
    });
    console.log(vm.name);
    console.log(vm.sayName());
</script>

再全局安裝npm i -g http-server啟動服務。

npm i -g http-server
cd examples
http-server .
// 如果碰到端口被占用,也可以指定端口
http-server -p 8081 .

這樣就能在http://localhost:8080/打開剛寫的index.html頁面了。

調試:在 F12 打開調試,source 面板,在例子中const vm = new Vue({打上斷點。

刷新頁面后按F11進入函數,這時斷點就走進了 Vue 構造函數。

2.1 Vue 構造函數

function Vue (options) {
    if (!(this instanceof Vue)
    ) {
        warn('Vue is a constructor and should be called with the `new` keyword');
    }
    this._init(options);
}
// 初始化
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);

值得一提的是:if (!(this instanceof Vue)){} 判斷是不是用了 new 關鍵詞調用構造函數。
一般而言,我們平時應該不會考慮寫這個。
當然看源碼庫也可以自己函數內部調用 new 。但 vue 一般一個項目只需要 new Vue() 一次,所以沒必要。
而 jQuery 源碼的就是內部 new ,對于使用者來說就是無new構造。

jQuery = function( selector, context ) {
  // 返回new之后的對象
  return new jQuery.fn.init( selector, context );
};

因為使用 jQuery 經常要調用。
其實 jQuery 也是可以 new 的。和不用 new 是一個效果。

調試:繼續(xù)在this._init(options);處打上斷點,按F11進入函數。

2.2 _init 初始化函數

進入 _init 函數后,這個函數比較長,做了挺多事情,我們猜測跟datamethods相關的實現(xiàn)在initState(vm)函數里。

// 代碼有刪減
function initMixin (Vue) {
    Vue.prototype._init = function (options) {
      var vm = this;
      // a uid
      vm._uid = uid$3++;

      // a flag to avoid this being observed
      vm._isVue = true;
      // merge options
      if (options && options._isComponent) {
        // optimize internal component instantiation
        // since dynamic options merging is pretty slow, and none of the
        // internal component options needs special treatment.
        initInternalComponent(vm, options);
      } else {
        vm.$options = mergeOptions(
          resolveConstructorOptions(vm.constructor),
          options || {},
          vm
        );
      }

      // expose real self
      vm._self = vm;
      initLifecycle(vm);
      initEvents(vm);
      initRender(vm);
      callHook(vm, 'beforeCreate');
      initInjections(vm); // resolve injections before data/props
      //  初始化狀態(tài)
      initState(vm);
      initProvide(vm); // resolve provide after data/props
      callHook(vm, 'created');
    };
}

調試:接著我們在initState(vm)函數這里打算斷點,按F8可以直接跳轉到這個斷點,然后按F11接著進入initState函數。

2.3 initState 初始化狀態(tài)

從函數名來看,這個函數主要實現(xiàn)功能是:

  • 初始化 props
  • 初始化 methods
  • 監(jiān)測數據
  • 初始化 computed
  • 初始化 watch
function initState (vm) {
    vm._watchers = [];
    var opts = vm.$options;
    if (opts.props) { initProps(vm, opts.props); }
    // 有傳入 methods,初始化方法
    if (opts.methods) { initMethods(vm, opts.methods); }
    // 有傳入 data,初始化 data
    if (opts.data) {
      initData(vm);
    } else {
      observe(vm._data = {}, true /* asRootData */);
    }
    if (opts.computed) { initComputed(vm, opts.computed); }
    if (opts.watch && opts.watch !== nativeWatch) {
      initWatch(vm, opts.watch);
    }
}

我們重點來看初始化 methods,之后再看初始化 data。

調試:initMethods 這句打上斷點,同時在initData(vm)處打上斷點,看完initMethods函數后,可以直接按F8回到initData(vm)函數。繼續(xù)按F11,先進入initMethods函數。

2.4 initMethods 初始化方法

function initMethods (vm, methods) {
    var props = vm.$options.props;
    for (var key in methods) {
      {
        if (typeof methods[key] !== 'function') {
          warn(
            "Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " +
            "Did you reference the function correctly?",
            vm
          );
        }
        if (props && hasOwn(props, key)) {
          warn(
            ("Method \"" + key + "\" has already been defined as a prop."),
            vm
          );
        }
        if ((key in vm) && isReserved(key)) {
          warn(
            "Method \"" + key + "\" conflicts with an existing Vue instance method. " +
            "Avoid defining component methods that start with _ or $."
          );
        }
      }
      vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
    }
}

initMethods函數,主要有一些判斷。

  • 判斷 methods 中的每一項是不是函數,如果不是警告。
  • 判斷 methods 中的每一項是不是和 props 沖突了,如果是,警告。
  • 判斷 methods 中的每一項是不是已經在 new Vue實例 vm 上存在,而且是方法名是保留的 _ $ (在JS中一般指內部變量標識)開頭,如果是警告。

除去這些判斷,我們可以看出initMethods函數其實就是遍歷傳入的methods對象,并且使用bind綁定函數的this指向為vm,也就是new Vue的實例對象。

這就是為什么我們可以通過this直接訪問到methods里面的函數的原因。

我們可以把鼠標移上 bind 變量,按alt鍵,可以看到函數定義的地方,這里是218行,點擊跳轉到這里看 bind 的實現(xiàn)。

2.4.1 bind 返回一個函數,修改 this 指向

function polyfillBind (fn, ctx) {
    function boundFn (a) {
      var l = arguments.length;
      return l
        ? l > 1
          ? fn.apply(ctx, arguments)
          : fn.call(ctx, a)
        : fn.call(ctx)
    }

    boundFn._length = fn.length;
    return boundFn
}

function nativeBind (fn, ctx) {
  return fn.bind(ctx)
}

var bind = Function.prototype.bind
  ? nativeBind
  : polyfillBind;

簡單來說就是兼容了老版本不支持 原生的bind函數。同時兼容寫法,對參數多少做出了判斷,使用callapply實現(xiàn),據說是因為性能問題。
如果對于callapply、bind的用法和實現(xiàn)不熟悉,能否模擬實現(xiàn)JS的callapply方法

調試:看完了initMethods函數,按F8回到上文提到的initData(vm)函數斷點處。

2.5 initData 初始化 data

  • initData 函數也是一些判斷。主要做了如下事情:
  • 先給 _data 賦值,以備后用。
  • 最終獲取到的 data 不是對象給出警告。
  • 遍歷 data ,其中每一項:
  • 如果和 methods 沖突了,報警告。
  • 如果和 props 沖突了,報警告。
  • 不是內部私有的保留屬性,做一層代理,代理到 _data 上。
  • 最后監(jiān)測 data,使之成為響應式的數據。
function initData (vm) {
    var data = vm.$options.data;
    data = vm._data = typeof data === 'function'
      ? getData(data, vm)
      : data || {};
    if (!isPlainObject(data)) {
      data = {};
      warn(
        'data functions should return an object:\n' +
        'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
        vm
      );
    }
    // proxy data on instance
    var keys = Object.keys(data);
    var props = vm.$options.props;
    var methods = vm.$options.methods;
    var i = keys.length;
    while (i--) {
      var key = keys[i];
      {
        if (methods && hasOwn(methods, key)) {
          warn(
            ("Method \"" + key + "\" has already been defined as a data property."),
            vm
          );
        }
      }
      if (props && hasOwn(props, key)) {
        warn(
          "The data property \"" + key + "\" is already declared as a prop. " +
          "Use prop default value instead.",
          vm
        );
      } else if (!isReserved(key)) {
        proxy(vm, "_data", key);
      }
    }
    // observe data
    observe(data, true /* asRootData */);
}

2.5.1 getData 獲取數據

是函數時調用函數,執(zhí)行獲取到對象。

function getData (data, vm) {
    // #7573 disable dep collection when invoking data getters
    pushTarget();
    try {
      return data.call(vm, vm)
    } catch (e) {
      handleError(e, vm, "data()");
      return {}
    } finally {
      popTarget();
    }
}

2.5.2 proxy 代理

其實就是用 Object.defineProperty 定義對象
這里用處是:this.xxx 則是訪問的 this._data.xxx。

/**
   * Perform no operation.
   * Stubbing args to make Flow happy without leaving useless transpiled code
   * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/).
   */
function noop (a, b, c) {}
var sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: noop,
    set: noop
};

function proxy (target, sourceKey, key) {
    sharedPropertyDefinition.get = function proxyGetter () {
      return this[sourceKey][key]
    };
    sharedPropertyDefinition.set = function proxySetter (val) {
      this[sourceKey][key] = val;
    };
    Object.defineProperty(target, key, sharedPropertyDefinition);
}

2.5.3 Object.defineProperty 定義對象屬性

  • Object.defineProperty 算是一個非常重要的API。還有一個定義多個屬性的API:Object.defineProperties(obj, props) (ES5)
  • Object.defineProperty 涉及到比較重要的知識點,面試也???。
  • value——當試圖獲取屬性時所返回的值。
  • writable——該屬性是否可寫。
  • enumerable——該屬性在for in循環(huán)中是否會被枚舉。
  • configurable——該屬性是否可被刪除。
  • set() —該屬性的更新操作所調用的函數。
  • get() —獲取屬性值時所調用的函數。

2.6 文中出現(xiàn)的一些函數,最后統(tǒng)一解釋下

2.6.1 hasOwn 是否是對象本身擁有的屬性

調試模式下,按alt鍵,把鼠標移到方法名上,可以看到函數定義的地方。點擊可以跳轉。
/**
   * Check whether an object has the property.
   */
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn (obj, key) {
  return hasOwnProperty.call(obj, key)
}

hasOwn({ a: undefined }, 'a') // true
hasOwn({}, 'a') // false
hasOwn({}, 'hasOwnProperty') // false
hasOwn({}, 'toString') // false
// 是自己的本身擁有的屬性,不是通過原型鏈向上查找的。

2.6.2 isReserved 是否是內部私有保留的字符串$  和 _ 開頭

/**
   * Check if a string starts with $ or _
   */
function isReserved (str) {
  var c = (str + '').charCodeAt(0);
  return c === 0x24 || c === 0x5F
}
isReserved('_data'); // true
isReserved('$options'); // true
isReserved('data'); // false
isReserved('options'); // false

3. 最后用60余行代碼實現(xiàn)簡化版

function noop (a, b, c) {}
var sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: noop,
    set: noop
};
function proxy (target, sourceKey, key) {
    sharedPropertyDefinition.get = function proxyGetter () {
      return this[sourceKey][key]
    };
    sharedPropertyDefinition.set = function proxySetter (val) {
      this[sourceKey][key] = val;
    };
    Object.defineProperty(target, key, sharedPropertyDefinition);
}
function initData(vm){
  const data = vm._data = vm.$options.data;
  const keys = Object.keys(data);
  var i = keys.length;
  while (i--) {
    var key = keys[i];
    proxy(vm, '_data', key);
  }
}
function initMethods(vm, methods){
  for (var key in methods) {
    vm[key] = typeof methods[key] !== 'function' ? noop : methods[key].bind(vm);
  } 
}

function Person(options){
  let vm = this;
  vm.$options = options;
  var opts = vm.$options;
  if(opts.data){
    initData(vm);
  }
  if(opts.methods){
    initMethods(vm, opts.methods)
  }
}

const p = new Person({
    data: {
        name: '若川'
    },
    methods: {
        sayName(){
            console.log(this.name);
        }
    }
});

console.log(p.name);
// 未實現(xiàn)前: undefined
// '若川'
console.log(p.sayName());
// 未實現(xiàn)前:Uncaught TypeError: p.sayName is not a function
// '若川'

4. 總結

本文涉及到的基礎知識主要有如下:

  • 構造函數
  • this 指向
  • call、bindapply
  • Object.defineProperty

本文源于解答源碼共讀群友的疑惑,通過詳細的描述了如何調試 Vue 源碼,來探尋答案。
解答文章開頭提問:
通過this直接訪問到methods里面的函數的原因是:因為methods里的方法通過 bind 指定了this為 new Vue的實例(vm)。
通過 this 直接訪問到 data 里面的數據的原因是:data里的屬性最終會存儲到new Vue的實例(vm)上的 _data對象中,訪問 this.xxx,是訪問Object.defineProperty代理后的 this._data.xxx。
Vue的這種設計,好處在于便于獲取。也有不方便的地方,就是props、methods 和 data三者容易產生沖突。
文章整體難度不大,但非常建議讀者朋友們自己動手調試下。調試后,你可能會發(fā)現(xiàn):原來 Vue 源碼,也沒有想象中的那么難,也能看懂一部分。
啟發(fā):我們工作使用常用的技術和框架或庫時,保持好奇心,多思考內部原理。能夠做到知其然,知其所以然。就能遠超很多人。
你可能會思考,為什么模板語法中,可以省略this關鍵詞寫法呢,內部模板編譯時其實是用了with。有余力的讀者可以探究這一原理。

到此這篇關于源碼揭秘為什么 Vue2 this 能夠直接獲取到 data 和 methods的文章就介紹到這了,更多相關Vue2 this 直接獲取到 data 和 methods內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Vue2.0在IE11版本瀏覽器中的兼容性問題

    Vue2.0在IE11版本瀏覽器中的兼容性問題

    這篇文章主要介紹了Vue2.0在IE11版本瀏覽器中的兼容性問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Vue入門之數據綁定(小結)

    Vue入門之數據綁定(小結)

    本篇文章主要介紹了探索Vue高階組件的使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • Vue+Vuex實現(xiàn)自動登錄的知識點詳解

    Vue+Vuex實現(xiàn)自動登錄的知識點詳解

    在本篇文章里小編給大家整理的是關于Vue+Vuex實現(xiàn)自動登錄的知識點詳解,需要的朋友們可以學習下。
    2020-03-03
  • 使用vue.js寫一個tab選項卡效果

    使用vue.js寫一個tab選項卡效果

    Vue 實現(xiàn) Tab切換實現(xiàn)的場景很多,比如,利用vue-router、利用第三方插件、利用組件等等.本文使用組件來實踐tab選項卡
    2017-03-03
  • Vue使用NProgress的操作過程解析

    Vue使用NProgress的操作過程解析

    這篇文章主要介紹了Vue使用NProgress的操作過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-10-10
  • element el-table如何實現(xiàn)表格動態(tài)增加/刪除/編輯表格行(帶校驗規(guī)則)

    element el-table如何實現(xiàn)表格動態(tài)增加/刪除/編輯表格行(帶校驗規(guī)則)

    這篇文章主要介紹了element el-table如何實現(xiàn)表格動態(tài)增加/刪除/編輯表格行(帶校驗規(guī)則),本篇文章記錄el-table增加一行可編輯的數據列,進行增刪改,感興趣的朋友跟隨小編一起看看吧
    2024-07-07
  • vue實現(xiàn)移動端懸浮窗效果

    vue實現(xiàn)移動端懸浮窗效果

    這篇文章主要為大家詳細介紹了vue實現(xiàn)移動端懸浮窗效果,vuejs實現(xiàn)div拖拽移動,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • vue-element-admin開發(fā)教程(v4.0.0之后)

    vue-element-admin開發(fā)教程(v4.0.0之后)

    由于vue-element-admin的架構再4.0.0版本后做了重構,整體結構上和之前的還是很相似的,但是也有些變化,本文就介紹vue-element-admin開發(fā)教程(v4.0.0之后),感興趣的可以了解一下
    2022-04-04
  • 詳解Vue-基本標簽和自定義控件

    詳解Vue-基本標簽和自定義控件

    本篇文章主要介紹了Vue-基本標簽和自定義控件,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-03-03
  • 在vue3項目中給頁面添加水印的實現(xiàn)方法

    在vue3項目中給頁面添加水印的實現(xiàn)方法

    這篇文章主要給大家介紹一下在vue3項目中添加水印的實現(xiàn)方法,文中有詳細的代碼示例供大家參考,具有一定的參考價值,感興趣的小伙伴跟著小編一起來看看吧
    2023-08-08

最新評論