其实原型这个东西一直以来都觉得是一个很简单的东西。但是因为原型链的原因反而感觉原型里面真正可以说的其实是继承与如何实现真正意义上的重载。
最近看到了关于jQuery中原型的使用,反有了点新的见解。
jQuery中是怎么实现原型的。
首先,我们要对原型有一个清楚的认识,什么时候需要使用原型。现在的ES6里面加入了一个class,而class其实也就是根据原型来实现的一种语法糖,以前我们需要使用原型来实现的功能,现在可以使用class来实现了。也就是说,我们在需要类的时候使用原型。
function cla() {
this.add = function () {}
this.puls = function () {}
}
而这样写,每次创建一个对象的时候都会开辟空间给add,puls函数所以我们使用原型
function cla() {
}
cla.prototype.add=function(){console.log('add');}
let a=new cla()
a.add()
这样就可以有效的节约空间了,可以看到这里我们使用了一个new,表示这些必须是new出来的对象才可以使用
而在jq中,我们可以先观察jq中是怎么使用的。
let $p=$('p')
$p.css()
这里的css就是通过prototype原型来实现的。
在
https://github.com/jquery/jquery/blob/master/src/core.js
中我们可以看到最后的返回值是
return jQuery;
而jQuery的返回值是
jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery.fn.init( selector, context );
},
返回的是new jQuery.fn.init
也就是说我们执行
$(‘p’)的时候其实是返回了一个init的对象
再继续看
jQuery.fn = jQuery.prototype = {
jq的原型跟jquery.fn进行了绑定
原型里面有一些方法比如
eq: function( i ) {
var len = this.length,
j = +i + ( i < 0 ? len : 0 );
return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
},
而css这个方法是通过扩展extend进行的
这里其实会有一个疑问,为什么非要进行返回init呢?
回想一下我们对jQuery的调用。
$(‘p’)
如果我们是这样写jq的
let jq = function (selector) {
let slice = Array.prototype.slice
let dom = slice.call(document.querySelectorAll(selector))
let len = dom ? dom.length : 0
for (let i = 0; i < len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
jq.prototype = {
css() {
alert('css')
},
html() {
alert('html')
},
}
window.$ = jq
那么我们会面临一个问题,那就是我们必须是
let $p=new $('p')
这个样子,就不够优雅了,所以我们就需要返回一个new的对象,所以就把jq实际的内容都写在了init里面去了,而为什么原型绑定到fn上面呢?
这个是因为插件机制的考虑
如果我们想在外部对jq进行扩展怎么扩展,可使用
$.fn.extend()
而在内部比如css的扩展其实是使用的
jQuery.fn.extend( {
但是这个是适用于内部,我们的jq暴露出来的只有一个$符号,这个是最小暴露,所以我们在外面对jq进行扩展就不能使用css的扩展方式了
这样就需要一层媒介,也就是fn
好处是
只会对外暴露一个$接口
将插件扩展统一到$.fn这个接口方便调用
所以我们可以这样写jq
let jq = function (selector) {
return new jq.fn.init(selector)
}
jq.fn={
css(){
alert('css')
},
html(){
alert('html')
}
}
let init = jq.fn.init = function (selector) {
let slice = Array.prototype.slice
let dom = slice.call(document.querySelectorAll(selector))
let len = dom ? dom.length : 0
for (let i = 0; i < len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ''
}
init.prototype=jq.fn
window.$ = jq
进行了简化
可见,虽然原型的使用看起来很简单,但是想要设计出来合理的使用方式,还是需要琢磨的。至于为啥jq非要赋值给init再来进行扩展,这个操作我就也不是很看得懂了。