<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 20173字,需瀏覽 41分鐘

           ·

          2022-07-24 18:50

          hey!我是pino。一枚小透明,期待關(guān)注? 點贊,共同成長~

          在平時使用vue來開發(fā)項目的時候,對于下面這一段代碼,我們可能每天都會見到:

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

          但是我們自己實現(xiàn)一個構(gòu)造函數(shù)卻實現(xiàn)不了這種效果呢?

          function Super(options){}

          const p = new Super({
              data: {
                  name'pino'
              },
              methods: {
                  print(){
                      console.log(this.name);
                  }
              }
          });

          console.log(p.name); // undefined
          p.print(); // p.print is not a function

          那么vue2中是怎么實現(xiàn)這種調(diào)用方式的呢?

          源碼

          首先可以找到vue2的入口文件:

          src/core/instance/index


          function Vue (options{
            if (process.env.NODE_ENV !== 'production' &&
              !(this instanceof Vue)
            ) {
              warn('Vue is a constructor and should be called with the `new` keyword')
            }
            this._init(options)
          }

          // 初始化操作是在這個函數(shù)完成的
          initMixin(Vue)

          stateMixin(Vue)
          eventsMixin(Vue)
          lifecycleMixin(Vue)
          renderMixin(Vue)

          export default Vue

          接下來看initMixin文件中是如何實現(xiàn)的


          export function initMixin (Vue: Class<Component>{
            Vue.prototype._init = function (options?: Object{
              const vm: Component = this
              // a uid
              vm._uid = uid++

              let startTag, endTag
              /* istanbul ignore if */
              if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
                startTag = `vue-perf-start:${vm._uid}`
                endTag = `vue-perf-end:${vm._uid}`
                mark(startTag)
              }

              // 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
                )
              }
              /* istanbul ignore else */
              if (process.env.NODE_ENV !== 'production') {
                initProxy(vm)
              } else {
                vm._renderProxy = vm
              }
              // expose real self
              vm._self = vm
              initLifecycle(vm)
              initEvents(vm)
              initRender(vm)
              callHook(vm, 'beforeCreate')
              initInjections(vm) // resolve injections before data/props
              
              // 初始化data/methods...
              initState(vm)
              initProvide(vm) // resolve provide after data/props
              callHook(vm, 'created')
            }
          }

          其實僅僅關(guān)注initState這個函數(shù)就好了,這個函數(shù)初始化了propsmethodswatchcomputed

          • 使用initProps初始化了props
          • 使用initMethods初始化了methods
          • 使用initData初始化了data
          • 使用initComputed初始化了computed
          • 使用initWatch初始化了watch
          function initState (vm{
              vm._watchers = [];
              var opts = vm.$options;
              // 判斷props屬性是否存在,初始化props
              if (opts.props) { initProps(vm, opts.props); }
              // 有傳入 methods,初始化方法methods
              if (opts.methods) { initMethods(vm, opts.methods); }
              // 有傳入 data,初始化 data
              if (opts.data) {
                initData(vm);
              } else {
                observe(vm._data = {}, true /* asRootData */);
              }
              // 初始化computed
              if (opts.computed) { initComputed(vm, opts.computed); }
              // 初始化watch
              if (opts.watch && opts.watch !== nativeWatch) {
                initWatch(vm, opts.watch);
              }
          }

          在這里只關(guān)注initMethodsinitData

          initMethods

          function initMethods (vm, methods{
              var props = vm.$options.props;
              for (var key in methods) {
                {
                    // 判斷是否為函數(shù)
                  if (typeof methods[key] !== 'function') {
                    warn(
                      "Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " +
                      "Did you reference the function correctly?",
                      vm
                    );
                  }
                  
                  // 判斷props存在且props中是否有同名屬性
                  if (props && hasOwn(props, key)) {
                    warn(
                      ("Method \"" + key + "\" has already been defined as a prop."),
                      vm
                    );
                  }
                  // 判斷實例中是否有同名屬性,而且是方法名是保留的 _ $ (在JS中一般指內(nèi)部變量標識)開頭
                  if ((key in vm) && isReserved(key)) {
                    warn(
                      "Method \"" + key + "\" conflicts with an existing Vue instance method. " +
                      "Avoid defining component methods that start with _ or $."
                    );
                  }
                }
                // 將methods中的每一項的this指向綁定至實例
                // bind的作用就是用于綁定指向,作用同js原生的bind
                vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
              }
          }

          其實整個initMethods方法核心就是將this綁定到了實例身上,因為methods里面都是函數(shù),所以只需要遍歷將所有的函數(shù)在調(diào)用的時候?qū)?code style="box-sizing: border-box;font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;overflow-wrap: break-word;padding: 3px;border-radius: 4px;margin: 3px;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);word-break: break-all;">this指向?qū)嵗涂梢詫崿F(xiàn)通過this直接調(diào)用的效果。

          其他的大部分代碼都是用于一些邊界條件的判斷:

          • 如果不為函數(shù) -> 報錯
          • props存在且props中是否有同名屬性 -> 報錯
          • 實例中是否有同名屬性,而且是方法名是保留的 -> 報錯

          bind函數(shù)

          function polyfillBind (fn, ctx{
              function boundFn (a{
                var l = arguments.length;
                // 判斷參數(shù)的個數(shù)來分別使用call/apply進行調(diào)用
                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)
          }
          // 判斷是否支持原生的bind方法
          var bind = Function.prototype.bind
            ? nativeBind
            : polyfillBind;

          bind函數(shù)中主要是做了兼容性的處理,如果不支持原生的bind函數(shù),則根據(jù)參數(shù)個數(shù)的不同分別使用call/apply來進行this的綁定,而call/apply最大的區(qū)別就是傳入?yún)?shù)的不同,一個分別傳入?yún)?shù),另一個接受一個數(shù)組。

          hasOwn 用于判斷是否為對象本身所擁有的對象,上文通過此函數(shù)來判斷是否在props中存在相同的屬性

          // 只判斷是否為本身擁有,不包含原型鏈查找
          var hasOwnProperty = Object.prototype.hasOwnProperty; 
          function hasOwn (obj, key
              return hasOwnProperty.call(obj, key) 
          }

          hasOwn({}, 'toString'// false
          hasOwn({ name'pino' }, 'name'// true

          isReserved

          判斷是否為內(nèi)部私有命名(以$_開頭)


          function isReserved (str{
            var c = (str + '').charCodeAt(0);
            return c === 0x24 || c === 0x5F
          }
          isReserved('_data'); // true
          isReserved('data'); // false

          initData

          function initData (vm{
              var data = vm.$options.data;
              // 判斷data是否為函數(shù),如果是函數(shù),在getData中執(zhí)行函數(shù)
              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
              // 取值 props/methods/data的值
              var keys = Object.keys(data);
              var props = vm.$options.props;
              var methods = vm.$options.methods;
              var i = keys.length;
              // 判斷是否為props/methods存在的屬性
              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
              // 監(jiān)聽數(shù)據(jù)
              observe(data, true /* asRootData */);
          }

          getData

          如果data為函數(shù)時,調(diào)用此函數(shù)對data進行執(zhí)行

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

          proxy

          代理攔截,當使用this.xxx訪問某個屬性時,返回this.data.xxx

          // 一個純凈函數(shù)
          function noop (a, b, c{}

          // 代理對象
          var sharedPropertyDefinition = {
              enumerabletrue,
              configurabletrue,
              get: noop,
              set: noop
          };

          function proxy (target, sourceKey, key{
              // get攔截
              sharedPropertyDefinition.get = function proxyGetter ({
                return this[sourceKey][key]
              };
              // set攔截
              sharedPropertyDefinition.set = function proxySetter (val{
                this[sourceKey][key] = val;
              };
              // 使用Object.defineProperty對對象進行攔截
              Object.defineProperty(target, key, sharedPropertyDefinition);
          }

          其實對data的處理就是將data中的屬性的key遍歷綁定至實例vm上,然后使用Object.defineProperty進行攔截,將真實的數(shù)據(jù)操作都轉(zhuǎn)發(fā)到this.data上。

          Object.defineProperty對象屬性

          value:屬性的默認值。 
          writable:該屬性是否可寫。 
          enumerable:該屬性是否可被枚舉。 
          configurable:該屬性是否可被刪除。 
          set():該屬性的更新操作所調(diào)用的函數(shù)。 
          get():獲取屬性值時所調(diào)用的函數(shù)。

          簡略實現(xiàn)

            function Person(options{
                let vm = this
                vm.$options = options

                if(options.data) {
                  initData(vm)
                } 
                if(options.methods) {
                  initMethods(vm, options.methods)
                }
              }

              function initData(vm{
                let data = vm._data = vm.$options.data

                let keys = Object.keys(data)

                let len = keys.length
                while(len--) {
                  let key = keys[len]
                  proxy(vm, "_data", key)
                }
              }

              var sharedPropertyDefinition = {
                  enumerabletrue,
                  configurabletrue,
                  get: noop,
                  set: noop
              };

              function proxy(target, sourceKeys, key{

                sharedPropertyDefinition.get = function({
                  return this[sourceKeys][key]
                }

                sharedPropertyDefinition.set = function(val{
                  this[sourceKeys][key] = val
                }

                Object.defineProperty(target, key, sharedPropertyDefinition)

              }

              function noop(a, b, c{}

              function initMethods(vm, methods{
                for(let key in methods) {
                  vm[key] = typeof methods[key] === 'function' ? methods[key].bind(vm) : noop
                }
              }

              let p1 = new Person({
                data: {
                  name'pino',
                  age18
                },
                methods: {
                  sayName() {
                    console.log('I am' + this.name)
                  }
                }
              })

              console.log(p1.name) // pino
              p1.sayName() // 'I am pino'

          總結(jié)

          所以就可以回答題目的問題了:

          通過this直接訪問到methods里面的函數(shù)的原因是:因為methods里的方法通過 bind 指定了this為 new Vue的實例(vm)。

          通過 this 直接訪問到 data 里面的數(shù)據(jù)的原因是:data里的屬性最終會存儲到new Vue的實例(vm)上的 _data對象中,訪問 this.xxx,是訪問Object.defineProperty代理后的 this._data.xxx

          寫在最后 ?

          未來可能會更新實現(xiàn)mini-vue3javascript基礎(chǔ)知識系列,希望能一直堅持下去,期待多多點贊????,一起進步!????

          關(guān)于本文

          來自:pino

          https://juejin.cn/post/7112255428452417549


          最后


          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端編程源碼算法群,每日一道面試題(工作日),第二天瓶子君都會很認真的解答喲!
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對你有幫助,在看」是最大的支持
           》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持
          瀏覽 26
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操美女视频网站 | 特一级一性一交一视频 | 色婷婷A V | 大香蕉伊人网婷婷 | 免费观看一级一片 |