《JavaScript 模式》读书笔记(4)— 函数4

这篇文章我们主要来学习下即时对象初始化、初始化时分支、函数属性-备忘模式以及配置对象。这篇的内容会有点多。六、即时对象初始化保护全局作用域不受污染的另一种方法,即时对象初始化模式。这种模式使用带有

大家好,又见面了,我是你们的朋友全栈君。

  这篇文章我们主要来学习下即时对象初始化初始化时分支、函数属性-备忘模式以及配置对象。这篇的内容会有点多。

 

六、即时对象初始化

  保护全局作用域不受污染的另一种方法,即时对象初始化模式。这种模式使用带有init()方法的对象,该方法在创建对象后将会立即执行。init()函数需要负责所有的初始化任务。

({
    // 在这里可以定义设定值
    // 又名配置常数
    maxwidth:600,
    maxheight:400,

    // 还可以定义一些实用的方法
    gimmeMax:function () {
        return this.maxwidth + "x" + this.maxheight;
    },
    
    // 初始化
    init:function() {
        console.log(this.gimmeMax());
        // 更多初始化任务
    }
}).init()

  就语法而言,对待这种模式就像在使用对象字面量创建一个普通的对象。也可以将字面量包装到括号中(分组操作符),它指示JavaScript引擎将大括号作为对象字面量,而不是作为一个代码块(也不是if或者for循环)。在该括号结束之后,可以立即调用init()方法。

  这两种方法都可以运行:

({...}).init();
({...}.init());

  这种方法可以在执行一次性初始化任务时,保护全局命名空间。

  与仅仅将一堆代码包装到匿名函数的方法相比,这种模式看起来涉及更多的语法特征,但是如果初始化任务更加复杂,它会使整个初始化过程显得更有结构化。比如,私有帮助函数是非常清晰可辩别的,因为他们是临时对象的属性,而在即时函数模式中,他们就很可能只是分散在各处的函数而已。

  这种模式主要适用于一次性的任务,而且在init()完毕后也没有对该对象的访问,如果想要在init()完毕后保存对该对象的一个引用,可以通过在init()尾部添加”return this;”语句实现该功能。

var a = ({
    // 在这里可以定义设定值
    // 又名配置常数
    maxwidth:600,
    maxheight:400,

    // 还可以定义一些实用的方法
    gimmeMax:function () {
        return this.maxwidth + "x" + this.maxheight;
    },
    
    // 初始化
    init:function() {
        console.log(this.gimmeMax());
        // 更多初始化任务
        return this;
    }
}).init()
console.log(a.maxheight)

 

七、初始化时分支

  初始化时分支(Init-time branching,也称为加载时分支Load-time branching)是一种优化模式。当知道某个条件在整个程序声明周期内都不会发生改变的时候,仅对该条件测试一次时很有意义的。浏览器嗅探就是一个典型的例子。

  查明DOM元素的计算样式或附加的事件处理程序是另外一个可以受益于初始化时分支模式的场景。绝大多数程序开发员都已经编写过这样的代码,至少有一次在他们的客户端编程生命周期内,既可用于附加或删除事件监听器的工具:

// 之前
var utils = {
    addListener: function(el,type,fn) {
        if(typeof window.addEventListener === 'function') {
            el.addEventListener(type,fn,false);
        } else if(typeof document.attachEvent === 'function') { //'IE'
            el.attachEvent('on' + type,fn);
        } else {
            el['on' + type] = fn;
        }
    },
    removeListener:function(el,type,fn){
        // 几乎一样
    }
}

  此段代码效率低下。每次在调用utils.addListener()或utils.removeListener()时,都会重复执行相同的检查。

  当使用初始化分支的时候,可以在脚本初始化加载时一次性探测出浏览器特征。此时,可以在整个页面生命周期内重定义函数运行方式:

