JavaScript设计模式(二十六):永无尽头-链模式

简介: 永无尽头-链模式

链模式(Operate of Responsibility)

通过在对象方法中将当前对象返回,实现对同一个对象多个方法的链式调用。从而简化对该对象的多个方法的多次调用时,对该对象的多次引用。

深究jQuery(参考版本: 2.2.4版本)

jQuery的点语法是基于原型继承实现的,并且在每一个原型方法的实现上都返回当前对象this,使当前对象一直处于原型链作用域的顶端。

实现类似于jQuery的框架语法

<p id="text"></p>
// 实现目标如下:
let text = $('#text');

console.log(text); // jQuery.fn.init [p#text, context: document, selector: '#text']

console.log(text.size()); // 1

text
    .css({
   
   
        padding: '10px',
        'line-height': '30px',
        'background-color': 'red'
    })
    .attr('class', 'user-name')
    .html('ProsperLee')
    .on('click', function () {
   
   
        console.log('clicked');
    });

console.log(text); // jQuery.fn.init [p#text.user-name, context: document, selector: '#text']

原型式继承

let A = function () {
   
   
};

A.prototype = {
   
   
    length: 0,
    size: function () {
   
   
        return this.length;
    }
};

let a = new A();

console.log(a);             // A{}
console.log(a.__proto__);   // {length: 0, size: ƒ}

a.size();                   // 0
A.prototype.size();         // 0

A();        // undefined
A().size(); // 【Error】 Uncaught TypeError: Cannot read properties of undefined (reading 'size')
A.size();   // 【Error】 Uncaught TypeError: A.size is not a function

找位助手

此时因为A()返回的是undefined,所以使用A().size();访问不到.于是我们可以借助第三方对象进行访问

let A = function () {
   
   
    return B;
};

let B = A.prototype = {
   
   
    length: 0,
    size: function () {
   
   
        return this.length;
    }
};

A().size(); // 0

而在jQuery中,为了减少变量的定义,将想使用的变量作为了A的一个属性

let A = function () {
   
   
    return A.fn;
};

A.fn = A.prototype = {
   
   
    length: 0,
    size: function () {
   
   
        return this.length;
    }
};

A().size(); // 0

获取元素

jQuery的目的是为了获取元素,返回的是一组元素簇(元素的聚合对象),但现在返回的却是一个A.fn对象,显然达不到我们的需求;所以,如果A.fn能提供给我们一个获取元素的方法init就好了.

let A = function (selector) {
   
   
    return A.fn.init(selector);
};

A.fn = A.prototype = {
   
   
    init: function (selector) {
   
   
        return document.getElementById(selector);
    },
    length: 0,
    size: function () {
   
   
        return this.length;
    }
};

A('container'); // <div id="container"></div>
A('container').size(); // Error ---> Uncaught TypeError: A(...).size is not a function

两个问题

此时是无法访问size方法的,因为init返回的是DOM元素,故可以使用this解决

let A = function (selector) {
   
   
    return A.fn.init(selector);
};

A.fn = A.prototype = {
   
   
    init: function (selector) {
   
   
        this.selector = selector;
        this[0] = document.getElementById(selector);
        this.length = 1; // 重置length
        return this;
    },
    length: 0,
    size: function () {
   
   
        return this.length;
    }
};

A('container'); // {0: div#container, length: 1, selector: 'container', init: ƒ, size: ƒ}
A('container').size(); // 1

覆盖问题: box2覆盖了box1

let box1 = A('container1');
let box2 = A('container2');

console.log(box1); // {0: div#container2, length: 1, selector: 'container2', init: ƒ, size: ƒ}
console.log(box2); // {0: div#container2, length: 1, selector: 'container2', init: ƒ, size: ƒ}

覆盖获取

后获取id的DOM元素会将先获取id的DOM元素进行覆盖,是因为每次在A的构造函数中返回的A.fn.init(selector);对象指向同一个对象造成的

解决方法: 使用new关键字进行处理,让被赋值的变量都是独立的对象

let A = function (selector) {
   
   
    return new A.fn.init(selector);
};

A.fn = A.prototype = {
   
   
    init: function (selector) {
   
   
        this.selector = selector;
        this[0] = document.getElementById(selector);
        this.length = 1; // 重置length
        return this;
    },
    length: 0,
    size: function () {
   
   
        return this.length;
    }
};

let box1 = A('container1');
let box2 = A('container2');

console.log(box1); // {0: div#container1, length: 1, selector: 'container1', init: ƒ, size: ƒ}
console.log(box2); // {0: div#container2, length: 1, selector: 'container2', init: ƒ, size: ƒ}

