Javascript模式(第四章函数)------读书笔记

一 背景

js函数的两个特点:1 函数是第一类对象(first-class object);2 函数可以提供作用域

  1 函数是对象:

    1 函数可以在运行时动态创建,还可以在程序执行过程中创建

    2 可以被赋值给变量,还可以被删除

    3 可以作为参数传递给别的函数,可以作为返回值,被别的函数返回,

    4 可以拥有自己的属性和方法

  2 由于JS没有块级作用域的概念,因此在涉及到控制变量作用域的时候,函数是必不可少的工具

  1.1 消除术语歧义:我们来看一下命名函数表达式、你们函数表达式以及函数声明的定义

    1 一般的函数表达式即为匿名函数表达式,简称匿名函数,即var add=function(){};

    2 命名函数表达式:var add1=function add(){};

    3 命名函数表达式是函数表达式的一种特殊情况,命名函数表达式与函数表达式的区别在于:其name属性

    4 函数声明:function add(){}

var add1=function add(){};//命名函数表达式
console.log(add1.name);//add

//函数表达式/匿名函数表达式/匿名函数
var add2=function (){};
console.log(add2.name);//""

//函数声明
function add3(){}
console.log(add3.name);//add3

  1.2 声明VS表达式:名称与变量声明提升

  函数声明只能出现在“程序代码”中,即函数声明只能存在于其他函数体内或者全局空间中,它们不能分配给变量或者某个属性,也不能作为参数出现在函数调用中

//命名函数表达式
callMe(function me(){
});

//匿名函数表达式
callMe(function(){
});

//函数表达式
var obj={
   say:function(){
    }
};

  1.3 函数的name属性

  函数的name属性是只读的

  name属性的用途:调试代码的时候,可以根据name属性作为一个标识符;可以用于自身的递归运算

  1.4、函数的提升

  虽然函数声明与函数命名表达式很相似,但是二者之间还是有很大区别的,这个区别就是函数的提升

  我们知道对于所有变量,无论是在函数体的何处进行的声明,都会在后台被提升到函数的顶部,函数声明也一样,无论函数声明在何处,都会被提升到顶部,

  而函数表达式不会得到提升,下面我们来看一个例子

var name="Jim";
fun();//函数声明!
var fun=function(){
    alert("函数表达式!");
}
fun();//函数表达式!
function fun(){
    alert("函数声明!");
}
fun();//函数表达式! 

二 回调模式

  2.1 回调模式:将函数A作为参数传递给函数B,且A在函数B中得到执行,那么函数A就被称之为回调函数,该模式称之为回调模式

function A(){
    console.log("I am a callback !");
}
function B(callback){
    if(typeof callback!=="function"){
        callback=false;
    }
    if(callback){
        callback();
    }
}
B(A);//I am a callback ! 

  2.2 回调示例

  假设我们需要抓取页面上的DOM树,并返回相应的DOM数组,对该数组里的DOM进行操作,例如隐藏

  根据函数的通用性,一个函数是找到相应的DOM,并返回数组(findNodes),另外一个函数是隐藏功能(hide)

  先执行findNodes,然后获取到返回的DOM数组,再进行for循环进行遍历,隐藏,效率有些低下,我们采取回调模式

  

var findNodes(callback){
    var nodes=[],found;
    if(typeof callback!="function"){
        callback=false;
    }
    while(条件为true){
        find;//......查找相关节点的操作
        if(callback){
            callback(found);
        }
    }
    return nodes;
};
findNodes(hide);

  2.3、回调模式与作用域

问题:在某些情况下,我们的回调函数不是一次性的匿名函数也不是全局函数,而是某个对象的方法,如果在该方法中使用了this来引用它所属的对象,将会出现以下问题

color="black";
//自定义对象myapp
var myapp={
    color:"red",
    paint:function(arg){
        arg=this.color;
        //将myapp的color赋值给参数的color属性
        console.log(arg);
    }
};

function B(callback){
    if(typeof callback!=="function"){
        callback=false;
    }
    var B_color={};
    if(callback){
        callback(B_color);
    }
}
B(myapp.paint);//black
//由于B函数是一个全局函数,因此,对象中的this指向全局对象window,而不是预期的myapp 

