【讀書筆記】【深入理解ES6】#12-代理(Proxy)和反射(Reflection)API

来源:http://www.cnblogs.com/Ryukaka/archive/2017/12/01/7943978.html
-Advertisement-
Play Games

介紹正式加入 JavaScript 的反射 API 和新的代理對象,開發者可以通過代理對象攔截每一個對象中執行的操作,代理也賦予了開發者空前的對象控制權,同樣也為定義新的交互模式帶來無限可能。 ...


代理(Proxy)是一種可以攔截並改變底層JavaScript引擎操作的包裝器,在新語言中通過它暴露內部運作的對象,從而讓開發者可以創建內建的對象。

數組問題

在ECMAScript6出現之前,開發者不能通過自己定義的對象模仿JavaScript數組對象的行為方式。當給數組的特定元素賦值時,影響到該數組的length屬性,也可以通過length屬性修改數組元素。

let colors = ["red", "green", "blue"];

console.log(colors.length); // 3

colors[3] = "black";

console.log(colors.length); // 4
console.log(colors[3]); // "black"

colors.length = 2;

console.log(colors.length); // 2
console.log(colors[3]); // undefined
console.log(colors[2]); // undefined
console.log(colors[1]); // "green"

Note

數值屬性和length屬性具有這種非標準行為,因而在ECMAScript中數組被認為是奇異對象(exotic object,與普通對象相對)。

代理和反射

調用 new Proxy() 可創建代替其他目標(taget)對象的代理,它虛擬化了目標,所以二者看起來功能一致。

代理可以攔截 JavaScript 引擎內部目標的底層對象操作,這些底層操作被攔截後會觸發響應特定操作的陷阱函數。

反射API可以Reflect對象的形式出現,對象中方法的預設特性與相同的底層操作一致,而代理可以覆寫這些操作,每個代理陷阱對應一個命名和參數都相同的Reflect方法。

代理陷阱 覆寫的特性 預設特性
get 讀取一個屬性值 Reflect.get()
set 寫入一個屬性值 Reflect.set()
has in 操作符 Reflect.has()
deleteProperty delete 操作符 Reflect.deleteProperty()
getPrototypeOf Object.getPrototypeOf() Reflect.getPrototypeOf()
setPrototypeOf Object.setPrototypeOf() Reflect.setPrototypeOf()
isExtensible Object.isExtensible() Reflect.isExtensible()
preventExtensions Object.preventExtensions() Reflect.preventExtensions()
getOwnPropertyDescriptor Object.getOwnPropertyDescriptor() Reflect.getOwnPropertyDescriptor()
defineProperty Object.defineProperty() Reflect.defineProperty()
ownKeys Object.keys()、Object.getOwnPropertyNames()和Object.getOwnPropertySymbols() Reflect.ownKeys()
apply 調用一個函數 Reflect.apply()
construct 用new調用一個函數 Reflect.construct()

創建一個簡單的代理

Proxy構造函數有兩個參數

  • 目標(target)
  • 處理程式(handler)
    處理程式是定義一個或多個陷阱的對象,在代理中,出了專門為操作定義的陷阱外,其餘操作均使用預設特性。
    不使用任何陷阱的處理程式等價於簡單的轉發代理。
let target = {};
let proxy = new Proxy(target, {});

proxy.name = "proxy";
console.log(proxy.name); // "proxy"
console.log(target.name); // "proxy"

target.name = "target";
console.log(proxy.name); // "target"
console.log(target.name); // "target"

使用set陷阱驗證屬性

set陷阱接受4個參數:

  • trapTarget
    用於接收屬性(代理的目標)的對象
  • key
    要寫入的屬性鍵(字元串或Symbol類型)
  • value
    被寫入屬性的值
  • receiver
    操作發生的對象(通常是代理)

Reflect.set()是set陷阱對應的反射方法和預設特性,它和set陷阱一樣也接受同樣的4個參數。

let target = {
    name: "target"
};

let proxy = new Proxy(target, {
    set(trapTarget, key, value, receiver) {
        if (!trapTarget.hasOwnProperty(key)) {
            if (isNaN(value)) {
                throw new TypeError("屬性必須是數字");
            }
        }

        return Reflect.set(trapTarget, key, value, receiver);
    }
});

proxy.count = 1;
console.log(proxy.count); // 1
console.log(target.count); // 1

proxy.name = "proxy";
console.log(proxy.name); // "proxy"
console.log(target.name); // "proxy"

