JavaScript对象创建模式与继承

  • 命名空间模式
  • 声明依赖关系
  • 私有化
  • 模块模式
  • 沙箱模式
  • 静态成员
  • 链模式
  • 代码复用与继承

 一、命名空间模式

1.1什么是命名空间模式?为什么要使用命名空间模式?

命名空间:用来组织和重用代码,区别不同代码之间的功能。简单的理解就是将不同功能的PIA名称按照指定结构和规律存储到一个列表中,并且所有API名称对应指向其实际地址。

命名空间的作用:避免命名冲突、减少过长的名字前缀、防止污染全局变量。

1.2命名空间模式的实现

 1 var MYAPP = MYAPP || {};
 2 MYAPP.namespace = function(ns_string){
 3     var parts = ns_string.split('.'),
 4         parent = MYAPP,
 5         i;
 6     if(parts[0] === "MYAPP"){
 7         parts = parts.slice(1);
 8     }
 9     for( i = 0; i < parts.length; i++){
10         if(typeof parent[parts[i]] === "undefined"){
11             parent[parts[i]] = {};
12         }
13         parent = parent[parts[i]];
14     }
15     return parent;
16 }

测试示例:

var module2 = MYAPP.namespace('MYAPP.modules.module2');
MYAPP.namespace('modules.module51')
MYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property');

查看命名空间:

 二、声明依赖关系

所谓声明依赖就是声明一个变量指向某个对象或模块,通过这个变量保持对这个对象的引用关系,即保持变量依赖对象的关系。

function(){
    let m2 = MYAPP.modules.module2;
    let once_upon = MYAPP.once.upon;
    //...
}

声明依赖关系的有点:

2.1通过显式依赖可以在实际的功能模块上明确的了解改模块所依赖的脚本或模块的信息;

2.2可以提高解析速度。

 三、私有化

JavaScript中在ES6之前没有私有成员的语法,但可以通过闭包来实现这一功能,ES6之后在2019年1月通过的方案开始支持私有成员语法。

function Gadget(){
    //私有成员
    let name = 'iPod';
    //公有函数
    this.getName = function(){
        return name;
    }
}

以上的私有化成员变量模式当私有成员为引用类型时就会失效,针对这种情况有两种解决方案:一是最低授权原则,即在对应的接口上只暴露需要暴露的成员;二是通过克隆的方式将每次需要暴露的成员暴露出去。

以上基于闭包实现了基础的私有化模式,但私有成员会在每次基于构造函数实例化对象时都会创建一个属于实例自己的私有成员,这时候可以基于对象原型来实现私有化:

Gadget.prototype = (function(){
    //私有成员
    let browser = "Mobile Webkit"return {
        getBrowser:function(){
            return break;
        }
    }
}());

将私有方法揭示为公共方法:就是将私有方法暴露出来,并且保证暴露出来的公共API被外部修改依然不会导致私有方法任何变化。

(function(){
    function fun(){}
    return {
        fun:fun
    }
}());    

 四、模块模式

所谓模块模式,就是将程序分隔成若干个独立的功能模块,基于模块的相互依赖和公共接口搭建起整个应用,模块内部与外部是完全独立,其目的是为了实现高内聚低耦合的程序设计思想,以保证程序可以在局部做修改实现迭代更新而不影响整个程序的可维护性。

关于模块模式在ES6中已经产生了新的原生语法,在最新的浏览器中已经可以基于这些原生语法实现,但由于需要考虑向后兼容,学习基于ES6之前的语法设计相似的模块功能实现兼容。

关于模块模式的具体实现详细内容可以参考之前的博客:javascript的作用域和闭包(三)闭包与模块

模块模式的具体思路:

a.基于闭包私有化实现模块的独立性;b.基于IIFE暴露接口;c.基于函数传参依赖模块;

MYAPP.utilities.module = (function(app,global){
    //模块内部的具体实现
    return {};//暴露接口      
}(MYAPP, this));

 五、沙箱模式

沙箱模式最先起源于操作系统的安全系统,其原理就是在系统的安全接口上包装一个隔离层,这个隔离层负责过滤不可靠的用户发送过来请求消息,让可靠的用户请求通过调用系统接口。

沙箱模式在很多可能出现不可靠的接口上都可以发挥其作用,比如接口参数与访问用户的验证、阻止局部变量污染全局、隐藏模块内部的具体实现逻辑。

MYAPP.utilities.module = (function(app,global){
    //模块内部的具体实现
    return {
        api:function(parameters){
            let parameters = (function parameterSandbox(parameters){
                //检查参数
                return securityParameters
            }(parameters));
            //执行接口的具体功能
        }
    };//暴露接口      
}(MYAPP, this));

通过沙箱模式还可以解决命名空间模式的缺点,比如在一个项目上同时引以来了一个程序的多个版本,可能两者都需要同一个全局符号,这时候就可以基于沙箱模式区分出不同的版本;还有对于以点分隔的命名模式,如果在每次使用时都输入完整的名称显得不那么友好,可以通过沙箱模式实现只需要输入名称的简称检索到对应的实际引用。

从某种意义上来来讲,立即执行函数(IIFE)就是一个沙箱模式。

 六、静态成员

在JavaScript中ES6之前没有特定的语法定义静态成员,所谓静态成员就是从一个实例到另一个实例不会发生改变的属性和方法。在JavaScript中可以通过闭包和构造函数等方式添加静态成员,ES6中可以基于Class定义静态成员。

