jQuery源码阅读笔记(9)

属性节点与属性

属性节点只设置在DOM元素上,而属性则是对象通用,有四个方法

  • attr(name, value) – 查询 | 替换 属性节点值
  • removeAttr(name) – 移除指定属性节点
  • prop(name, value) – 查询 | 替换 属性值
  • removeProp(name) – 移除指定属性

jQuery在这点上有点坏坏的,如果给一个DOM元素,使用prop()添加的属性名是class时,它会自动替换成className

attr(name[, value])
  • 概述:查询 | 替换 属性节点值

  • 解析:

    • 没有参数时会直接报错
    • 只有一个参数时
      • 是对象参数,则拆分键值对,设置为属性节点和属性节点值
      • 是字符串,则返回第一个匹配元素对应的属性节点值
    • 而第二参数可选属性值和函数,依然按照遍历法
  • 实现:

    attr(name, value) {
      if (!arguments.length) {
        return;
      }
      if (arguments.length === 1) {
        if (type(name) === "object") {
          return this.each(function() {
            for (const k in name) {
              this.setAttribute(k, name[k]);
            }
          });
        } else {
          return this[0].getAttribute(name);
        }
      } else {
        if (isFunction(value)) {
          return this.each(function(index) {
            const data = value.call(this, index, this.getAttribute(name));
            if (typeof data !== undefined) {
              this.setAttribute(name, data);
            }
          });
        }
        return this.each(function() {
          this.setAttribute(name, value);
        });
      }
    }
    
removeAttr(name)
  • 概述:移除属性节点

  • 实现:

    removeAttr(name) {
      return this.each(function() {
        this.removeAttribute(name);
      });
    }
    
prop(name[, value])
  • 概述:查询 | 替换 属性值

  • 解析:

    • 对于参数的处理和attr是一致的,直接过
  • 实现:

    prop(name, value) {
      if (!arguments.length) {
        return;
      }
      //一个参数时检查一下情况
      if (arguments.length === 1) {
        if (type(name) === "object") {
          return this.each(function() {
            for (const k in name) {
              this[k] = name[k];
            }
          });
        } else {
          return this[0][name];
        }
      } else {
        //对于函数形式
        if (isFunction(value)) {
          return this.each(function(index) {
            const data = value.call(this, index, this[name]);
            if (typeof data !== undefined) {
              this[name] = data;
            }
          });
        } else {
          return this.each(function() {
            this[name] = value;
          });
        }
      }
    }
    
removeProp(name)
  • 概述:移除属性

  • 实现:

    removeProp(name) {
      return this.each(function() {
        delete this[name];
      });
    }
    

事件处理

事实上,在实现事件函数前,我觉得不会很难吧,像click(),不就是把onclick()给简写一下而已

不过后续发现,并不能使用onclick这个属性,因为需要存在多个事件,需要更专业的addEventListener()

而后还需要移除事件,事实上也有对应的removeEventListener(),不过它需要相应的引用值,像匿名函数是不行的。可对应off()函数是一键移除所有事件,所以所有的函数都需要自己做一个存档保管

再者,可以使用选择器,过滤可以触发事件的后代元素,也就是委派

当然,只需要关注on()函数的实现,其他事件都只是简写形式

过程当中会使用递归,所以先在外部书写,后续在原型当中调用

  • 参数

    • 参数最多可以接受六个

      • ele – 元素
      • type – 事件类型
      • selector – 过滤选择器
      • data – 绑定数据
      • fn – 触发的回调函数
      • one – 是否触发一次后就移除事件,提供给one()函数的绿道
    • 参数比较多,主要关注对外开放的四个

    • type可以是一个对象,将触发的事件类型和事件集合在一起写上,主要用于一次绑定多个事件

    • 字符串形式下也可以绑定多个事件,多个类型中间使用空格隔开

    • 回调函数会提供相应的事件对象参数,如果填写了data,那么data会绑定至事件对象上

    • 触发函数,至少要有事件类型回调函数,以下是对于参数的处理部分

      const on = function(ele, types, selector, data, fn, one) {
        if (typeof types === "object") {
          //对于第二参数非字符串时
          if (!isString(selector)) {
            [selector, data] = [null, selector];
          }
          //遍历这个对象,无视函数参数位置
          $.each(types, (key, value) => {
            on(ele, key, selector, data, value, one);
          });
          return ele;
        }
        //对于非对象时,那就只有字符串状态了
        //除了对象情况,分配参数
        if (data == null && fn == null) {
          [fn, selector] = [selector, fn];
        } else if (fn == null) {
          if (isString(selector)) {
            [selector, data, fn] = [selector, null, data];
          } else {
            [selector, data, fn] = [null, selector, data];
          }
        }
        //...
      };
      
  • 事件存储

    • 后续要移除事件,因此对于匿名函数也需要进行存储,原生并不提供一键清空事件

    • 对于这方面的处理,jQuery有一套完整的系统,不过过于复杂,此处选择在对应元素上开辟一个属性,将相应事件存入当中,取名为_eventsCache,初始化为对象

    • 而不同类型的事件,需要在此对象上再开辟一个数组空间,存储多个事件

  • 委托

    • 因为有委托的存在,在触发所填函数前,需要进行条件判断
    • 对此需要存储两个函数,一个是自己所填写的函数,另一个是委托判断函数
    • 填写的函数在委托判断函数当中,最后绑定的函数也是委托判断函数
    • 填写的函数除了触发之外,还要与事件移除函数对接,当对应时,移除掉相应的委托判断函数

将两点结合

//...参数判断之下
return ele.each(function() {
  //type有可能有多个类型存在,需要正则分割
  $.each(types.split(/\s+/), (_i, type) => {
    //第一次使用事件监听的元素,给其赋值
    if (!this._eventsCache) {
      this._eventsCache = {};
    }
    //需要为对应类型的事件再开辟一个数组空间
    if (!this._eventsCache[type]) {
      this._eventsCache[type] = [];
    }
    //执行的回调函数,也就是委托判断函数
    const callback = function(e) {
      //在事件上绑定data参数
      e.data = data;
      //如果有选择器参数
      if (selector) {
        //将选择器拼接后搜寻
        const elems = document.querySelectorAll(this.localName + " " + selector);
        //根据path来往上飘
        for (const v of e.path) {
          //当遍历到自身,就该结束了
          if (v === this) {
            break;
          } else {
            //遍历寻找的元素,当符合条件时,修改函数this指向并执行
            for (const target of elems) {
              if (target === v) {
                fn.call(target, e);
                if (one) {
                  $(this).off(type, fn);
                }
              }
            }
          }
        }
      } else {
        fn.call(this, e);
        if (one) {
          $(this).off(type, fn);
        }
      }
    };
    //将两个函数放入一个对象当中,并塞入数组
    this._eventsCache[type].push({ fn, callback });
    //绑定事件监听
    this.addEventListener(type, callback);
  });
});
//...

对于当中的选择器拼接,事实上我回看的时候也感觉挺怪的,这难道不能直接写成这样?

const elems1 = document.querySelectorAll(this.localName + " " + selector);
const elems2 = this.querySelectorAll(selector);

关于这一点,回看核心函数处理吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值