// 拋出錯誤:
// Uncaught TypeError: 屬性必須是數字
proxy.anotherName = "proxy";

用get陷阱驗證對象結構(Object Shape)

get陷阱接受3個參數:

  • trapTarget
    被讀取屬性的源的對象(代理的目標)
  • key
    被讀取的屬性鍵
  • value
    操作發生的對象(通常是代理)

Reflect.get()也接受同樣3個參數並返回屬性的預設值。

let proxy = new Proxy({}, {
    get(trapTarget, key, receiver) {
        if (!(key in receiver)) {
            throw new TypeError("屬性" + key + "不存在");
        }

        return Reflect.get(trapTarget, key, receiver);
    }
});

proxy.name = "proxy";
console.log(proxy.name); // "proxy"

// 拋出錯誤:
// Uncaught TypeError: 屬性nme不存在
console.log(proxy.nme);

使用has陷阱隱藏已有屬性

可以用in操作符來檢測給定對象中是否含有某個屬性,如果自由屬性或原型屬性匹配的名稱或Symbol就返回true。

let target = {
    value: 42
};

console.log("value" in target); // true
console.log("toString" in target); // true

在代理中使用has陷阱可以攔截這些in操作並返回一個不同的值。

in陷阱接受2個參數:

  • trapTarget
    讀取屬性的對象(代理的目標)
  • key
    要檢查的屬性值(字元串或Symbol)
let target = {
    name: "target",
    vlaue: 42
};

let proxy = new Proxy(target, {
    has (trapTarget, key) {
        if (key === "value") {
            return false;
        } else {
            return Reflect.has(trapTarget, key);
        }
    }
});

console.log("value" in proxy); // false
console.log("name" in proxy); // true
console.log("toString" in proxy); // true

用deleteProperty陷阱防止刪除屬性

delete操作符可以從對象中刪除屬性,如果成功則返回true,不成功則返回false。
在嚴格模式下,如果你嘗試刪除一個不可配置(nonconfigurable)屬性則會導致程式拋出錯誤,而在非嚴格模式下只是返回false。

每當通過delete操作符刪除對象屬性時,deleteProperty陷阱都會被調用,它接受2個參數:

  • trapTarget
    要刪除屬性的對象(代理的目標)
  • key
    要刪除的屬性鍵(字元串或Symbol)

Reflect.deleteProperty()方法為deleteProperty陷阱提供預設實現,並且接受同樣的兩個參數。

let target = {
    name: "target",
    value: 42
};

let proxy = new Proxy(target, {
    deleteProperty(trapTarget, key) {
        if (key === "value") {
            return false;
        } else {
            return Reflect.deleteProperty(trapTarget, key);
        }
    }
});

console.log("value" in proxy); // true

let result1 = delete proxy.value;
console.log(result1); // false

console.log("value" in proxy); // true

console.log("name" in proxy); // true

let result2 = delete proxy.name;
console.log(result2); // true

console.log("name" in proxy); // false

原型代理陷阱

ES6中新增的Object.setPrototypeOf()方法,它被用於作為ES5中的Object.getPrototype()方法的補充。通過代理中的setPrototypeOf陷阱和getPrototypeOf陷阱可以攔截這兩個方法的執行過程。

setPrototypeOf陷阱接受2個參數:

  • trapTarget
    接受原型設置的對象(代理的目標)
  • proto
    作為原型使用的對象

傳入Object.setPrototypeOf()方法和Reflect.setPrototypeOf()方法的均是以上兩個參數。
getPrototypeOf陷阱、Object.getPrototypeOf()方法和Reflect.getPrototypeOf()方法只接受參數trapTarget。

原型代理陷阱的運行機制

原型代理陷阱有一些限制:

  1. getPrototypeOf陷阱必須返回對象或null

  2. 在setPrototypeOf陷阱中,如果操作失敗則返回的一定是false,此時Object.setPrototypeOf()會拋出錯誤,如果setPrototypeOf返回了任何不是false的值,那麼Object.setPrototypeOf()便假設操作成功。

let target = {};
let proxy = new Proxy(target, {
    getPrototypeOf(trapTarget) {
        return null;
    },
    setPrototypeOf(trapTarget, proto) {
        return false;
    }
});

let targetProto = Object.getPrototypeOf(target);
let proxyProto = Object.getPrototypeOf(proxy);

