我是一名经验丰富的Java程序员,但对JavaScript来说相当陌生。
我正在JavaScript中创建一个聊天库,并让它正常工作,但我想知道我是否正确地做了这件事,并遵循了正确的JavaScript编码标准。
我有一个SDK对象、LiveChatConnection类以及LiveChatListener和Credentials接口(我知道JavaScript中不存在类和接口,但这似乎就是如何创建它们)。
声明我正在使用的方法
this.myMethod = function() {...};我见过其他库将方法放在类原型上。
MyClass.prototype.myMethod = function() {...}或者宣布他们,
myMethod : function() {...}不知道哪一个最好。
因此,我的SDK将打包在一个sdk.js文件在我的网站上,然后用户应该能够导入和使用它在他们的网页。
不确定从使用角度看我的代码是否有意义,或者我是否遗漏了其他任何东西。
var SDK = {};
function Credentials() {
this.host = "";
this.app = "";
this.url = "";
this.applicationId = "";
}
/**
* Listener interface for a LiveChatConnection.
* This gives asynchronous notification when a channel receives a message, or notice.
*/
function LiveChatListener() {
/**
* A user message was received from the channel.
*/
this.message = function(message) {};
/**
* An informational message was received from the channel.
* Such as a new user joined, private request, etc.
*/
this.info = function(message) {};
/**
* An error message was received from the channel.
* This could be an access error, or message failure.
*/
this.error = function(message) {};
/**
* Notification that the connection was closed.
*/
this.closed = function() {};
/**
* The channels users changed (user joined, left, etc.)
* This contains a comma separated values (CSV) list of the current channel users.
* It can be passed to the SDKConnection.getUsers() API to obtain the UserConfig info for the users.
*/
this.updateUsers = function(usersCSV) {};
/**
* The channels users changed (user joined, left, etc.)
* This contains a HTML list of the current channel users.
* It can be inserted into an HTML document to display the users.
*/
this.updateUsersXML = function(usersXML) {};
}
/**
* Connection class for a Live Chat, or chatroom connection.
* A live chat connection is different than an SDKConnection as it is asynchronous,
* and uses web sockets for communication.
*/
function LiveChatConnection(credentials) {
this.debug = false;
this.channel = null;
this.user = null;
this.credentials = credentials;
this.socket = null;
this.listener = null;
this.keepAlive = false;
this.keepAliveInterval = null;
/**
* Connection to the live chat server channel.
* Validate the user credentials.
* This call is asynchronous, any error or success with be sent as a separate message to the listener.
*/
this.connect = function(channel, user) {
if (this.credentials == null) {
throw "Mising credentials";
}
this.channel = channel;
this.user = user;
var host = "ws://" + this.credentials.host + this.credentials.app + "/live/chat";
if ('WebSocket' in window) {
this.socket = new WebSocket(host);
} else if ('MozWebSocket' in window) {
this.socket = new MozWebSocket(host);
} else {
this.socket = new WebSocket(host);
//throw 'Error: WebSocket is not supported by this browser.';
}
this.listener.connection = this;
var self = this;
this.socket.onopen = function () {
if (self.user == null) {
self.socket.send("connect " + self.channel.id + " " + self.credentials.applicationId);
} else {
self.socket.send(
"connect " + self.channel.id + " " + self.user.user + " " + self.user.token + " " + self.credentials.applicationId);
}
self.setKeepAlive(this.keepAlive);
};
this.socket.onclose = function () {
self.listener.message("Info: Closed");
self.listener.closed();
};
this.socket.onmessage = function (message) {
user = "";
data = message.data;
text = data;
index = text.indexOf(':');
if (index != -1) {
user = text.substring(0, index);
data = text.substring(index + 2, text.length);
}
if (user == "Online-xml") {
self.listener.updateUsersXML(data);
return;
}
if (user == "Online") {
self.listener.updateUsers(data);
return;
}
if (self.keepAlive && user == "Info" && text.contains("pong")) {
return;
}
if (user == "Info") {
self.listener.info(text);
return;
}
if (user == "Error") {
self.listener.error(text);
return;
}
self.listener.message(text);
};
};
/**
* Sent a text message to the channel.
* This call is asynchronous, any error or success with be sent as a separate message to the listener.
* Note, the listener will receive its own messages.
*/
this.sendMessage = function(message) {
this.checkSocket();
this.socket.send(message);
};
/**
* Accept a private request.
* This is also used by an operator to accept the top of the waiting queue.
* This can also be used by a user to chat with the channel bot.
* This call is asynchronous, any error or success with be sent as a separate message to the listener.
*/
this.accept = function() {
this.checkSocket();
this.socket.send("accept");
};
/**
* Test the connection.
* A pong message will be returned, this message will not be broadcast to the channel.
* This call is asynchronous, any error or success with be sent as a separate message to the listener.
*/
this.ping = function() {
this.checkSocket();
this.socket.send("ping");
};
/**
* Exit from the current private channel.
* This call is asynchronous, any error or success with be sent as a separate message to the listener.
*/
this.exit = function() {
this.checkSocket();
this.socket.send("exit");
};
/**
* Request a private chat session with a user.
* This call is asynchronous, any error or success with be sent as a separate message to the listener.
*/
this.pvt = function(user) {
this.checkSocket();
this.socket.send("pvt: " + user);
};
/**
* Disconnect from the channel.
*/
this.disconnect = function() {
this.setKeepAlive(false);
if (this.socket != null) {
this.socket.disconnect();
}
};
this.checkSocket = function() {
if (this.socket == null) {
throw "Not connected";
}
};
this.toggleKeepAlive = function() {
setKeepAlive(!this.keepAlive);
}
this.setKeepAlive = function(keepAlive) {
this.keepAlive = keepAlive;
if (!keepAlive && this.keepAliveInterval != null) {
clearInterval(this.keepAliveInterval);
} else if (keepAlive && this.keepAliveInterval == null) {
this.keepAliveInterval = setInterval(
function() {
this.ping()
},
600000);
}
}
}
SDK.chime = function() {
var sound = new Audio('chime.wav');
sound.play();
}
SDK.url = "/botlibre/rest/botlibre";
SDK.tts = function(text) {
try {
var url = SDK.url + '/form-speak?&text=';
url = url + encodeURIComponent(text);
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState != 4) return;
if (request.status != 200) {
console.log('Error: Speech web request failed');
return;
}
var audio = new Audio(request.responseText);
audio.mediaGroup = 'voice';
audio.play();
}
request.open('GET', url, true);
request.send();
} catch (error) {
console.log('Error: Speech web request failed');
}
}发布于 2014-09-08 13:30:01
我很高兴看到我不是唯一一个发现JavaScript中的“接口”有用的人。是的,该语言不支持接口,但是如果您期望第三方代码与其集成,那么定义它们是有价值的。
中的接口
在JavaScript中没有正式的工具来定义接口。尽管如此,当有人看到构造函数(如您的LiveChatListener函数)时,他们希望它是一个可实例化的可用类。我建议使用对象文字来定义您的接口,因为它实际上只是供参考的,而且实际上并不有用。
/**
* Listener interface for a LiveChatConnection.
* This gives asynchronous notification when a channel receives a message, or notice.
*/
var ILiveChatListener = {
/**
* A user message was received from the channel.
*/
message: function(message) {},
/**
* An informational message was received from the channel.
* Such as a new user joined, private request, etc.
*/
info: function(message) {},
/**
* An error message was received from the channel.
* This could be an access error, or message failure.
*/
error: function(message) {},
/**
* Notification that the connection was closed.
*/
closed: function() {},
/**
* The channels users changed (user joined, left, etc.)
* This contains a comma separated values (CSV) list of the current channel users.
* It can be passed to the SDKConnection.getUsers() API to obtain the UserConfig info for the users.
*/
updateUsers: function(usersCSV) {},
/**
* The channels users changed (user joined, left, etc.)
* This contains a HTML list of the current channel users.
* It can be inserted into an HTML document to display the users.
*/
updateUsersXML: function(usersXML) {}
};我知道这是一种观点,但我确实喜欢以大写"I“作为接口名称的前缀的.NET惯例,我认为它更好地传达了这是一个接口。于是LiveChatListener变成了ILiveChatListener。
另一方面,如果您打算让人们使用LiveChatListener作为某种类型的基类,那么您所创建的是一个抽象基类:
function AbstractLiveChatListener() {
}
AbstractLiveChatListener.prototype = {
constructor: AbstractLiveChatListener,
message: function(message) {
throw new Error("Not Implemented");
},
info: function(message) {
throw new Error("Not Implemented");
},
...
};我喜欢你只定义界面的想法,所以我会坚持这样做。
中编写“类”的样式
在构造函数中为类创建公共方法和属性。虽然这不会伤害任何东西,但这确实意味着该类的每个实例对于每个公共方法都有全新的Function实例。“最佳实践”是在原型上定义公共方法,除非它们需要访问“私有”数据。
以下是我的一般风格指南:
Foo.prototype.bar = function()样式来定义原型上的方法:函数Point3D(x,y,z) { Point.call(this,x,y);this.z = z;} Point3D.prototype = Object.create(Point.prototype);Point3D.prototype.contains = function(p) {返回this.x >= p.x & this.y >= p.y & this.z >= p.z;};由于此时不需要继承或“私有”数据,所以我推荐样式#1,因为我发现它更容易阅读,代码中的杂乱也较少。
)
您有一个名为"SDK“的全局对象。将代码放在JavaScript中的“命名空间”中是个好主意,但是您编写的类似乎也是全局的。我建议使用一个立即调用的函数表达式(IIFE),它将为您提供一个函数范围来定义您只想在库内部定义的类和变量,并有选择地公开一些内容。其次,我不会将它命名为"SDK“,因为它非常通用,可能与使用此命名空间的其他人发生冲突。使名称空间以您的库命名。例如,如果您将lib称为“烘焙的Ziti",那么名称空间将是BakedZiti。(顺便提一句,现在用香肠烤的意大利烤饼听起来不错。)
(function(global) {
function Credentials() {
...
}
var ILiveChatListener = {
...
};
function LiveChatConnection(...) {
...
}
// Public Namespace
global.BakedZiti = {
Credentials: Credentials,
ILiveChatListener: ILiveChatListener,
LiveChatConnection: LiveChatConnection,
url: "...",
tts: function() {
...
},
chime: function() {
...
}
};
})(this);现在,您可以通过以下方式获得一个新的连接:
var credentials = new BakedZiti.Credentials(),
connection = new BakedZiti.LiveChatConnection(credentials);中
您提到您将有一个名为sdk.js的文件,人们可以将其包含在他们的站点上。我建议将每个类保存在自己的文件中,然后使用像鲍尔这样的包管理器将文件连接起来并缩小为类似于"sdk.js“的文件。此外,您可以在Bower上发布您的包,在开发期间通过命令行上的bower install将其提供给任何人。
我在我的JavaScript库中使用了这个基本文件夹结构:
BakedZiti/
build/
header.js
footer.js
demo/
index.html
dist/
BakedZiti-v1.0.0.concat.js
BakedZiti-v1.0.0.min.js
BakedZiti-v1.0.1.concat.js
BakedZiti-v1.0.1.min.js
BakedZiti-v2.0.0.concat.js
BakedZiti-v2.0.0.min.js
src/
BakedZiti/
Credentials.js
ILiveChatListener.js
LiveChatConnection.js
BakedZiti.js
tests/
BakedZiti
LiveChatConnectionTests.js
bower.json
Gruntfile.js
package.jsondemo文件夹将有一个快速而肮脏的库实现。
dist目录将打包和缩小每个主要版本的版本。
显然,src目录中的原始源文件具有无连接和无限制的形式,便于开发和维护。
如果适用的话,tests目录将有任何JavaScript单元测试。另外,您可以创建一个返回WebSocket对象的工厂方法,并且可以在测试中模拟这个对象,从而使LiveChatConnection类具有可测试性。
bower.json文件是将依赖项文件连接在一起的地方:
{
"name": "BakedZiti",
"description": "BakedZiti is a tasty chat library for JavaScript with no dependencies.",
"version": "2.0.0",
"homepage": "http://example.com/BakedZiti",
"authors": [
"Your Name <here@example.com>"
],
"license": "MIT",
"repository": { "type": "git", "url": "https://github.com/foo/BakedZiti.git" },
"main": "dist/BakedZiti-v2.0.js"
}然后可以使用Grunt构建库(Gruntfile.js)并创建可分发的表单:
module.exports = function(grunt) {
var files = [
"build/header.js",
"src/BakedZiti/Credentials.js",
"src/BakedZiti/ILiveChatListener.js",
"src/BakedZiti/LiveChatConnection.js"
"build/footer.js",
];
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
concat: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
},
main: {
src: files.main,
dest: 'dist/<%= pkg.name %>.concat.js'
},
},
min: {
main: {
src: 'dist/<%= pkg.name %>.concat.js',
dest: 'dist/<%= pkg.name %>.min.js',
}
}
});
// Load the plugin that provides the "concat" task.
grunt.loadNpmTasks('grunt-contrib-concat');
// Load the plugin that provides the "min" task.
grunt.loadNpmTasks('grunt-yui-compressor');
// Default task(s).
grunt.registerTask('default', ['concat', 'min']);
};然后Grunt所需的package.json:
{
"name": "BakedZiti",
"version": "2.0.0",
"devDependencies": {
"grunt": "~0.4.2",
"grunt-contrib-concat": "~0.1.2",
"grunt-contrib-jshint": "~0.6.3",
"grunt-contrib-nodeunit": "~0.2.0",
"grunt-yui-compressor": "~0.3.3"
}
}然后是build/header.js:
(function(global) {和build/footer.js:
// Public Namespace
global.BakedZiti = {
Credentials: Credentials,
ILiveChatListener: ILiveChatListener,
LiveChatConnection: LiveChatConnection,
url: "...",
tts: function() {
...
},
chime: function() {
...
}
};
})(this);然后,您所需要的就是从命令行运行grunt来打包东西,您需要在本地安装Node。
这确实使您的代码组织起来,并允许您根据需要扩展库。
https://codereview.stackexchange.com/questions/62227
复制相似问题