20230507----重返学习-jQuery源码-循环处理-this处理-迭代对象方法each的封装

day-064-sixty-four-20230507-jQuery源码-循环处理-this处理-迭代对象方法each的封装

jQuery源码

多库共存命名冲突

  • jQuery及$的命名冲突

    // 多个使用到了$符的库共存,及多版本jQuery共存。
    var _jQuery = window.jQuery; //在覆盖的情况下映射jQuery。
    var _$ = window.$; //在$被覆盖的情况下映射。
    jQuery.noConflict = function (deep) {
      // 校验当前用的jQuery版本是自己这一版,也就是意味着当前作用域下的$就是之前window上的旧window.$,再进行转移。
      if (window.$ === jQuery) {
        window.$ = _$;
      }
    
      // 深度转移-如果传入了deep且为true。校验当前用的jQuery版本是自己这一版,也就是意味着当前作用域下的_jQuery就是之前window上的旧window.jQuery,再进行转移。
      if (deep && window.jQuery === jQuery) {
        window.jQuery = _jQuery;
      }
    
      return jQuery;
    };
    
  • 转移_的使用权

    // 转移“_”的使用权,可以用另一个变量来接收当前的utils,以便可以正常使用。
    let origin = null;
    origin = window._;
    const noConflict = function noConflict() {
        if (window._ === utils) {
            window._ = origin;
        }
        return utils;
    };
    
    window._=utils;//utils是核心工具对象。
    
    • lodash 是目前项目中常用的开源工具库,以_为简写`

简化创建实例对象的流程

  • 使用$('body')new $('body')的效果一致,都会返回一个jQuery实例对象。

    • 要实现这个效果,就要用一个中转的构造函数,它返回一个jQuery实例对象。

      • 因为这个中转的构造函数,内部可能要使用到this,否则直接在jQuery函数中返回一个原型为jQuery.prototype的实例对象应该也可以。
      // 我们在外面使用的JQ选择器 “ $('...') ”,其实就是把 jQuery 函数执行
      var jQuery = function (selector, context) {
          return new jQuery.fn.init(selector, context);
      };
      
      // JQ的原型对象
      jQuery.fn = jQuery.prototype = {
          constructor: jQuery,
          // ...
      };
      
      // init构造函数
      var init = jQuery.fn.init = function (selector, context, root) {
        // ...
      };
      init.prototype = jQuery.fn;
      
  • 基础的骨架搭建

    (function (global, factory) {
      "use strict";
      if (typeof module === "object" && typeof module.exports === "object") {
        module.exports = global.document
          ? factory(global, true)
          : function (w) {
              if (!w.document) {
                throw new Error("jQuery requires a window with a document");
              }
              return factory(w);
            };
      } else {
        factory(global);
      }
    })(
      typeof window !== "undefined" ? window : this,
      function factory(window, noGlobal) {
        // 我们在外面使用的JQ选择器 “ $('...') ”,其实就是把 jQuery 函数执行
        var jQuery = function (selector, context) {
          return new jQuery.fn.init(selector, context);
        };
    
        // JQ的原型对象
        jQuery.fn = jQuery.prototype = {
          constructor: jQuery,
          // ...
        };
    
        // init构造函数
        var rootjQuery = jQuery(document),
          init = (jQuery.fn.init = function (selector, context, root) {
            // ...
          });
        init.prototype = jQuery.fn;
    
        /* 暴露API */
        if (typeof define === "function" && define.amd) {
          define("jquery", [], function () {
            return jQuery;
          });
        }
        if (typeof noGlobal === "undefined") {
          window.jQuery = window.$ = jQuery;
        }
        return jQuery;
      }
    );
    

jQuery上的方法

  • 第一部分方法,放在jQuery原型上,供实例调取使用的
    • 大部分是操作DOM的
      • css
      • add
      • remove
      • toggleClass
      • addClass,如$(…).addClass()
  • 第二部分方法,把jQuery当做普通对象,设置的静态私有属性方法
    • 大部分是工具类方法
      • each
      • ajax,如$.ajax()

循环处理

  • JavaScript中常用的循环操作语句

    • for
    • while
      • do-while
    • 数组的迭代方法
      • forEach()
      • map()
      • reduce()
      • reduceRight()
      • filter()
      • find()
      • findIndex()
      • some()
      • every()
    • 对象的迭代方法
    • for-in
    • for-of
  • 项目中选择那一种循环方案

    1. 首先看性能
      • 好(性能高) --> 坏(性能低)
      • forwhiledo-while --> 数组的迭代方法 --> for-of --> for-in
        • 数组的迭代方法内部也用的for循环,中间还有一些判断的消耗,所以比for循环要性能低一点
    2. 其次看各循环的作用
      • for:一般指定起始和结束的条件进行循环,可用来创建指定次数的循环,或迭代数组/类数组中的内容
      • while:一般用于不确定具体执行次数的循环
        • 基本上就是只知道循环的运行条件的那类循环
      • for-of:直接迭代数据中的内容
      • for-in:可以迭代对象
    3. 最后,优先使用函数式编程
      • 因为可以简化操作,实现代码的低耦合高内聚
      • 优先用数组的方法
  • 示例

    • 已知循环的具体次数

      // 需求1:循环五次
      // for (let i = 0; i < 5; i++) { }  //命令式编程
      new Array(5).fill(null).forEach(() => { //函数式编程
          console.log('AA');
      });
      
      • 已知具体次数的,可以用for循环的命令式编辑方式,也可以用函数式编程
        • 优先使用函数式编程,即借用数组的方式来
    • 数组与伪数组的遍历迭代

      // 需求2:依次迭代数组/伪数组每一项
      let arr = [10, 20, 30];
      arr.forEach((item, index) => {
          console.log(item, index);
      });
      
      • 优先使用函数式编程,即借用数组的方式来
        • 也可以用for循环配合length属性
    • 要求灵活控制到具体迭代项的

      // 需求3:迭代数组的奇数项-这种要求灵活控制到具体迭代项的,需要用for循环。
      let arr = [10, 20, 30, 40, 50, 60, 70];
      for (let i = 0; i < arr.length; i += 2) {
          console.log(arr[i]);
      }
      
      /* // 这样处理:每一项都被迭代了,只不过在奇数项才输出。不推荐使用
      let arr = [10, 20, 30, 40, 50, 60, 70];
      arr.forEach((item, index) => {
          if (index % 2 === 0) {
              console.log(item);
          }
      }); */
      
      • 推荐使用for循环,能具体控制循环那些项
    • 不确定具体要循环多少项

      // 需求4:创建一个4位不重复的验证码
      let area = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890'; //0~61
      let code = ``;
      while (code.length < 4) { //只要验证码长度不足4位,我们就要一直处理
          let ran = Math.round(Math.random() * 61),
              char = area.charAt(ran);
          if (new RegExp(char, 'i').test(code)){ continue;}
          code += char;
      }
      console.log(code);
      
      • 推荐使用while循环或do-while循环
  • 创建一个可循环特定次数的数组:new Array(数字).fill(null)

    // 需求1:循环五次
    // for(let i=0;i<5;i++){}//命令式编程
    // new Array(5).fill(null).forEach(()=>{console.log('11111')})//函数式编程
    // new Array(5)创建一个长度为5的稀疏数组,forEach等迭代方法是不支持稀疏数组的
    // new Array(5).fill(null)对长度为5的稀疏数组进行填充,让其变成密集数组
    // new Array(5).fill(null).forEach()对密集数组进行forEach()循环。
    
  • for-in循环的问题

    Object.prototype.AAA = 'AAA';
    let obj = {
      name: '挖呀挖呀挖',
      x: 100,
      1: 20,
      0: 30,
      [Symbol('AA')]: '哇咔咔'
    };
    for(let key in obj){
      console.log(key, obj[key]);
    }
    
    • 不仅仅会迭代私有的属性,还会按照原型链去所属类的原型对象上查找。

      • 一直遍历到Object.prototype为止。
        • 所以for-in循环的性能很差,因为要涉及到原型链。
          • 优化方式,看当前迭代到的属性是否是私有属性,若为私有,就退出循环。

            for (let key in obj) {
              if (!obj.hasOwnProperty(key)){ break;} //此操作仅仅是不让其输出公有“可枚举”的成员,但是不能完全保证让其不迭代公有成员!
              console.log(key, obj[key]);
            }
            
    • 只能迭代可枚举非Symbol类型的成员属性。

      • 具有迭代限制。
    • 往后想要迭代对象的成员,应尽可能的不要使用for-in循环。

  • 对象成员的类型:字符串、Symbol类型

  • 对象成员的规则

    • 查看规则的方式
      • Object.getOwnPropertyDescriptor(obj,key)
      • Object.getOwnPropertyDescriptors(obj)
    • 规则类型种类
      • configurable 是否可删除
      • writable 是否可修改
      • enumerable 是否可枚举
      • value 成员值
    • 规则情况
      1. 默认情况下,基于obj.xxx或者在大括号中设置的成员,其相应规则都是true
      2. 一般情况下,浏览器内置的成员,其规则:enumerable=false(不可枚举),其余的都是true
      3. 一般可以基于object.defineProperty 设置成员及其规则。
        • 如果成员存在,可以为其修改规则

          let obj={name:'01'}
          Object.defineProperty(obj,'name',{
            value:'02'
          })//只修改obj['name']的enumerable规则为false。其余规则不变。
          obj.name = '03'
          console.log(obj)//{name: '03'}
          
        • 如果成员不存在,则新增一个成员,其默认规则都是false

          let obj = {}
          Object.defineProperty(obj,'name',{
              value:'02'
          })//设置obj['name']的value规则为'02'。其余规则都为false。
          obj.name = '03'
          console.log(obj)//{name: '02'}
          
  • 常用的替代对象私有方法的方式

    • 对象构造函数上的静态方法

      • Object.keys(obj):获取对象 私有的可枚举的非Symbol类型的 成员
      • Object.getOwnPropertyNames(obj):获取对象 私有的非Symbol类型的 成员「不管枚举性
      • Object.getOwnPropertySymbols(obj):获取对象 私有的Symbol类型的 成员「不管枚举性
    • 获取对象所有的私有成员「不考虑类型和枚举的限制」

      let keys = Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj));
      console.log(keys);
      
      • 获取对象所有的私有成员-简单的办法
        • Reflect.ownKeys(obj) ES6提供的这个API,可以直接获取对象所有的私有成员
  • 迭代对象中的每一项

    • 一般的需求都是只迭代私有的即可,不考虑公有的成员

      // 需求5:迭代对象中的每一项「一般的需求都是只迭代私有的即可,不考虑公有的成员」
      Object.prototype.AAA = 'AAA';
      let obj = {
        name: '挖呀挖呀挖',
        x: 100,
        1: 20,
        0: 30,
        [Symbol('AA')]: '哇咔咔'
      };
      let keys = Reflect.ownKeys(obj);
      keys.forEach(key => {
        console.log(key, obj[key]);
      });
      

this处理

  • this: 函数的执行主体。

    • 通俗来讲,就是谁把这个函数执行的,谁就是this。
      • 但这个不太靠谱,因为会有new及各种环境下很难确定是谁把这个函数给执行了。比如匿名函数及对象身上的方法,以及把对象身上的方法赋值给另一个变量后通过变量直接执行。
    • this并不是函数执行所处的上下文。
      • 执行上下文,就是函数执行时的环境,里面会包含各种变量。
    • 这里主要研究的是函数中的this。
    • 全局上下文中的this是window。
      • 前提:是在浏览器环境下的运行。
  • 关于函数中的this到底是谁,一般只需要记住这几条规律即可

    • 这个指的是原生JavaScript在不经过处理时的场景。
      • 有些框架如vue会用bind把this指向改变,那在使用层面上初看时就不适用这几条规律了。不过框架中的原理还是一样的,只是可能框架会自动帮我们把一些函数的this指向给改变。
    • 具体规律
      1. 给元素进行事件绑定,在DOM0/DOM2事件绑定时。

        • 当事件触发,绑定的方法执行,方法中的this是当前被操作的元素。
          • 给谁绑定的,this就是谁
        document.body.onclick = function () {
          console.log(this); // --> document.body
        };
        document.body.addEventListener("click", function () {
          console.log(this); // --> document.body
        });
        
      2. 普通函数执行,看函数前面是否有点.

        • 点.点.前面是谁,函数中的this就是谁

          • 例如:

            Array.prototype.push()//此时push()中的this->Array.prototype
            [].push()//此时push()中的this->[]
            
        • 没有:函数中的this是window(非严格模式)或者undefined(严格模式)

          • 自执行函数回调函数等匿名函数,如果没有经过特殊的处理,那么函数中的this,一般都是window/undefined
      3. 构造函数执行(new执行),函数体中的this指向新创建的实例对象

      4. 我们可以基于call()/apply()/bind()强制改变函数中的this的指向。

        • 这个是这五条规则中优先级第二高的,是在有this的情况中优先级最高的。
      5. 箭头函数中没有this,所用到的this都是其宿主环境中的

        • 宿主环境就是上级上下文。
        • 这个优先级最高,无论有没有this,箭头函数内this都不是该箭头函数执行时生成的,并没有独立于上级上下文,都是来源于上级上下文中的
  • 箭头函数普通函数

    • 最核心的区别:this
      • 箭头函数中没有this
      • 箭头函数没有arguments
        • 可以基于...剩余运算符获取传递的实参
      • 箭头函数没有prototype,不能被new执行
      • 语法上的区别: 箭头函数写起来更方便与更简单
    • 项目中用箭头函数还是普通函数
      1. 不涉及到this的问题,用谁都可以

        • 但推荐使用箭头函数
      2. 但若想作为一个构造函数,只能用普通函数

      3. 涉及this问题,用谁处理起来方便,就选择用谁

        • 一般是外层用普通函数,内层用箭头函数

          //不推荐这样在对象的方法属性上直接用箭头函数作为第一层,这样箭头函数的this就不能访问到对象自身。而是对象所在的执行上下文中的this对象。
          let obj = {
            name: "obj",
            fn: () => {
              console.log(this,this.name); //window, 不是`obj`
            },
          };
          obj.fn();
          
          • 比如定义对象的方法属性时,该方法属性使用普通函数或普通函数的快速写法。

            let obj = {
              name: "obj",
              fn: function () {
                console.log(this, this.name); //obj 、 `obj`
              },
            };
            obj.fn();
            
            let obj = {
              name: "obj",
              fn() {
                console.log(this, this.name); //obj 、 `obj`
              },//等价于 `fn: function () {console.log(this, this.name);},`;
            };
            obj.fn();
            
        • 或者是要用到当前执行上下文中的this时,优先用箭头函数

        //在obj的fn的外层函数中使用普通函数或普通函数的快速写法,不过在其内部这里使用的是普通函数,导致其访问的this有问题。
        let obj = {
          name: "obj",
          fn() {
            console.log(this, this.name); //obj 、 `obj`
        
            let self = this;
            // 这样在定时器的回调函数内就可以访问到fn()执行时的执行上下文中的this--一般就是obj对象自身。而不是window或undefined。
            setTimeout(function () {
              console.log(this.name); //window.name
              console.log(self.name); //obj.name
            }, 1000);
          },
        };
        obj.fn();
        
        //推荐在obj的fn的外层函数中使用普通函数或普通函数的快速写法,而在内部中使用箭头函数。
        let obj = {
          name: "obj",
          fn() {
            console.log(this, this.name); //obj 、 `obj`
        
            // 这样在定时器的回调函数内就可以访问到fn()执行时的执行上下文中的this--一般就是obj对象自身。而不是window或undefined。
            setTimeout(() => {
              console.log(this.name); //obj.name
            }, 1000);
          },
        };
        obj.fn();
        
  • 例子:

    //-------------------------------
    let fang = "全局的fang";
    let f = "全局中f";
    let fn1 = null;
    const fn2 = {
      f2: function () {
        let fang = "fn2的fang";
        let f = "fn2中f";
        fn1 = {
          inner: function () {
            let f = "私有f";
            console.log(f, fang,this);
          },
        };
      },
    };
    fn2.f2();
    const fn3 = {
      f3: function () {
        let fang = "fn3的fang";
        let f = "fn3中f";
        fn1.inner();
      },
    };
    fn3.f3(); //"私有f","fn2的fang" {inner: ƒ}
    
    //-------------------------------
    let fang = "全局的fang";
    let f = "全局中f";
    let fn1 = null;
    const fn2 = {
      f2: function () {
        let fang = "fn2的fang";
        let f = "fn2中f";
        fn1 = {
          inner: function () {
            let f = "私有f";
            console.log(f, fang,this);
          },
        };
      },
    };
    fn2.f2();
    const fn3 = {
      f3: function () {
        let fang = "fn3的fang";
        let f = "fn3中f";
        const fn3 = fn1.inner
        fn3()
      },
    };
    fn3.f3(); //"私有f","fn2的fang" Window
    
    
    //-------------------------------
    let fang = "全局的fang";
    let f = "全局中f";
    let fn1 = null;
    const fn2 = {
      f2: function () {
        let fang = "fn2的fang";
        let f = "fn2中f";
        fn1 = {
          inner: ()=> {
            let f = "私有f";
            console.log(f, fang,this);
          },
        };
      },
    };
    fn2.f2();
    const fn3 = {
      f3: function () {
        let fang = "fn3的fang";
        let f = "fn3中f";
        fn1.inner();
      },
    };
    fn3.f3(); //"私有f","fn2的fang" {f2: ƒ}
    
    
    //-------------------------------
    let fang = "全局的fang";
    let f = "全局中f";
    let fn1 = null;
    const fn2 = {
      f2: function () {
        let fang = "fn2的fang";
        let f = "fn2中f";
        fn1 = {
          inner: ()=> {
            let f = "私有f";
            console.log(f, fang,this);
          },
        };
      },
    };
    fn2.f2();
    const fn3 = {
      f3: function () {
        let fang = "fn3的fang";
        let f = "fn3中f";
        const fn3 = fn1.inner
        fn3()
      },
    };
    fn3.f3(); //"私有f","fn2的fang" {f2: ƒ}
    

例子

let obj = {
  // 把自执行函数执行的返回值,赋值给 obj.fn
  // obj.fn -> 0x001
  fn: (function () {
    return function () {
      //地址:0x001
      console.log(this);
    };
  })(),
};
obj.fn(); //this->obj
let fn = obj.fn;
fn(); //this->window
var fullName = "language"; //window.fullName=...
var obj = {
  fullName: "javascript",
  prop: {
    getFullName: function () {
      return this.fullName;
    },
  },
};
console.log(obj.prop.getFullName()); //this->obj.prop => obj.prop.fullName => undefined
var test = obj.prop.getFullName;
console.log(test()); //this->window => window.fullName => 'language'
var name = "window";
var Tom = {
  name: "Tom",
  show: function () {
    console.log(this.name);
  },
  wait: function () {
    var fun = this.show;
    fun(); //this->window
  },
};
Tom.wait(); // this->Tom
window.val = 1; //2 4
var json = {
  val: 10, //20
  dbl: function () {
    this.val *= 2;
  },
};
json.dbl(); //this->json => json.val *= 2
var dbl = json.dbl;
dbl(); //this->window => window.val *= 2
json.dbl.call(window); //this->window => window.val *= 2
alert(window.val + json.val); //"24"
(function () {
  var val = 1; //2
  var json = {
    val: 10,
    dbl: function () {
      val *= 2;
    },
  };
  json.dbl();
  alert(json.val + val); //"12"
})();

迭代对象方法each的封装

  • 检测是否是window对象

    • window上有一个window属性,指向它自身

      const isWindow = function isWindow(obj) {
        return obj != null && obj === obj.window;
      };
      
  • 迭代对象方法each的封装

// 笼统检测是否为对象
const isObject = function isObject(obj) {
  return obj !== null && /^(object|function)$/.test(typeof obj);
};

// 检测是否是window对象
const isWindow = function isWindow(obj) {
  return obj != null && obj === obj.window;
};

// 检测是否为数组或者伪数组
const isArrayLike = function isArrayLike(obj) {
  if (typeof obj !== "object" || obj === null) {
    return false;
  }
  if (Array.isArray(obj)) {
    return true;
  }
  // 检测是否为伪数组
  let length = !!obj && "length" in obj && obj.length;
  if (typeof obj === "function" || isWindow(obj)) return false;
  return (
    length === 0 ||
    (typeof length === "number" && length > 0 && length - 1 in obj)
  );
};

// 迭代数组、伪数组、对象「支持中途结束循环」
const each = function each(obj, callback) {
  if (typeof callback !== "function") {
    callback = () => {};
  }
  // 让其支持“纯数字”控制循环
  if (typeof obj === "number" && !isNaN(obj) && obj > 0) {
    obj = new Array(obj).fill(null);
  }
  // 让其支持字符串
  if (typeof obj === "string") obj = Object(obj);
  if (!isObject(obj)) return obj;
  // 迭代数组/伪数组
  if (isArrayLike(obj)) {
    for (let i = 0; i < obj.length; i++) {
      let item = obj[i];
      let res = callback.call(obj, item, i);
      if (res === false) break;
    }
    return obj;
  }
  // 迭代对象
  let keys = Reflect.ownKeys(obj);
  for (let i = 0; i < keys.length; i++) {
    let key = keys[i],
      value = obj[key];
    let res = callback.call(obj, value, key);
    if (res === false) break;
  }
  return obj;
};

进阶参考

  1. Lodash - 一个工具函数 中文文档
  2. Lodash - 一个工具函数 英文官网
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值