jQuery inspired DOM selection and manipulation library.
Why not just use jQuery?
Because we can! Because there're certain cases that jQuery might overkill.
We are a small team working on internet advertisements. There are cases that
require us to limit the total tranfer of the first load to be within 50k.
This might be flame bait, but jQuery isn't that modularized.
What if I don't fancy a Promise?
What if I prefer superagent over jQuery.ajax
?
So with all of those questionable parts stripped out. We've got DOM selection and manipulation left.
That makes Yen.
If your project's setup enables using CMD modules directly, you can just install
Yen via npm
.
$ npm install yen --save
There's no precompiled version of Yen yet, but we'll see to it shortly.
And if your project is based on express or koa, you can just use oceanify as the CMD decorator.
var koa = require('koa')
var http = require('http')
var oceanify = require('oceanify/g')
var app = koa()
app.use(oceanify({ base: 'components' }))
http.createServer(app.callback()).listen(3000)
Then Yen will be avaiable in your modules within components
folder. You can
simply require
it like it's in Node.
var $ = require('yen')
$('body').css('background-color', 'red')
See oceanify-example
If you've got no interests in the backend, you can give oceanifier a spin.
$ npm install ocean-kit -g
$ ocean serve
Then Yen will be available at the modules within current working directory's
components
folder.
See oceanify-example
Yen mimics jQuery's API mostly. Functionalities like jQuery.ajax
,
jQuery.Deferred
, and jQuery.fn.animate
are all not included because the goal
of Yen. And there're better options than the ones jQuery provide out there.
The API documentations of Yen is not completed yet. Stay tuned.
During the implementation of Yen, we found certain issues in rare or ancient browsers that cannot be easily fixed or bypassed. To fix them will require a lot of code. And the benefit is quite disproportional.
So issues like this are all found and documented here. Mostly will begone when we can finally drop support of browsers like IE[6-8].
In IE 6 and 7, if the element hasn't got any id attribute, .getAttribute('id')
will return an empty string instead of the expected null. This causes a bug in
yen.fn.hasAttr
. #5
@luckydrq found this issue.
In firefox and safari 5, .css('margin')
won't return the shorthand value.
They'll return an empty string instead.
The position value returned by jQuery.fn.position() will
subtract the element's marginTop and marginLeft. On the other hand,
Element.offsetLeft
and Element.offsetTop
will include elements margin.
yen.fn.position
will stick with the native one:
return {
top: el.offsetTop,
left: el.offsetLeft
}
In IE 6 and 7, [offsetTop and offsetLeft is problematic][cssom#offsetLeft]. The ancestors that have
their position
set to relative
will be ignored.
.querySelectorAll()
in IE 8 is crippled. It only support CSS 2.1
selectors. CSS 3 selectors are supported partially.
We've found:
document.querySelectorAll('#fixture li:last-child') // invaid argument
document.getElementById('fixture').querySelectorAll('li:last-child') // okay
In jQuery, the callback passed to .each()
和 .map()
will get its this
point to the element of current iteration:
$('div').each(function(index, el) {
expect(this === el).to.be(true)
})
$('div').map(function(index, el) {
expect(this === el).to.be(true)
})
In Yen however, .each()
and .map()
shares the behaviour of the one provided
on Array.prototype
:
$('div').each(function(el, index) {
expect(this === window).to.be(true)
})
$('div').map(function(el, index) {
expect(this === window).to.be(true)
})
// Yen has got .forEach() too. It won't return current instance.
$('div').forEach(function(el, index) {
expect(this === window).to.be(true)
})
To summerize, the differences of .each()
and .map()
in Yen and jQuery are:
- In jQuery the callback function footprint is
function(index, el) {}
. But Yen usesfunction(el, index) {}
, the one made popular in es5. - In Yen
this
is still. It points to window, but you can pass in a second argument to havethis
dynamically bound to that.
$('div').each(function(el) {
// calls method on someInstance
this.someMethod(el)
}, someInstance)
I think you will prefer the one Yen uses.
Yen uses [oceanifer][oceanifer] to run the development server.
$ npm install
$ npm start
$ open http://localhost:5000/test/runner.html
类似 jQuery 的 DOM 选择与操作库。
如果你的 Web 应用后端是基于 Express 或者 Koa 开发的,那么推荐使用 oceanify 管理前端组件,用过的都说好。也可以使用 browserify、webpack 这种静态编译方案。
以上方式无论哪种,使用 npm 就可以安装、更新 Yen 了:
$ npm install yen --save
如果你的网站需要支持 IE[6-8],请在你的页面中加入如下条件注释:
<!--[if lte IE 9]><!-->
<script src="http://amo.alicdn.com/L1/377/10010/assets/es5-7dfe7fb63d161c704e2f35874957b921.js"></script>
<script src="http://amo.alicdn.com/L1/377/10010/assets/json2-0caa31693309e1d7afbb55b0b2b0410e.js"></script>
<!--<![endif]-->
这两段 JS 分别提供了基本的 es5 垫片和 JSON 支持,两者在 yen 里都有用到。json2.js 来自 https://github.com/douglascrockford/JSON-js
Yen 仅适合在浏览器中选择、操作 DOM。如果你需要在 Node.js 中用 DOM 或者 jQuery API 操作 HTML 字符串,可以考虑使用模仿 jQuery API 的 cheerio。
TODO
TODO
TODO
TODO
在实践过程中,我们遇到一些坑,有些可以填,有些填起来很麻烦,代价比较大。Yen 非常重视文件尺寸, 也尽量不在源代码中做 UA 判断,因此有些性价比不高的填坑方案,都被我们放弃了,随着网速提升、 浏览器更迭,这些坑可能在不久的将来会成为历史,暂且统一列在这里。
在 IE 6 和 7 里执行 .getAttribute('id')
返回的会是一个空字符串,而不是规范的 null,
会导致 yen.fn.hasAttr
所采用的兼容方式判断失败,详见 #5。
@luckydrq 是第一个踩到这个坑的人。
在 Firefox 与 Safari 5 里执行 .css('margin')
并不会返回缩写的 margin 值,而是返回空。
jQuery.fn.position() 返回的 top
与 left
会去掉节点的
marginTop 和 marginLeft,而 Element#offsetLeft 和 Element#offsetTop 是会把节点的
margin 考虑进去的。不清楚这个区别的前因后果,但我一直以为是等价的,所以 yen.fn.position
的返回值采用的后者,即:
return {
top: el.offsetTop,
left: el.offsetLeft
}
不过,这个返回值[在 IE[67] 里是有问题的][cssom#offsetLeft],会无视 position: relative
的父节点。
IE8 里的 .querySelectorAll()
功能有所残缺,仅支持 CSS 2.1 标准中所列的
选择器,部分支持 CSS 3 标准里的选择器。
目前踩到的有:
document.querySelectorAll('#fixture li:last-child') // 参数无效
document.getElementById('fixture').querySelectorAll('li:last-child') // 没问题
在 jQuery 里,.each()
和 .map()
在调用传入的回调函数时,会动态改变这个函数的 this,
变为当前迭代的节点:
$('div').each(function(index, el) {
expect(this === el).to.be(true)
})
$('div').map(function(index, el) {
expect(this === el).to.be(true)
})
在 Yen 里,.each()
和 .map()
的行为和 Array.prototype
上的一致(其实就是直接用了
数组原型链上的这两个方法):
$('div').each(function(el, index) {
expect(this === window).to.be(true)
})
$('div').map(function(el, index) {
expect(this === window).to.be(true)
})
// 还有 .forEach(),和 .each() 的区别是 .forEach 没有返回值
$('div').forEach(function(el, index) {
expect(this === window).to.be(true)
})
总结一下,yen 里的 .each()
和 .map()
和 jQuery 的区别在于:
- jQuery 是
function(index, el) {}
,而 Yen 则是function(el, index) {}
- 不会动态绑定
this
,和Array.prototype
上的一样,默认指向全局,但可以传入第二个 参数指定context
。
但是我相信你会用得更加顺手。
我们使用 oceanifier 运行 HTTP 服务,默认端口 5000。
$ npm install
$ npm start
$ open http://localhost:5000/test/runner.html
我们用的测试框架是 mocha,用的断言写法是 expect.js 风格。
我们使用 totoro 运行多个浏览器的集成测试:
$ npm start
$ totoro --runner http://`oceanify ip`/test/runner.html
也可以直接执行:
$ npm start
$ npm test