console.log(targetProto === Object.prototype); // true
console.log(proxyProto === Object.prototype); // false
console.log(proxyProto); // null

// 成功
Object.setPrototypeOf(target, {});

// 給不存在的屬性賦值會拋出錯誤:
// Uncaught TypeError: 'setPrototypeOf' on proxy: trap returned falsish
Object.setPrototypeOf(proxy, {});

可以使用Reflect上的對應方法實現這兩個陷阱的預設行為。

let target = {};
let proxy = new Proxy(target, {
    getPrototypeOf(trapTarget) {
        return Reflect.getPrototypeOf(trapTarget);
    },
    setPrototypeOf(trapTarget, proto) {
        return Reflect.setPrototypeOf(trapTarget, proto);
    }
});

let targetProto = Object.getPrototypeOf(target);
let proxyProto = Object.getPrototypeOf(proxy);

console.log(targetProto === Object.prototype); // true
console.log(proxyProto === Object.prototype); // true

// 成功
Object.setPrototypeOf(target, {});

// 成功:
Object.setPrototypeOf(proxy, {});

對象可擴展性陷阱

ECMAScript 5 已經通過 Object.preventExtensions() 方法和 Object.isExtensible() 方法修正了對象的可擴展性;
ECMAScript 6 可以通過代理中的 preventExtensions 和 isExtensible 陷阱攔截這兩個方法並調用底層對象。
兩個陷阱都接受唯一參數 trapTarget 對象,並調用它上面的方法。
isExtensible 陷阱返回的一定是一個 boolean 值,表示對象是否可擴展;
preventExtensions 陷阱返回的也一定是布爾值,表示操作是否成功。

Reflect.preventExtensions() 方法和 Reflect.isExtensible() 方法實現了相應陷阱中的預設行為,二兩都返回布爾值。

兩個基礎示例

預設實現:

let target = {};
let proxy = new Proxy(target, {
    isExtensible(trapTarget) {
        return Reflect.isExtensible(trapTarget);
    },
    preventExtensions(trapTarget) {
        return Reflect.preventExtensions(trapTarget);
    }
});

console.log(Object.isExtensible(target)); // true
console.log(Object.isExtensible(proxy)); // true

Object.preventExtensions(proxy);

console.log(Object.isExtensible(target)); // false
console.log(Object.isExtensible(proxy)); // false

使用陷阱使 Object.preventExtensions() 對 proxy 失效。

let target = {};
let proxy = new Proxy(target, {
    isExtensible(trapTarget) {
        return Reflect.isExtensible(trapTarget);
    },
    preventExtensions(trapTarget) {
        return false;
    }
});

console.log(Object.isExtensible(target)); // true
console.log(Object.isExtensible(proxy)); // true

// 拋出錯誤:
// Uncaught TypeError: 'preventExtensions' on proxy: trap returned falsish
Object.preventExtensions(proxy);

console.log(Object.isExtensible(target)); // true
console.log(Object.isExtensible(proxy)); // true

屬性描述符陷阱

ECMAScript 5 最重要的特性之一是可以使用 Object.defineProperty() 方法定義屬性特性(property attribute)。可以通過 Object.getOwnPropertyDescriptor() 方法來獲取這些屬性。
在代理中可以分別用 defineProperty 陷阱和 getOwnPropertyDescriptor 陷阱攔截 Object.defineProperty() 方法和 Object.getOwnPropertyDescriptor() 方法的調用。

defineProperty 陷阱接受以下參數:

  • trapTarget
    要定義屬性的對象(代理的目標)
  • key
    屬性的鍵(字元串或Symbol)
  • descriptor
    屬性的描述符對象

操作成功後返回 true,否則返回 false。

getOwnPropertyDescriptor 陷阱接受以下參數:

  • trapTarget
    要定義屬性的對象(代理的目標)
  • key
    屬性的鍵(字元串或Symbol)

最終返回描述符。

陷阱預設行為示例:

let proxy = new Proxy({}, {
    defineProperty(trapTarget, key, descriptor) {
        return Reflect.defineProperty(trapTarget, key, descriptor);
    },
    getOwnPropertyDescriptor(trapTarget, key) {
        return Reflect.getOwnPropertyDescriptor(trapTarget, key);
    }
});

Object.defineProperty(proxy, "name", {
    value: "proxy"
});

console.log(proxy.name); // "proxy"

let descriptor = Object.getOwnPropertyDescriptor(proxy, "name");

