jQuery重新封装DOM的分析过程

198 阅读5分钟

addClass()的实现

1.完整版思路

window.jQuery = function (selector) {
  const elements = document.querySelectorAll(selector);
  const api = {
    addClass: function (className) {
      //注意,传进来的选择器里面的元素可能不唯一,所以需要遍历,对每一个都进行addClass
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
      return undefined; //我达到了自己的需求:在某些元素里添加属性名。那么就已经完成了,所以不需要在return什么了
    },
  };
  return api;
};
//使用如下:
const api = jQuery(".container");
api.addClass("red");
//思考:怎样实现链式操作,即直接在addClass后面继续addClass?

2.优化思路 (一)

接上段代码的思考, 答:只要让addClass执行完后返回前面的api即可,再加上之前所学的this知识可得,这时的api即this 第一次优化后,代码如下

window.jQuery = function (selector) {
  const elements = document.querySelectorAll(selector);
  const api = {
    addClass: function (className) {
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
      return this; //为了能在addClass执行完后立马又能调用addClass
    },
  };
  return api;
};
//使用如下:
const api = jQuery(".container");
api.addClass("red").addClass("green");

3.优化思路 (二)

既然我在jQuery函数里构造了一个api对象,然后最后我又返回了这个api,那么我是不是可以直接跳过这个api的名字而直接return

window.jQuery = function (selector) {
  const elements = document.querySelectorAll(selector);
  return {
    addClass(className) {   //新语法可以简化
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
      return this;
    },
  };
};
const api = jQuery(".container");
api.addClass("red").addClass("green");
  1. 总结以上代码,使用到的知识:

a. 闭包。addClass函数使用了它这个函数外面的变量elements. ⅰ. 闭包变量,this 和elements

b. 链式操作的精髓:函数返回调用它的这个东西。 变成this.

c. this的理解,举例如下:

obj.fn(p1)
obj.fn.call(obj,p1)

find()的实现

注意点一:我要找一个元素,应该是在一个div里面找的,但是elements是整个document里面的元素。所以需要对每个document里面的元素再进行遍历一次 ,给elements2,这个是个伪数组,需要使用Array,from()转换成真数组

初步代码如下:

window.jQuery = function (selector) {
  const elements = document.querySelectorAll(selector);
  return {
    find(selector) {
      let array = [];
      for (let i = 0; i < elements.length; i++) {
        const elements2 =Array.from(elements[i].querySelectorAll(selector));
        array = array.concat(elements2);
      }
      return array;   
        //这里就有个问题,返回的是数组,数组就没有addClass,也就没有链式操作了
        //若改成return this,这个this是pai这个对象,这个对象是用来操作elements的
        //所以不能操作array
    },
  };
};

1.优化思路一(错误的)

window.jQuery = function (selector) {
  const elements = document.querySelectorAll(selector);
  return {
    find(selector) {
      let array = [];
      for (let i = 0; i < elements.length; i++) {
        const elements2 =Array.from(elements[i].querySelectorAll(selector));
        array = array.concat(elements2);
      }
      return this;
    },
  };
};
//使用:
const x1 = jQuery('.test').find('.child')
x1.addClass("red")  //这个用了this的find,使用addClass之后是加到了test上面而不是child上面,而我们的需求就是加到child上面

2.优化思路二(重新封装上面代码,然后返得到一个新的api)

为了使得不影响elements,我们需要得到一个新的api用新的api来操作新的elements。这个新的api用jQuery来构造,这时候jQuery传入的参数就得做判断,如果传进来的是数组,那么我们就将数组传给elements,如果传进来的是选择器,那么我们就直接querySelectorAll查找所有元素。 这样就有不同的elements来供新api来操作,旧api不会受到影响。 优化后的代码如下:

window.jQuery = function (selectorOrArray) {
  let elements;
  if (typeof selectorOrArray === "string") {    //重载,根据不同参数做不同操作
    elements = document.querySelectorAll(selectorOrArray);
  } else if (selectorOrArray instanceof Array) {
    elements = selectorOrArray;
  }
  return {
    addClass: function (className) {
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
      return this;
    },
    find(selector) {
      let array = [];
      for (let i = 0; i < elements.length; i++) {
        const elements2 = Array.from(elements[i].querySelectorAll(selector));
        array = array.concat(elements2);
      }
      const newApi = jQuery(array);   
      return newApi;//这里可以简写,去掉中间这个newApi的变量名
    },
  };
};

总结以上代码,使用到知识如下:

a. 重载:判断传入的参数,做不同的操作

b. api操作elements,新方法需要返回新api来操作新elements

end()的实现

精髓在第22、27和9行 22行来历:find()函数里面的newApi是新的api,在使用find函数return之前,就是旧的api这个旧的api就是调用find时的api 【api1.find()】find里面的this就是我这里所说的旧的api1。记录这个return之前的this,就是上一步嘛,就是旧的api,给了数组。 27行来历:使用end函数的时候直接返回到这个旧的api,也就是使用find之前的位置。 这个时候运行会报错,是因为oldApi只给了数组array,没有给总的return的这个对象,所以end函数访问的时候访问不到, 9行来历:所以需要给这个对象添加一个oldApi的属性,值就是selectorOrArray.oldApi

window.jQuery = function (selectorOrArray) {
  let elements;
  if (typeof selectorOrArray === "string") {
    elements = document.querySelectorAll(selectorOrArray);
  } else if (selectorOrArray instanceof Array) {
    elements = selectorOrArray;
  }
  return {
    oldApi: selectorOrArray.oldApi,
    addClass(className) {
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
      return this;
    },
    find(selector) {
      let array = [];
      for (let i = 0; i < elements.length; i++) {
        const elements2 = Array.from(elements[i].querySelectorAll(selector));
        array = array.concat(elements2);
      }
      array.oldApi = this;
      const newApi = jQuery(array);
      return newApi;
    },
    end() {
      return this.oldApi;
    },
  };
};

each()的实现 (回顾数组的foreach)

each(fn) {
      for (let i = 0; i < elements.length; i++) {
        fn.call(null, elements[i], i);
      }
      return this;
}
//使用:
const x = jQuery("#test").find(".child");
x.each((x, i) => console.log(x, i));

print()的实现

print() {
      console.log(elements);
}

parent()的实现

parent() {
      const array = [];
      this.each((node) => {
        if (array.indexOf(node.parentNode) === -1) {
          array.push(node.parentNode);
        }
      });
      return jQuery(array);
}

children()的实现

在push中直接node.children的话是直接得到一个数组里面分开的,如下图:

使用数组前面加 ...可以展开放在一个数组里

children() {
      const array = [];
      this.each((node) => {
        array.push(...node.children);
        //等价于:array.push(node.children[0],node.children[1],node.children[2])
        //这样写不知道要写多少个,所以有了...这个符号用来展开数组里面的数组
      });
      return jQuery(array);
}
//使用:
const x = jQuery("#test");
x.children().print();

得到结果:

siblings()的实现 找兄弟,但是要排除自己

siblings() {
      const array = [];
      this.each((node) => {
        const siblings2 = Array.from(node.parentNode.children).filter(
          (n) => n !== node
        );
        array.push(...siblings2);
      });
      return jQuery(array);
}