这篇文章主要介绍了jQuery2.0.3源码分析之core(一)整体架构,需要的朋友可以参考下

拜读一个开源框架,最想学到的就是设计的思想和实现的

废话不多说,jquery这么多年了分析都写烂了,老早以前就拜读过,

不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery又给扫一遍

我也不会照本宣科的翻译源码,结合自己的实际一起拜读吧!

github上最新是jquery-master,加入了AMD规范了,我就以官方最新2.0.3为准

整体架构

jQuery框架的核心就是从HTML文档中匹配元素并对其执行操作、

例如:


$().find().css()
$().hide().html('....').hide().

从上面的写法上至少可以发现2个问题

1. jQuery对象的构建方式

2 .jQuery方法的调用方式

分析一:jQuery的无new构建

JavaScript是函数式语言,函数可以实现类,类就是面向对象编程中最基本的概念


var aQuery = function(selector, context) {
  //构造函数
}
aQuery.prototype = {
  //原型
  name:function(){},
  age:function(){}
}

var a = new aQuery();

a.name();

这是常规的使用方法,显而易见jQuery不是这样玩的

jQuery没有使用new运行符将jQuery显示的实例化,还是直接调用其函数

按照jQuery的书写方式


$().ready()
$().noConflict()

要实现这样,那么jQuery就要看成一个类,那么$()应该是返回类的实例才对

所以把代码改一下:


var aQuery = function(selector, context) {
return new aQuery();
}
aQuery.prototype = {
  name:function(){},
  age:function(){}
}

通过new aQuery(),虽然返回的是一个实例,但是也能看出很明显的问题,死循环了!

那么如何返回一个正确的实例?

在javascript中实例this只跟原型有关系

那么可以把jQuery类当作一个工厂方法来创建实例,把这个方法放到jQuery.prototye原型中


var aQuery = function(selector, context) {
return  aQuery.prototype.init();
}
aQuery.prototype = {
  init:function(){
  return this;
  }
  name:function(){},
  age:function(){}
}

当执行aQuery() 返回的实例:

 

很明显aQuery()返回的是aQuery类的实例,那么在init中的this其实也是指向的aQuery类的实例

问题来了init的this指向的是aQuery类,如果把init函数也当作一个构造器,那么内部的this要如何处理?


var aQuery = function(selector, context) {
return  aQuery.prototype.init();
}
aQuery.prototype = {
  init: function() {
  this.age = 18
  return this;
  },
  name: function() {},
  age: 20
}

aQuery().age  //18

这样的情况下就出错了,因为this只是指向aQuery类的,所以需要设计出独立的作用域才行

jQuery框架分隔作用域的处理


jQuery = function( selector, context ) {
  // The jQuery object is actually just the init constructor 'enhanced'
  return new jQuery.fn.init( selector, context, rootjQuery );
  },

很明显通过实例init函数,每次都构建新的init实例对象,来分隔this,避免交互混淆

那么既然都不是同一个对象那么肯定又出现一个新的问题

例如:


var aQuery = function(selector, context) {
return  new aQuery.prototype.init();
}
aQuery.prototype = {
  init: function() {
  this.age = 18
  return this;
  },
  name: function() {},
  age: 20
}

//Uncaught TypeError: Object [object Object] has no method 'name'
console.log(aQuery().name())

抛出错误,无法找到这个方法,所以很明显new的init跟jquery类的this分离了

怎么访问jQuery类原型上的属性与方法?

做到既能隔离作用域还能使用jQuery原型对象的作用域呢,还能在返回实例中访问jQuery的原型对象?

实现的关键点


// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;

通过原型传递解决问题,把jQuery的原型传递给jQuery.prototype.init.prototype

换句话说jQuery的原型对象覆盖了init构造器的原型对象

因为是引用传递所以不需要担心这个循环引用的性能问题


