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();
目录
相关文章
|
12天前
|
设计模式 前端开发 JavaScript
【JavaScript 技术专栏】JavaScript 设计模式与实战应用
【4月更文挑战第30天】本文探讨JavaScript设计模式在提升开发效率和代码质量中的关键作用。涵盖单例、工厂、观察者、装饰器和策略模式,并通过实例阐述其在全局状态管理、复杂对象创建、实时数据更新、功能扩展和算法切换的应用。理解并运用这些模式能帮助开发者应对复杂项目,提升前端开发能力。
|
1月前
|
设计模式 SQL 算法
设计模式了解哪些,模版模式
设计模式了解哪些,模版模式
22 0
|
2月前
|
设计模式 Java uml
C++设计模式之 依赖注入模式探索
C++设计模式之 依赖注入模式探索
56 0
|
1月前
|
设计模式 Java 数据库
小谈设计模式(2)—简单工厂模式
小谈设计模式(2)—简单工厂模式
|
10天前
|
设计模式 前端开发 Java
19:Web开发模式与MVC设计模式-Java Web
19:Web开发模式与MVC设计模式-Java Web
20 4
|
12天前
|
JavaScript 前端开发 IDE
【JavaScript与TypeScript技术专栏】JavaScript与TypeScript混合编程模式探讨
【4月更文挑战第30天】本文探讨了在前端开发中JavaScript与TypeScript的混合编程模式。TypeScript作为JavaScript的超集,提供静态类型检查等增强功能,但完全切换往往不现实。混合模式允许逐步迁移,保持项目稳定性,同时利用TypeScript的优点。通过文件扩展名约定、类型声明文件和逐步迁移策略,团队可以有效结合两者。团队协作与沟通在此模式下至关重要,确保代码质量与项目维护性。
|
15天前
|
设计模式 消息中间件 Java
Java 设计模式:探索发布-订阅模式的原理与应用
【4月更文挑战第27天】发布-订阅模式是一种消息传递范式,被广泛用于构建松散耦合的系统。在 Java 中,这种模式允许多个对象监听和响应感兴趣的事件。
35 2
|
18天前
|
设计模式 存储 JavaScript
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
|
18天前
|
设计模式 Java Go
[设计模式Java实现附plantuml源码~创建型] 集中式工厂的实现~简单工厂模式
[设计模式Java实现附plantuml源码~创建型] 集中式工厂的实现~简单工厂模式
|
20天前
|
设计模式
设计模式(一)简单工厂模式
设计模式(一)简单工厂模式
14 0