// 之后
var utils = {
    addListener:null,
    removeListener:null
};
//实现
if(typeof window.addEventListener === 'function') {
    utils.addListener = function(el,type,fn){
        el.addEventListener(type,fn,false);
    };
    utils.removeListener = function(el,type,fn){
        el.removeEventListener(type,fn,false);
    };
} else if(typeof document.attachEvent === 'function') { //判断为IE浏览器
    utils.addListener = function(el,type,fn){
        el.attachEvent('on' + type,fn);
    };
    utils.removeListener = function(el,type,fn){
        el.detachEvent('on' + type,fn);
    };
} else { //更早的版本
    utils.addListener = function(el,type,fn){
        el['on' + type] = fn;
    };
    utils.removeListener = function(el,type,fn){
        el['on' + type] = null;
    };
}

  其实,简单来说,个人理解,分支初始化的意义就在于:把重复的事情仅做一次。

 

八、函数属性—备忘模式

  函数是对象,因此它们具有属性。事实上,它们确实还有属性和方法。比如,对于每一个函数,无论使用什么样的语法来创建它,它都会自动获得一个length属性,其中包含了该函数期望的参数数量。

function func(a, b, c) {}
console.log(func.length); //3

  可以在任何时候将自定义属性添加到你的函数中。自定义属性的其中一个用例是缓存函数结果(即返回值),因此,在下一次调用该函数时就不用重做潜在的繁重计算。缓存函数结果,也被称为备忘。

var myFunc = function (param) {
    if(!myFunc.cache[param]) {
        var result = {};
        // ... 开销很大的操作 ...
        myFunc.cache[param] = result;
    }
    return myFunc.cache[param];
};

// 缓存存储
myFunc.cache = {};

  上面的例子中,函数myFunc创建了一个属性cache,该属性可以通过myFunc.cache像通常那样进行访问。cache属性是一个对象,其中使用传递给函数的参数param作为键,而计算结果作为值。计算结果可以是需要的任意复杂数据结构。

  上面的代码假定该函数只需要一个参数param,并且它是一个基本数据类型。如果有更多以及更复杂的参数,对此的通用解决方案是将它们序列化。即,可以将参数对象序列化为一个JSON字符串,并使用该字符串作为cache对象的键:

var myFunc = function () {
    var cachekey = JSON.stringify(Array.prototype.slice.call(arguments)),
        result;
    if(!myFunc.cache[cachekey]) {
        result = {};
        // ... 开销很大的操作 ...
        myFunc.cache[cachekey] = result;
    }
    return myFunc.cache[cachekey];
};

// 缓存存储
myFunc.cache = {};

  请注意,在序列化的过程中,对象的“标识”将会丢失。如果有两个不同的对象并且恰好都具有相同的属性,这两个对象将会共享同一个缓存条目。

  编写前面的函数的另一种方法是使用arguments.callee来引用该函数,而不是使用硬编码函数名称。虽然在目前这是可行的,但是在ES5的严格模式中并不支持arguments.callee。

var myFunc = function (param) {
    var f = arguments.callee,
        result;
    if(!f.cache[param]) {
        result = {};
        // ... 开销很大的操作 ...
        f.cache[param] = result;
    }
    return f.cache[param];
};

// 缓存存储
myFunc.cache = {};

 

九、配置对象

  配置对象模式(configuration object pattern)是一种提供更简洁API的方法,尤其是在建立一个库或任何将被其他程序使用的代码的情况。

  其实配置对象的概念很简单,或许你已经在开发工作中使用了:

// 由于软件的开发是不断推进的,需求必然会频繁变更。
// 假设,我们正在编写一个addPerson()的函数,该函数接受人员的名和姓作为参数。
function addPerson(first,last){
    //...
};

// 后来,又要加入人员的性别,住址,电话啥的,住址和电话是可选的
// 这里把可选的参数放在末尾
function addPerson(first,last,gender,address,phone){
    //...
};

// 然后,又需要直到用户名,注册的日期等,这是必填的。
// 于是,就是这样了
function addPerson(first,last,gender,address,phone,username,addDate){
    //...
};

// 这时候,可选的参数,也必须要传递了
addPerson('first','last','gender',null,null,'username','addDate');