var aQuery = function(selector, context) {
return  new aQuery.prototype.init();
}
aQuery.prototype = {
  init: function() {
  return this;
  },
  name: function() {
  return this.age
  },
  age: 20
}
aQuery.prototype.init.prototype = aQuery.prototype;
console.log(aQuery().name()) //20

百度借网友的一张图,方便直接理解:

fn解释下,其实这个fn没有什么特殊意思,只是jQuery.prototype的引用

 

分析二:链式调用

DOM链式调用的处理:

1.节约JS代码.

2.所返回的都是同一个对象,可以提高代码的效率

通过简单扩展原型方法并通过return this的形式来实现跨浏览器的链式调用。

利用JS下的简单工厂模式,来将所有对于同一个DOM对象的操作指定同一个实例。

这个原理就超简单了


aQuery().init().name()
分解
a = aQuery();
a.init()
a.name()

把代码分解一下,很明显实现链式的基本条件就是实例this的存在,并且是同一个


aQuery.prototype = {
  init: function() {
  return this;
  },
  name: function() {
  return this
  }
}

所以我们在需要链式的方法访问this就可以了,因为返回当前实例的this,从而又可以访问自己的原型了


aQuery.init().name()

优点:节省代码量,提高代码的效率,代码看起来更优雅

最糟糕的是所有对象的方法返回的都是对象本身,也就是说没有返回值,这不一定在任何环境下都适合。

Javascript是无阻塞语言,所以他不是没阻塞,而是不能阻塞,所以他需要通过事件来驱动,异步来完成一些本需要阻程的操作,这样处理只是同步链式,异步链式jquery从1.5开始就引入了Promise,jQuery.Deferred后期在讨论。

分析三:插件接口

jQuery的主体框架就是这样,但是根据一般设计者的习惯,如果要为jQuery或者jQuery prototype添加属性方法,同样如果要提供给开发者对方法的扩展,从封装的角度讲是不是应该提供一个接口才对,字面就能看懂是对函数扩展,而不是看上去直接修改prototype.友好的用户接口,

jQuery支持自己扩展属性,这个对外提供了一个接口,jQuery.fn.extend()来对对象增加方法

从jQuery的源码中可以看到,jQuery.extend和jQuery.fn.extend其实是同指向同一方法的不同引用