box1.length; // 1
box1.size(); // Error ---> Uncaught TypeError: box1.size is not a function

方法丢失

因为我们使用new关键字,导致无法在init原型上找到size方法,导致方法丢失,所以访问size方法报错

let A = function (selector) {
   
   
    return new A.fn.init(selector);
};

A.fn = A.prototype = {
   
   
    init: function (selector) {
   
   
        console.log(this === A.fn);
        console.log(this === A.prototype);
        this.selector = selector;
        this[0] = document.getElementById(selector);
        this.length = 1; // 重置length
        return this;
    },
    length: 0,
    size: function () {
   
   
        return this.length;
    }
};

// 未使用new的
A.fn.init('container');     // true true

// 使用了new的,所以找不到size方法,解决方案请向下看
A('container');             // false false

对比jQuery

对比返回对象的构造器constructor

let A = function (selector) {
   
   
    return new A.fn.init(selector);
};

A.fn = A.prototype = {
   
   
    init: function (selector) {
   
   
        this.selector = selector;
        this[0] = document.getElementById(selector);
        this.length = 1; // 重置length
        return this;
    },
    length: 0,
    size: function () {
   
   
        return this.length;
    }
};

console.log($('#container').constructor.name);      // jQuery
console.log(A('container').constructor.name);       // init

解决方法: 故需要我们去强化构造器

let A = function (selector) {
   
   
    return new A.fn.init(selector);
};

A.fn = A.prototype = {
   
   
    constructor: A, // 强化构造函数,使构造器constructor返回A对象
    init: function (selector) {
   
   
        this.selector = selector;
        this[0] = document.getElementById(selector);
        this.length = 1; // 重置length
        return this;
    },
    length: 0,
    size: function () {
   
   
        return this.length;
    }
};

// 要访问size方法需要添加这一行
A.fn.init.prototype = A.fn; // 单独添加这一行会导致constructor返回的是Object

console.log($('#container').constructor.name);          // jQuery
console.log(A('container').constructor.name);       // A

A('container').size(); // 1

丰富元素获取

丰富功能,可以获取各种选择器返回的DOM元素


<div id="box0">
    <ul id="box1">
        <li class="item"></li>
        <li></li>
        <li class="item"></li>
    </ul>
    <div class="item"></div>
</div>
let A = function (selector, context) {
   
   
    return new A.fn.init(selector, context);
};

A.fn = A.prototype = {
   
   
    constructor: A,
    /**
     * 初始化
     * @param selector 选择器
     * @param context 上下文
     * @returns {A} 返回A对象
     */
    init: function (selector, context) {
   
   
        this.selector = selector;
        this.context = context || document;
        let doms = this.context.querySelectorAll(this.selector); // 获取元素集合
        this.length = doms.length;
        doms.forEach((dom, index) => this[index] = dom);
        return this;
    },
    length: 0,
    size: function () {
   
   
        return this.length;
    }
};

A.fn.init.prototype = A.fn;
/**
 * init {
 *      0: div#box0,
 *      selector: '#box0',
 *      context: document,
 *      length: 1
 * }
 */
console.log(A('#box0'));
/**
 * init {
 *      0: li.item,
 *      1: li.item,
 *      2: div.item,
 *      selector: '.item',
 *      context: document,
 *      length: 3
 * }
 */
console.log(A('.item'));
/**
 * init {
 *      0: li.item,
 *      1: li,
 *      2: li.item,
 *      selector: 'li',
 *      context: document,
 *      length: 3
 * }
 */
console.log(A('li'));
/**
 * init {
 *      0: li.item,
 *      1: li.item,
 *      selector: '.item',
 *      context: ul#box1,
 *      length: 2
 * }
 */
console.log(A('.item', document.getElementById('box1')));
/**
 * init {
 *      selector: 'xxx', 
 *      context: document, 
 *      length: 0
 * }
 */
console.log(A('xxx'));

数组与对象

由于JavaScript的弱类型语言,并且数组、对象、函数都被看成是对象的实例,所以JavaScript中并没有一个纯粹的数组类型,而且JavaScript引擎的实现也没有做严格的校验,也是基于对象实现的.

一些浏览器解析引擎在判断对象是否是数组的时候不仅仅判断其有没有length属性,可否通过[索引值]方式访问元素,还会判断其是否具有数组方法来确定是否要用数组的形式展现,所以我们只需要在A.fn中添加几个数组常用的方法来增强数组特性就可以解决问题了

let A = function (selector, context) {
   
   
    return new A.fn.init(selector, context);
};