解决方案:将回调函数及其所属对象一并传递给函数B

color="black";
var myapp={
         color:"red",
         paint:function(arg){
                   arg=this.color;
                   console.log(arg);
         }
};
//将回调函数callback与回调函数所属的对象一并传递进去
function B(callback,callback_obj){
         var B_color;
         if(typeof callback==="function"){
                   callback.call(callback_obj,B_color);
                   //注意,这里不再只是单纯的直接调用回调函数,而是回调函数所属的对象对回调函数加以调用            
         }
}

B(myapp.paint,myapp);//red
 

进一步优化方案:由上面可以看出,在调用myapp的paint方法时,需要输入两次对象名myapp,我们可以对其进行如下优化,将该方法作为字符串来传递,无需两次输入该对象的名称

function B(callback,callback_obj){
    var B_name={};
    if(typeof callback==="string"){
        callback=callback_obj[callback];
    }
    if(typeof callback==="function"){
        callback.call(callback_obj,B_name);
    }
}
B("paint",myapp);//red

六、回调函数的用途

  1 异步事件监听器

  例如:给页面元素提供一个回调函数的指针,使得当该事件触发时可以得到调用,该模式支持异步方式,即允许以乱序的方式运行

  2  超时调用

  当使用window提供的超时方法:setTimeout和setInterval中的参数是一个函数指针,也使用了回调模式,在某一个时刻触发

3  JS库中回调模式的应用

  在设计JS库的时候,回调模式可以帮助JS库实现通用性,使开发者不必预测和实现每一个功能

  因为一方面过多的功能会使JS库过于庞大,另一方面,有些功能可以绝大多数用户永远也不会使用到。

  因此在开发js库的时候:专注于核心功能的开发,提供“挂钩”形式的回调函数,使得JS库可以很容易的扩展。

三 返回函数:函数是一个对象,因此可以作为返回值

  1   应用场景:一个函数执行一部分工作,这些工作可能包含一些一次性的初始化,后续调用它的时候,是其返回值,其返回值也是一个函数,后续操作就由其返回函数来执行

function init(){
    console.log(1);
    return function(){
        console.log(2);
    };
};
var s=init();//1
s();//2 

注意:init函数返回了一个匿名函数,即创建了一个闭包,(这里提到闭包的一个作用:创建私有数据,只有该匿名函数可以访问,外部代码不能访问),下面我们来看一下对该特点的应用

function init(){
    var count=0;
    return function(){
        return count+=1;
    };
};
var s=init();
console.log(s());//1
console.log(s());//2
console.log(s());//3 

四 自定义函数(惰性函数模式):一个函数有一些初始化的准备工作,且只需要执行一次,使用自定义函数模式可以使重新定义的函数执行更少的工作

function init(){
    console.log("Boo!");
    init=function(){
        console.log("Boo Boo!");
    };
};
init();//Boo!
init();//Boo Boo!
init();//Boo Boo! 

  2   该模式又被称为惰性函数模式,即该函数直到第一次使用才会被正确定义,并且具有后向惰性,即得到正确定义后,会执行更少的工作  

  3   该模式的缺陷:当它重新定义自身时已经添加到原函数的任何属性都会丢失,如果再将其赋值给其他变量,那么使用新的变量来调用该函数的话,重定义的部分永远也得不到执行

var a,b;
a=b=function(){
    console.log("1");
    b=function(){
        console.log("2");
    }
};
b.age=18;
a();//1
a();//1
b();//2
b();//2
console.log(b.age);//undefined

五 即时函数:函数创建后立即执行该函数的语法

1  该模式的实现方法:使用函数表达式定义一个函数;在该函数表达式末尾加一组括号,让其立即执行;将这个包装到括号中

(function(){
    console.log("Oops!");
})(); 

  2   应用场景:该模式提供了一个作用域沙箱,当页面加载时,代码必须执行一些设置任务,例如设置事件监听器、创建对象等,但这些工作只需要执行一次,因此没有 必要定义一个可复用的函数,如果写在全局作用域下,有可能初始化操作还需要一些临时变量,这些变量可能会污染全局作用域,因此我们可以使用即时函数模式, 将所有临时变量包装到它的局部作用域中

