stephen's blog

[object Object] object(s)
 

从jquery源码看无new构造

前言

无new构造是怎么一回事呢?我们知道,jquery中可以通过以下两种方式来构造对象:

1
2
3
4
5
// 无new
$('#id').context('hello world')
// 有new
var id = new $('#id')
id.context('hello world')

显然,我们用得最多就是第一种无new的构造方式,那这种方式内部又是怎么实现的呢?下面我们一步步来实现这样的效果。
js中函数是一等公民,我们在用oop形式编程的时候,很容易的会使用类来实例化对象:

1
2
3
4
5
6
7
8
9
10
11
12
// 构造函数
var cQuery = function(selector, contex){
}
cQuery.prototype = {
setName: function(name){
return this.name = name
},
name: 'stephen',
age: 20
}
var c = new cQuery()
c.age // 20

如果我们想通过无new来构造对象的话,我们第一反应可能是这样:

1
2
3
4
5
6
7
8
9
10
var cQuery = function(selector, contex){
return new cQuery()
}
cQuery.prototype = {
setName: function(name){
return this.name = name
},
name: 'stephen',
age: 20
}

我们会发现这样就陷入了一个无穷的循环之中,显然是不行的。我们其实可以把cQuery类当作一个工厂方法来创建实例,把这个方法放cQuery.prototye原型对象中:

1
2
3
4
5
6
7
8
9
10
11
12
13
var cQuery = function(selector, contex){
return new cQuery.prototype.init()
}
cQuery.prototype = {
init: function(){
return this
},
setName: function(name){
return this.name = name
},
name: 'stephen',
age: 20
}

看起来似乎解决了我们的问题,但是如果我们想在init使用this呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var cQuery = function(selector, contex){
return new cQuery.prototype.init()
}
cQuery.prototype = {
init: function(){
this.age = 18
return this
},
setName: function(name){
return this.name = name
},
name: 'stephen',
age: 20
}
cQuery().age //18
cQuery().name //undefined

显然,这时候由于是实例init()函数,所以this和原来cQuery的this作用域分离了,age会指向init中的age,而name由于init中未定义,所以为undefine。 这显然不是我们要的效果,那我们该解决呢? 其实很简单:

1
cQuery.prototype.init.prototype = cQuery.prototype

这便是最精妙的地方,将cQuery的prototype的属性赋给init的prototype,这样就保证了this虽然指向init,但仍然继承aQuery上的属性。
上面内容其实就是jquery中实现无new构造的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function(window, undefined) {
var
// ...
  jQuery = function(selector, context) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init(selector, context, rootjQuery);
},
jQuery.fn = jQuery.prototype = {
init: function(selector, context, rootjQuery) {
// ...
}
}
jQuery.fn.init.prototype = jQuery.fn;
})(window);

最后在谈谈underscore中的无new构造,放上源码注释:

1
2
3
4
5
6
7
8
9
10
var _ = function (obj) {
if (obj instanceof _) return obj;
// 如果Obj已经是underscore对象,则原样返回
if (!(this instanceof _)) return new _(obj);
// 采用这种写法即使没用new操作符也可以构造新undersocre对象
this._wrapped = obj;
// underscore对象有实例属性_wrapped,保存着obj(用户传入的待处理的数据)
};
root._ = _

其实也很简单,判断是否为underscore的对象,是就直接返回,不是就new一下(注意: 这里不会造成死循环的原因是因为第一个判断的存在 。