<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>

          JavaScript 元編程

          共 12258字,需瀏覽 25分鐘

           ·

          2021-02-04 09:28

          JavaScript 有許多開發(fā)者熟知的有用特性,同時(shí)也有一些鮮為人知的特性能夠幫助我們解決棘手問題。

          很多人可能不太了解 JavaScript 元編程的概念,本文會介紹元編程的知識和它的作用。

          ES6(ECMAScript 2015)新增了對  Reflect  和  Proxy  對象的支持,使得我們能夠便捷地進(jìn)行元編程。讓我們通過示例來學(xué)習(xí)它們的用法。

          什么是元編程?

          元編程  無異于  編程中的魔法!如果編寫一個(gè)“能夠讀取、修改、分析、甚至生成新程序”的程序?qū)绾??是不是聽起來很神奇、很?qiáng)大?

          元編程很神奇

          維基百科這樣描述元編程:

          元編程  是一種編程技術(shù),編寫出來的計(jì)算機(jī)程序能夠?qū)⑵渌绦蜃鳛閿?shù)據(jù)來處理。意味著可以編寫出這樣的程序:它能夠讀取、生成、分析或者轉(zhuǎn)換其它程序,甚至在運(yùn)行時(shí)修改程序自身。

          簡而言之,元編程能夠?qū)懗鲞@樣的代碼:

          • 可以生成代碼
          • 可以在運(yùn)行時(shí)修改語言結(jié)構(gòu),這種現(xiàn)象被稱為  反射編程  或  反射

          什么是反射?

          反射  是元編程的一個(gè)分支。反射又有三個(gè)子分支:

          1. 自?。↖ntrospection):代碼能夠自我檢查、訪問內(nèi)部屬性,我們可以據(jù)此獲得代碼的底層信息。
          2. 自我修改(Self-Modification):顧名思義,代碼可以修改自身。
          3. 調(diào)解(Intercession):字面意思是“代他人行事”。在元編程中,調(diào)解的概念類似于包裝(wrapping)、捕獲(trapping)、攔截(intercepting)。

          ES6 為我們提供了  Reflect  對象(Reflect API)來實(shí)現(xiàn)  自省,還提供了  Proxy  對象幫助我們實(shí)現(xiàn)  調(diào)解。我們要盡力避免  自我修改,所以本文不會過多談及這一點(diǎn)。

          需要說明的是,元編程并不是由 ES6 引入的,JavaScript 語言從一開始就支持元編程,ES6 只是讓它變得更易于使用。

          ES6 之前的元編程

          還記得  eval  嗎?看看它的用法:

          const blog = {
              name'freeCodeCamp'
          }
          console.log('Before eval:', blog);

          const key = 'author';
          const value = 'Tapas';
          testEval = () => eval(`blog.${key} = '${value}'`);

          // 調(diào)用函數(shù)
          testEval();

          console.log('After eval magic:', blog);

          eval  生成了額外的代碼。示例代碼執(zhí)行時(shí)為  blog  對象增加了一個(gè)  author  屬性。

          Before eval: {name: freeCodeCamp}
          After eval magic: {name: "freeCodeCamp", author: "Tapas"}

          自省

          在 ES6 引入  Reflect 對象  之前,我們也可以實(shí)現(xiàn)自省。下面是讀取程序結(jié)構(gòu)的示例:

          var users = {
              'Tom'32,
              'Bill'50,
              'Sam'65
          };

          Object.keys(users).forEach(name => {
              const age = users[name];
              console.log(`User ${name} is ${age} years old!`);
          });

          我們讀取了  users  對象的結(jié)構(gòu)并以鍵值對的形式打印出來。

          User Tom is 32 years old!
          User Bill is 50 years old!
          User Sam is 65 years old!

          自我修改

          以一個(gè)包含修改其自身的方法的  blog  對象為例:

          var blog = {
              name'freeCodeCamp',
              modifySelffunction(key, value{blog[key] = value}
          }

          blog  對象可以這樣來修改自身:

          blog.modifySelf('author''Tapas');

          調(diào)解(Intercession)

          元編程中的  調(diào)解  指的是改變其它對象的語義。在 ES6 之前,可以用  Object.defineProperty()  方法來改變對象的語義:

          var sun = {};
          Object.defineProperty(sun, 'rises', {
              valuetrue,
              configurablefalse,
              writablefalse,
              enumerablefalse
          });

          console.log('sun rises', sun.rises);
          sun.rises = false;
          console.log('sun rises', sun.rises);

          輸出:

          sun rises true
          sun rises true

          如你所見,我們創(chuàng)建了一個(gè)普通對象  sun,之后改變了它的語義:為其定義了一個(gè)不可寫的  rises  屬性。

          現(xiàn)在,我們深入了解一下  Reflect  和  Proxy  對象以及它們的用法。

          Reflect API

          在 ES6 中,Reflect 是一個(gè)新的  全局對象(像 math 一樣),它提供了一些工具函數(shù),其中一些函數(shù)與  Object  或  Function  對象中的同名方法功能是相同的。

          這些都是自省方法,可以用它們在運(yùn)行時(shí)獲取程序內(nèi)部信息。

          以下是  Reflect  對象提供的方法列表。點(diǎn)擊此處可以查看這些方法的詳細(xì)信息。

          // Reflect 對象方法

          Reflect.apply()
          Reflect.construct()
          Reflect.get()
          Reflect.has()
          Reflect.ownKeys()
          Reflect.set()
          Reflect.setPrototypeOf()
          Reflect.defineProperty()
          Reflect.deleteProperty()
          Reflect.getOwnPropertyDescriptor()
          Reflect.getPrototypeOf()
          Reflect.isExtensible()

          等等,現(xiàn)在問題來了:既然  Object  或  Function  對象中已經(jīng)有這些方法了,為什么還要引入新的 API 呢?

          困惑嗎?讓我們一探究竟。

          集中在一個(gè)命名空間

          JavaScript 已經(jīng)支持對象反射,但是這些 API 沒有集中到一個(gè)命名空間中。從 ES6 開始,它們被集中到  Reflect  對象中。

          與其他全局對象不同,Reflect 不是一個(gè)構(gòu)造函數(shù),不能使用 new 操作符來調(diào)用它,也不能將它當(dāng)做函數(shù)來調(diào)用。Reflect  對象中的方法和 math 對象中的方法一樣是  靜態(tài)  的。

          易于使用

          Object  對象中的  自省  方法在操作失敗的時(shí)候會拋出異常,這給開發(fā)者增加了處理異常的負(fù)擔(dān)。

          也許你更傾向于把操作結(jié)果當(dāng)做布爾值來處理,而不是去處理異常,借助 Reflect 對象就可以做到。

          以下是使用 Object.defineProperty 方法的示例:

           try {
                  Object.defineProperty(obj, name, desc);
                  // 執(zhí)行成功
              } catch (e) {
                  // 執(zhí)行失敗,處理異常
              }

          使用 Reflect API 的方式如下:

          if (Reflect.defineProperty(obj, name, desc)) {
            // 執(zhí)行成功
          else {
           // 處理執(zhí)行失敗的情況。(這種處理方式好多了)
          }

          一等函數(shù)的魅力

          我們可以通過  (prop in obj)  操作來判斷對象中是否存在某個(gè)屬性。如果多次用到這個(gè)操作,我們需要把它封裝成函數(shù)。

          在 ES6 的  Reflect API  中已經(jīng)包含了這些方法,例如,Reflect.has(obj, prop) 和 (prop in obj) 功能是一樣的。

          看看另一個(gè)刪除對象屬性的示例:

          const obj = { bartruebazfalse};

          // We define this function
          function deleteProperty(object, key{
              delete object[key];
          }
          deleteProperty(obj, 'bar');

          使用 Reflect API 的方式如下:

          // 使用 Reflect API
          Reflect.deleteProperty(obj, 'bar');

          以更可靠的方式來使用 apply() 方法

          在 ES5 中,我們可以使用  apply()  方法來調(diào)用一個(gè)函數(shù),并指定  this  上下文、傳入一個(gè)參數(shù)數(shù)組。

          Function.prototype.apply.call(func, obj, arr);
          // or
          func.apply(obj, arr);

          這種方式比較不可靠,因?yàn)? func  可能是一個(gè)具有自定義  apply  方法的對象。

          ES6 提供了一個(gè)更加可靠、優(yōu)雅的方式來解決這個(gè)問題:

          Reflect.apply(func, obj, arr);

          這樣,如果  func  不是可調(diào)用對象,會拋出  TypeError。此外  Reflect.apply()  也更簡潔、易于理解。

          幫助實(shí)現(xiàn)其他類型的反射

          等我們了解  Proxy  對象之后就能理解這句話意味著什么。在許多場景中,Reflect API 方法可以和 Proxy 結(jié)合使用。

          Proxy 對象

          ES6 的  Proxy  對象可以用于  調(diào)解(intercession)

          proxy  對象允許我們自定義一些基本操作的行為(例如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)。

          以下是一些有用的術(shù)語:

          • target:代理為其提供自定義行為的對象。
          • handler:包含“捕獲器”方法的對象。
          • trap:“捕獲器”方法,提供了訪問目標(biāo)對象屬性的途徑,這是通過 Reflect API 中的方法實(shí)現(xiàn)的。每個(gè)“捕獲器”方法都對應(yīng)著 Reflect API 中的一個(gè)方法。

          它們的關(guān)系如圖所示:

          先定義一個(gè)包含“捕獲器”函數(shù)的 handler 對象,再使用這個(gè) handler 和目標(biāo)對象來創(chuàng)建一個(gè)代理對象,這個(gè)代理對象會應(yīng)用 handler 中的自定義行為。

          如果你對上面介紹的內(nèi)容不太理解也沒關(guān)系,我們可以通過代碼示例來掌握它。

          以下是創(chuàng)建代理對象的語法:

          let proxy = new Proxy(target, handler);

          有許多捕獲器(handler 方法)可以用來訪問或者自定義目標(biāo)對象。以下是捕獲器方法列表,可以在此處查看更多詳細(xì)介紹。

          handler.apply()
          handler.construct()
          handler.get()
          handler.has()
          handler.ownKeys()
          handler.set()
          handler.setPrototypeOf()
          handler.getPrototypeOf()
          handler.defineProperty()
          handler.deleteProperty()
          handler.getOwnPropertyDescriptor()
          handler.preventExtensions()
          handler.isExtensible()

          注意每個(gè)捕獲器都對應(yīng)著  Reflect  對象的方法,也就是說可以在許多場景下同時(shí)使用  Reflect  和  Proxy

          如何獲取不可用的對象屬性值

          以下是一個(gè)打印  employee  對象的屬性的示例:

          const employee = {
              firstName'Tapas',
              lastName'Adhikary'
          };

          console.log(employee.firstName);
          console.log(employee.lastName);
          console.log(employee.org);
          console.log(employee.fullName);

          預(yù)期輸出:

          Tapas
          Adhikary
          undefined
          undefined

          使用 Proxy 對象為  employee  對象增加一些自定義行為。

          步驟 1:創(chuàng)建一個(gè)使用 get 捕獲器的 Handler

          我們使用名為  get  的捕獲器,可以通過它來獲取對象的屬性值。handler 代碼如下:

          let handler = {
              getfunction(target, fieldName{        

                  if(fieldName === 'fullName' ) {
                      return `${target.firstName} ${target.lastName}`;
                  }

                  return fieldName in target ?
                      target[fieldName] :
                          `No such property as, '${fieldName}'!`

              }
          };

          以上 handler 代碼創(chuàng)建了  fullName  屬性的值,還為訪問的屬性不存在的情況提供了更優(yōu)雅的錯(cuò)誤提示。

          步驟 2:創(chuàng)建 Proxy 對象

          目標(biāo)對象  employee  和 handler 都準(zhǔn)備好了,可以這樣來創(chuàng)建 Proxy 對象:

          let proxy = new Proxy(employee, handler);

          步驟 3:訪問 Proxy 對象的屬性

          現(xiàn)在可以通過 proxy 對象來訪問 employee 對象的屬性,如下所示:

          console.log(proxy.firstName);
          console.log(proxy.lastName);
          console.log(proxy.org);
          console.log(proxy.fullName);

          預(yù)期輸出:

          Tapas
          Adhikary
          No such property as, 'org'!
          Tapas Adhikary

          注意我們是如何神奇地改變  employee  對象的。

          使用 Proxy 來驗(yàn)證屬性值

          創(chuàng)建一個(gè) proxy 對象來驗(yàn)證整數(shù)值。

          步驟 1:創(chuàng)建一個(gè)使用 set 捕獲器的 handler

          handler 代碼如下:

          const validator = {
              setfunction(obj, prop, value{
                  if (prop === 'age') {
                      if(!Number.isInteger(value)) {
                          throw new TypeError('Age is always an Integer, Please Correct it!');
                      }
                      if(value < 0) {
                          throw new TypeError('This is insane, a negative age?');
                      }
                  }
              }
          };

          步驟 2:創(chuàng)建一個(gè) Proxy 對象

          代碼如下:

          let proxy = new Proxy(employee, validator);

          步驟 3:將一個(gè)非整數(shù)值賦值給 age 屬性

          代碼如下:

          proxy.age = 'I am testing a blunder'// string value

          預(yù)期輸出:

          TypeError: Age is always an Integer, Please Correct it!
              at Object.set (E:\Projects\KOSS\metaprogramming\js-mtprog\proxy\userSetProxy.js:28:23)
              at Object.<anonymous> (E:\Projects\KOSS\metaprogramming\js-mtprog\proxy\userSetProxy.js:40:7)
              at Module._compile (module.js:652:30)
              at Object.Module._extensions..js (module.js:663:10)
              at Module.load (module.js:565:32)
              at tryModuleLoad (module.js:505:12)
              at Function.Module._load (module.js:497:3)
              at Function.Module.runMain (module.js:693:10)
              at startup (bootstrap_node.js:188:16)
              at bootstrap_node.js:609:3

          再試試以下操作:

          p.age = -1// 拋出 TypeError

          如何同時(shí)使用 Proxy 和 Reflect

          下面是一個(gè)在 handler 中使用 Reflect API 方法的示例:

          const employee = {
              firstName'Tapas',
              lastName'Adhikary'
          };

          let logHandler = {
              getfunction(target, fieldName{        
                  console.log("Log: ", target[fieldName]);
                  
                  // Use the get method of the Reflect object
                  return Reflect.get(target, fieldName);
              }
          };

          let func = () => {
              let p = new Proxy(employee, logHandler);
              p.firstName;
              p.lastName;
          };

          func();

          其它使用場景

          還有許多場景可以用到 Proxy 概念:

          • 保護(hù)對象的  ID  字段不被刪除(deleteProperty 捕獲器)
          • 追蹤屬性訪問的過程(get、set 捕獲器)
          • 數(shù)據(jù)綁定(set 捕獲器)
          • 可撤銷的引用
          • 控制  in  操作符的行為

          ......以及更多

          元編程陷阱

          盡管  元編程  概念為我們提供了強(qiáng)大的功能,但是使用不當(dāng)也會引發(fā)錯(cuò)誤。

          要當(dāng)心強(qiáng)大功能的副作用

          注意:

          • 功能過于強(qiáng)大,務(wù)必理解了之后再使用。
          • 可能會影響性能。
          • 可能會使代碼難以調(diào)試。

          總結(jié)

          總而言之:

          • Reflect  和  Proxy  是 JavaScript 的優(yōu)秀特性,有助于實(shí)現(xiàn)元編程。
          • 利用它們可以解決許多復(fù)雜的問題。
          • 同時(shí)也要注意它們的弊端。
          • ES6 Symbols 也能用來改變現(xiàn)有的類或?qū)ο蟮男袨椤?/section>

          希望本文對你有所幫助,文中所有源碼都可以在我的 GitHub 倉庫 中查看。

          歡迎分享本文。歡迎關(guān)注我的 Twitter 賬號(@tapasadhikary)并留言討論。



          原文鏈接:https://www.freecodecamp.org/news/what-is-metaprogramming-in-javascript-in-english-please/

          作者:TAPAS ADHIKARY

          譯者:Humilitas


          ??愛心三連擊

          1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的點(diǎn)贊,在看是我創(chuàng)作的動力。

          2.關(guān)注公眾號程序員成長指北,回復(fù)「1」加入高級前端交流群!「在這里有好多 前端 開發(fā)者,會討論 前端 Node 知識,互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長。

          “在看轉(zhuǎn)發(fā)”是最大的支持

          瀏覽 36
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  天堂中文8资源在线8 | 日批视频在线免费观看 | 亚洲无码蜜桃视频 | 激情乱伦视频网站 | 夏晴子在线观看 |