(function(){
    var days=[‘星期日‘,‘星期一‘,‘星期二‘,‘星期三‘,‘星期四‘,‘星期五‘,‘星期六‘],
    today=new Date(),
    msg="今天"+days[today.getDay()];//这里days,today以及msg都是临时变量
    console.log(msg);// 今天星期五
})(); 

  5.1 即时函数的参数:这里不建议过多的参数传递给即时函数,避免造成阅读负担

(function(who,when){
    var days=[‘星期日‘,‘星期一‘,‘星期二‘,‘星期三‘,‘星期四‘,‘星期五‘,‘星期六‘];
    console.log("我在"+days[when.getDay()]+"遇见了"+who);
})("我的偶像",new Date());//我在星期五遇见了我的偶像 

  4    另外,应该注意全局对象也可以作为参数传递给即时函数,为了使代码可以在浏览器之外的环境有更好的互操作性,这里不建议在即时函数内部使用window

(function(global){
    //通过global访问全局变量
})(this);

  5.2 即时函数的返回值

var dd=(function(){
  var count=0;
  return function(){
    count++;
    console.log(count);
  };
})();
dd();//1
dd();//2
dd();//3
console.log(count);//ReferenceError: count is not defined

  5.3 优点和用法

  不会污染全局空间,用于书签工具,因为书签工具可以在任何网页上运行,并保持全局命名空间的整洁;确保页面在存在或不存在该代码的两种情况下都能良好运行

六 即时函数对象化

  1 保护全局作用域不被污染的方法,除了上面的即时执行函数,还有即时对象初始化模式

  2 该模式的init方法在创建对象后将会立即执行,init方法负责所有的初始化任务

  3 缺点:js压缩不能有效的缩减代码

({
    max:600,
    min:400,
    getMax:function(){
        return this.max;
    },
    init:function(){
        console.log(this.getMax());
        return this;//如果想保存对该对象的一个引用,可以返回this
    }
}).init();

七 初始化时分支:加载时分支

当知道某个条件在整个程序的生命周期内是不会发生改变的时候,仅对该条件进行一次测试即可,例如浏览器嗅探(浏览器版本的检测)等

/*每次调用utils.addListener方法给dom元素绑定事件的时候,都需要对其进行检测
typeof window.addEventListener
typeof document.attachEvent
*/
var utils={
    addListener:function(el,type,fn){
        if(typeof window.addEventListener==="function"){
            el.addEventListener(type,fn,false);
        }else if(typeof document.attachEvent==="function"){
            el.attachEvent(‘on‘+type,fn);
        }else{
            el["on"+type]=fn;
        }
    }
}; 
/*
typeof window.addEventListener
typeof document.attachEvent
只需要执行一次就可以了
*/
var utils={
    addListener:null
};
if(typeof window.addEventListener==="function"){
    utils.addListener=function(el,type,fn){
        el.addEventListener(type,fn,false);
    };
}else if(typeof document.attachEvent==="function"){
    utils.addListener=function(el,type,fn){
        el.attachEvent(‘on‘+type,fn);
    }
}else{
    utils.addListener=function(el,type,fn){
        el["on"+type]=fn;
    }
} 

八 函数属性---备忘模式

  1 函数是对象,因此可以拥有属性,使用任何语法定义的函数都会自动获取一个length的属性,该属性是函数期望的参数的数量

  2 自定义一个属性,用来缓存函数的结果,下次调用函数的话就不需要做潜在的繁重的计算了,这种缓存函数就诶过的方式称之为备忘

  3 但是如果有两个名称一致,但是值不一致的话,就会得不到想要的结果

var fun=function(param){
    if(!fun.cache[param]){
        var result={};
        //...计算
        fun.cache[param]=result;
    }
    return fun.cache[param];
}fun.cache={};

九 配置对象

随着需求的不断变化,我们所需要的参数可能不断增多,这样我们向构造函数传递的参数也越来越多,参数会越来越长,实参与形参的顺序,也必须保持一致

例如addPerson(firstName,lastName,age,gender,address,telphone,birthday,.......);

配置对象模式

