new 一个 jQuery 对象-jq源码篇(二)

new 一个 jQuery对象


文接上篇:juejin.cn/post/729816...

简单几句

这篇进入 jQuery 初始化篇章......了解 jQuery 是如何 new 出来的

撸起袖子先观察

在使用库时我们常常是直接使用,而没有一个 new 的实例化操作,同时还支持链式调用操作。

javascript 复制代码
$('#app').add();
$('p')

那么就可以初步猜测

  • jQuery 执行后返回的是一个实例而非直接挂载的构造函数到 window 上
  • 想要支持链式调用,那么最简单的方式就是进行 return this

拿出键盘就是淦

首先通过 console.log('jquery', $)打印一下 $看看 jquery 是什么东西:

执行后 $打印出来是一个函数,含有入参和返回值

那么再对执行 $()可以得到什么

得到的是一个由 init() 方法构造的实例,同时原型对象上存在 n 多种方法

从上面看知道 jquery 实际上是做了 new操作的,同时将 init() 作为构造函数

jQuery 的 new 操作是如何做的?

先来复习一下一个对象被实例化是如何操作的

ini 复制代码
  var z_jQuery = function () {
    this.name = 'kobe';
  };
​
  z_jQuery.prototype.getName = function() {
      return this.name;
  }
​
  var newJq = new z_jQuery();   // 实例化

而 jQuery 不同的是将 newJq最为实例返回,按照这个思路改造 z_jQuery

javascript 复制代码
  var z_jQuery = function () {
    return new z_jQuery();
  };
  // ncaught RangeError: Maximum call stack size exceeded

无限引用爆栈了,报错!!!

按照 jQuery 的思路,使用 init 方法作为构造函数,同时将其添加到原型对象上

javascript 复制代码
  var z_jQuery = function (selector, context) {
    return new z_jQuery.prototype.init(selector, context);
  };
​
  z_jQuery.prototype = {
    init: function (selector, context) {
      return this;
    },
    add: function () {},
    addClass: function () {},
    // ...
  };
​
  console.log("z_jquery", z_jQuery());

这时候 new 出来的实例是这样的

可以看到原型上面没有东西。这是因为这个 new 操作中,init 构造函数原型对象上没有东西

这里回顾一下 new操作如何实现的

  • 在内存中创建一个空对象
  • 将构造函数的原型对象赋值给空对象的原型
  • 执行构造函数
  • 返回构造函数里的新对象或空对象

那么既然没有东西,可以改造代码:

javascript 复制代码
  var z_jQuery = function (selector, context) {
    return new z_jQuery.prototype.init(selector, context);
  };
​
  z_jQuery.prototype = {
    init: function (selector, context) {
      return this;
    },
    add: function () {},
    addClass: function () {},
  };
​
  z_jQuery.prototype.init.prototype = {
    addClass: function () {},
  };
​
  console.log("z_jquery", z_jQuery());

这个时候的实例上原型就存在了属性 addClass

那么想要使用 jQuery 上的原型方法。可以将 jQuery 原型对象赋值给 init 构造函数的原型。最终方案:

javascript 复制代码
  var z_jQuery = function (selector, context) {
    return new z_jQuery.prototype.init(selector, context);
  };
​
  z_jQuery.prototype = {
    init: function (selector, context) {
      return this;
    },
    add: function () {
      return this;
    },
    addClass: function () {},
  };
  // 重写原型
  z_jQuery.prototype.init.prototype = z_jQuery.prototype;
​
  console.log("z_jquery", z_jQuery());

那么就可以达到预期效果:

既然是返回的实例而非构造函数,为什么不直接用 init 返回实例,而是使用 new 操作?

根据这个思路改造代码

javascript 复制代码
  var z_jQuery = function (selector, context) {
    return z_jQuery.prototype.init(selector, context);
  };
​
  z_jQuery.prototype = {
    init: function (selector, context) {
      return this;
    },
    add: function () {
      return this;
    },
    addClass: function () {},
  };
​
  console.log("z_jquery", z_jQuery());

得到的实例:

这时候 init 中的 this指向的是 z_jQuery构造函数创建的实例。 那么就存在一个问题:

javascript 复制代码
  var z_jQuery = function (selector, context) {
    return z_jQuery.prototype.init(selector, context);
  };
​
  z_jQuery.prototype = {
    init: function (selector, context) {
      this.age = 18;
      return this;
    },
    add: function () {
      return this.age + 1;
    },
    addClass: function () {},
    age: 12,
  };
​
  console.log("z_jquery", z_jQuery());
  console.log("z_jquery", z_jQuery().add());    // 19

这里的 age 是 19 而不是 13。

init方法是在 z_jQuery.prototype上定义的,而非构造函数内部定义。而 init被作为构造器。在实例化时实例会继承原型对象上的属性与方法 。在实例化时继承是发生在构造函数执行之前 的,这就导致 age被覆盖。导致作用域污染。

那么就需要分割作用域。使用 new 操作来重写原型。


解决完作用域问题,此时还存在一个问题:

z_jQuery 的 prototype 被重写,构造函数的原型链断开

z_jQuery 构造函数的原型 z_jQuery.prototype 被重写为一个新对象,这个新对象没有原生的 constructor 属性,因此它的 constructor 属性将指向 Object 构造函数。

这就使得 instanceof失效。解决这个问题:

javascript 复制代码
  // jQuery 构造函数
  var z_jQuery = function (selector, context) {
    return new z_jQuery.prototype.init(selector, context);
  };
​
  // jQuery 原型
  z_jQuery.prototype = {
    constructor: z_jQuery,  // 设置构造函数
    // jQuery 构造
    init: function (selector, context) {
      if (!selector) {
        return this;
      }
      return this;
    },
    age: function () {
      return this.age;
    },
  };
​
  z_jQuery.prototype.init.prototype = z_jQuery.prototype;
​
  // test
  console.log("z_jquery", z_jQuery());
  console.log("age func", z_jQuery().age());

总结

  • 通过在原型对象上 init 构造函数来创建实例,然后重写构造函数的原型,即可为新实例添加 z_jQuery 原型对象上的属性&方法
相关推荐
ZC跨境爬虫1 小时前
跟着 MDN 学JavaScript day_7:数学运算与逻辑判断实战测试
开发语言·前端·javascript·学习·ecmascript
fangdengfu1231 小时前
ES分析系统各个服务日志占用量
java·前端·elasticsearch
凌云拓界1 小时前
文件管理:让AI安全操作你的电脑 ——CogitoAgent开发实战(三)
javascript·人工智能·架构·开源·node.js
凌云拓界2 小时前
联网能力:让AI看见更广阔的世界 ——CogitoAgent开发实战(四)
javascript·人工智能·架构·node.js·创业创新
JustHappy3 小时前
古法编程秘籍(六):程序到底是怎么跑起来的?从 IO 到中断,一次讲明白
前端·后端·全栈
HYCS3 小时前
用pixi.js实现fabric.js(六):从线性代数的角度理解编辑器交互
前端·javascript·canvas
卷帘依旧3 小时前
useImperativeHandle的作用
前端
卷帘依旧3 小时前
Hooks在Fiber上的存储原理
前端
you45803 小时前
学成在线--day02 CMS前端开发(含Vue基础知识得回顾)
前端·javascript·vue.js