console.log(descriptor.value); // "proxy"

給 Object.defineProperty() 添加限制

defineProperty 陷阱返回布爾值來表示操作是否成功。
返回 true 時,Object.defineProperty() 方法成功執行;
返回 false 時, Object.defineProperty() 方法拋出錯誤。

例:阻止 Symbol 類型的屬性

let proxy = new Proxy({}, {
    defineProperty(trapTarget, key, descriptor) {
        if (typeof key === "symbol") {
            return false;
        }

        return Reflect.defineProperty(trapTarget, key, descriptor);
    }
});

Object.defineProperty(proxy, "name", {
    value: "proxy"
});

console.log(proxy.name); // "proxy"

let nameSymbol = Symbol("name");

// 拋出錯誤:
// Uncaught TypeError: 'defineProperty' on proxy: trap returned falsish for property 'Symbol(name)'
Object.defineProperty(proxy, nameSymbol, {
    value: "proxy"
});

描述符對象限制

defineProperty 陷阱

defineProperty 陷阱的描述對象已規範化,只有下列屬性會被傳遞給 defineProperty 陷阱的描述符對象。

  • enumerable
  • configurable
  • value
  • writable
  • get
  • set
let proxy = new Proxy({}, {
    defineProperty(trapTarget, key, descriptor) {
        console.log(descriptor.value); // "proxy"
        console.log(descriptor.name); // undefined

        return Reflect.defineProperty(trapTarget, key, descriptor);
    }
});

Object.defineProperty(proxy, "name", {
    value: "proxy",
    name: "custom"
});

getOwnPropertyDescriptor 陷阱

getOwnPropertyDescriptor 陷阱的返回值必須是 null、undefined或一個對象;
如果返回對象,則對象自己的屬性只能是 enumerable、configurable、vlaue、writable、get和set;
在返回的對象中使用不被允許的屬性會拋出一個錯誤。

let proxy = new Proxy({}, {
    getOwnPropertyDescriptor(trapTarget, key) {
        return {
            name: "proxy"
        };
    }
});

// 給不存在的屬性賦值會拋出錯誤
// Uncaught TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property 'name' which is either non-existant or configurable in the proxy target
let descriptor = Object.getOwnPropertyDescriptor(proxy, "name");

這條限制可以確保無論代理中使用了什麼方法,Object.getOwnPropertyDescriptor() 返回值的結構總是可靠的。

ownKeys 陷阱

ownKeys 陷阱可以攔截內部方法 [[OwnPropertyKeys]],通過返回一個數組的值可以覆寫其行為。
這個數組被用於 Object.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols() 和 Object.assign() 4個方法,Object.assign() 方法用數組來確定需要複製的屬性。

ownKeys 陷阱通過 Reflect.ownKeys() 方法實現預設的行為,返回的數組中包含所有自有屬性的鍵名,字元串類型和 Symbol 類型的都包含在內。
Object.getOwnPropertyNames() 方法和 Object.keys() 方法返回的結果將 Symbol 類型的屬性名排除在外;
Object.getOwnPropertySymbols() 方法返回的結果將字元串類型的屬性名排除在外;
Object.assign() 方法支持字元串和 Symbol 兩種類型。

ownKeys 陷阱唯一接受的參數時操作的目標,返回值必須是一個數組或類數組對象,否則就拋出錯誤。

例:過濾任何以下劃線字元開頭的屬性名稱。

let proxy = new Proxy({}, {
    ownKeys(trapTarget) {
        return Reflect.ownKeys(trapTarget).filter(key => {
            return typeof key !== "string" || key[0] !== "_";
        });
    }
});

let nameSymbol = Symbol("name");

proxy.name = "proxy";
proxy._name = "private";
proxy[nameSymbol] = "symbol";

let names = Object.getOwnPropertyNames(proxy),
    keys = Object.keys(proxy),
    symbols = Object.getOwnPropertySymbols(proxy);

console.log(names.length); // 1
console.log(names[0]); // "name"

console.log(keys.length); // 1
console.log(keys[0]); // "name"

console.log(symbols.length); // 1
console.log(symbols[0]); // "Symbol(name)"

函數代理的 apply 和 construct 陷阱

所有代理陷阱中,只有 apply 和 construct 的代理目標是一個函數。
函數有兩個內部方法 [[Call]] 和 [[Construct]],apply 陷阱和 construct 陷阱可以覆寫這些內部方法。
若使用 new 操作符調用函數,則執行 [[Construct]] 方法;若不用,則執行 [[Call]] 方法。