addPerson(conf);
conf={
    firstName:"...",
    lastName:"...",
    age:"...",
    gender:"...",
    address:"...",
    telphone:"...",
    birthday:"..."
};
/*优点:
    不需要记住众多参数及其顺序,
    可以忽略可选参数
    易于阅读和维护
    易于添加和删除
缺点:
    需要记住参数的名称
    属性名称不能压缩
*/

十 curry

  什么是curry,术语:一个转换过程,即我们执行函数转换的过程

  当我们调用某一个函数的时候,发现多个调用函数的参数大部分都一致,我们想这些函数只执行一遍,执行其中相同的一部分,然后再各自执行剩余的部分

  例如add(1,2,3,4,5,6,10);add(1,2,3,4,5,6,100);add(1,2,3,4,5,6,1000);add(1,2,3,4,5,6,10000),其中前面的6个参数都是一致的,这种情况下,我们先执行add(1,2,3,4,5,6),然后再各自与add(1,2,3,4,5,6)相加

function add(a,b,c,d,e,f,g){
    return a+b+c+d+e+f+g;
}
add(1,2,3,4,5,6,10);
add(1,2,3,4,5,6,100);
add(1,2,3,4,5,6,1000);
add(1,2,3,4,5,6,10000);

/*下面只是一个示意图或者说我们想要的一个效果图,并不是真正的要这样计算

*/
var newAdd=add(1,2,3,4,5,6);
newAdd(10);
newAdd(100);
newAdd(1000);
newAdd(10000);

   10.1  Curry化

/*特殊函数的curry化*/
function add(x,y){
    if(typeof y==="undefined"){
        return function(y){
            return x+y;
        };
    }
    return x+y;
}
var newAdd=add(1);
var result=newAdd(2);
console.log(result);//3

 

/*特殊函数的curry化*/
function add(a,b,c,d,e,f,g){
    return a+b+c+d+e+f+g;
}

/*下面是通用的curry化*/
function schonfinkelize(fn){
    var slice=Array.prototype.slice,
    stored_args=slice.call(arguments,1);
    return function(){
        var new_args=slice.call(arguments),
        args=stored_args.concat(new_args);
        return fn.apply(null,args);
    };
}
var newAdd=schonfinkelize(add,1,2,3,4,5,6);
console.log(newAdd(10));//31
console.log(newAdd(100));//121
console.log(newAdd(1000));//1021
console.log(newAdd(10000));//10021

 10.2 何时使用Curry化

当发现正在调用同一个函数,且传递的参数大多数是相同的,那么这个函数可能用于Curry化。可以通过将一个函数参数部分应用到函数中,从而动态的创建一个新函数,这个新函数将会保存重复的参数,因此不必每次都传递这些重复的参数

时间: 2024-11-06 20:22:01

Javascript模式(第四章函数)------读书笔记的相关文章

《C++primer》v5 第6章 函数 读书笔记 习题答案

6.1 实参是在函数调用处填写的参数.形参是在函数体使用的参数. 实参是形参的初始值. 具体参见:http://blog.163.com/zhengguo_li/blog/static/703014802013423501214/ 6.2 (a)该函数试图返回一个局部变量.当函数调用结束后,s所占内存将会被释放,所以返回s是无效的 (b)该函数缺少返回值 (c)定义了两个同名的形参 (d)后面的语句应该写在{}里 6.3.6.4 using namespace std; int fact(int

开始开发 Dashboard Widgets,第四章,读书笔记

目录:http://blog.csdn.net/wide288/article/details/40298693 怎么开始开发基础的 widget 怎么使用 HTML 和 CSS 取得 widget 的形状 怎么使用 javascript 到你的 widget 从 html 开始 这里的项目是个天气地图.每个项目开始的点不一样,不过要先列出此 widget 的特性. 特性列表: 它应该有地图雷达,显示一块区域. 它应该有当前的临时地图. 它应该有昨天的临时地图. 它应该有明天的临时地图. 地图是

第四章进程调度读书笔记

