在昨天的 JavaScript 程式中,有個東西需要介紹給各位,
理解 require 物件,對於有些模組的程式只有執行,但沒有賦值的情況會有更多的了解
首先,在昨天的 widget.js 中,執行 require 物件後取得 FieldText 物件
odoo.define('ironman_js.widget', function (require) {
'use strict';
const { FieldText } = require('web.basic_fields');
FieldText.include({
start: function () {
console.log('Hello world');
},
})
});
簡單解釋 require 的功用就是 “等待目標 JS 執行完成”
因此也能理解成確保目前的程式,至少會在 require 目標執行後,自己才會執行
還是很亂嗎?用 python 的角度來解釋,可以想像成下面程式
from module_a import Text
但跟 python import 不一樣的是,若該模組的 JS 沒有 return,那取得就會是 undefined
因此在 require 時,要查詢目標是否有回傳,沒有的話就有很大的機率會發生錯誤
主要論述講完了,接著帶各位看一下 odoo.define 的程式
// addons/web/static/src/js/boot.js
odoo.define = function () {
var args = Array.prototype.slice.call(arguments);
var name = typeof args[0] === 'string' ? args.shift() : ('__odoo_job' + (jobUID++));
var factory = args[args.length - 1];
var deps;
if (args[0] instanceof Array) {
deps = args[0];
} else {
deps = [];
factory.toString()
.replace(commentRegExp, '')
.replace(cjsRequireRegExp, function (match, dep) {
deps.push(dep);
});
}
if (odoo.debug) {
if (!(deps instanceof Array)) {
throw new Error('Dependencies should be defined by an array', deps);
}
if (typeof factory !== 'function') {
throw new Error('Factory should be defined by a function', factory);
}
if (typeof name !== 'string') {
throw new Error("Invalid name definition (should be a string", name);
}
if (name in factories) {
throw new Error("Service " + name + " already defined");
}
}
factory.deps = deps;
factories[name] = factory;
jobs.push({
name: name,
factory: factory,
deps: deps,
});
deps.forEach(function (dep) {
jobDeps.push({from: dep, to: name});
});
this.processJobs(jobs, services);
};
重點在 factory 的處理,factory 就是一開始 widget.js 傳入的 function
而過了處理後,會執行工作 (processJobs),所以再看到對應的程式
// addons/web/static/src/js/boot.js
odoo.processJobs = function (jobs, services) {
var job;
function processJob(job) {
var require = makeRequire(job);
var jobExec;
var def = new Promise(function (resolve) {
try {
jobExec = job.factory.call(null, require);
jobs.splice(jobs.indexOf(job), 1);
} catch (e) {
job.error = e;
console.error('Error while loading ' + job.name + ': '+ e.stack);
}
if (!job.error) {
Promise.resolve(jobExec).then(
function (data) {
services[job.name] = data;
resolve();
odoo.processJobs(jobs, services);
}).guardedCatch(function (e) {
job.rejected = e || true;
jobs.push(job);
resolve();
}
);
}
});
jobPromises.push(def);
}
function isReady(job) {
return !job.error && !job.rejected && job.factory.deps.every(function (name) {
return name in services;
});
}
function makeRequire(job) {
var deps = {};
Object.keys(services).filter(function (item) {
return job.deps.indexOf(item) >= 0;
}).forEach(function (key) {
deps[key] = services[key];
});
return function require(name) {
if (!(name in deps)) {
console.error('Undefined dependency: ', name);
}
return deps[name];
};
}
while (jobs.length) {
job = undefined;
for (var i = 0; i < jobs.length; i++) {
if (isReady(jobs[i])) {
job = jobs[i];
break;
}
}
if (!job) {
break;
}
processJob(job);
}
return services;
};
會發現迴圈處理所有工作,而在內部函式 processJob,會執行 factory
jobExec = job.factory.call(null, require);
這樣就能理解為什麼 odoo.define 的傳入函式第一個參數一定是 require 物件了
當然要定義其他名稱也是可以,不過依照整個 odoo 開源社群的理解,
大家都共同定義都是 require,後續維護者也知道說這是做什麼的,就不會混淆