jQuery.extend = jQuery.fn.extend = function() {

jQuery.extend 对jQuery本身的属性和方法进行了扩展

jQuery.fn.extend 对jQuery.fn的属性和方法进行了扩展

通过extend()函数可以方便快速的扩展功能,不会jQuery的原型结构

jQuery.extend = jQuery.fn.extend = function(){...}; 这个是连等,也就是2个指向同一个函数,怎么会实现不同的功能呢?这就是this 力量了!

针对fn与jQuery其实是2个不同的对象,在之前有讲述:

  jQuery.extend 调用的时候,this是指向jQuery对象的(jQuery是函数,也是对象!),所以这里扩展在jQuery上。
  而jQuery.fn.extend 调用的时候,this指向fn对象,jQuery.fn 和jQuery.prototype指向同一对象,扩展fn就是扩展jQuery.prototype原型对象。
  这里增加的是原型方法,也就是对象方法了。所以jQuery的api中提供了以上2中扩展函数。

extend的实现


jQuery.extend = jQuery.fn.extend = function() {
  var src, copyIsArray, copy, name, options, clone,
  target = arguments[0] || {},  // 常见用法 jQuery.extend( obj1, obj2 ),此时,target为arguments[0]
  i = 1,
  length = arguments.length,
  deep = false;

  // Handle a deep copy situation
  if ( typeof target === "boolean" ) {  // 如果第一个参数为true,即 jQuery.extend( true, obj1, obj2 ); 的情况
  deep = target;  // 此时target是true
  target = arguments[1] || {};  // target改为 obj1
  // skip the boolean and the target
  i = 2;
  }

  // Handle case when target is a string or something (possible in deep copy)
  if ( typeof target !== "object" && !jQuery.isFunction(target) ) {  // 处理奇怪的情况,比如 jQuery.extend( 'hello' , {nick: 'casper})~~
  target = {};
  }

  // extend jQuery itself if only one argument is passed
  if ( length === i ) { // 处理这种情况 jQuery.extend(obj),或 jQuery.fn.extend( obj )
  target = this;  // jQuery.extend时,this指的是jQuery;jQuery.fn.extend时,this指的是jQuery.fn
  --i;
  }

  for ( ; i < length; i++ ) {
  // Only deal with non-null/undefined values
  if ( (options = arguments[ i ]) != null ) { // 比如 jQuery.extend( obj1, obj2, obj3, ojb4 ),options则为 obj2、obj3...
  // Extend the base object
  for ( name in options ) {
  src = target[ name ];
  copy = options[ name ];

  // Prevent never-ending loop
  if ( target === copy ) {  // 防止自引用,不赘述
  continue;
  }

  // Recurse if we're merging plain objects or arrays
  // 如果是深拷贝,且被拷贝的属性值本身是个对象
  if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
  if ( copyIsArray ) {  // 被拷贝的属性值是个数组
  copyIsArray = false;
  clone = src && jQuery.isArray(src) ? src : [];

  } else {  被拷贝的属性值是个plainObject,比如{ nick: 'casper' }
  clone = src && jQuery.isPlainObject(src) ? src : {};
  }

  // Never move original objects, clone them
  target[ name ] = jQuery.extend( deep, clone, copy );  // 递归~

  // Don't bring in undefined values
  } else if ( copy !== undefined ) {  // 浅拷贝,且属性值不为undefined
  target[ name ] = copy;
  }
  }
  }
  }

  // Return the modified object
  return target;

总结:

  通过new jQuery.fn.init() 构建一个新的对象,拥有init构造器的prototype原型对象的方法
  通过改变prorotype指针的指向,让这个新的对象也指向了jQuery类的原型prototype
  所以这样构建出来的对象就继续了jQuery.fn原型定义的所有方法了

最新资讯
彭博:腾讯音乐指导发售价在招股区间中位数附近

彭博:腾讯音乐指导发售

据彭博,腾讯音乐指导发售价在招股区间中位数附近。
高通苹果专利之争:谁都有软肋

高通苹果专利之争:谁都

12月10日,高通对外宣布,中国福建省福州中级人民法(tian)院(ping)授予
亚马逊加速推进自研芯片 英特尔领先地位岌岌可危

亚马逊加速推进自研芯

作为全球最大的在线零售商和最大的云计算公司,亚马逊正
Uber拟接入伦敦巴士及地铁数据 有野心打败谷歌地图

Uber拟接入伦敦巴士及

据知情人士透露,Uber试图将伦敦巴士和地铁的时间表整合
中国式支付崛起 支付宝国内用户数是美国人口2倍

中国式支付崛起 支付

现在已经有相当多的国家接受微信支付和支付宝,广泛程度
马斯克称特斯拉正测试自动驾驶功能

马斯克称特斯拉正测试

马斯克称特斯拉近期为其自动驾驶系统重写了神经网络,以
最新文章
详解mpvue小程序中怎么引入iconfont字体图标

详解mpvue小程序中怎

这篇文章主要介绍了详解mpvue小程序中怎么引入iconfon
JS如何获取地址栏的参数实例讲解

JS如何获取地址栏的参

本篇文章给大家带来了关于JS如何获取地址栏的参数实例
angularJs利用$scope处理升降序的方法

angularJs利用$scope

今天小编就为大家分享一篇angularJs利用$scope处理升
CKEditor4配置与开发详细中文说明文档

CKEditor4配置与开发

网上分享的CKEditor4中文说明很多都只是的部分使用方
angularjs获取到My97DatePicker选中的值方法

angularjs获取到My97D

今天小编就为大家分享一篇angularjs获取到My97DatePic
vue实现一个炫酷的日历组件

vue实现一个炫酷的日

公司业务新开了一个商家管理微信H5移动端项目,日历控件