Artikel ini membawakan anda pengetahuan yang berkaitan tentang prinsip javascript penghias JavaScript mungkin dipinjam daripada Python atau Java Perbezaan yang paling ketara ialah Penghias beberapa bahasa mesti dipisahkan oleh baris, manakala penghias js boleh berada dalam satu baris saya harap ia akan membantu semua orang.
Kata keterangan yang bermula dengan @. Kata kerja bahasa Inggeris penghias ialah menghias, yang bermaksud menghias. Kata akar dek (disebut dec) bermaksud "menerima" dalam bahasa Proto-Indo-Eropah. Iaitu, sesuatu yang baru menjadi sesuatu yang baru (dan menjadi lebih baik).
Diterangkan dari perspektif lain, penghias terutamanya bekerja pada bahagian luar objek yang dihias, dan bukannya menyerang perubahan di dalamnya. Mod penghias juga merupakan mod pembangunan Walaupun statusnya lebih lemah daripada MVC, IoC, dll., ia masih merupakan mod yang sangat baik.
Penghias JavaScript boleh dipinjam daripada Python atau Java. Perbezaan yang lebih jelas ialah penghias dalam kebanyakan bahasa mesti dipisahkan baris demi baris, manakala penghias dalam js boleh berada dalam satu baris.
Contohnya: Saya memasuki bangunan ibu pejabat syarikat dengan kad pekerja saya. Oleh kerana setiap pekerja berada di jabatan dan peringkat yang berbeza, mereka tidak boleh memasuki mana-mana bilik di dalam bangunan. Setiap bilik mempunyai pintu; maka, syarikat perlu mengatur sekurang-kurangnya seorang di setiap pejabat untuk mengesahkan pelawat:
Daftarkan pelawat dahulu
Sahkan sama ada mereka mempunyai kebenaran untuk masuk, jika tidak, minta mereka keluar
Rekod masa berlepas mereka
Ada pilihan lain , iaitu memasang kunci pintu elektronik Kunci pintu hanya menghantar maklumat kad pekerja ke bilik komputer, yang disahkan oleh program tertentu.
Yang pertama dipanggil mod bodoh buat sementara waktu, dan kodnya adalah seperti berikut:
function A101(who){ record(who,new Date(),'enter'); if (!permission(who)) { record(who,new Date(),'no permission') return void; } // 继续执行 doSomeWork(); record(who,new Date(),'leave')} function A102(who){record(who,new Date(),'enter'); if (!permission(who)) { record(who,new Date(),'no permission') return void; } // 继续执行 doSomeWork(); record(who,new Date(),'leave')} // ...
Semua orang yang berpengalaman mesti memikirkannya serta-merta, merangkum pernyataan berulang tersebut ke dalam kaedah dan memanggilnya secara seragam. Ya, ini menyelesaikan kebanyakan masalah, tetapi ia tidak cukup "elegan". Pada masa yang sama, terdapat masalah lain Jika terdapat terlalu banyak "bilik", atau hanya bilik bernombor ganjil dalam bangunan perlu disahkan tetapi nombor genap tidak disahkan, bukankah itu "tidak normal"? Jika anda menggunakan corak penghias, kod akan kelihatan seperti ini:
@verify(who)class Building { @verify(who) A101(){/*...*/} @verify(who) A102(){/*...*/} //...}
sahkan ialah penghias pengesahan dan intipatinya ialah satu set fungsi.
Seperti contoh sebelumnya, penghias itu sendiri sebenarnya adalah fungsi, yang dilaksanakan sebelum melaksanakan objek yang dihias.
Dalam JavaScript, jenis penghias ialah:
Kelas
Kaedah akses (dapatkan atribut dan tetapkan )
Medan
Kaedah
Parameter
Sejak konsep penghias masih dalam peringkat cadangan dan bukan fungsi JS yang tersedia secara rasmi, jika anda ingin menggunakan fungsi ini, anda perlu menggunakan alat penterjemah, seperti alat Babel atau TypeScript untuk menyusun kod JS sebelum ia boleh dilaksanakan. Kita perlu menyediakan persekitaran operasi terlebih dahulu dan mengkonfigurasi beberapa parameter. (Proses berikut mengandaikan bahawa persekitaran pembangunan NodeJS dan alatan pengurusan pakej telah dipasang dengan betul)
cd project && npm initnpm i -D @babel/cli @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/preset-env babel-plugin-parameter-decorator
Buat fail konfigurasi .babelrc seperti berikut:
{ "presets": ["@babel/preset-env"], "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }], "babel-plugin-parameter-decorator" ]}
Gunakan penukaran berikut arahan, Kita boleh mendapatkan program penukaran ES5:
npx babel source.js --out-file target.js
Buat satu program JS decorate-class.js menggunakan penghias
@classDecoratorclass Building { constructor() { this.name = "company"; }} const building = new Building(); function classDecorator(target) { console.log("target", target);}
Di atas ialah program penghias yang paling mudah Kami menggunakan babel untuk "terjemah" ke dalam atur cara ES5, dan kemudian mencantikkannya dan mendapatkan atur cara berikut.
"use strict"; var _class; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); }} var Building = classDecorator( (_class = function Building() { _classCallCheck(this, Building); this.name = "company"; }) ) || _class; var building = new Building(); function classDecorator(target) { console.log("target", target);}
第12行就是在类生成过程中,调用函数形态的装饰器,并将构造函数(类本身)送入其中。同样揭示了装饰器的第一个参数是类的构造函数的由来。
稍微修改一下代码,依旧是尽量保持最简单:
class Building { constructor() { this.name = "company"; } @methodDecorator openDoor() { console.log("The door being open"); }} const building = new Building(); function methodDecorator(target, property, descriptor) { console.log("target", target); if (property) { console.log("property", property); } if (descriptor) { console.log("descriptor", descriptor); } console.log("=====end of decorator========="); }
然后转换代码,可以发现,这次代码量突然增大了很多。排除掉_classCallCheck、_defineProperties和_createClass三个函数,关注_applyDecoratedDescriptor函数:
function _applyDecoratedDescriptor( target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ("value" in desc || desc.initializer) { desc.writable = true; } desc = decorators .slice() .reverse() .reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc;}
它在生成构造函数之后,执行了这个函数,特别注意,这个装饰器函数是以数组形式的参数传递的。然后到上述代码的17~22行,将装饰器逐个应用,其中对装饰器的调用就在第21行。
它发送了3个参数,target指类本身。property指方法名(或者属性名),desc是可能被先前装饰器被处理过的descriptor,如果是第一次循环或只有一个装饰器,那么就是方法或属性本身的descriptor。
JS关于类的定义中,支持get和set关键字针对设置某个字段的读写操作逻辑,装饰器也同样支持这类方法的操作。
class Building { constructor() { this.name = "company"; } @propertyDecorator get roomNumber() { return this._roomNumber; } _roomNumber = ""; openDoor() { console.log("The door being open"); }}
有心的读者可能已经发现了,存取器装饰的代码与上面的方法装饰代码非常接近。关于属性 get和set方法,其本身也是一种方法的特殊形态。所以他们之间的代码就非常接近了。
继续修改源代码:
class Building { constructor() { this.name = "company"; } @propertyDecorator roomNumber = ""; } const building = new Building(); function propertyDecorator(target, property, descriptor) { console.log("target", target); if (property) { console.log("property", property); } if (descriptor) { console.log("descriptor", descriptor); } console.log("=====end of decorator========="); }
转换后的代码,还是与上述属性、存取器的代码非常接近。但除了_applyDecoratedDescriptor外,还多了一个_initializerDefineProperty函数。这个函数在生成构造函数时,将声明的各种字段绑定给对象。
参数装饰器的使用位置较之前集中装饰器略有不同,它被使用在行内。
class Building { constructor() { this.name = "company"; } openDoor(@parameterDecorator num, @parameterDecorator zoz) { console.log(`${num} door being open`); }} const building = new Building(); function parameterDecorator(target, property, key) { console.log("target", target); if (property) { console.log("property", property); } if (key) { console.log("key", key); } console.log("=====end of decorator========="); }
转换后的代码区别就比较明显了,babel并没有对其生成一个特定的函数对其进行特有的操作,而只在创建完类(构造函数)以及相关属性、方法后直接调用了开发者自己编写的装饰器函数:
var Building = /*#__PURE__*/function () { function Building() { _classCallCheck(this, Building); this.name = "company"; } _createClass(Building, [{ key: "openDoor", value: function openDoor(num, zoz) { console.log("".concat(num, " door being open")); } }]); parameterDecorator(Building.prototype, "openDoor", 1); parameterDecorator(Building.prototype, "openDoor", 0); return Building;}();
以上所有的案例,装饰器本身均没有使用任何参数。然实际应用中,经常会需要有特定的参数需求。我们再回到一开头的例子中verify(who),其中需要传入一个身份变量。哪又怎么做?我们少许改变一下类装饰器的代码:
const who = "Django";@classDecorator(who)class Building { constructor() { this.name = "company"; }}
转换后得到
// ...var who = "Django";var Building = ((_dec = classDecorator(who)), _dec( (_class = function Building() { _classCallCheck(this, Building); this.name = "company"; }) ) || _class); // ...
请注意第4第5行,它先执行了装饰器,然后再用返回值将类(构造函数)送入。相对应的,我们就应该将构造函数写成下面这样:
function classDecorator(people) { console.log(`hi~ ${people}`); return function (target) { console.log("target", target); }; }
同样的,方法、存取器、属性和参数装饰器均是如此。
到此,我们已经可以将装饰器参数与目标对象结合起来,进行一些逻辑类的操作。那么再回到文章的开头的例子中:需求中要先验证来访者权限,然后记录,最后在来访者离开时再做一次记录。此时需要监管对象方法被调用的整个过程。
请大家留意那个方法装饰器的descriptor,我们可以利用这个对象来“重写”这个方法。
class Building { constructor() { this.name = "company"; } @methodDecorator("Gate") openDoor(firstName, lastName) { return `The door will be open, when ${firstName} ${lastName} is walking into the ${this.name}.`; }} let building = new Building();console.log(building.openDoor("django", "xiang")); function methodDecorator(door) { return function (target, property, descriptor) { let fn = descriptor.value; descriptor.value = function (...args) { let [firstName, lastName] = args; console.log(`log: ${firstName}, who are comming.`); // verify(firstName,lastName) let result = Reflect.apply(fn, this, [firstName, lastName]); console.log(`log: ${result}`); console.log(`log: ${firstName}, who are leaving.`); return result; }; return descriptor; };}
代码第17行,将原方法暂存;18行定义一个新的方法,20~25行,记录、验证和记录离开的动作。
log: Django, who are comming.log: The door will be open, when Django Xiang is walking in to the company.log: Django, who are leaving.The door will be open, when Django Xiang is walking in to the company
通过阅读转换后的代码,我们知道装饰器工作的时刻是在类被实例化之前,在生成之中完成装饰函数的动作。那么,如果不同类型的多个装饰器同时作用,其过程是怎样的?我们将先前的案例全部整合到一起看看:
const who = "Django";@classDecorator(who)class Building { constructor() { this.name = "company"; } @propertyDecorator roomNumber = ""; @methodDecorator openDoor(@parameterDecorator num) { console.log(`${num} door being open`); } @accessorDecorator get roomNumber() { return this._roomNumber; }} const building = new Building(); function classDecorator(people) { console.log(`class decorator`); return function (target) { console.log("target", target); };} function methodDecorator(target, property, descriptor) { console.log("method decorator");} function accessorDecorator(target, property, descriptor) { console.log("accessor decorator");} function propertyDecorator(target, property, descriptor) { console.log("property decoator");} function parameterDecorator(target, property, key) { console.log("parameter decorator");}
class decorator
parameter decorator
property decoator
method decorator
accessor decorator
还可以通过阅读转换后的源代码得到执行顺序:
类装饰器(在最外层)
参数装饰器(在生成构造函数最里层)
按照出现的先后顺序的:属性、方法和存取器
Decorator ialah model pembangunan yang elegan, yang sangat memudahkan proses pengekodan pembangun dan meningkatkan kebolehbacaan kod. Apabila kita menggunakan penghias untuk membangunkan, ia masih sangat perlu untuk memahami mekanisme operasinya.
Cadangan berkaitan: Tutorial pembelajaran javascript
Atas ialah kandungan terperinci Penjelasan terperinci dan pengenalan kes prinsip penghias Javascript. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!