zoukankan      html  css  js  c++  java
  • jQuery内核详解与实践读书笔记1:原型技术分解1

      一直以来都有研究一下jQuery源代码的想法,但是每次看到jQuery几千行的代码,头就大了,没有一点头绪,也不知道从哪里开始。昨天去图书馆无意间发现了这本《jQuery内核详解和实践》,翻看了一下里面的内容,这正是我寻觅多时剖析jQuery源码的好书。

      废话不多说,直入正题吧。第一章介绍了一下jQuery的起步和一些历史故事,没什么重要内容。这里直接进入第二章,jQuery技术解密,从这一章开始就全部是干货了。这一章主要分四部分:jQuery原型技术分解,破解jQuery选择器接口,解析jQuery选择器引擎Sizzle,类数组。

      jQuery原型技术分解主要就是从0开始一步步搭建一个简易的jQuery框架,讲述了jQuery框架的搭建过程,书中主要分成了9个步骤,最后形成一个jQuery框架的雏形。

    1. 起源--原型继承

    模仿jQuery框架源码,添加两个成员,一个原型属性jquery,一个原型方法size(),源代码如下:

    1 var $ = jQuery = function() {};
    2 jQuery.fn = jQuery.prototype = {
    3     jquery : "1.3.2",         //原型属性
    4     size : function() {       //原型方法
    5        return this.length;
    6     }
    7 };
    View Code

    此时这个框架最基本的样子就孕育出来了。这几行代码都很简单,但却是整个框架的基础。

    2. 生命--返回实例

    如果用上面的代码时,得到一个jQuery的对象是需要new出来的,但是我们使用的jQuery并不是通过new来得到jQuery对象的,而是通过$()得到的。jQuery是如何实现$()的方式进行函数的调用?

    我们应该把jQuery看做是一个类,同时也应该把它视为一个普通的函数,并让这个函数的返回值为jQuery类的实例。但是如果直接在jQuery函数中返回一个new出来的jQuery实例,会造成死循环,导致内存外溢。

    考虑:在创建jQuery类实例时,this关键字就是指向对象实例的,而且不论是在jQuery.prototype中原型属性还是方法,this关键字总是指向类的实例。

    结论:在jQuery中使用一个工厂方法来创建一个实例,把这个方法放在jQuery.prototype 原型对象中,然后在jQuery()函数中返回这个原型方法的调用。

    这样就可以将1中的代码修改成下面的代码:

     1 var $ = jQuery = function() {
     2   return jQuery.fn.init();    //调用原型方法init()
     3 };
     4 jQuery.fn = jQuery.prototype = {
     5     init : function() {      //在初始化原型方法中返回实例的引用
     6        return this;
     7     },
     8     jquery : "1.3.2",         //原型属性
     9     size : function() {       //原型方法
    10        return this.length;
    11     }
    12 }; 
    View Code

    3. 学步--分隔作用域

    如果按照2的代码,我们又会出现问题。如下代码:

     1 var $ = jQuery = function() {
     2   return jQuery.fn.init();    //调用原型方法init()
     3 };
     4 jQuery.fn = jQuery.prototype = {
     5     init : function() {      //在初始化原型方法中返回实例的引用
     6        this.length = 0;
     7        this.test = function() {
     8          return this.length;
     9        };
    10        return this;
    11     },
    12     jquery : "1.3.2",         //原型属性
    13     length : 1,
    14     size : function() {       //原型方法
    15        return this.length;
    16     }
    17 }; 
    View Code

    上述代码中jQuery原型对象中包含一个length属性,同时init()从一个普通函数变成了构造器,它也包含一个length属性和一个test()方法。this关键字引用了init()函数作用域所在的对象,此时它访问length属性时,返回0.而this关键字也能够访问上一级对象jQuery.fn对象的作用域,所以$().jquery返回"1.3.2"。但是调用$().size()方法时,返回的是0,而不是1?

    解决方法:jQuery框架是通过下面的方式调用init()初始化构造函数,达到隔离作用域的目的:

    1 var $ = jQuery = function() {
    2   return new jQuery.fn.init();    //实例化init初始化类型,分隔作用域
    3 };
    View Code

    这样就可以把init()构造器中的this和jQuery.fn对象中的this关键字隔离开来,避免相互混淆。
    此时源代码就变成如下:

     1 var $ = jQuery = function() {
     2   return new jQuery.fn.init();    //实例化init初始化类型,分隔作用域
     3 };
     4 jQuery.fn = jQuery.prototype = {
     5     init : function() {      //在初始化原型方法中返回实例的引用
     6        this.length = 0;
     7        this.test = function() {
     8          return this.length;
     9        };
    10        return this;
    11     },
    12     jquery : "1.3.2",         //原型属性
    13     length : 1,
    14     size : function() {       //原型方法
    15        return this.length;
    16     }
    17 }; 
    View Code

    但是,这种方式也会带来另一个问题:无法访问jQuery.fn对象的属性或方法。

    4. 生长--跨域访问

    上一节抛出了一个问题:无法访问jQuery.fn对象的属性或方法,如何解决?

    方法:通过原型传递,jQuery框架把jQuery.fn传递给jQuery.fn.init.prototype,也就是说用jQuery的原型对象覆盖init构造器的原型对象,从而实现跨域访问,其源代码如下:

     1 var $ = jQuery = function() {
     2   return new jQuery.fn.init();    //实例化init初始化类型,分隔作用域
     3 };
     4 jQuery.fn = jQuery.prototype = {
     5     init : function() {      //在初始化原型方法中返回实例的引用
     6        this.length = 0;
     7        this.test = function() {
     8          return this.length;
     9        };
    10        return this;
    11     },
    12     jquery : "1.3.2",         //原型属性
    13     length : 1,
    14     size : function() {       //原型方法
    15        return this.length;
    16     }
    17 };
    18 jQuery.fn.init.prototype = jQuery.fn; //使用jQuery的原型对象覆盖init的原型对象
    View Code

    new jQuery.fn.init()创建的新对象拥有init构造器的prototype原型对象的方法,通过改变prototype指针的指向,使其指向jQuery类的prototype,这样创建出来的对象就继承了jQuery.fn原型对象定义的方法。

    5. 成熟--选择器

    jQuery函数包含两个参数selector和context,其中selector表示选择器,而context表示的内容范围,它表示一个DOM元素。在此,为了简化操作,假设选择器的类型仅限定为标签选择器,其实现代码如下:

     1 var $ = jQuery = function(selector, context) {       //定义类
     2   return new jQuery.fn.init(selector, context);    //返回选择器的实例
     3 };
     4 jQuery.fn = jQuery.prototype = {                  //jQuery类的原型对象
     5     init : function(selector, context) {      //定义选择器构造器
     6        selector = selector || document;      //设置默认值为document
     7        context = context || document;        //设置默认值为document
     8        if(selector.nodeType) {               //如果选择符为节点对象
     9          this[0] = selector;               //把参数节点传递给实例对象的数组
    10          this.length = 1;                  //并设置实例对象的length属性,定义包含的元素个数
    11          this.context = selector;          //设置实例的属性,返回选择范围
    12          return this;                      //返回当前实例
    13        }
    14        if(typeof selector === "string") {                    //如果选择符是字符串
    15          var e = context.getElementsByTagName(selector);   //获取指定名称的元素
    16          for(var i=0; i<e.length; i++) {                   //遍历元素集合,并把所有元素填入到当前实例数组中
    17            this[i] = e[i];
    18          }
    19          this.length = e.length;                          //设置实例的length属性,即定义包含的元素个数
    20          this.context = context;                          //设置实例的属性,返回选择范围
    21          return this;                                     //返回当前实例
    22        } else {
    23          this.length = 0;                  //否则,设置实例的length属性值为0
    24          this.context = context;           //设置实例的属性,返回选择范围
    25          return this;                      //返回当前实例
    26        }
    27     },
    28     jquery : "1.3.2",         //原型属性
    29     size : function() {       //原型方法
    30        return this.length;
    31     }
    32 };
    33 jQuery.fn.init.prototype = jQuery.fn; //使用jQuery的原型对象覆盖init的原型对象 
    View Code

    这里就实现了一个最简单的选择器了,当然jQuery框架中的选择器比这里的要复杂的多,这里只是为了搭建一个jQuery框架的最简单形式,以后再收入去研究它的选择器。

    6. 延续--迭代器

    在jQuery框架中,jQuery对象是一个比较特殊的对象,具有多重身份,可以分解如下:

    第一, jQuery对象是一个数组集合,它不是一个个具体对象。因此,无法直接使用JavaScript的方法来操作它。

    第二, jQuery对象实际上就是一个普通的对象,因为它是通过new运算符创建的一个新的实例对象。它可以继承原型方法或属性,同样也拥有Object类型的方法和属性。

    第三, jQuery对象包含数组特性,因为它赋值了数组元素,以数组结构存储返回的数据。可以以JavaScript的概念理解jQuery对象,jQuery对象就是对象和数组的混合体,但是它不拥有数组的方法,因为它的数组结构是人为附加的,也就是说它不是Array类型数据,而是Object类型数据。

    第四, jQuery对象包含的数据都是DOM元素,是通过数组形式存储的,即通过jQuery[n]形式获取。同时jQuery对象又定义了几个模仿Array基本特性的属性,如length等

    所以,jQuery对象是不允许直接操作的,只有分别读取它包含的每一个DOM元素,才能够实现各种操作,如插入,删除,嵌套,赋值和读写DOM元素属性等。

    如何实现直接操作jQuery对象中的DOM元素呢?例如$("div").html()

    jQuery定义了一个工具函数each(),利用这个工具函数可以遍历jQuery对象中所有的DOM元素,并把需要操作的内存封装到一个回调函数中,然后通过在每个DOM元素上调用这个回调函数即可。实现代码如下:

     1 var $ = jQuery = function(selector, context) {       //定义类
     2   return new jQuery.fn.init(selector, context);    //返回选择器的实例
     3 };
     4 jQuery.fn = jQuery.prototype = {                  //jQuery类的原型对象
     5     init : function(selector, context) {      //定义选择器构造器
     6        selector = selector || document;      //设置默认值为document
     7        context = context || document;        //设置默认值为document
     8        if(selector.nodeType) {               //如果选择符为节点对象
     9          this[0] = selector;               //把参数节点传递给实例对象的数组
    10          this.length = 1;                  //并设置实例对象的length属性,定义包含的元素个数
    11          this.context = selector;          //设置实例的属性,返回选择范围
    12          return this;                      //返回当前实例
    13        }
    14        if(typeof selector === "string") {                    //如果选择符是字符串
    15          var e = context.getElementsByTagName(selector);   //获取指定名称的元素
    16          for(var i=0; i<e.length; i++) {                   //遍历元素集合,并把所有元素填入到当前实例数组中
    17            this[i] = e[i];
    18          }
    19          this.length = e.length;                          //设置实例的length属性,即定义包含的元素个数
    20          this.context = context;                          //设置实例的属性,返回选择范围
    21          return this;                                     //返回当前实例
    22        } else {
    23          this.length = 0;                  //否则,设置实例的length属性值为0
    24          this.context = context;           //设置实例的属性,返回选择范围
    25          return this;                      //返回当前实例
    26        }
    27     },
    28     jquery : "1.3.2",         //原型属性
    29     size : function() {       //原型方法
    30        return this.length;
    31     },
    32     
    33     //定义jQuery对象方法
    34     html : function(val) {                   //模仿jQuery框架中的html()方法,为匹配的每一个DOM元素插入html代码
    35        jQuery.each(this, function(val) {    //调用jQuery.each()工具函数,为每一个DOM元素执行回调函数
    36          this.innerHTML = val;
    37        }, val);
    38     }
    39 };
    40 jQuery.fn.init.prototype = jQuery.fn; //使用jQuery的原型对象覆盖init的原型对象
    41 
    42 //扩展jQuery工具函数
    43 jQuery.each = function(object, callback, args) {
    44   for(var i=0; i<object.length; i++) {
    45     callback.call(object[i], args);
    46   }
    47   return object;
    48 };
    View Code

    注意:在上面的代码中,each()函数的当前作用对象是jQuery对象,故this指向当前jQuery对象,即this表示一个集合对象;而在html()方法中,由于each()函数是在指定DOM元素上执行的,所以该函数内的this指针指向的是当前DOM元素对象,即this表示一个元素。

    以上定义的each()工具函数比较简单,适应能力很有限。在jQuery框架中,它封装的each()函数功能强大很多,具体代码如下:

     1 var $ = jQuery = function(selector, context) {       //定义类
     2     return new jQuery.fn.init(selector, context);    //返回选择器的实例
     3 };
     4 jQuery.fn = jQuery.prototype = {                  //jQuery类的原型对象
     5         init : function(selector, context) {      //定义选择器构造器
     6             selector = selector || document;      //设置默认值为document
     7             context = context || document;        //设置默认值为document
     8             if(selector.nodeType) {               //如果选择符为节点对象
     9                 this[0] = selector;               //把参数节点传递给实例对象的数组
    10                 this.length = 1;                  //并设置实例对象的length属性,定义包含的元素个数
    11                 this.context = selector;          //设置实例的属性,返回选择范围
    12                 return this;                      //返回当前实例
    13             }
    14             if(typeof selector === "string") {                    //如果选择符是字符串
    15                 var e = context.getElementsByTagName(selector);   //获取指定名称的元素
    16                 for(var i=0; i<e.length; i++) {                   //遍历元素集合,并把所有元素填入到当前实例数组中
    17                     this[i] = e[i];
    18                 }
    19                 this.length = e.length;                          //设置实例的length属性,即定义包含的元素个数
    20                 this.context = context;                          //设置实例的属性,返回选择范围
    21                 return this;                                     //返回当前实例
    22             } else {
    23                 this.length = 0;                  //否则,设置实例的length属性值为0
    24                 this.context = context;           //设置实例的属性,返回选择范围
    25                 return this;                      //返回当前实例
    26             }
    27         },
    28         jquery : "1.3.2",         //原型属性
    29         size : function() {       //原型方法
    30             return this.length;
    31         },
    32         
    33         //定义jQuery对象方法
    34         html : function(value) {                   
    35             return value === undefined ? 
    36                     (this[0] ? 
    37                             this[0].innerHTML.repalce(/ jQueryd+="(?:d+|null)"/g, "") :
    38                                 null) : 
    39                     this.empty().append(value);
    40         }
    41 };
    42 jQuery.fn.init.prototype = jQuery.fn; //使用jQuery的原型对象覆盖init的原型对象
    43 
    44 //扩展jQuery工具函数
    45 jQuery.extend({
    46     //参数说明:object表示jQuery对象,callback表示回调函数,args回调函数的参数数组
    47     each : function(object, callback, args) {
    48         var name, i = 0, length = object.length;
    49         if(args) {//如果存在回调函数的参数数组
    50             if(length === undefined) {//如果object不是jQuery对象
    51                 for(name in object) {//遍历object的属性
    52                     if(callback.apply(object[name], args) === false) {//在对象上调用回调函数
    53                         break;//如果回调函数返回值为false,则跳出循环
    54                     }
    55                 }
    56             } else {//如果object是jQuery对象
    57                 for( ; i< length; ) { //遍历jQuery对象数组
    58                     if(callback.apply(object[i++], args) === false) { //在对象上调用回调函数
    59                         break;//如果回调函数返回值为false,则跳出循环
    60                     }
    61                 }
    62             }
    63         } else {
    64             if(length === undefined) {//如果object不是jQuery对象
    65                 for(name in object) {//遍历object对象
    66                     if(callback.call(object[name], name, object[name]) === false) {//在对象上调用回调函数
    67                         break;//如果回调函数返回值为false,则跳出循环
    68                     }
    69                 }
    70             } else {//如果object是jQuery对象
    71                 //遍历jQuery对象数组,并在对象上调用回调函数
    72                 for(var value=object[0]; i<length && callback.call(value, i, value) !== false; value=object[i++]) {}
    73             }
    74         }
    75         return object;//返回jQuery对象
    76     }
    77 });
    View Code

    同时jQuery框架定义的html()方法包含的功能比较多,它不仅可以插入HTML源代码,还可以返回匹配元素包含的HTML源代码,故使用了一个条件结构分别进行处理。首先,判断参数是否为空,如果为空,则表示获取匹配元素中第一个元素包含的HTML源代码,此时返回该innerHTML的值。如果不为空,则先清空匹配元素中每个元素包含的内容,并使用append()方法插入HTML源代码。

    好了,暂时只看到了这里,下次把剩下的三步完成。

    个人微信公众号:programmlife,如有兴趣敬请关注,主要一个码农的所看所思所想所叹,或扫描下方二维码关注:

  • 相关阅读:
    IXmlSerializable With WCFData Transfer in Service Contracts
    Difference Between XmlSerialization and BinarySerialization
    Using XmlSerializer (using Attributes like XmlElement , XmlAttribute etc ) Data Transfer in Service Contracts
    Introducing XML Serialization
    Version Tolerant Serialization
    Which binding is bestWCF Bindings
    Data Transfer in Service Contracts
    DataContract KnownTypeData Transfer in Service Contracts
    Using the Message ClassData Transfer in Service Contracts
    DataContract POCO SupportData Transfer in Service Contracts
  • 原文地址:https://www.cnblogs.com/noah-wung/p/3985262.html
Copyright © 2011-2022 走看看