精读JavaScript模式(五),函数的回调、闭包与重写模式
一、前言
今天地铁上,看到很多拖着行李箱的路人,想回家了。
在上篇博客结尾,记录到了函数的几种创建方式,简单说了下创建差异,以及不同浏览器对于name属性的支持,这篇博客将从第四章函数的回调模式说起。我想了想,还是把一篇博客的知识点控制在五个以内,太长了我自己都懒得看,而且显得特别混杂。标题还是简要说下介绍了哪些知识,也方便自己以后查阅,那么开始。
二、函数的回调模式
1.什么是函数回调模式?
当调用函数时,我们可以将函数作为参数传入到需要调用的函数中,例如我们为函数A传入一个函数B,当函数A执行时调用了函数B,那么我们可以说函数B是一个回调函数,简称回调。
function A(data){
data();
};
function B(){
console.log(1);
};
A(B);//将函数B作为参数传入到A函数中进行调用
当B函数作为参数传入A函数时,此时的B函数是不带括号的,因为函数名带括号时表示立即执行,这点大家应该都知道。
回调函数可以是一个匿名函数,其实这种写法在编程中反而更为常见。
function A(data){
data();
}; A(function (){
console.log(2);
});//
2.回调函数作为对象的方法时this指向问题
回调函数通常的写法是callback(parameters),通常parameters是一个匿名函数,或者一个可调用的全局函数。但当函数是某个对象的方法时,常规的回调执行会出现问题。
//回调函数sayName是obj的一个方法
let obj = {
name : 'echo',
sayName : function () {
console.log(this.name);
}
};
let func = function (callback) {
callback()
};
func(obj.sayName);//并不会输出echo
我们原本预期是输出echo,但实际执行时this指向了全局window而非obj,所以不能拿到name属性,如何解决呢,在传递回调函数时,我们也可以把回调函数所属对象也作为参数传进去。调用方式改为如何即可,通过call或者apply改变this指向。
//回调函数sayName是obj的一个方法
let obj = {
name : 'echo',
sayName : function () {
console.log(this.name);
}
};
let func = function (callback, obj) {
callback.call(obj)
};
func(obj.sayName, obj);//echo
在上述代码中,回调函数作为参数的写法是obj.sayName,其实也可以直接传一个字符串sayName进去,通过obj[sayName]执行,这样做的好处是,函数执行时this直接指向了调用的obj,所以就不需要call额外修改this指向了。
//回调函数sayName是obj的一个方法
let obj = {
name : 'echo',
sayName : function () {
console.log(this.name);
}
};
let func = function (callback, obj) {
obj[callback]();
};
func('sayName', obj);//echo
3.回调模式的使用价值
在浏览器中大部分编程都是事件驱动的,例如页面加载完成触发load事件,用户点击触发click事件,也真是因为回调模式的灵活性,才让JS事件驱动编程如此灵活。回调模式能让程序'异步'执行,也就是不按代码顺序执行。
我们可以在程序中定义多个回调函数,但它们并不是在代码加载就会执行,等到时间成熟,例如用户点击了某个元素,回调函数才会根据开始执行。
document.addEventListener("click", console.log, false);
除了事件驱动,另一个常用回调函数的情景就是结合定时器,setTimeout()与setInterval(),这两个方法的参数都是回调模式。
let func = function () {
console.log(1);
};
setTimeout(func, 500);//500ms后执行一次func函数
setInterval(func, 500)//每隔500ms执行一次func函数
再次强调的是,定时器中回调函数的写法是func,并未带括号,如果带了括号就是立即执行。另一种写法是setTimeout('func()', 500),但这是一种反模式,并不推荐。
三、返回函数作为返回值
函数是对象,除了可以作为参数同样也能作为返回值,也就是说函数的返回值也能是一个函数。
function demo () {
console.log(1);
return function () {
console.log(2);
};
};
let func1 = demo();//
func1()//
在上述代码中函数demo将返回的函数包裹了起来,创建了一个闭包,我们可以利用闭包存储一些私有属性,而私有属性可以通过返回的函数操作,且外部函数不能直接读取这些私有属性。
const setup = function () {
let count = 0;
return function () {
return (count += 1);
};
};
let next = setup();
next();//
next();//
next();//
四、函数重写
当我们希望一个函数做一些初始化操作,并且初始化的操作只执行一次时,面对这种情况我们就需要使用函数重写。
let handsomeMan = function () {
console.log('echo is handsome man!');
handsomeMan = function () {
console.log('Yes,echo is handsome man!');
};
};
handsomeMan()//echo is handsome man!
handsomeMan()//Yes,echo is handsome man!
上方代码中,第一次调用的输出只会执行一次,这是因为新的函数覆盖掉了旧函数,虽然一直都是调用handsomeMan,但前后执行函数完全不同。
这种模式的另一名字是函数的懒惰定义,因为函数是执行一次后才重新定义,相比分开两个函数来写,这样的执行效率更为高效。
此模式有个明显的问题就是,一旦重新函数被重写,最初函数的所有方法属性都将丢失。
let handsomeMan = function () {
console.log('echo is handsome man!');
handsomeMan = function () {
console.log('Yes,echo is handsome man!');
};
};
handsomeMan.property = "properly"; let boy = handsomeMan;
boy();//echo is handsome man!
boy();//echo is handsome man!
console.log(boy.property);//properly
//property属性已丢失
handsomeMan()//echo is handsome man!
handsomeMan()//Yes,echo is handsome man!
console.log(handsomeMan.property);//undefined
五、立即执行函数
所谓立即执行函数就是一个在创建后就会被立即执行的函数表达式,也可以叫自调用函数(IIFE)。
//调用括号在里面
(function () {
console.log(1);
}());
//调用括号在外面
(function () {
console.log(2);
})();
它主要由三部分组成,一对括号(),里面包裹着一个函数表达式,以及一个自调的括号(),这个括号可紧跟函数,也可以写在外面。我个人常用第二种写法,但JSLint更倾向于第一种写法。
立即执行函数长用于处理代码初始化的工作,因为它提供了一个独立的作用域,所有初始化中存在的的变量都不会污染到全局环境。
立即执行函数也可以传递参数,像这样
(function (data) {
console.log(data);
})(1);
除了传参,立即执行函数也可以返回值,并且这些返回值可以赋值给变量。
var result = (function () {
return 2 + 2;
})();
console.log(result);//
在对应一个对象的属性时,也可以使用立即执行函数,假设对象的某个属性是一个待确定的值,那我们就可以使用此模式来初始化该值。
let o = {
message: (function () {
return 2+2
})(),
getMsg: function () {
return this.message;
}
};
o.message;//
o.getMsg()//
需要注意的是,此时的o.message是一个字符串,并非一个函数。
但是伴随着ES6中块级作用域的出现,利用自执行函数保护全局作用域免受初始化变量污染的做法已经没有必要了。
{
let a = 1;
}
console.log(a);//无权访问
六、代码初始化的意义
在函数重写和自调用函数模式中多次提到了代码初始化,为什么要做代码初始化,简单举例说下。
JS的函数监听大家都不会陌生,而早期IE与大部分浏览器提供的监听绑定方法不同,如果不使用初始化,可能是这样
let o = {
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;
}
}
};
o.addListener();
当我们调用o.addListener()方法时,很明显效率不高,每次调用都要把各浏览器判断走一遍,才能确定最终的监听绑定方式;我们初始化监听方式。
let o = {
addListener: null
};
if (typeof window.addEventListener === 'function') {
o.addListener = function (el, type, fn) {
el.addEventListener(type, fn, false);
};
}else if (typeof document.attachEvent === 'function') {
o.addListener = function (el, type, fn){
el.attachEvent('on' + type, fn);
}
}else{
o.addListener = function () {
el['on' + type] = fn;
}
};
在当我们调用o.addListener()方法时,此时addListener已经初始化过了,不用反反复复走监听绑定判断,这就是代码初始化的意义,把那些你能确定下来,但需要繁琐执行的逻辑一次性确定好,之后就是直接使用的操作了,就是这么个意思。
这篇就记录这么多吧,还有五天回家过年了!
精读JavaScript模式(五),函数的回调、闭包与重写模式的更多相关文章
- javascript基础(五)函数
原文http://pij.robinqu.me/ 通过call和apply间接调用函数(改变this) call 和 apply带有多个参数,call和apply把当前函数的this指向第一个参数给定 ...
- javascript基础:函数参数与闭包问题
今天在写东西的时候,对函数参数的概念有些模糊,查阅相关资料后,在博客上记点笔记,方便日后复习. 首先,在js中函数参数并没有强语言中那么要求严格,他不介意传递进来多少个参数,也不在乎传进来的参数是什么 ...
- JavaScript 基础(五) 函数 变量和作用域
函数定义和调用 定义函数,在JavaScript中,定义函数的方式如下: function abs(x){ if(x >=0){ return x; }else{ return -x; } } ...
- JavaScript函数表达式、闭包、模仿块级作用域、私有变量
函数表达式是一种非常有用的技术,使用函数表达式可以无需对函数命名,从而实现动态编程.匿名函数,是一种强大的方式,一下总结了函数表达式的特点: 1.函数表达式不同于函数声明,函数声明要求有名字,但函数表 ...
- JavaScript高级程序设计--函数小记
执行环境和作用域链 每个函数都有自己的执行环境.当执行流进入一个函数时,函数 的环境就会被推入一个环境栈中.而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境. 当代码在一个环境中 ...
- JavaScript(五):函数(闭包,eval)
1.函数的申明:三种方法: function命令 函数表达式:变量赋值 Function构造函数 //method 1: function命令 function test(){ console.log ...
- 精读JavaScript模式(六),Memoization模式与函数柯里化的应用
假期就这么结束了!十天假就有三天在路上,真的难受!想想假期除了看了两场电影貌似也没做什么深刻印象的事情.流浪地球,特效还是很赞,不过对于感情的描写还是逃不掉拖沓和尴尬的通病,对于国产科幻还是抱有支持的 ...
- 前端基本知识(四):JS的异步模式:1、回调函数;2、事件监听;3、观察者模式;4、promise对象
JavaScript语言将任务的执行模式可以分成两种:同步(Synchronous)和异步(Asychronous). “同步模式”就是一个任务完成之后,后边跟着一个任务接着执行:程序的执行顺序和排列 ...
- JavaScript学习总结(一)——闭包、对象、函数
一.闭包(Closure) 1.1.闭包相关的问题 请在页面中放10个div,每个div中放入字母a-j,当点击每一个div时显示索引号,如第1个div显示0,第10个显示9:方法:找到所有的div, ...
随机推荐
- 将VSCode设置成中文语言环境
VSCode是一款轻量级的好用的编译软件,今天小编来将软件默认的英文语言环境变为我们熟悉的中文语言环境. 工具/原料 电脑一台 安装有VSCode 方法/步骤 首先打开VSCode软件,可 ...
- 查看memcached连接数
netstat -n | grep : | wc -l
- redis CentOS6.5安装及集群部署
.下载redis source包 链接:https://pan.baidu.com/s/122ZCjNvjl9Jx6M2YsLrncw 密码:92ze 2.解压 tar -xzf redis-3.2. ...
- centos7通过yum安装JDK1.8
安装之前先检查一下系统有没有自带open-jdk 命令: rpm -qa |grep java rpm -qa |grep jdk rpm -qa |grep gcj 如果没有输入信息表示没有安装. ...
- sock5客户端解密数据流
一.安装 略 二.配置 vi /etc/shadowsocks.json { "server":"x.x.x.x", , , "password&qu ...
- easy ui Uncaught Error: cannot call methods on tooltip prior to initialization; attempted to call method 'hide'
今天bootstrap 和easy ui混用时候报了这么一个错误,本来可以点击编辑的,但是现在一点击就报错,
- Windows 远程栈溢出挖掘与利用
缓冲区溢出攻击很容易被攻击者利用,因为 C 和 C++等语言并没有自动检测缓冲区溢出操作,同时程序编写人员在编写代码时也很难始终检查缓冲区是否可能溢出.利用溢出,攻击者可以将期望数据写入漏洞程序内存中 ...
- Flask中的before_request after_request
1.@app.before_request 在请求(request)之前做出响应 @app.before_request 也是一个装饰器,他所装饰的函数,都会在请求进入视图函数之前执行 2.@app. ...
- Markdown新手教程
目录 什么是Markdown? 用Markdown写作有什么优缺点? 有哪些比较好的Markdown写作工具? markdown语法 标题 水平分区线 引用 中划线 斜体 粗体 斜粗体 链接 图片 无 ...
- HttpClient 传输文件的两种方式
1. org.apache.commons.httpclient.HttpClient 1.1 pom <dependency> <groupId>org.apache.htt ...