最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • jQuery 的设计思想(上)

    正文概述 掘金(SamRock)   2021-04-05   449

    前言

    • 本节内容,把上节封装的 dom 代码,改用 jQuery 风格再次重新封装
    • jQuery 非常简单

    用 jQuery 风格重新封装

    准备工作

    新建项目目录 dom-2 > src > index.html 、 main.js 、 jquery.js

    index.html

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport"
            content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>手写jQuery</title>
    </head>
    <body>
      你好
      <script src="jquery.js"></script>
      <script src="main.js"></script>
    </body>
    </html>
    

    jquery.js

    // 第一步,声明  window.jQuery 是一个函数(?!先不用管为什么是函数)
    window.jQuery = function () {
      console.log(`我是jQuery`)
    }
    

    main.js

    jQuery() // window.jQuery()    // output:我是jQuery
    

    开启本地服务

    yarn global add parcel
    parcel src/index.html
    

    链式风格 ❤️

    也叫 jQuery 风格

    • window.jQuery() 是我们提供的全局函数

    特殊函数 jQuery

    • jQuery(选择器) 用于获取对应的元素
    • 但它却不返回这些元素
    • 相反,它返回一个对象,称为 jQuery 构造出来的对象 (也就是上节手写DOM中的那个 api)
    • 这个对象可以操作对应的元素
    • 听不懂?直接看代码!

    代码 ⭕️

    index.html

    <body>
      <div class="test">你好1</div>
      <div class="test">你好2</div>
      <div class="test">你好3</div>
      <script src="jquery.js"></script>
      <script src="main.js"></script>
    </body>
    

    jquery.js

    // 第一步,声明  window.jQuery 是一个函数(?!)(先不用管为什么是函数)
    window.jQuery = function (selector) {
      const elements = document.querySelectorAll(selector)  // 获取 selector 的全部元素(得到一个数组)
      // return elements
      // 常规操作:就直接返回这个通过选择器找到的元素。
      // 但jQuery做了反常规的操作:获取到元素后,没有返回这个元素,而是返回了可以操作这个元素的 api
      // 如下:
      // api 可以操作 elements
      // api 是个对象,里面包含各种可以操作 elements 的函数。
      // 如,addClass 就是给 elements 添加类名的函数
      const api = {
        // 函数内访问了函数外部的变量,这就是「闭包」
        addClass(className) {
          // elements 是 addClass 这个函数外部的变量
          for (let i = 0; i < elements.length; i++) { // 遍历所有获取到的元素,添加类名
            elements[i].classList.add(className)
          }
          // return null
          return api // 方法仍返回 api,api里又包含很多方法,可通过返回值继续调用.addClass 形成一个链条?
          // 这就是链式风格 ?
        }
      }
      return api
    }
    

    main.js

    const api = jQuery(".test") // 通过选择器获取到元素,但返回的不是该元素
    // 而是返回一个 api 对象,api 里包含很多可以控制该元素的方法
    // console.log(api.addClass)
    // 遍历所有获取到的元素,添加 .red 类名
    api.addClass("red").addClass('blue')
    // api.addClass 方法的返回值仍是 api,所以可以通过返回值继续调用.addClass 形成一个链条
    
    jQuery 的设计思想(上)

    jQuery 代码变型 1️⃣

    return 的骚操作 1️⃣

    window.jQuery = function (selector) {
      const elements = document.querySelectorAll(selector)  // 获取 selector 的全部元素(得到一个数组)
      const api = {
        addClass(className) {
          for (let i = 0; i < elements.length; i++) { 
            elements[i].classList.add(className)
          }
          // return api
          return this  
          /*
          * 如果用一个对象来调用函数,那么这个函数中的this,就是前面的对象
          * obj.fn(p1) 等价于 ↓
          * obj.fn.call(obj, p1)   // 在fn中,this就是obj
          * 调用时 api.addClass("red") => 同理,在 addClass 中 this 就是 api,二者等价
          * 那 addClass 函数中,原本是 return api,就可以换成 return this
          * 注:this 的值,与调用时前面写了什么有关,只在函数被调用时才能确定this指代什么
          * */
        }
      }
      return api
    }
    

    return 的骚操作 2️⃣

    window.jQuery = function (selector) {
      const elements = document.querySelectorAll(selector) 
      const api = {  // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        addClass(className) {
          for (let i = 0; i < elements.length; i++) {
            elements[i].classList.add(className)
            
          }
          return this 
        }
      }
      return api    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    
    // 省略 api 的赋值环节 ? 
    window.jQuery = function (selector) {
      const elements = document.querySelectorAll(selector)  // 获取 selector 的全部元素(得到一个数组)
      // const api = {
      return {
        addClass(className) {
          for (let i = 0; i < elements.length; i++) {
            elements[i].classList.add(className)
            
          }
          return this
        }
      }
      // return api 
    }
    

    jQuery 的核心思想

    1. jQuery 函数,接收一个 css 选择器
    2. 通过选择器,获取到这个元素 elements(但不会返回这个元素),它会返回一个对象
    3. 返回的对象中,包含很多函数。这些函数都可以操作这个元素 elements

    原理:

    • 用「闭包」去维持这个 elements
      • 因为 addClass 函数在访问 elements。被访问的变量,是不会随便就被浏览器回收掉的
      • 这就是 jQuery 的核心思想之一
    • addClass 函数,肯定能猜到:用户在调用 addClass 时,肯定是通过 jQuery(选择器) 得到的 api 来调用的
    • 所以才会大胆的 return this。
    • addClass 函数,希望把 「点 . 」前面的东西,作为 addClass 的返回值 api.addClass("red")
    • 这样就相当于,api 从 addClass 函数前面,传递到了函数后面,这样就可以接着调用 addClass ?api.addClass("red")?.addClass("blue")
    • 这就是 「链式操作」

    jQuery 代码变型 2️⃣

    main.js 简化调用

    const x = jQuery(".test")  // 声明出来 x ,接着直接使用。那赋值操作,显得多此一举
    x.addClass("red").addClass('blue').addClass('green')
    
    // ? 最终写成 ?
    jQuery(".test").addClass("red").addClass('blue').addClass('green')
    

    小总结

    • 所谓高级的前端代码,就是把中间过程全部省掉了
    • 把所有多次一举、无关紧要的东西,都尽量删掉
    • 最后只留下一个最少信息的、最精炼的代码
    • 虽然代码特别简洁、优雅,但对于学习者来说,就是看不懂。(说明「源码」真的不适合学习者)

    jQuery 是构造函数吗?

    • 因为 jQuery 函数确实构造出了一个对象

    不是

    • 因为不需要写 new jQuery() 就能构造一个对象
    • 以前讲的构造函数都要结合 new 才行

    结论

    • jQuery 是一个不需要加 new (就可以构造出对象)的构造函数
    • jQuery 不是常规意义(严格意义)上的构造函数
    • 这是因为 jQuery 用了一些技巧(目前没必要讲,讲了新手就更迷惑了)

    术语

    口头约定 ?

    口头约定:

    • 以后说到 jQuery对象 就代指 jQuery 函数构造出来的对象 (为了省事,少说几个字)
    • 不是说 「 jQuery 这个对象 」
    • 一定要记清楚

    其他举例

    • Object 是个函数
    • Object 对象,表示 Object 这个构造函数 构造出来的对象(不是 Object 本身是对象)
    • Array 是个函数
    • Array 对象/数组对象,表示 Array 构造出来的对象(不是 Array 本身是对象)
    • Function 是个函数
    • Function 对象 / 函数对象,表示 Function 构造出来的对象(不是 Function 本身是对象)

    更多功能的封装 ⭕️

    jQuery('#xxx')                // 返回值并不是元素,而是一个api对象 
    jQuery('#xxx').find('.red')   // 查找#xxx里的.red元素 
    jQuery('#xxx').parent()       // 获取爸爸 
    jQuery('#xxx').children()     // 获取儿子 
    jQuery('#xxx').siblings()     // 获取兄弟 
    jQuery('#xxx').index()        // 获取排行老几(从0开始) 
    jQuery('#xxx').next()         // 获取弟弟 
    jQuery('#xxx').prev()         // 获取哥哥 
    jQuery('.red').each(fn)       // 遍历并对每个元素执行fn
    

    代码

    window.jQuery = function (selectorOrArray) {
      /*
      * elements 永远表示选择器的目标元素的集合(伪数组)
      * */
      let elements
      if (typeof selectorOrArray === "string") {  // 重载
        elements = document.querySelectorAll(selectorOrArray)
      } else if (selectorOrArray instanceof Array) {
        elements = selectorOrArray
      }
      // ? 返回 jQuery函数 构造的对象 api(this就是这个api、api可以操作elements)
      return {
        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++) {
            array = array.concat(Array.from(elements[i].querySelectorAll(selector)))
          }
          return jQuery(array)  // <<<<<<<< 重点理解这句 【代码分析,见本地项目dom-2的注释】
        },
        oldApi: selectorOrArray.oldApi,
        end() {
          return this.oldApi
        },
        each(fn) {
          for (let i = 0; i < elements.length; i++) {
            fn.call(null, elements[i], i, elements)  // 遍历每项,对每一项都执行某个方法
          }
          return this
        },
        print() {
          console.log(elements)
          return this
        },
    		parent() {
          const array = []
          this.each(node => {
            if (array.indexOf(node.parentNode) === -1) { // 去重 
              array.push(node.parentNode)
            }
          })
          array.oldApi = this
          return jQuery(array)
        },
        children() {
          const array = []
          this.each(node => {
            if (node.children) { 
              array.push(...node.children)  // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
            }
          })
          array.oldApi = this
          return jQuery(array)
        },
        /********************* 下面是前面没提到的 *********************/
        /*
        siblings()
        index()
        next()
        prev()
        */
      }
    }
    

    练习

    /*
      <div id="test">
        <div class="child">1</div>
        <div class="child">2</div>
        <div class="child">3</div>
      </div>
    */
    window.jQuery = function(selectorOrArray){
      let elements
      if(typeof selectorOrArray === 'string'){
        elements = document.querySelectorAll(selectorOrArray)
      }else if(selectorOrArray instanceof Array){
        elements = selectorOrArray
      }
      return {
        addClass(className){
          this.each(n=>n.classList.add(className))
        },
        find(selector){
          let array = []
          this.each(n=>{
            array.push(...n.querySelectorAll(selector))
          })
          return jQuery(array)
        },
        each(fn){
          for(let i=0;i<elements.length;i++){
            fn.call(null, elements[i], i)
          }
        }
      }
    }
    
    window.$ = window.jQuery
    $('#test').find('.child').addClass('red') // 请确保这句话成功执行
    

    代码

    // 先简单回顾 dom 创建节点 ?(两种方式)
    const div = document.createElement('div')  // ①传入标签名
    template.innerHTML = '<div></div>'  // ②传入html结构,最后返回 template.content.firstChild
    
    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;
      }
    	
      // 返回jQuery创建的api
      return{
        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));
          }
        },
        // ...
      }
    }
    
    // 创建 div,插入到 body 中
    $('<div><span>1</span></div>').appendTo(document.body) 
    

    $div.remove() 
    $div.empty()  
    

    $div.text(?) // 读写文本内容  // 传了参数就是「写」,不传参数就是「读」
    $div.html(?) // 读写HTML内容  // 传了参数就是「写」,不传参数就是「读」
    $div.attr('title', ?)  // 读写属性 
    $div.css({color: 'red'})  // 读写style // 注意方法名是css
    $div.addClass('blue') 
    $div.on('click', fn) 
    $div.off('click', fn) ·  
    

    注意

    • $div 大部分时候,对应了多个 div 元素
    • 一定要默认 $div 是一个数组,然后遍历它 (每个操作都要遍历)

    window.$ = window.jQuery

    // 一定在代码最后添加
    window.jQuery = function (selectorOrArray){...}
    window.$ = window.jQuery
    
    • 之后在任何地方使用 $ 就相当于使用 jQuery
    • 还可以再省事 ?
    window.$ = window.jQuery = function (selectorOrArray){...}   
    // 写在一行上,顺序是从右向左执行
    // 先执行 window.jQuery = function(){}
    // 然后再把 window.jQuery 的结果,赋值给 window.$
    
    • 这就是很多高级程序员会使用的写法

    起源地下载网 » jQuery 的设计思想(上)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元