公有静态成员示例:将静态成员作为构造函数的属性,模拟实现静态特性。

let Gadget = function(price){
    this.price = price;
}
//静态方法
Gadget.isShiny = function(){
    let msg = "you bet";
    if(this instanceof Gadget){
        msg += ", it costs $" + this.price + '!';
    }
    return msg;
}
//向原型上添加一个普通方法
Gadget.prototype.isShiny = function(){
    return Gadget.isShiny.call(this);
}

Gadget.isShiny(); //"you bet"
let a = new Gadget("$49.99");
a.isShiny(); //"you bet, it costs $$49.99!"

私有成员示例:将静态成员和构造函数放在同一个闭包内,模拟实现静态特性。

let Gadget = (function(){
    //静态属性
    let counter = 0,
        NewGadget;
    //构造函数
    NewGadget = function(){
        counter ++;
    }
    //特权方法
    NewGadget.prototype.getLastId = function(){
        return counter;
    }
    return NewGadget;
}());

静态属性可以带来很多便利,他们可以包含非实例的相关属性和方法,并且不会为每个实例创建新的静态属性。

 七、链模式

关于链模式在一个非常著名的JS库中非常常见,就是JQuery。

链模式的优点:1.减少重复的代码,是代码更容易阅读,即设计模式的可读性;2.基于链模式可以将一个复杂的函数分隔成多个特定功能的函数,可以提高代码的可维护性。

let obj = {
    value : 1,
    add:function(){
        this.value++;
        return this;
    },
    shout:function(){
        console.log(this.value);
        return this;
    }
}
undefined
obj.shout().add().shout();

 八、代码复用与继承

谈到代码复用第一个想到的就是继承,这是因为面向对象编程思想的经典特性之一的类对我们当下的编程产生了深刻的影响,在JavaScript中的代码复用继承依然是主要形式,当然也会有一些其他形式,既然是代码复用,就是最求最大可能将可以复用的成员添加到原型中,所以后面在考虑一个代码复用模式是否优秀主要围绕这个话题展开,然看在考虑在满足这个条件的情况下是否会带来新的问题。

首先我们对继承方式实现代码复用做一个预期,详情参考代码:

//父级构造函数
function Parent(name){
    this.name = name || 'Adam';
}
//父级原型上添加方法
Parent.prototype.say = function(name){
    return this.name;
}
//空白的子构造函数
function Child(name){}
//实现继承
inherit(Child, Parent);

在后面关于继承实现代码复用都是基于这个预期来实现inherit,然后考虑其优劣及适应性。

8.1类式继承模式——默认模式:

function inherit(c, p){
    c.prototype = new p();
}
//测试
inherit(Child, Parent);
let kid = new Child();
kid.say();//"Adam"

缺点:默认模式所继承的实际上是两个对象,一个是父级原型,另一个是父级对象本身;由于父级对象构造时不支持传参可能会导致原本在父级上拥有的成员不能实现继承。

8.2类式继承模式——借用构造函数:

function Child(){
    //借用父级构造函数构造自身的成员
    Parent.apply(this,arguments);
    //...
}

优缺点:无法继承父级原型上的成员;优点是可以获得父级自身成员的副本,并且不会在对象意外覆盖父对象属性的风险;继承父级自身上的成员成为了直接的属性和方法,而不是按照预期的作为原型成员。

8.3类式继承模式——借用和设置原型:

function Child(){
    Parent.apply(this,arguments);
}
function inherit(c, p){
    c.prototype = new p();
}

优缺点:借用和设置原型是前面两种模式的总和,所以其优缺点都被综合的承接了,详细参考前面的介绍。

8.4类式继承模式——共享原型:

function inherit(c, p){
    c.prototype = p.prototype;
}

共享原型有一个前提,就是默认所有要继承的成员都在父级的原型上。

优缺点:能完全继承所有成员,并且保证所有继承的成员都在原型上,但这也是它的缺点,必须保证需要继承的成员在父级的原型上,并且子对象可能发生修改父级原型的风险。

8.5类式继承模式——临时构造函数

let inherit = (function(){
   function F(){};
    return function(c, p){
        F.prototype = P.prototype;
        C.prototype = new F();
        C.prototype.constructor = C;
        C.prototype.uber = P.prototype
    }
}());

临时构造函数又被称为圣杯模式,在之前的JS对象与类中有详细的介绍,可以参考这篇博客:深入JavaScript对象(Object)与类(class),详细了解类、原型

这里我们重点类分析以下关于圣杯模式的优劣:圣杯模式也是从共享原型演变而来,但完美的避免了子对象修改父级原型的风险,而且还可以在实际应用中选择是否继承父级自身的成员,如由必要就可以在使用字类构造对象时将借用构造函数引入子类构造函数。

8.6JavaScript中实现代码复用的其他模式:

Klass:在js语法的基础上约定一种代码结构或形式实现代码复用,这种方式也被称为语法糖,这种方式在ES6中的Class中就有应用,约定的形式非常多就不逐一介绍。

基于call、apply、bind实现方法借用,这是非常常用的JS自带特性和功能实现的代码复用模式,详细应用和其他关于这些方法的内容可以参考:

源码来袭:call、apply手写实现与应用

源码来袭:bind手写实现

 

posted @ 2021-12-22 17:08  他乡踏雪  阅读(31)  评论(0编辑  收藏  举报