// 这个参数列表变得越来越长,越来越无法维护

  所以,我们可以仅使用一个参数来代表所有参数,让我们将该参数对象成为conf,即“配置”的意思。

addPerson(conf);

  然后,该函数的使用者可以这样做:

var conf = {
    username:'zaking',
    first:"Bruce",
    last:"Wayne"
};
addPerson(conf);

  配置对象的优点在于:

  • 不需要记住众多的参数以及其顺序。
  • 可以安全忽略可选参数。
  • 更加易于阅读和维护。
  • 更加易于添加和删除参数。

  缺点是这样的:

  • 需要记住参数名称。
  • 属性名称无法被压缩。

  当函数创建DOM元素时,这种模式可能是非常有用的,例如,可以用在设置元素的CSS样式中,因为元素和样式可能具有大量的可选特征和属性。

 

  这篇文章的内容就到这里了,都是一些小的point,但是其实也很有必要去学习一下,下一章将会是函数部分的最后一章,我们会聊一聊“Curry化”。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/124390.html原文链接:https://javaforall.net

(0)
上一篇 2022年3月25日 下午4:35
下一篇 2022年3月25日 下午4:35


相关推荐

  • 计算机信息计量单位中的1k代表多少字节,1k等于多少字节

    计算机信息计量单位中的1k代表多少字节,1k等于多少字节一、1K字节等于多少字节1KB=1024B,其中1024=2的10次方。字节(Byte/bait/n.)是计算机信息技术用于计量存储容量的一种计量单位,通常情况下一字节等于八位,也表示一些计算机编程语言中的数据类型和语言字符。国际单位制(SI)1KB=1024B;1MB=1024KB=1024*1024B。1B(byte,字节)=8bit;1KB(Kilobyte,千字节)=1000B…

    2022年5月26日
    220
  • anaconda3如何卸载干净「建议收藏」

    anaconda3如何卸载干净「建议收藏」最近跑代码的时候老出现各种错误,因为之前卸载过一次anaconda,所以猜测可能是没有卸载干净,所以又重新卸载了一遍,为了防止再次出现卸载不干净的情况,找了很久,终于从官网上找到了能够卸载干净的方法。安装Anaconda-Cleanpackage 打开AnacondaPrompt,输入命令行: condainstallanaconda-clean 接着输入命令行进行卸载…

    2022年6月29日
    299
  • 微信小程序轮播图片自适应[通俗易懂]

    微信小程序轮播图片自适应[通俗易懂]微信小程序轮播图片自适应//xml代码<viewclass=”rotation”><swiperclass=”home-swiper”bindchange=”bindchange”style=”height:{{imgheights[current]}}rpx;”><blockwx:for-items=”{{lunboData}}”wx:key=”{{index}}”><swiper-item>

    2022年5月11日
    37
  • Python 逻辑运算符

    Python 逻辑运算符Python 逻辑运算符可以用来操作任何类型的表达式 不管表达式是不是 bool 类型 同时 逻辑运算的结果也不一定是 bool 类型 它也可以是任意类型 这点是和 C C 等语言不同的 C C 语言中逻辑运算符的结果就是 bool 类型 即 true 或者 false 下面来一一介绍 1 not 逻辑非结果只有 True 和 False not 可以对符号右侧的值进行非运算 1 对于布尔值 非运算会对其进行取反操作 True 变 False False 变 True 2 对于非布尔值 非运算会先将其转

    2026年3月20日
    1
  • linux下修改docker容器RabbitMQ端口映射(修改RabbitMQ默认端口)

    linux下修改docker容器RabbitMQ端口映射(修改RabbitMQ默认端口)1.xshell执行以下命令获取docker容器IDdockerps-a2.修改/var/lib/docker/containers/{容器ID+一些字符串}/hostconfig.json中”PortBindings”:{“容器端口/tcp”:[{“HostIp”:””,”HostPort”:”改成你要改的端口”}]3.修改并上传配置文件后执行以下代码sy…

    2025年7月14日
    7
  • JAVA数组的定义及用法

    JAVA数组的定义及用法

    2021年11月24日
    43

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号