• JavaScript中的链式调用


    链模式

    链模式是一种链式调用的方式,链模式不属于一般定义的23种设计模式的范畴,而通常将其看作广义上的技巧型设计模式。

    描述

    链式调用在JavaScript语言中很常见,如jQueryPromise等,都是使用的链式调用,当我们在调用同一对象多次其属性或方法的时候,我们需要多次书写对象进行.()操作,链式调用是一种简化此过程的一种编码方式,使代码简洁、易读。
    链式调用通常有以下几种实现方式,但是本质上相似,都是通过返回对象供之后进行调用。

    • this的作用域链,jQuery的实现方式,通常链式调用都是采用这种方式。
    • 返回对象本身, 同this的区别就是显示返回链式对象。
    • 闭包返回对象的方式实现,这种方式与柯里化有相似之处。
    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}
    
    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}
    
    function numsChain(num){
        var nums = num;
        function chain(num){
            nums = `${nums} -> ${num}`;
            return chain;
        }
        chain.get = () => nums;
        return chain;
    }
    var des = numsChain(1)(2)(3).get();
    console.log(des); // 1 -> 2 -> 3
    

    可选链操作符

    说到链式调用,就有必要说一下JavaScript的可选链操作符,属于ES2020新特性运算符?.????=,可选链操作符?.允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?.操作符的功能类似于.链式操作符,不同之处在于在引用为空nullishnull或者undefined的情况下不会引起错误,该表达式短路返回值是undefined。与函数调用一起使用时,如果给定的函数不存在,则返回undefined。当尝试访问可能不存在的对象属性时,可选链操作符将会使表达式更短更简明。在探索一个对象的内容时,如果不能确定哪些属性必定存在,可选链操作符也是很有帮助的。

    语法

    obj?.prop
    obj?.[expr]
    arr?.[index]
    func?.(args)
    

    示例

    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 && obj.a); // {}
    console.log(obj && obj.a && obj.a.b && 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?.[prop]); // undefined
    console.log(test?.[0]); // undefined
    console.log(test?.()); // undefined
    

    jQuery中的链式调用

    jQuery是一个高端而不失奢华的框架,其中有许多非常精彩的方法和逻辑,虽然现在非常流行于类似于VueReactMVVM模式的框架,但是jQuery的设计实在是棒,非常值得学习,在这里以最基础的实例化jQuery为例探查一下jQuery如何通过this实现的链式调用。
    首先定义一个最基本的类,通过原型链去继承方法。

    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
    

    通过定义一个类并且实现实例化之后,在实例之间可以共享原型上的方法,而直接通过_jQuery类直接去调用显然是不行的,抛出的第一种异常是因为在_jQuery类上不存在静态方法,第二种异常是因为_jQuery作为函数执行后未返回值,通过这里可以看出jQuery在通过$()方式调用的时候是返回了一个包含多个方法的对象的,而只是通过自己是访问不到的,我们就借助另一个变量去访问。

    function _jQuery(){
        return  _fn;
    }
    var _fn = _jQuery.prototype = {
        constructor: _jQuery,
        length: 2,
        size: function(){
            return this.length;
        }
    }
    console.log(_jQuery().size()); // 2
    

    实际上jQuery为了减少变量的创建,直接将_fn看做了_jQuery的一个属性。

    function _jQuery(){
        return  _jQuery.fn;
    }
    _jQuery.fn = _jQuery.prototype = {
        constructor: _jQuery,
        length: 2,
        size: function(){
            return this.length;
        }
    }
    console.log(_jQuery().size()); // 2
    

    到这里确实能够实现_jQuery()方式调用原型上的方法,但是在jQuery$()的主要目标还是作为选择器用来选择元素,而现在返回的是一个_jQuery.fn对象,显然是达不到要求的,为了能够取得返回的元素,那就在原型上定义一个init方法去获取元素,这里为了省事直接使用了document.querySelector,实际上jQuery的选择器构建是很复杂的。

    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")); // <body>...</body>
    

    但是似乎这样又把链式调用的this给漏掉了,这里就需要利用this的指向了,因为在调用时this总是指向调用他的对象,所以我们在这里将选择的元素挂载到this对象上即可。

    function _jQuery(selector){
        return  _jQuery.fn.init(selector);
    }
    _jQuery.fn = _jQuery.prototype = {
        constructor: _jQuery,
        init: function(selector){
            this[0] = 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: ƒ}
    

    但是此时又出现了一个问题,我们的选择器选择的元素是直接挂载到了_jQuery.fn上,这样的话由于原型是共享的,在之后的定义的选择器就会将前边定义的选择器覆盖掉,这样显然是不行的,于是我们使用new操作符新建一个对象。

    function _jQuery(selector){
        return new _jQuery.fn.init(selector);
    }
    _jQuery.fn = _jQuery.prototype = {
        constructor: _jQuery,
        init: function(selector){
            this[0] = 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
    

    这样又出现了问题,当我们使用new实例化_jQuery.fn.init时返回的this指向的是_jQuery.fn.init的实例,我们就不能进行链式调用了,jQuery用了一个非常巧妙的方法解决了这个问题,直接将_jQuery.fn.init的原型指向_jQuery.prototype,虽然会有循环引用的问题,但是相对来说这一点性能消耗并不算什么,由此我们完成了jQuery选择器以及链式调用的实现。

    function _jQuery(selector){
        return new _jQuery.fn.init(selector);
    }
    _jQuery.fn = _jQuery.prototype = {
        constructor: _jQuery,
        init: function(selector){
            this[0] = 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
    

    每日一题

    https://github.com/WindrunnerMax/EveryDay
    

    参考

    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
    
  • 相关阅读:
    关于正餐智能POS6.0.1.1改版后,点击反结账进入点菜界面后无法进行加菜的FAQ
    关于正餐智能POS6.0.1.1改版后,订单模块无法进行部分退款的FAQ
    退款证书相关
    简易付主副机和打印机配置
    秒点直连操作文档
    智能POS打印配置&常见问题FAQ 12-14 后期持续更新
    ERP主副机和打印机配置FAQ
    ASP.NET没有魔法——ASP.NET MVC Controller的实例化与执行
    ASP.NET没有魔法——ASP.NET MVC 路由的匹配与处理
    ASP.NET没有魔法——ASP.NET MVC路由
  • 原文地址:https://www.cnblogs.com/WindrunnerMax/p/14043455.html
Copyright © 2020-2023  润新知