|
導讀網頁的本質就是超級文本標記語言,通過結合使用其他的Web技術(如:腳本語言、公共網關接口、組件等),可以創(chuàng)造出功能強大的網頁。因而,超級文本標記語言是萬維網(Web)編程的基礎,也就是說萬維網是建立... 網頁的本質就是超級文本標記語言,通過結合使用其他的Web技術(如:腳本語言、公共網關接口、組件等),可以創(chuàng)造出功能強大的網頁。因而,超級文本標記語言是萬維網(Web)編程的基礎,也就是說萬維網是建立在超文本基礎之上的。超級文本標記語言之所以稱為超文本標記語言,是因為文本中包含了所謂“超級鏈接”點。 本篇文章給大家?guī)淼膬热菔顷P于淺談ES6中的裝飾器,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。 Decorator 裝飾器主要用于:
裝飾類@annotation
class MyClass { }
function annotation(target) {
target.annotated = true;
}裝飾方法或屬性class MyClass {
@readonly
method() { }
}
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}Babel安裝編譯我們可以在 Babel 官網的 Try it out,查看 Babel 編譯后的代碼。 不過我們也可以選擇本地編譯: npm init npm install --save-dev @babel/core @babel/cli npm install --save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties 新建 .babelrc 文件 {
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", {"loose": true}]
]
}再編譯指定的文件 babel decorator.js --out-file decorator-compiled.js 裝飾類的編譯編譯前: @annotation
class MyClass { }
function annotation(target) {
target.annotated = true;
}編譯后: var _class;
let MyClass = annotation(_class = class MyClass {}) || _class;
function annotation(target) {
target.annotated = true;
}我們可以看到對于類的裝飾,其原理就是: @decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;裝飾方法的編譯編譯前: class MyClass {
@unenumerable
@readonly
method() { }
}
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
function unenumerable(target, name, descriptor) {
descriptor.enumerable = false;
return descriptor;
}編譯后: var _class;
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context ) {
/**
* 第一部分
* 拷貝屬性
*/
var desc = {};
Object["ke" + "ys"](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;
}
/**
* 第二部分
* 應用多個 decorators
*/
desc = decorators
.slice()
.reverse()
.reduce(function(desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
/**
* 第三部分
* 設置要 decorators 的屬性
*/
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["define" + "Property"](target, property, desc);
desc = null;
}
return desc;
}
let MyClass = ((_class = class MyClass {
method() {}
}),
_applyDecoratedDescriptor(
_class.prototype,
"method",
[readonly],
Object.getOwnPropertyDescriptor(_class.prototype, "method"),
_class.prototype
),
_class);
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}裝飾方法的編譯源碼解析我們可以看到 Babel 構建了一個 _applyDecoratedDescriptor 函數,用于給方法裝飾。 Object.getOwnPropertyDescriptor()在傳入參數的時候,我們使用了一個 Object.getOwnPropertyDescriptor() 方法,我們來看下這個方法: Object.getOwnPropertyDescriptor() 方法返回指定對象上的一個自有屬性對應的屬性描述符。(自有屬性指的是直接賦予該對象的屬性,不需要從原型鏈上進行查找的屬性) 順便注意這是一個 ES5 的方法。 舉個例子: const foo = { value: 1 };
const bar = Object.getOwnPropertyDescriptor(foo, "value");
// bar {
// value: 1,
// writable: true
// enumerable: true,
// configurable: true,
// }
const foo = { get value() { return 1; } };
const bar = Object.getOwnPropertyDescriptor(foo, "value");
// bar {
// get: /*the getter function*/,
// set: undefined
// enumerable: true,
// configurable: true,
// }第一部分源碼解析在 _applyDecoratedDescriptor 函數內部,我們首先將 Object.getOwnPropertyDescriptor() 返回的屬性描述符對象做了一份拷貝: // 拷貝一份 descriptor
var desc = {};
Object["ke" + "ys"](descriptor).forEach(function(key) {
desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;
// 如果沒有 value 屬性或者沒有 initializer 屬性,表明是 getter 和 setter
if ("value" in desc || desc.initializer) {
desc.writable = true;
}那么 initializer 屬性是什么呢?Object.getOwnPropertyDescriptor() 返回的對象并不具有這個屬性呀,確實,這是 Babel 的 Class 為了與 decorator 配合而產生的一個屬性,比如說對于下面這種代碼: class MyClass {
@readonly
born = Date.now();
}
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
var foo = new MyClass();
console.log(foo.born);Babel 就會編譯為: // ...
(_descriptor = _applyDecoratedDescriptor(_class.prototype, "born", [readonly], {
configurable: true,
enumerable: true,
writable: true,
initializer: function() {
return Date.now();
}
}))
// ...此時傳入 _applyDecoratedDescriptor 函數的 descriptor 就具有 initializer 屬性。 第二部分源碼解析接下是應用多個 decorators: /**
* 第二部分
* @type {[type]}
*/
desc = decorators
.slice()
.reverse()
.reduce(function(desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);對于一個方法應用了多個 decorator,比如: class MyClass {
@unenumerable
@readonly
method() { }
}Babel 會編譯為: _applyDecoratedDescriptor(
_class.prototype,
"method",
[unenumerable, readonly],
Object.getOwnPropertyDescriptor(_class.prototype, "method"),
_class.prototype
)在第二部分的源碼中,執(zhí)行了 reverse() 和 reduce() 操作,由此我們也可以發(fā)現,如果同一個方法有多個裝飾器,會由內向外執(zhí)行。 第三部分源碼解析/**
* 第三部分
* 設置要 decorators 的屬性
*/
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["define" + "Property"](target, property, desc);
desc = null;
}
return desc;如果 desc 有 initializer 屬性,意味著當裝飾的是類的屬性時,會將 value 的值設置為: desc.initializer.call(context) 而 context 的值為 class MyClass {
@readonly
value = this.getNum() + 1;
getNum() {
return 1;
}
}最后無論是裝飾方法還是屬性,都會執(zhí)行: Object["define" + "Property"](target, property, desc); 由此可見,裝飾方法本質上還是使用 應用1.log為一個方法添加 log 函數,檢查輸入的參數: class Math {
@log
add(a, b) {
return a + b;
}
}
function log(target, name, descriptor) {
var oldValue = descriptor.value;
descriptor.value = function(...args) {
console.log(`Calling ${name} with`, args);
return oldValue.apply(this, args);
};
return descriptor;
}
const math = new Math();
// Calling add with [2, 4]
math.add(2, 4);再完善點: let log = (type) => {
return (target, name, descriptor) => {
const method = descriptor.value;
descriptor.value = (...args) => {
console.info(`(${type}) 正在執(zhí)行: ${name}(${args}) = ?`);
let ret;
try {
ret = method.apply(target, args);
console.info(`(${type}) 成功 : ${name}(${args}) => ${ret}`);
} catch (error) {
console.error(`(${type}) 失敗: ${name}(${args}) => ${error}`);
}
return ret;
}
}
};2.autobindclass Person {
@autobind
getPerson() {
return this;
}
}
let person = new Person();
let { getPerson } = person;
getPerson() === person;
// true我們很容易想到的一個場景是 React 綁定事件的時候: class Toggle extends React.Component {
@autobind
handleClick() {
console.log(this)
}
render() {
return (
<button onClick={this.handleClick}>
button
</button>
);
}
}我們來寫這樣一個 autobind 函數: const { defineProperty, getPrototypeOf} = Object;
function bind(fn, context) {
if (fn.bind) {
return fn.bind(context);
} else {
return function __autobind__() {
return fn.apply(context, arguments);
};
}
}
function createDefaultSetter(key) {
return function set(newValue) {
Object.defineProperty(this, key, {
configurable: true,
writable: true,
enumerable: true,
value: newValue
});
return newValue;
};
}
function autobind(target, key, { value: fn, configurable, enumerable }) {
if (typeof fn !== 'function') {
throw new SyntaxError(`@autobind can only be used on functions, not: ${fn}`);
}
const { constructor } = target;
return {
configurable,
enumerable,
get() {
/**
* 使用這種方式相當于替換了這個函數,所以當比如
* Class.prototype.hasOwnProperty(key) 的時候,為了正確返回
* 所以這里做了 this 的判斷
*/
if (this === target) {
return fn;
}
const boundFn = bind(fn, this);
defineProperty(this, key, {
configurable: true,
writable: true,
enumerable: false,
value: boundFn
});
return boundFn;
},
set: createDefaultSetter(key)
};
}3.debounce有的時候,我們需要對執(zhí)行的方法進行防抖處理: class Toggle extends React.Component {
@debounce(500, true)
handleClick() {
console.log('toggle')
}
render() {
return (
<button onClick={this.handleClick}>
button
</button>
);
}
}我們來實現一下: function _debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
}
function debounce(wait, immediate) {
return function handleDescriptor(target, key, descriptor) {
const callback = descriptor.value;
if (typeof callback !== 'function') {
throw new SyntaxError('Only functions can be debounced');
}
var fn = _debounce(callback, wait, immediate)
return {
...descriptor,
value() {
fn()
}
};
}
}4.time用于統(tǒng)計方法執(zhí)行的時間: function time(prefix) {
let count = 0;
return function handleDescriptor(target, key, descriptor) {
const fn = descriptor.value;
if (prefix == null) {
prefix = `${target.constructor.name}.${key}`;
}
if (typeof fn !== 'function') {
throw new SyntaxError(`@time can only be used on functions, not: ${fn}`);
}
return {
...descriptor,
value() {
const label = `${prefix}-${count}`;
count++;
console.time(label);
try {
return fn.apply(this, arguments);
} finally {
console.timeEnd(label);
}
}
}
}
}5.mixin用于將對象的方法混入 Class 中: const SingerMixin = {
sing(sound) {
alert(sound);
}
};
const FlyMixin = {
// All types of property descriptors are supported
get speed() {},
fly() {},
land() {}
};
@mixin(SingerMixin, FlyMixin)
class Bird {
singMatingCall() {
this.sing('tweet tweet');
}
}
var bird = new Bird();
bird.singMatingCall();
// alerts "tweet tweet"mixin 的一個簡單實現如下: function mixin(...mixins) {
return target => {
if (!mixins.length) {
throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`);
}
for (let i = 0, l = mixins.length; i < l; i++) {
const descs = Object.getOwnPropertyDescriptors(mixins[i]);
const keys = Object.getOwnPropertyNames(descs);
for (let j = 0, k = keys.length; j < k; j++) {
const key = keys[j];
if (!target.prototype.hasOwnProperty(key)) {
Object.defineProperty(target.prototype, key, descs[key]);
}
}
}
};
}6.redux實際開發(fā)中,React 與 Redux 庫結合使用時,常常需要寫成下面這樣。 class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);有了裝飾器,就可以改寫上面的代碼。 @connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {};相對來說,后一種寫法看上去更容易理解。 7.注意以上我們都是用于修飾類方法,我們獲取值的方式為: const method = descriptor.value; 但是如果我們修飾的是類的實例屬性,因為 Babel 的緣故,通過 value 屬性并不能獲取值,我們可以寫成: const value = descriptor.initializer && descriptor.initializer(); 以上就是淺談ES6中的裝飾器的詳細內容,更多請關注php中文網其它相關文章! 網站建設是一個廣義的術語,涵蓋了許多不同的技能和學科中所使用的生產和維護的網站。 |
溫馨提示:喜歡本站的話,請收藏一下本站!