4.1多任务 多任务系统就是能同时并发地交互执行多个进程的操作系统 1.抢占式多任务 2.非抢占式多任务 4.2Linux的进程调度 调度程序设计的基础和完全公平调度程序 如何运用 如何设计 如何实现 相关的系统调用 4.3策略 策略决定调度程序在何时让什么进程运行 4.3.1 I/O消耗型和处理器消耗型的进程 调度策略通常要在两个矛盾之间寻找平衡: 1.相应时间短 2.高吞吐量 4.3.2进程优先级 1.nice值 -20~19 2.实时优先 0~99 4.3.3时间片 时间片是一个数值 表示

《C++primer》v5 第5章 语句 读书笔记 习题答案

5.1 空语句只有一个";".如果什么也不想做可以使用空语句. 5.2 用花括号{}括起来的叫块,也叫复合语句.有多条语句作用在同一个作用域时,需要用花括号括起来. 5.3 降低了. 5.4 (a)每次迭代时候会初始化iter,但是iter缺少初值,所以这段代码根本不会通过编译.另外这里的括号需要一个bool类型的,而定义迭代器根本不会返回一个bool类型.假如上面那些问题都可以通过,每次迭代都会初始化这个iter,会导致死循环. (b)我试了一下编译未通过是因为没找到适合的find函

《JavaScript设计模式与开发实践》读书笔记之观察者模式

1.<JavaScript设计模式与开发实践>读书笔记之观察者模式 观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. JavaScript中通常采用事件模型替代传统的观察者模式 1.1 逐步实现观察者模式 以客户看房为例 首先指定谁充当发布者,如售楼处 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者.这里为了让订阅者只接收自己感兴趣的消息,增加一个标识key 最后发布消息时候,发布者遍历缓存列表,依次触发里面存放的订阅者的回

《企业应用架构模式》(POEAA)读书笔记

原文地址:<企业应用架构模式>(POEAA)读书笔记作者:邹齐龙(技术-5013 什么是架构 Rolph Johnson认为:架构是一种主观上的东西,是专家级的项目开发人员对系统设计的一些可共享的理解 架构中包括一些决定,开发者希望这些决定能尽早作出,因为在开发者看来它们是难以改变的. 如果你发现某些决定不像你想象中的那么难以改变,那么它就不再与架构相关 理解: B/S (SmartClient.C/S) 架构, DotNet 架构, J2EE架构 企业应用的特点 涉及到持久化数据 很多人同时

《Effective C++》第5章 实现-读书笔记

章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(2)-读书笔记 <Effective C++>第3章 资源管理(1)-读书笔记 <Effective C++>第3章 资源管理(2)-读书笔记 <Effective C++>第4章 设计与声明(1)-读书笔记 <Eff

《C++primer》v5 第1章 开始 读书笔记 习题答案

从今天开始在博客里写C++primer的文字.主要以后面的习题作业为主,会有必要的知识点补充. 本人也是菜鸟,可能有不对之处,还望指出. 前期内容可能会比较水. 1.1略 1.2略 1.3 cin和cout分别是istream和ostream的对象. #include<iostream> using namespace std; int main() { cout<<"Hello,world"<<endl; return 0; } 1.4 #incl

《C++primer》v5 第4章 表达式 读书笔记 习题答案

4.1 105 4.2 *vec.begin()=*(vec.begin())//先调用点运算符,再解引用 *vec.begin()+1=(*vec.begin())+1//先解引用,再加一 4.3略? 4.4 (12/3*4)+(5*15)+(24%4/2)=91 4.5 (a)-86(b)-16 (c)0 (d)0 4.6 n%2 4.7 溢出:计算结果超出该数据类型所能表示的范围 2147483647+1 1U-2 ... 4.8 比较低.. 4.9 首先判断cp是否为空指针,若非空指针则

《操作系统概论》第一章引论读书笔记

计算机系统分为硬件和软件. 硬件主要由:CPU.存储器.输入输出控制系统.各种输入输出设备组成. 软件主要分为:系统软件.支撑软件.以及应用软件. 操作系统定义:一般认为,操作系统是管理计算机系统资源.控制程序执行.改善人机界面和为应用软件提供支持的一种系统软件. 操作系统的作用: 管理计算机系统的资源. 为用户提供方便的使用接口. 具有扩充硬件的功能. 操作系统的功能: 从资源管理的观点看,操作系统的功能可以分为:处理器管理.存储管理.文件管理和设备管理. 处理器管理的主要工作是进行处理器的分