亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

為什么Vue2 this能夠直接獲取到data和methods

發布時間:2021-09-24 17:53:24 來源:億速云 閱讀:151 作者:柒染 欄目:開發技術

這篇文章將為大家詳細講解有關為什么Vue2 this能夠直接獲取到data和methods,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。

    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

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

    2. 準備環境調試源碼一探究竟

    可以在本地新建一個文件夾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({打上斷點。

    為什么Vue2 this能夠直接獲取到data和methods

    刷新頁面后按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 是一個效果。

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

    2.2 _init 初始化函數

    進入 _init 函數后,這個函數比較長,做了挺多事情,我們猜測跟datamethods相關的實現在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
          //  初始化狀態
          initState(vm);
          initProvide(vm); // resolve provide after data/props
          callHook(vm, 'created');
        };
    }

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

    2.3 initState 初始化狀態

    從函數名來看,這個函數主要實現功能是:

    • 初始化 props

    • 初始化 methods

    • 監測數據

    • 初始化 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)函數。繼續按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 的實現。

    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實現,據說是因為性能問題。
    如果對于callapplybind的用法和實現不熟悉,能否模擬實現JS的callapply方法

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

    2.5 initData 初始化 data

    • initData 函數也是一些判斷。主要做了如下事情:

    • 先給 _data 賦值,以備后用。

    • 最終獲取到的 data 不是對象給出警告。

    • 遍歷 data ,其中每一項:

    • 如果和 methods 沖突了,報警告。

    • 如果和 props 沖突了,報警告。

    • 不是內部私有的保留屬性,做一層代理,代理到 _data 上。

    • 最后監測 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 獲取數據

    是函數時調用函數,執行獲取到對象。

    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循環中是否會被枚舉。

    • configurable——該屬性是否可被刪除。

    • set() —該屬性的更新操作所調用的函數。

    • get() —獲取屬性值時所調用的函數。

    2.6 文中出現的一些函數,最后統一解釋下

    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余行代碼實現簡化版

    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);
    // 未實現前: undefined
    // '若川'
    console.log(p.sayName());
    // 未實現前:Uncaught TypeError: p.sayName is not a function
    // '若川'

    4. 總結

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

    • 構造函數

    • this 指向

    • callbindapply

    • 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的這種設計,好處在于便于獲取。也有不方便的地方,就是propsmethods 和 data三者容易產生沖突。
    文章整體難度不大,但非常建議讀者朋友們自己動手調試下。調試后,你可能會發現:原來 Vue 源碼,也沒有想象中的那么難,也能看懂一部分。

    關于為什么Vue2 this能夠直接獲取到data和methods就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    雅江县| 卢氏县| 大港区| 隆德县| 泗水县| 太湖县| 济南市| 镇安县| 凯里市| 潮州市| 四平市| 斗六市| 安阳县| 曲沃县| 大理市| 灯塔市| 萨迦县| 台南县| 微山县| 申扎县| 临漳县| 桃源县| 库车县| 玉环县| 芜湖市| 安远县| 兖州市| 锦屏县| 北流市| 伊川县| 武宁县| 锡林浩特市| 墨竹工卡县| 当雄县| 沙洋县| 永泰县| 金山区| 久治县| 新龙县| 瑞金市| 肃北|