A.fn = A.prototype = {
   
   
    constructor: A,

    // 增强数组(参见访问者模式)
    push: [].push,
    sort: [].sort,
    splice: [].splice,

    /**
     * 初始化
     * @param selector 选择器
     * @param context 上下文
     * @returns {A} 返回A对象
     */
    init: function (selector, context) {
   
   
        this.selector = selector;
        this.context = context || document;
        let doms = this.context.querySelectorAll(this.selector); // 获取元素集合
        this.length = doms.length;
        doms.forEach((dom, index) => this[index] = dom);
        return this;
    },
    length: 0,
    size: function () {
   
   
        return this.length;
    }
};

A.fn.init.prototype = A.fn;

console.log(A('#text')); // init [p#text.user-name, selector: '#text', context: document] ---> length: 1

方法拓展

对内部对象的拓展和对外部对象的拓展

let A = function (selector, context) {
   
   
    return new A.fn.init(selector, context);
};

A.fn = A.prototype = {
   
   
    constructor: A,

    // 增强数组
    push: [].push,
    sort: [].sort,
    splice: [].splice,

    /**
     * 初始化
     * @param selector 选择器
     * @param context 上下文
     * @returns {A} 返回A对象
     */
    init: function (selector, context) {
   
   
        this.selector = selector;
        this.context = context || document;
        let doms = this.context.querySelectorAll(this.selector); // 获取元素集合
        this.length = doms.length;
        doms.forEach((dom, index) => this[index] = dom);
        return this;
    },
    length: 0,
    size: function () {
   
   
        return this.length;
    }
};

A.fn.init.prototype = A.fn;

/**
 * 对象拓展 - 对内部对象的拓展和对外部对象的拓展
 * @type {A.extend}
 */
A.extend = A.fn.extend = function (...args) {
   
   
    // 如果只存在一个参数,那么是对内部对象进行拓展;
    // 如果是多个参数,那么第一个参数作为需要拓展的对象,为外部对象的拓展;
    let target = args.length <= 1 ? this : args[0];
    args.forEach(arg => {
   
   
        for (const k in arg) {
   
   
            target[k] = arg[k];
        }
    });
    return target;
};
A.extend({
   
   name: 'Tom'}, {
   
   age: 18}); // {name: 'Tom', age: 18}
A.extend(A.fn, {
   
   
    version: '1.0.0',
    getVersion: function () {
   
   
        return this.version;
    }
});
console.log(A('container').getVersion()); // 1.0.0
A.extend({
   
   
    version: 'v1.0.0',
    getVersion: function () {
   
   
        return this.version;
    }
});
console.log(A.getVersion()); // v1.0.0

添加方法并使用


<button class="btn">按钮01</button>
<button class="btn">按钮02</button>
<button class="btn">按钮03</button>
let A = function (selector, context) {
   
   
    return new A.fn.init(selector, context);
};

A.fn = A.prototype = {
   
   
    constructor: A,
    // 增强数组
    push: [].push,
    sort: [].sort,
    splice: [].splice,
    /**
     * 初始化
     * @param selector 选择器
     * @param context 上下文
     * @returns {A} 返回A对象
     */
    init: function (selector, context) {
   
   
        this.selector = selector;
        this.context = context || document;
        let doms = this.context.querySelectorAll(this.selector); // 获取元素集合
        this.length = doms.length;
        doms.forEach((dom, index) => this[index] = dom);
        return this;
    },
    length: 0,
    size: function () {
   
   
        return this.length;
    }
};

A.fn.init.prototype = A.fn;
/**
 * 对象拓展 - 对内部对象的拓展和对外部对象的拓展
 * @type {A.extend}
 */
A.extend = A.fn.extend = function (...args) {
   
   
    // 如果只存在一个参数,那么是对内部对象进行拓展;
    // 如果是多个参数,那么第一个参数作为需要拓展的对象,为外部对象的拓展;
    let target = args.length <= 1 ? this : args[0];
    args.forEach(arg => {
   
   
        for (const k in arg) {
   
   
            target[k] = arg[k];
        }
    });
    return target;
};
A.extend({
   
   
    // 将'-'分割线转化为驼峰式,如:'border-color' -> 'borderColor'
    camelCase: function (str) {
   
   
        return str.replace(/-(\w)/g, function (all, letter) {
   
   
            return letter.toUpperCase();
        });
    },

});