apply 陷阱和 Reflect.apply() 都接受以下參數:

  • trapTarget
    被執行的函數(代理的目標)
  • thisArg
    函數被調用時內部this的值
  • argumentList
    傳遞給函數的參數數組

當使用 new 調用函數時調用的 construct 陷阱接受以下參數:

  • trapTarget
    被執行的函數(代理的目標)
  • argumentList
    傳遞給函數的參數數組

Reflect.construct() 方法也接受這兩個參數,其還有一個可選的第三個參數 newTarget。

let target = function() { return 42; },
    proxy = new Proxy(target, {
        apply: function(trapTarget, thisArg, argumentList) {
            return Reflect.apply(trapTarget, thisArg, argumentList);
        },
        construct: function(trapTarget, argumentList) {
            return Reflect.construct(trapTarget, argumentList);
        }
    });

// 一個目標是函數的代理開起來也像一個函數
console.log(typeof proxy); // function
console.log(proxy()); // 42
var instance = new proxy();
console.log(instance instanceof proxy); // true
console.log(instance instanceof target); // true

驗證函數參數

例:驗證所有參數必須是數字:

// 將所有參數相加
function sum(...values) {
    return values.reduce((previous, current) => previous + current, 0);
}

let sumProxy = new Proxy(sum, {
    apply: function(trapTarget, thisArg, argumentList) {
        argumentList.forEach((arg) => {
            if (typeof arg !== "number") {
                throw new TypeError("所有參數必須是數字");
            }
        });

        return Reflect.apply(trapTarget, thisArg, argumentList);
    },
    construct: function(trapTarget, argumentList) {
        throw new TypeError("該函數不可通過new來調用");
    }
});

console.log(sumProxy(1, 2, 3, 4)); // 10

// 拋出錯誤
// Uncaught TypeError: 所有參數必須是數字
console.log(sumProxy(1, "2", 3, 4));

// 拋出錯誤
// Uncaught TypeError: 該函數不可通過new來調用
let result = new sumProxy();

可調用的類構造函數

使用 apply 陷阱創建實例

class Person {
    constructor(name) {
        this.name = name;
    }
}

let PersonProxy = new Proxy(Person, {
    apply: function(trapTarget, thisArg, argumentList) {
        return new trapTarget(...argumentList);
    }
});

let me = PersonProxy("JiaJia");
console.log(me.name); // JiaJia
console.log(me instanceof Person); // true
console.log(me instanceof PersonProxy); // true

可撤銷代理

可以使用 Proxy.revocable() 方法創建可撤銷的代理,該方法採用與 Proxy 構造函數相同的參數:目標對象和代理處理程式。
返回值是具有以下屬性的對象:

  • proxy
    可被撤銷的代理對象
  • revoke
    撤銷代理要調用的函數
let target = {
    name: "target"
};

let { proxy, revoke } = Proxy.revocable(target, {});

console.log(proxy.name); // target

revoke();

// 拋出錯誤
// Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
console.log(proxy.name);

解決數組問題

數組問題

let colors = ["red", "green", "blue"];

console.log(colors.length); // 3

colors[3] = "black";

console.log(colors.length); // 4
console.log(colors[3]); // black

colors.length = 2;

自定義數組類型

function toUint32(value) {
    return Math.floor(Math.abs(Number(value))) % Math.pow(2, 32);
}

function isArrayIndex(key) {
    let numericKey = toUint32(key);
    return String(numericKey) == key && numericKey < (Math.pow(2, 32) - 1);
}


class MyArray {
    constructor(length = 0) {
        this.length = length;
        return new Proxy(this, {
            set(trapTarget, key, value) {
                let currentLength = Reflect.get(trapTarget, "length");

                if (isArrayIndex(key)) {
                    let numericKey = Number(key);
                    if (numericKey >= currentLength) {
                        Reflect.set(trapTarget, "length", numericKey + 1);
                    }
                } else if (key === "length") {
                    if (value < currentLength) {
                        for (let index = currentLength - 1; index >= value; index--) {
                            Reflect.deleteProperty(trapTarget, index);
                        }
                    }
                }

                return Reflect.set(trapTarget, key, value);
            }
        });
    }
}

let colors = new MyArray(3);
console.log(colors instanceof MyArray); // true
console.log(colors.length); // 3

