<script>
(function () {
var s =document.createElement('script');
s.type='text/javascript';
s.async=true;
s.src='http://xxx.com/sdk.js';
var x =document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
})();
// execute your script immediately hereSDKName('some arguments');
</script>
<script>
(function () {
// add a queue event here
SDKName = SDKName ||function () {
(SDKName.q=SDKName.q|| []).push(arguments);
};
var s =document.createElement('script');
s.type='text/javascript';
s.async=true;
s.src='http://xxx.com/sdk.js';
var x =document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
})();
// execute your script immediately hereSDKName('some arguments');
</script>
或者用 [ ].push
<script>
(function () {
// add a queue event here
SDKName =window.SDKName|| (window.SDKName= []);
var s =document.createElement('script');
s.type='text/javascript';
s.async=true;
s.src='http://xxx.com/sdk.js';
var x =document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
})();
// execute your script immediately hereSDKName.push(['some arguments']);
</script>
其他方式
还有其它不同方式加载脚本
Import in ES2015
import"your-sdk";
模块加载
这里有完整的源码和非常棒的教程. Loading JavaScript Modules
module('sdk.js',['sdk-track.js', 'sdk-beacon.js'],function(track, beacon) {
// sdk definitions, split into local and global/exported definitions// local definitions// exports
});
// you should contain this "module" method
(function () {
var modules = {}; // private record of module data// modules are functions with additional informationfunctionmodule(name,imports,mod) {
// record module informationwindow.console.log('found module '+name);
modules[name] = {name:name, imports: imports, mod: mod};
// trigger loading of import dependenciesfor (var imp in imports) loadModule(imports[imp]);
// check whether this was the last module to be loaded// in a given dependency grouploadedModule(name);
}
// function loadModule// function loadedModulewindow.module=module;
})();
Free yourself from the chains of jQuery by embracing and understanding the modern Web API and discovering various directed libraries to help you fill in the gaps.
<span id="black" style="color: black">
This is black color span
</span>
<script> document.getElementById('black').style.color; // => black</script>
获取真正的样式
<style>
#black { color: red !important;}
</style>
<span id="black" style="color: black">
This is black color span
</span>
<script>
document.getElementById('black').style.color; // => black
// real var black = document.getElementById('black');
window.getComputedStyle(black, null).getPropertyValue('color'); // => rgb(255, 0, 0)
</script>
翻译背景
前段时间因为业务需要设计开发了服务于公司内部的Javascript-SDK,开发之前的调研阶段发现这篇文章,感觉写的很好,方方面面都有提到,与其说是SDK设计指南,实际上对类库、框架、开源项目设计都有指导作用。其中参考的很多网站和技术博客都极具价值,基本囊括了一个开源项目从开始到最后上线发布所有需要注意的所有技术细节和流程。网上有关设计开发的文档极少,所以决定把这篇文章翻译成中文。虽然借助了很多翻译工具,也请教了周围英语比较好的同事,但是鉴于水平有限,篇幅太长,难免有所疏漏或者翻译不准确的地方欢迎大家留言指出。
原文地址:http://sdk-design.js.org/
Javascript-SDK设计指南
介绍
本指南为您介绍了在台式机和移动网络在不同的平台和浏览器( < 99.99 %我可能会跳过一些浏览器)开发的JavaScript SDK ,对于那些非浏览器开发的支持(硬件,嵌入式,节点/ IO JS )被排除在本文档之外,在未来予以考虑。
因为我没有找到一个关于设计JavaScript SDK的比较好的文档,所以我在这里收集并记下了我个人的经验。这份文档已经写了好几个月,有一点我们需要知道,JavaScript的SDK-设计不仅仅是设计SDK本身,这也是有关于开发者与设备浏览器中间的联系。我们写的越多,越会更多的思考我们真正关心的是不同平台和浏览器之间的性能和兼容问题。你可以根据情况自由的更改或者完全放弃我在文章里列出的建议。
什么是SDK
我知道它确实是很普通很常见。一般是一些软件工程师为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件时的开发工具的集合。通常一个SDK包含一个或多个API,编程工具和档。
设计理念
这取决于你的SDK用来干什么的,但是它必须具备原生的,短,速度快,干净,可读可测试特性。用原生javascript写,不要用像Livescript, Coffeescript, Typescript和其它的编译语言。必须有更好的方法来编写自己的javascript原生代码比别人更快。请不要在你的SDK里用JQuery,除非它非常有必要。你可以使用其它的类似jQuery的库,譬如zetpo.js,用于DOM操作,如果你需要用到HTTP Ajax请求,可以使用另外一种轻量库像window.fetch。
每一次的SDK版本发布,确保它不仅适用于旧版本而且适应于未来的新版本。所以,记得为你的SDK写文档,代码要写注释,同时做好单元测试和用户场景测试。
适应范围
基于《Third-Party JavaScript》这本书。在何种情况下,你应该为你的应用设计一个JavaScript SDK?
在什么情况下,我们应该在JavaScript环境中使用SDK呢?大家可以想想还有其它情没?
引入SDK
建议你采用异步加载脚本的方式。我们要优化网站的用户体验,所以不希望我们的SDK库阻塞其它主要进程。
异步加载
在新的现代浏览器(chrome)你可以使用
传统加载方法
对比:
下面是简单的图形显示异步加载和传统同步加载方式之间的区别
异步:
同步:
异步和延迟脚本执行解释
异步延迟.jpg
异步的问题
当你使用异步加载的时候,将会出现,页面中的函数无法正常调用SDK方法的情况。
结果会报undefined错误,因为SDKName()在脚本加载之前执行了。所以我们应该使用点技巧让脚本正确执行。把事件保存在SDKName.q数组里,SDK初始化的时候执行SDKName.q。
或者用 [ ].push
其他方式
还有其它不同方式加载脚本
Import in ES2015
模块加载
这里有完整的源码和非常棒的教程. Loading JavaScript Modules
SDK版本
避免使用自己的特例作为版本名称像
标识-v<时间戳>.js 标识-v<日期>.js 标识-v1-v2.js
它可能导致使用SDK的开发者很混乱不知道哪个是最新版本。
使用 Semantic Versioning (语义化版本规范)去定义SDK的版本号以"大.小.补丁"形式。
版本以v1.0.0 v1.5.0 v2.0.0的形式,会让使用者搜索跟踪日志文件更容易。
通常情况下,我们会有不同的方式去声明SDK的版本,这取决于具体针对的业务和设计。
使用查询字符串路径
使用文件夹命名
使用主机名或者子域名
为了以后版本的升级迭代,建议用stable unstable alpha latest experimental 版本。
更新日志文件
你应该注意到如果你升级你的SDK却没通知用户,用户不会知道。记得写更新日志来记录无论是主要、次要甚至bug修复等修改。这将是一个好的开发经验,我们能快速的跟踪到SDK某个API的修改。所以保持更新日志 - Keep a Changelog, Github Repo
每个版本的日志应该有:
命名空间
在你的SDK里只定义一个全局命名空间,并且不要用太过通用的名字,避免和其它类库名发生冲突。SDK的主体用(function () { ... })()包裹。这种做法越来越普遍的应用于各种流行的javascript类库譬如jQuery,Node.js等等。这种创建私有的命名空间的技术很重要,有助于避免各种类库之间命名的冲突。
为了避免命名空间冲突
学习Google Analytics的做法,你可以通过改变 ga的值来定义你自己的命名空间。
下面的是 openX的做法,支持通过给地址传递参数定义命名空间。
存储机制
cookie
使用cookie就会面临复杂的作用域范围问题,而且涉及到子域和路径问题。
比如在路径 path=/下, cookie first=value1 在域名 http://github.com下, 另外一个 cookie second=value2 在域名 http://sub.github.com下
| http://github.com | http://sub.github.com
----|------|----
first=value1 | ✓ | ✓
second=value2 | ✘ | ✓
有个 cookie first=value1 在 http://github.com下, cookie second=value2 在 http://github.com/path1 另外一个 cookie third=value3 在 http://sub.github.com下,
| http://github.com | http://github.com/path1 |http://sub.github.com
----|------|----|----
first=value1 | ✓ | ✓|✓
second=value2 | ✘ | ✓|✘
third=value3 | ✘ | ✘|✓
检查 Cookie 可读写
给定一个域 (默认当前主机域名), 检查cookie是否可读写。
检查第三方 Cookie 可读写
检查第三方cookie仅仅通过客户端js是办不到的,需要服务器端配合。
写 读 删除 Cookie 代码
代码片段写/读/删除cookie的脚本。
Session
js写不了session,需要服务器端写。
一个页面的session会一直保存着只要浏览器是开着的即使页面重新加载。打开一个新页面会生成一个新的session。子窗口会和父窗口共享一个session。
LocalStorage
存储的数据没有时间限制。存储数据量大(至少5MB)并且信息不会传送到服务器。而且同一个域名从http和https访问localStorage是不共享的。你可以在你的网页上创建个iframe,然后用postMessage方法去传值到父页面。HOW TO?
检查 LocalStorage 可写
window.localStorage 并不是任何浏览器都支持,SDK在用之前要检查是否可用。
SessionStorage
针对一个 session 的数据存储(当用户关闭浏览器窗口后,数据会被删除).
检查 SessionStorage 可写
事件
在客户端浏览器有很多事件加载、卸载、绑定等会存在兼容问题。polyfills是个解决不同平台事件绑定的不错的解决方案。
Document Ready
确保整个页面完成加载了再执行SDK方法。
DOMContentLoaded - 所有DOM解析完会触发整个事件 不需要等到样式表、图片等加载完。
load 页面完整加载。
Message Event
这里是实现iframe和父页面之间的数据通信, 这里有文档 API documentation.
发送的数据是字符串, 对于使用更高级的json字符串. 不是所有的浏览器对支持 Structured Clone Algorithm on the parameter, (参数的结构化克隆)。
Orientation Change 横屏事件
检测设备横屏
获取旋转方向和角度
Screen portrait-primary(竖屏正方向), portrait-secondary(竖屏反方向), landscape-primary(横屏正方向), landscape-secondary (横屏反方向)(Experimental)
Request
我们的SDK和服务器之间通信通过Ajax请求,因为我们知道我们可以使用jQuery的Ajax 方法。但是有更好的方案来实现它。
图片预加载
通过创建一个Image对象预加载一张图片。为了防止浏览器缓存记得加上时间戳。
要注意通过GET方式传输参数最大长度是2048个字节(取决于不同的浏览器和服务器)。这里要做一些处理如果超过长度。
你可能遇到问题在使用encodeURI 还是 encodeURIComponent的时候,最好理解它们的区别。 See below.
对于图像加载成功/错误回调
单个 Post 请求
普通表单发送一个对应元素和值
多个 Post 请求
服务通常比较复杂,需要通过POST方法发送更多数据。
Iframe
当你在需要在页面中生成内容时候,你可以通过iframe嵌入。
清除iframe的边框,内部margin值。
iframe中插入html
jsonp
这种情况下,你的服务器需要响应JavaScript 代码,并让浏览器执行它,仅仅通过js脚本链接。
关于jsonp你需要了解:
XMLHttpRequest
自己写XMLHttpRequest不是个好主意,因为你要浪费很多时间去做IE或者其它浏览器的兼容。这里提供一些现成的解决方案供大家参考:
1 - window.fetch - A window.fetch JavaScript polyfill.
2 - got - Simplified HTTP/HTTPS requests
3 - microjs - list of ajax lib
4 - more
Maximum Number of Connection
检查不同浏览器的最大连接数 browserscope
最大连接数.png
调试
模拟多个域
你不需要注册多个域名来模拟域,在本地搭建个虚拟服务器,绑定host的方式就可以:
添加以下条目
#refer to localhost
127.0.0.1 publisher.net
127.0.0.1 sdk.net
然后你就可以访问该页面http://publisher.net和http://sdk.net
Developer Tools
用浏览器自带的调试工具,Chrome Developer Tool 、Safari Developer Tools、Firebug都是不错的选择。
开发工具也简称为工具。
工具提供Web开发者深进入浏览器和Web应用程序的内部。使用工具来有效地追踪布局问题,将JavaScript打断点,并获得代码优化的建议。
控制台日志
用于测试和输出文本和其
他一般的调试, 控制台日志可通过浏览器的API log()输出显示。有各种各样的方法和格式输出你的信息,了解更多API: Console API.
控制台.png
调试代理
代理在你调试SDK的很多时候都很有用。 修改cookies, headers, cache, 编辑 http request/response, SSL Proxying, ajax 调试等等。
这里推荐一些代理工具:
BrowserSync
BrowserSync Browsersync能让浏览器实时、快速响应您的文件更改(html、js、css、sass、less等)并自动刷新页面。更重要的是** Browsersync可以同时在PC、平板、手机等设备下进项调试**。它真的很有帮助如果你需要跨平台测试你的SDK)。
提示和小技巧
Console Logs Polyfill
(Polyfilling 是由 RemySharp 提出的一个术语,它是用来描述复制缺少的 API 和API 功能的行为)
这不是一个真正的polyfill,只是保证在调用console.log API的时候不抛出错误。
EncodeURI or EncodeURIComponent
理解三者的不同 escape()、encodeURI()、encodeURIComponent()
here.
记住使用 encodeURI()和encodeURIComponent()有11个字符不同。 它们是: # $ & + , / : ; = ? @ more discussion。
encodeurl.png
你可能真的不需要JQuery
正如标题所说, 你可能真的不需要JQuery。如果你正在找一些公共的代码那下面这些会很有用:- AJAX EFFECTS, ELEMENTS, EVENTS, UTILS
你不需要 jQuery
http://blog.garstasio.com/you-dont-need-jquery/
有用的 Tips
Selecting Elements
DOM Manipulation
回调函数加载脚本
类似于 异步加载脚本 增加回调函数。
执行一次函数
这里展示了如何实现函数只执行一次。
每当你想有一个只运行一次的函数。通常这些函数是以事件监听的方式,很难管理。当然如果很容易管理,你只需要删除监听事件,但是这是个理想的状态,很多时候你只需要允许一个函数执行一次。下面的代码可以实现:
获取样式
获取行间样式
获取真正的样式
ref:https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle
检测当前窗口
了解更多: here。
模板
有些人要求提供一些SDK的模板这里有一些列子给大家:
TEMPLATE.md
书/相关注意
Third-Party JavaScript
JQuery Plugin
LightningJS
本文参考
What is Software Development Kit
A window.fetch JavaScript polyfill.
POST Request
Semantic VersioningVersioning 2.0.0
HTTP API design guide extracted from work on the Heroku Platform API
Understanding URIs
URI Parsing with JavaScript
Modernizr: the feature detection library for HTML5/CSS3
HTML5 Web Storage
Check if third-party cookies are enabled
Introduction to Analytics.js - Universal Analytics Web Tracking
Facebook Conversion Tracking Pixel
What is the maximum length of a URL
YOU MIGHT NOW NEED JQUERY
What is a Polyfill?
Asynchronous and deferred JavaScript execution explained
generate random UUIDs
DOMContentLoaded and Load Event
Must See JavaScript Dev Tools That Put Other Dev Tools to Shame
(inspired by http-api-design)