【讀書筆記】【深入理解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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...