21. js的设计思想

278 阅读4分钟

本博客参考: 阮一峰<<jQuery设计思想>>www.ruanyifeng.com/blog/2011/0….

不同在于: 本文增加了 jQuery的原理的讲解, 同时会附上简化版jQuery的源码

21.1 jQuery 如何获取元素

jQuery的基本设计思想和主要用法,就是**"选择某个网页元素,然后对其进行某种操作"**。这是它区别于其他Javascript库的根本特点。

使用jQuery的第一步,往往就是将一个选择表达式,放进构造函数jQuery()(简写为$),然后得到被选中的元素。

选择表达式可以是CSS选择器

  $(document) //选择整个文档对象

  $('#myId') //选择ID为myId的网页元素

  $('div.myClass') // 选择class为myClass的div元素

  $('input[name=first]') // 选择name属性等于first的input元素

注意:jquery 查找不返回html元素, 而是返回一个可以操作元素的对象, 又称jQuery对象,


jQuery对象和dom元素对象有什么不同?

dom对象, jQuery对象, 都是对象, 区别在于能使用的api不同.

习惯上, 两种命名也有区别如下

elDiv是一个Dom对象. $div是一个jQuery对象.


返回对象有什么好处呢?

答案: 链式操作

21.2 jQuery 的链式操作是怎样的?

jQuery设计思想之三,就是最终选中网页元素以后,可以对它进行一系列操作,并且所有操作可以连接在一起,以链条的形式写出来,比如:

  $('div').find('h3').eq(2).html('Hello');

分解开来,就是下面这样:

  $('div') //找到div元素

   .find('h3') //选择其中的h3元素

   .eq(2) //选择第3个h3元素

   .html('Hello'); //将它的内容改为Hello

这是jQuery最令人称道、最方便的特点。它的原理在于每一步的jQuery操作,返回的都是一个jQuery对象,所以不同操作可以连在一起。

jQuery还提供了.end()方法,使得结果集可以后退一步:

  $('div')

   .find('h3')

   .eq(2)

   .html('Hello')

   .end() //退回到选中所有的h3元素的那一步

   .eq(0) //选中第一个h3元素

   .html('World'); //将它的内容改为World

例如$('#test').find('.child').addClass('red') 的源码如下:

window.jQuery = function(selector){
    let elements
    if(Array.isArray(selector)){
        elements = selector
    }else{
        elements = document.querySelectorAll(selector)
    }
    return {

        addClass(value){
            for(let i=0;i<elements.length;i++){  //踩坑: elements前不需要加this
                elements[i].classList.add(value)
            }
            return this  //请注意this是一个对象
        },
        find(selector){
            let arr = []
            for(let i=0;i<elements.length;i++){
                arr = arr.concat(Array.from(elements[i].querySelectorAll(selector)))  //踩坑: 这里是一个伪数组, 需要转化一下
            }
            return jQuery(arr)  //这里也是一个对象
        }
    }
}

不断的返回对象, 我就可以不断的接链式函数


那jQuery是不是一个构造函数?

是也不是.这就是jQuery的创新之处

  • 不是的原因:jQuery 不需要new
  • 是的原因: jQuery的确返回了一个新对象

如果我得到的是数组, 怎么进行链式操作?

通过api 得到一些数组, 但是没法链式操作, 那就再封装成新的对象

如下面的源码:

        find(selector){
            let arr = []
            for(let i=0;i<elements.length;i++){
                arr = arr.concat(Array.from(elements[i].querySelectorAll(selector)))  //踩坑: 这里是一个伪数组, 需要转化一下
            }
            return jQuery(arr)  //jQuery就用来再封装成对象
        }

怎么返回到上一层对象?

end() 回到上一次api

21.3 jQuery 如何移动元素

jQuery设计思想之五,就是提供两组方法,来操作元素在网页中的位置移动。一组方法是直接移动该元素,另一组方法是移动其他元素,使得目标元素达到我们想要的位置。

假定我们选中了一个div元素,需要把它移动到p元素后面。

第一种方法是使用.insertAfter(),把div元素移动p元素后面:

  $('div').insertAfter($('p'));

第二种方法是使用.after(),把p元素加到div元素前面:

  $('p').after($('div'));

表面上看,这两种方法的效果是一样的,唯一的不同似乎只是操作视角的不同。但是实际上,它们有一个重大差别,那就是返回的元素不一样。第一种方法返回div元素,第二种方法返回p元素。你可以根据需要,选择到底使用哪一种方法。

使用这种模式的操作方法,一共有四对:

  .insertAfter().after():在现存元素的外部,从后面插入元素

  .insertBefore().before():在现存元素的外部,从前面插入元素

  .appendTo().append():在现存元素的内部,从后面插入元素

  .prependTo().prepend():在现存元素的内部,从前面插入元素

21.4 jQuery 如何修改元素的属性

