今天我们先来 聊聊 jQuery 中的 无 new 构造
写过 js 面向对象的同学知道,一般我们是这么来写的
//构造函数
function myjQuery () {
this.age = 20;
}
//方法挂载到原型上
myjQuery.prototype.say = function () {
console.log( 'my age is ' + this.age );
}
var myjQuery = new myjQuery(); // 实例化
console.log( myjQuery.age ); // 20
console.log( myjQuery.say() ); // my age is 20
这是一种比较常见的写法,然而 jQuery 并不是这么做的,回想一下,我们平时在用 jQuery 获取元素的时候,经常都是 $('#myId') , $('.myClass')
这种方式去调用的,难道说 jQuery 不用 new 去创建一个实例?
其实不然,jQuery 内部也是用 new 创建一个 jQuery 对象 , 只不过用的十分巧妙, 接下来我们来看看jQuery是怎么做到的!
在 jQuery 源码中有这么一段
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
}
这个就是 jQuery 的入口方法 , 它返回的 是一个 new jQuery.fn.init( selector, context );
我们知道, 用 new 一个对象 ,肯定是返回一个对象的实例,那么也就是返回的是jQuery.fn.init( selector, context )
的实例,那么这个 jQuery.fn
又是什么? 我们查看源码中有这么一段:
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
/***code***/
}
也就是说,jQuery.fn
就是 jQuery
构造函数的原型。
接着我们看看jQuery.fn.init()
这个方法,源码中可以找到是这么写的:
init = jQuery.fn.init = function( selector, context, root ) {
/****code****/
return this;
}
有的同学可能认为到这里就结束了,是吗?ok,我们来测试一下,我们把上面我们的代码改造跟 jQuery 一样结构,
var myjQuery = function () {
return new myjQuery.fn.init();
}
myjQuery.fn = myjQuery.prototype= {
init : function(){ //初始化
return this;
},
age : 20,
say : function(){
return this.age;
}
}
myjQuery().age; //'undefiend'
myjQuery().say();
// 'Uncaught TypeError: myjQuery(...).say is not a function'
什么?出错了,没错,得到的结果确实 令人意外 , 不过也是意料之中 , 我们稍加分析一下就知道了。
上面代码我们使用了return new myjQuery.fn.init();
返回的是 myjQuery 原型上的 init() 方法的实例 , 我们在发现在 init() 方法中返回了this ; 此时的 this 指向的是 myjQuery.fn.init的实例 ,
调用myjQuery() 得到的结果是 myjQuery.fn.init 的实例;
而在myjQuery.prototype.init内部中根本就没有 age 属性 和 say 方法, 所以此时通过这个结果访问不到 myjQuery.prototype 的属性 和方法的 , 因此上面得到的结果也就 不意外了。
可是我们用 jQuery 为什么没有出现这样的情况呢?原来我们还少了一句关键的代码, jQuery 中是这么写的:
init.prototype = jQuery.fn;
我们在上面就看到了 init = jQuery.fn.init
,所以说这句代码相当于
jQuery.fn.init.prototype = jQuery.fn;
这句话是关键,现在我们可以发现:
jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype;
通过原型传递来解决上面的问题,把jQuery的原型传递给
jQuery.prototype.init.prototype
也就是说 jQuery 的原型对象 覆盖了内部 init 构造器的原型对象。
这样new init 的出来的实例对象也就等价于new jQuery的实例对象,所以也就可以访问jQuery的原型方法了。
我们把这句话加上去,把上面的函数改写成:
var myjQuery = function () {
return new myjQuery.fn.init();
}
myjQuery.fn = myjQuery.prototype= {
init : function(){ //初始化
return this;
},
age : 20,
say : function(){
return this.age ;
}
}
myjQuery.prototype.init.prototype = myjQuery.prototype;
myjQuery().age; //20
myjQuery().say() //20
世界清静多了,不是么?
ok,这就是 jQuery 实现无 new 操作的 原理 ~
上面还有一点点东西,我们补充一下:
1. 我们看到 jQuery 对象的是通过原型中的 init 方法 return 回来的,如下:
return new jQuery.fn.init( selector, context );
也就是说,我们每次调用 jQuery 的时候去 new 一个新的对象 , 因此,我们在写代码的时候,如果频繁使用一个 jQuery 对象的话,我们不妨把它用一个变量存起来,可以避免每次去创建对象~
2. 上面 jQuery 原型上,我单独拿出来一句:
constructor: jQuery
jQuery 这么做是为了保证 constructor 的指向,防止构造函数指向错误,引起调用的问题。那么为什么会产生这样的问题呢?
//方式一
fn.prototype.say = function(){}
//方式二
fn.prototype = {
say : function() {}
}
jQuery 采用的是 第二种 方式,这种方式与第一种方式差别很大,第一种是给原型添加一个方法,这种方式是不会影响fn原型内部 constructor 的指向问题,而第二种方式是 重写原型 , 相当于原型覆盖,原来的原型已不复存在,所以需要重新定义 constructor 的指向,小伙伴们以后写代码的时候多注意一点~