在 Odoo 中,你可以使用两种方法来扩展 JavaScript 对象:extends 和 patch。这两种方法在功能上有一些区别。
extends 方法:
extends 方法可以创建一个新的 JavaScript 对象,并继承自现有的对象。这意味着你可以在新对象中添加、修改或覆盖现有对象的属性和方法。extends 方法适用于对现有对象进行全面的扩展和修改,以满足特定的需求。你可以通过创建一个新的对象,并在其中添加自定义的属性和方法来实现扩展。extends 方法时,你需要确保新对象的原型链正确设置,以便正确继承和覆盖现有对象的属性和方法。patch 方法:
patch 方法可以直接修改现有的 JavaScript 对象,而无需创建新的对象。你可以通过 patch 方法来修改对象的属性和方法,添加新的属性和方法,或者删除现有的属性和方法。patch 方法适用于对现有对象进行局部的修改和调整,以满足特定的需求。你可以直接在现有对象上进行修改,而无需创建新的对象。patch 方法时,你需要确保正确地定位到要修改的对象,并进行相应的修改操作。总的来说,extends 方法适用于全面的扩展和修改,而 patch 方法适用于局部的修改和调整。选择使用哪种方法取决于你的具体需求和场景。
patch 一个简单的例子
/** @odoo-module **/
import { _t } from "@web/core/l10n/translation";
import { patch } from "@web/core/utils/patch";
import { SearchBar } from "@web/search/search_bar/search_bar";
patch(SearchBar.prototype, {
getPreposition(searchItem) {
let preposition = super.getPreposition(searchItem);
if (this.fields[searchItem.fieldName].name == 'payment_date') {
preposition = _t("until");
}
return preposition
}
});
先学习一下object对象,再来学习patch.js
patch对象的原型,调用了很多Object的静态方法,原则上就是在原型对象上动态增加或者替换新的属性。
// Replace the old property by the new one.
Object.defineProperty(objToPatch, key, newProperty);
附录: patch.js 源文件
/** @odoo-module **/
/**
* @typedef {{
* originalProperties: Map;
* skeleton: object;
* extensions: Set
/** @type {WeakMap
const patchDescriptions = new WeakMap();
/**
* Create or get the patch description for the given `objToPatch`.
* @param {object} objToPatch
* @returns {PatchDescription}
*/
function getPatchDescription(objToPatch) {
if (!patchDescriptions.has(objToPatch)) {
patchDescriptions.set(objToPatch, {
originalProperties: new Map(),
skeleton: Object.create(Object.getPrototypeOf(objToPatch)),
extensions: new Set(),
});
}
return patchDescriptions.get(objToPatch);
}
/**
* @param {object} objToPatch
* @returns {boolean}
*/
function isClassPrototype(objToPatch) {
// class A {}
// isClassPrototype(A) === false
// isClassPrototype(A.prototype) === true
// isClassPrototype(new A()) === false
// isClassPrototype({}) === false
return Object.hasOwn(objToPatch, "constructor") && objToPatch.constructor?.prototype === objToPatch;
}
/**
* Traverse the prototype chain to find a potential property.
* @param {object} objToPatch
* @param {string} key
* @returns {object}
*/
function findAncestorPropertyDescriptor(objToPatch, key) {
let descriptor = null;
let prototype = objToPatch;
do {
descriptor = Object.getOwnPropertyDescriptor(prototype, key);
prototype = Object.getPrototypeOf(prototype);
} while (!descriptor && prototype);
return descriptor;
}
/**
* Patch an object
*
* If the intent is to patch a class, don't forget to patch the prototype, unless
* you want to patch static properties/methods.
*
* @template T
* @template {T} U
* @param {T} objToPatch The object to patch
* @param {U} extension The object containing the patched properties
* @returns {() => void} Returns an unpatch function
*/
export function patch(objToPatch, extension) {
if (typeof extension === "string") {
throw new Error(`Patch "${extension}": Second argument is not the patch name anymore, it should be the object containing the patched properties`);
}
const description = getPatchDescription(objToPatch);
description.extensions.add(extension);
const properties = Object.getOwnPropertyDescriptors(extension);
for (const [key, newProperty] of Object.entries(properties)) {
const oldProperty = Object.getOwnPropertyDescriptor(objToPatch, key);
if (oldProperty) {
// Store the old property on the skeleton.
Object.defineProperty(description.skeleton, key, oldProperty);
}
if (!description.originalProperties.has(key)) {
// Keep a trace of original property (prop before first patch), useful for unpatching.
description.originalProperties.set(key, oldProperty);
}
if (isClassPrototype(objToPatch)) {
// A property is enumerable on POJO ({ prop: 1 }) but not on classes (class A {}).
// Here, we only check if we patch a class prototype.
newProperty.enumerable = false;
}
if ((newProperty.get && 1) ^ (newProperty.set && 1)) {
// get and set are defined together. If they are both defined
// in the previous descriptor but only one in the new descriptor
// then the other will be undefined so we need to apply the
// previous descriptor in the new one.
const ancestorProperty = findAncestorPropertyDescriptor(objToPatch, key);
newProperty.get = newProperty.get ?? ancestorProperty?.get;
newProperty.set = newProperty.set ?? ancestorProperty?.set;
}
// Replace the old property by the new one.
Object.defineProperty(objToPatch, key, newProperty);
}
// Sets the current skeleton as the extension's prototype to make
// `super` keyword working and then set extension as the new skeleton.
description.skeleton = Object.setPrototypeOf(extension, description.skeleton);
return () => {
// Remove the description to start with a fresh base.
patchDescriptions.delete(objToPatch);
for (const [key, property] of description.originalProperties) {
if (property) {
// Restore the original property on the `objToPatch` object.
Object.defineProperty(objToPatch, key, property);
} else {
// Or remove the property if it did not exist at first.
delete objToPatch[key];
}
}
// Re-apply the patches without the current one.
description.extensions.delete(extension);
for (const extension of description.extensions) {
patch(objToPatch, extension);
}
};
}