吴莎滨 發表於 2020-11-26 17:30:00

JavaScript中的链式调用

<h1 id="链模式">链模式</h1>
<p>链模式是一种链式调用的方式,链模式不属于一般定义的<code>23</code>种设计模式的范畴,而通常将其看作广义上的技巧型设计模式。</p>
<h2 id="描述">描述</h2>
<p>链式调用在<code>JavaScript</code>语言中很常见,如<code>jQuery</code>、<code>Promise</code>等,都是使用的链式调用,当我们在调用同一对象多次其属性或方法的时候,我们需要多次书写对象进行<code>.</code>或<code>()</code>操作,链式调用是一种简化此过程的一种编码方式,使代码简洁、易读。<br>
链式调用通常有以下几种实现方式,但是本质上相似,都是通过返回对象供之后进行调用。</p>
<ul>
<li><code>this</code>的作用域链,<code>jQuery</code>的实现方式,通常链式调用都是采用这种方式。</li>
<li>返回对象本身, 同<code>this</code>的区别就是显示返回链式对象。</li>
<li>闭包返回对象的方式实现,这种方式与柯里化有相似之处。</li>
</ul>
<pre><code class="language-javascript">var Person = function() {};
Person.prototype.setAge = function(age){
    this.age = age;
    return this;
}
Person.prototype.setWeight = function(weight){
    this.weight = weight;
    return this;
}
Person.prototype.get = function(){
    return `{age: ${this.age}, weight: ${this.weight}}`;
}

var person = new Person();
var des = person.setAge(10).setWeight(30).get();
console.log(des); // {age: 10, weight: 30}
</code></pre>
<pre><code class="language-javascript">var person = {
    age: null,
    weight: null,
    setAge: function(age){
      this.age = age;
      return this;
    },
    setWeight: function(weight){
      this.weight = weight;
      return this;
    },
    get: function(){
      return `{age: ${this.age}, weight: ${this.weight}}`;
    }
};
var des = person.setAge(10).setWeight(30).get();
console.log(des); // {age: 10, weight: 30}
</code></pre>
<pre><code class="language-javascript">function numsChain(num){
    var nums = num;
    function chain(num){
      nums = `${nums} -&gt; ${num}`;
      return chain;
    }
    chain.get = () =&gt; nums;
    return chain;
}
var des = numsChain(1)(2)(3).get();
console.log(des); // 1 -&gt; 2 -&gt; 3
</code></pre>
<h2 id="可选链操作符">可选链操作符</h2>
<p>说到链式调用,就有必要说一下<code>JavaScript</code>的可选链操作符,属于<code>ES2020</code>新特性运算符<code>?.</code>、<code>??</code>、<code>??=</code>,可选链操作符<code>?.</code>允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。<code>?.</code>操作符的功能类似于<code>.</code>链式操作符,不同之处在于在引用为空<code>nullish</code>即<code>null</code>或者<code>undefined</code>的情况下不会引起错误,该表达式短路返回值是<code>undefined</code>。与函数调用一起使用时,如果给定的函数不存在,则返回<code>undefined</code>。当尝试访问可能不存在的对象属性时,可选链操作符将会使表达式更短更简明。在探索一个对象的内容时,如果不能确定哪些属性必定存在,可选链操作符也是很有帮助的。</p>
<h3 id="语法">语法</h3>
<pre><code class="language-javascript">obj?.prop
obj?.
arr?.
func?.(args)
</code></pre>
<h3 id="示例">示例</h3>
<pre><code class="language-javascript">const obj = {a: {}};
console.log(obj.a); // {}
console.log(obj.a.b); // undefined
// console.log(obj.a.b.c); // Uncaught TypeError: Cannot read property 'c' of undefined
console.log(obj &amp;&amp; obj.a); // {}
console.log(obj &amp;&amp; obj.a &amp;&amp; obj.a.b &amp;&amp; obj.a.b.c); // undefined
console.log(obj?.a?.b?.c); // undefined

const test = void 0;
const prop = "a";
console.log(test); // undefined
console.log(test?.a); // undefined
console.log(test?.); // undefined
console.log(test?.); // undefined
console.log(test?.()); // undefined
</code></pre>
<h2 id="jquery中的链式调用">jQuery中的链式调用</h2>
<p><code>jQuery</code>是一个高端而不失奢华的框架,其中有许多非常精彩的方法和逻辑,虽然现在非常流行于类似于<code>Vue</code>、<code>React</code>的<code>MVVM</code>模式的框架,但是<code>jQuery</code>的设计实在是棒,非常值得学习,在这里以最基础的实例化<code>jQuery</code>为例探查一下<code>jQuery</code>如何通过<code>this</code>实现的链式调用。<br>
首先定义一个最基本的类,通过原型链去继承方法。</p>
<pre><code class="language-javascript">function _jQuery(){}
_jQuery.prototype = {
    constructor: _jQuery,
    length: 2,
    size: function(){
      return this.length;
    }
}