A.fn.extend({
   
   
    css: function (obj) {
   
   
        const len = this.length;
        for (let i = 0; i < len; i++) {
   
   
            for (const k in obj) {
   
   
                this[i].style[A.camelCase(k)] = obj[k];
            }
        }
        return this;
    },
    attr: function (key, value) {
   
   
        const len = this.length;
        for (let i = 0; i < len; i++) {
   
   
            this[i].setAttribute(key, value);
        }
        return this;
    },
    html: function (htmlStr) {
   
   
        const len = this.length;
        for (let i = 0; i < len; i++) {
   
   
            this[i].innerHTML = htmlStr;
        }
        return this;
    },
    on: function (type, fn) {
   
   
        const len = this.length;
        for (let i = 0; i < len; i++) {
   
   
            this[i].addEventListener(type, function (e) {
   
   
                fn.call(null, e);
            }, false);
        }
        return this;
    },
});

image.png

let btns = A('.btn');

btns
    .css({
   
   
        padding: '3px 10px',
        borderRadius: '3px',
        borderStyle: 'solid',
        'border-width': '1px',
        'border-color': 'red',
        color: 'red'
    })
    .attr('class', 'btn btn-item')
    .html('按钮喽~~~')
    .on('click', function (e) {
   
   
        console.log(e.target);
    });

/**
 * init(3) [
 *      button.btn.btn-item,
 *      button.btn.btn-item,
 *      button.btn.btn-item,
 *      selector: '.btn',
 *      context: document,
 *      length: 3
 * ]
 */
console.log(btns);

函数、函数简写、箭头函数区别

let obj = {
   
   
    fnA: function () {
   
   
        // console.log(fnA); // Error ---> Uncaught ReferenceError: fnA is not defined
        console.log(this);   // obj
    },
    fnB() {
   
   
        // console.log(fnB); // Error ---> Uncaught ReferenceError: fnB is not defined
        console.log(this);   // obj
    },
    fnC: function fnD() {
   
   
        // console.log(fnC); // Error ---> Uncaught ReferenceError: fnC is not defined
        console.log(fnD);    // ƒ fnD() { ... }
        console.log(this);   // obj
    },
    fnE: () => {
   
   
        // console.log(fnE); // Error ---> Uncaught ReferenceError: fnE is not defined
        console.log(this);   // window
    }
};

obj.fnA();
obj.fnB();
obj.fnC();
obj.fnE();
目录
相关文章
|
13天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
2月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
15天前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###
|
8天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
24 1
|
19天前
|
前端开发 JavaScript UED
探索JavaScript中的异步编程模式
【10月更文挑战第21天】在数字时代的浪潮中,JavaScript作为一门动态的、解释型的编程语言,以其卓越的灵活性和强大的功能在Web开发领域扮演着举足轻重的角色。本篇文章旨在深入探讨JavaScript中的异步编程模式,揭示其背后的原理和实践方法。通过分析回调函数、Promise对象以及async/await语法糖等关键技术点,我们将一同揭开JavaScript异步编程的神秘面纱,领略其带来的非阻塞I/O操作的魅力。让我们跟随代码的步伐,开启一场关于时间、性能与用户体验的奇妙之旅。
|
7天前
|
前端开发 JavaScript UED
探索JavaScript的异步编程模式
【10月更文挑战第33天】在JavaScript的世界里,异步编程是提升应用性能和用户体验的关键。本文将带你深入理解异步编程的核心概念,并展示如何在实际开发中运用这些知识来构建更流畅、响应更快的Web应用程序。从回调函数到Promises,再到async/await,我们将一步步解锁JavaScript异步编程的秘密,让你轻松应对各种复杂的异步场景。
|
1月前
|
设计模式 Java Kotlin
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
本教程详细讲解Kotlin语法,适合希望深入了解Kotlin的开发者。对于快速学习Kotlin语法,推荐查看“简洁”系列教程。本文重点介绍了构建者模式在Kotlin中的应用与改良,包括如何使用具名可选参数简化复杂对象的创建过程,以及如何在初始化代码块中对参数进行约束和校验。
21 3
|
1月前
|
JavaScript 前端开发 API
探索Node.js中的异步编程模式
【10月更文挑战第4天】在JavaScript的世界中,异步编程是提升应用性能和用户体验的关键。本文将深入探讨Node.js中异步编程的几种模式,包括回调函数、Promises、async/await,并分享如何有效利用这些模式来构建高性能的后端服务。
|
1月前
|
JavaScript 前端开发 调度
探索Node.js中的异步编程模式
在Node.js的世界里,异步编程是核心。本文将带你深入了解异步编程的精髓,通过代码示例和实际案例分析,我们将一起掌握事件循环、回调函数、Promises以及async/await等关键概念。准备好迎接挑战,让你的Node.js应用飞起来!
|
1月前
|
设计模式 JavaScript 前端开发
JavaScript设计模式--访问者模式
【10月更文挑战第1天】
30 3