let colors2 = new MyArray(5);
console.log(colors.length); // 3
console.log(colors2.length); // 5

colors[0] = "red";
colors[1] = "green";
colors[2] = "blue";
colors[3] = "black";

console.log(colors.length); // 4

colors.length = 2;
console.log(colors.length); // 2
console.log(colors[3]); // undefined
console.log(colors[2]); // undefined
console.log(colors[1]); // "green"
console.log(colors[0]); // "red"

將代理作為原型

let target = {};
let proxy = new Proxy(target, {
    defineProperty(trapTarget, name, descriptor) {
        return false;
    }
});
let newTarget = Object.create(proxy);

Object.defineProperty(newTarget, "name", {
    value: "newTarget"
});

console.log(newTarget.name); // "newTarget"
console.log(newTarget.hasOwnProperty("name")); // true
console.log(Object.getPrototypeOf(newTarget) === proxy); // true

關於 Object.create() 方法可以參照 這裡

上例中 newTarget 的原型是代理,但是在對象上定義屬性的操作不需要操作對象原型,所以沒有觸發代理中的陷阱。

儘管代理作為原型使用時及其受限,但有幾個陷阱仍然有用。

在原型上使用 get 陷阱

let target = {};
let thing = Object.create(new Proxy(target, {
    get(trapTarget, key, value) {
        throw new ReferenceError(`${key} deesn't exist`);
    }
}));

thing.name = "thing";
console.log(thing.name); // "thing"

// 拋出異常:
// Uncaught ReferenceError: unknown deesn't exist
let unknown = thing.unknown;

訪問對象上不存在的屬性時,會觸發原型中的 get 陷阱。

在原型上使用 set 陷阱

let target = {};
let thing = Object.create(new Proxy(target, {
    set (trapTarget, key, value, receiver) {
        return Reflect.set(trapTarget, key, value, receiver);
    }
}));
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 《Linux設備驅動》 -- 也就是我們所說的LDD3了; 適合一定基礎的人閱讀,深入學習Linux不可或缺的知識; 《UNIX環境高級編程》 這本書並不是面對linux內核的書,但是我是從最基礎看這本書逐步入門的; 《Linux內核完全剖析》 本書對早期Linux內核(v0.12)全部代碼文件進行 ...
  • 基礎知識: 1、Redis的數據類型: 字元串、列表(lists)、集合(sets)、有序集合(sorts sets)、哈希表(hashs)2、Redis和memcache相比的獨特之處: (1)redis可以用來做存儲(storge)、而memcache是來做緩存(cache)。這個特點主要是因為 ...
  • 1.1 負載均衡介紹 1.1.1 負載均衡的妙用 負載均衡(Load Balance)集群提供了一種廉價、有效、透明的方法,來擴展網路設備和伺服器的負載、帶寬、增加吞吐量、加強網路數據處理能力、提高網路的靈活性和可用性。 ü 單台電腦無法承受大規模的併發訪問或數據流量了,此時需要搭建負載均衡集群把 ...
  • 1、下載軟體包 wget http://pecl.php.net/get/redis-2.2.5.tgz 2、解壓 tar zxvf redis-2.2.5.tgz 3、進入安裝目錄 cd redis-2.2.5 4、用phpize生成configure配置文件 /usr/local/php/bin ...
  • 描述 前面我們已經對領域內的名詞進行了抽取,並且已經確定了業務流程中參與的核心對象。 但是對象只是靜態的描述,系統中往往會有很多的業務操作,偏演算法的,之前我們說過 領域內的對象往往是比較穩定不怎麼變化的,但是,業務的流程以及業務操作這些是往往 千變萬化,防不勝防,那麼我們如何去及時發現這些系統內變化 ...
  • 同步首發:http://www.yuanrengu.com/index.php/20171130.html 項目開發接近尾聲,開始著手在生產環境部署項目,開發階段部署項目都沒用nginx。項目是採用SOA架構,多系統開發,主要包括服務系統、中台系統、後臺系統、金融系統、介面系統、調度系統、報表系統等 ...
  • 1、一行超出文字隱藏 2、div顯示兩行文字,超出兩行部分省略號顯示 3、white-space屬性可設置不換行 4、word-break屬性設置自動換行的處理方法 ...
  • 詳述JavaScript的官方模塊風格。加入這一定義旨在代替過去幾年中出現過的許多非正式的模塊定義風格。 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...