var instance = new _jQuery();
console.log(instance.size()); // 2
// _jQuery.size() // Uncaught TypeError: _jQuery.size is not a function
// _jQuery().size() / /Uncaught TypeError: Cannot read property 'size' of undefined
</code></pre>
<p>通过定义一个类并且实现实例化之后,在实例之间可以共享原型上的方法,而直接通过<code>_jQuery</code>类直接去调用显然是不行的,抛出的第一种异常是因为在<code>_jQuery</code>类上不存在静态方法,第二种异常是因为<code>_jQuery</code>作为函数执行后未返回值,通过这里可以看出<code>jQuery</code>在通过<code>$()</code>方式调用的时候是返回了一个包含多个方法的对象的,而只是通过自己是访问不到的,我们就借助另一个变量去访问。</p>
<pre><code class="language-javascript">function _jQuery(){
    return_fn;
}
var _fn = _jQuery.prototype = {
    constructor: _jQuery,
    length: 2,
    size: function(){
      return this.length;
    }
}
console.log(_jQuery().size()); // 2
</code></pre>
<p>实际上<code>jQuery</code>为了减少变量的创建,直接将<code>_fn</code>看做了<code>_jQuery</code>的一个属性。</p>
<pre><code class="language-javascript">function _jQuery(){
    return_jQuery.fn;
}
_jQuery.fn = _jQuery.prototype = {
    constructor: _jQuery,
    length: 2,
    size: function(){
      return this.length;
    }
}
console.log(_jQuery().size()); // 2
</code></pre>
<p>到这里确实能够实现<code>_jQuery()</code>方式调用原型上的方法,但是在<code>jQuery</code>中<code>$()</code>的主要目标还是作为选择器用来选择元素,而现在返回的是一个<code>_jQuery.fn</code>对象,显然是达不到要求的,为了能够取得返回的元素,那就在原型上定义一个<code>init</code>方法去获取元素,这里为了省事直接使用了<code>document.querySelector</code>,实际上<code>jQuery</code>的选择器构建是很复杂的。</p>
<pre><code class="language-javascript">function _jQuery(selector){
    return_jQuery.fn.init(selector);
}
_jQuery.fn = _jQuery.prototype = {
    constructor: _jQuery,
    init: function(selector){
      return document.querySelector(selector);
    },
    length: 3,
    size: function(){
      return this.length;
    }
}
console.log(_jQuery("body")); // &lt;body&gt;...&lt;/body&gt;
</code></pre>
<p>但是似乎这样又把链式调用的<code>this</code>给漏掉了,这里就需要利用<code>this</code>的指向了,因为在调用时<code>this</code>总是指向调用他的对象,所以我们在这里将选择的元素挂载到<code>this</code>对象上即可。</p>
<pre><code class="language-javascript">function _jQuery(selector){
    return_jQuery.fn.init(selector);
}
_jQuery.fn = _jQuery.prototype = {
    constructor: _jQuery,
    init: function(selector){
      this = document.querySelector(selector);
      this.length = 1;
      return this;
    },
    length: 3,
    size: function(){
      return this.length;
    }
}
var body = _jQuery("body");
console.log(body); // {0: body, length: 1, constructor: ƒ, init: ƒ, size: ƒ}
console.log(body.size()); // 1
console.log(_jQuery.fn); // {0: body, length: 1, constructor: ƒ, init: ƒ, size: ƒ}
</code></pre>
<p>但是此时又出现了一个问题,我们的选择器选择的元素是直接挂载到了<code>_jQuery.fn</code>上,这样的话由于原型是共享的,在之后的定义的选择器就会将前边定义的选择器覆盖掉,这样显然是不行的,于是我们使用<code>new</code>操作符新建一个对象。</p>
<pre><code class="language-javascript">function _jQuery(selector){
    return new _jQuery.fn.init(selector);
}
_jQuery.fn = _jQuery.prototype = {
    constructor: _jQuery,
    init: function(selector){
      this = document.querySelector(selector);
      this.length = 1;
      return this;
    },
    length: 3,
    size: function(){
      return this.length;
    }
}
var body = _jQuery("body");
console.log(body); // init {0: body, length: 1}
// console.log(body.size()); // Uncaught TypeError: body.size is not a function
</code></pre>
<p>这样又出现了问题,当我们使用<code>new</code>实例化<code>_jQuery.fn.init</code>时返回的<code>this</code>指向的是<code>_jQuery.fn.init</code>的实例,我们就不能进行链式调用了,<code>jQuery</code>用了一个非常巧妙的方法解决了这个问题,直接将<code>_jQuery.fn.init</code>的原型指向<code>_jQuery.prototype</code>,虽然会有循环引用的问题,但是相对来说这一点性能消耗并不算什么,由此我们完成了<code>jQuery</code>选择器以及链式调用的实现。</p>
<pre><code class="language-javascript">function _jQuery(selector){
    return new _jQuery.fn.init(selector);
}
_jQuery.fn = _jQuery.prototype = {
    constructor: _jQuery,
    init: function(selector){
      this = document.querySelector(selector);
      this.length = 1;
      return this;
    },
    length: 3,
    size: function(){
      return this.length;
    }
}
_jQuery.fn.init.prototype = _jQuery.fn;
var body = _jQuery("body");
console.log(body); // init {0: body, length: 1}
console.log(body.size()); // 1
console.log(_jQuery.fn.init.prototype.init.prototype.init.prototype === _jQuery.fn); // true
</code></pre>
<h2 id="每日一题">每日一题</h2>
<pre><code>https://github.com/WindrunnerMax/EveryDay
</code></pre>
<h2 id="参考">参考</h2>
<pre><code>https://zhuanlan.zhihu.com/p/110512501
https://juejin.cn/post/6844904030221631495
https://segmentfault.com/a/1190000011863232
https://github.com/songjinzhong/JQuerySource
https://leohxj.gitbooks.io/front-end-database/content/jQuery/jQuery-source-code/index.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/%E5%8F%AF%E9%80%89%E9%93%BE
</code></pre><br><br>
来源:https://www.cnblogs.com/WindrunnerMax/p/14043455.html
頁: [1]
查看完整版本: JavaScript中的链式调用