操作网页元素,最常见的需求是取得它们的值,或者对它们进行赋值。

jQuery设计思想之四,就是使用同一个函数,来完成取值(getter)和赋值(setter),即"取值器"与"赋值器"合一。到底是取值还是赋值,由函数的参数决定。

  $('h1').html(); //html()没有参数,表示取出h1的值

  $('h1').html('Hello'); //html()有参数Hello,表示对h1进行赋值

常见的取值和赋值函数如下:

  .html() 取出或设置html内容

  .text() 取出或设置text内容

  .attr() 取出或设置某个属性的值

  .width() 取出或设置某个元素的宽度

  .height() 取出或设置某个元素的高度

  .val() 取出某个表单元素的值

需要注意的是,如果结果集包含多个元素,那么赋值的时候,将对其中所有的元素赋值;取值的时候,则是只取出第一个元素的值(.text()例外,它取出所有元素的text内容)。


jQuery如何让即"取值器"与"赋值器"合一?

答案: jQuery创新的重载,请看源码:

    html(node, string){
      if(arguments.length === 2){
          node.innerHTML = string
      }else if(arguments.length === 1){
          return node.innerHTML
      }
    },

if(arguments.length === 2) 根据参数个数不同决定是"取值器"还是"赋值器"

21.5 jQuery 其他创新之处

  1. 简介的别名方法:window.$ = window.jQuery
  2. 不用new, 可以创建一个对象
  3. 用闭包隐藏细节: 不能直接操作变量, 需要通过函数, 实现的java的私有变量
  4. 重载:同一个函数: 重载可读可写

21.6 jQuery 如何添加原型

window.$ = window.jQuery = function(selectorOrArrayOrTemplate) {
  let elements;
  if (typeof selectorOrArrayOrTemplate === "string") {
    if (selectorOrArrayOrTemplate[0] === "<") {
      // 创建 div
      elements = [createElement(selectorOrArrayOrTemplate)];
    } else {
      // 查找 div
      elements = document.querySelectorAll(selectorOrArrayOrTemplate);
    }
  } else if (selectorOrArrayOrTemplate instanceof Array) {
    elements = selectorOrArrayOrTemplate;
  }

  function createElement(string) {
    const container = document.createElement("template"); 
    container.innerHTML = string.trim();
    return container.content.firstChild;
  }
  // api 可以操作elements
  const api = Object.create(jQuery.prototype) // 创建一个对象,这个对象的 __proto__ 为括号里面的东西
  // const api = {__proto__: jQuery.prototype}
  Object.assign(api, {
    elements: elements,
    oldApi: selectorOrArrayOrTemplate.oldApi
  })
  // api.elements = elements
  // api.oldApi = selectorOrArrayOrTemplate.oldApi
  return api
};

jQuery.fn = jQuery.prototype = {  //这里定义原型
  constructor: jQuery,
  jquery: true,
  get(index) {
    return this.elements[index];
  },
  appendTo(node) {
    if (node instanceof Element) {
      this.each(el => node.appendChild(el));
    } else if (node.jquery === true) {
      this.each(el => node.get(0).appendChild(el));
    }
  },
  append(children) {
    if (children instanceof Element) {
      this.get(0).appendChild(children);
    } else if (children instanceof HTMLCollection) {
      for (let i = 0; i < children.length; i++) {
        this.get(0).appendChild(children[i]);
      }
    } else if (children.jquery === true) {
      children.each(node => this.get(0).appendChild(node));
    }
  },
  find(selector) {
    let array = [];
    for (let i = 0; i < this.elements.length; i++) {
      const elements2 = Array.from(this.elements[i].querySelectorAll(selector));
      array = array.concat(this.elements2);
    }
    array.oldApi = this; // this 就是 旧 api
    return jQuery(array);
  },
  each(fn) {
    for (let i = 0; i < this.elements.length; i++) {
      fn.call(null, this.elements[i], i);
    }
    return this;
  },
  parent() {
    const array = [];
    this.each(node => {
      if (array.indexOf(node.parentNode) === -1) {
        array.push(node.parentNode);
      }
    });
    return jQuery(array);
  },
  children() {
    const array = [];
    this.each(node => {
      if (array.indexOf(node.parentNode) === -1) {
        array.push(...node.children);
      }
    });
    return jQuery(array);
  },
  print() {
    console.log(this.elements);
  },
  // 闭包:函数访问外部的变量
  addClass(className) {
    for (let i = 0; i < this.elements.length; i++) {
      const element = this.elements[i];
      element.classList.add(className);
    }
    return this;
  },
  end() {
    return this.oldApi; // this 就是新 api
  }
};

什么是适配器, 设计模式?

不同浏览器不同代码, 称之为适配器

设计模式: 通用代码取个好听的名字, 不是学的, 不断的写, 不断的重写, 然后抽象总结, 然后看自己代码符合哪几个设计模式