• 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,如有兴趣敬请关注,主要一个码农的所看所思所想所叹,或扫描下方二维码关注:

  • 相关阅读:
    turtle库笔记
    使用turtle库绘制一个红色五角星图形‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪
    postgresql更新sequence的起始值
    es启动报错-系统设置
    mybatis批量update
    postgresql数据库连接数查询
    org.postgresql.util.PSQLException: 栏位索引超过许可范围:1,栏位数:0。
    postgresql创建SEQUENCE
    unzip解压所有zip格式
    jdk8中map的merge方法介绍
  • 原文地址:https://www.cnblogs.com/noah-wung/p/3985262.html
Copyright © 2020-2023  润新知