JavaScript 反科里化 this [译]


Posted in Javascript onSeptember 20, 2012

本文主要讲了JavaScript中科里化和反科里化this的方法.话题来自于Brendan Eich(JavaScript之父)的一个tweet.

1.反科里化(Uncurrying)this

反科里化this的意思是:把一个签名如下的方法:

obj.foo(arg1, arg2)转换成另外一个签名如下的函数:

foo(obj, arg1, arg2)想要知道这么做有什么用,我们首先得了解一下通用方法.

2.通用方法(Generic methods)

通常情况下,某个特定的方法只能在某种特定类型的对象实例上使用.但是,有一些方法如果还可以使用在其他类型的对象实例上的话,那会非常有用,例如:

// 实际实现的简化版本: 
Array.prototype.forEach = function (callback) { 
for(var i=0; i<this.length; i++) { 
if (i in this) { 
callback(this[i], i); 
} 
} 
};

this可以看做是forEach()方法的隐含参数.满足下面这三条规则的对象都可以调用forEach()方法,都可以作为这个隐含的this:

•具有length属性: this.length
•能够通过索引访问对象元素: this[i]
•能够检查属性的存在性: i in this
arguments对象(包含了一次函数调用的所有实参)不是一个Array实例,所以它不能直接调用forEach()方法.但是你它满足调用forEach方法的三个条件.为了让该对象能够调用到forEach()方法,我们只需要让隐含的this参数作为显式参数.幸运的是,每个函数都有call()方法让我们来做件事:

function printArgs() {
    Array.prototype.forEach.call(arguments, function (elem, index) {
        console.log(index+". "+elem);
    });
}

forEach.call()比forEach()方法多一个参数:它的第一个参数就是指定的this值:
> printArgs("a", "b")
0. a
1. b

JavaScript中有几个类似的通用方法都可以以这种方式来调用,这些方法大部分来自Array.prototype.

3.反科里化this的几个用途

用例1:通过map()调用一个方法. Array.prototype.map()方法允许你在一个数组中的每个元素上调用一个函数.但如果你想调用的不是函数还是方法呢?可以利用反科里化this这么做:

> var toUpperCase = String.prototype.toUpperCase.uncurryThis();
> [ "foo", "bar", "baz" ].map(toUpperCase)
[ 'FOO', 'BAR', 'BAZ' ]

用例2:将一个通用方法转换成函数. 利用反科里化this可以将一个方法转换成一个用法更简单的函数.比如:
Array.forEach = Array.prototype.forEach.uncurryThis();
function printArgs() {
    Array.forEach(arguments, function (elem, index) {
        console.log(index+". "+elem);
    });
}

在未来版本的ECMAScript规范建议中已经有了很多类似的数组方法.
译者注:Firefox已经实现了Array.map,Array.forEach等方法.

4.实现uncurryThis()
下面是实现uncurryThis方法的三种方式.

实现1: Brendan Eich写的

Function.prototype.uncurryThis = function () { 
var f = this; 
return function () { 
var a = arguments; 
return f.apply(a[0], [].slice.call(a, 1)); 
}; 
};

实现2: 调用反科里化过的函数相当于在原方法上通过调用它的call()方法来执行.我们可以通过bind()方法把这个call()方法借过来:
Function.prototype.uncurryThis = function () { 
return this.call.bind(this); 
};

实现3: 定义的标准方法最好不要依赖过多的外部方法.此外,bind()方法只在ECMAScript 5中可用.因此我们重写了上面的实现2,如下:
Function.prototype.uncurryThis = function () { 
var f = this; 
return function () { 
return f.call.apply(f, arguments) 
}; 
};

上面的代码仍然是隐式的借用了call()方法.

5.反向操作也很有用 ? 科里化this
uncurryThis()的反向操作称之为curryThis().它将原函数的第一个参数转换成隐含的this参数.假如有个原函数:

function(self, arg) { 
return self.foo + arg; 
}

科里化this后成为:
function(arg) { 
return this.foo + arg; 
}

用例: 让一个方法把自己的this值传递到一个内嵌函数里.原来的写法:
var obj = {
    method: function (arg) {
        var self = this; // 让嵌套的函数访问到this
        someFunction(..., function() {
            self.otherMethod(arg);
        });
    },
    otherMethod: function (arg) { ... }
}

科里化后你可以这么写:
var obj = {
    method: function (self, arg) { // 附加参数`self`
        someFunction(..., function() {
            self.otherMethod(arg);
        });
    }.curryThis(), // 传入附加参数
    otherMethod: function (arg) { ... }
}

我们把隐含的参数this转换成了显式的参数self.换句话说:我们把一个动态的this转换成了一个静态的变量self.如果this总是作为一个显式的参数,则JavaScript会变的更简单点.

实现curryThis():

Function.prototype.curryThis = function () {
    var f = this;
    return function () {
        var a = Array.prototype.slice.call(arguments);
        a.unshift(this);
        return f.apply(null, a);
    };
};

6.如果你不想扩展函数原型

上面实现的方法都是加在了内置构造函数Function()的原型上.你应该可以轻松的将它们重写为独立的函数.

function uncurryThis(f) {
    return function () {
        return f.call.apply(f, arguments)
    };
}
function curryThis(f) {
    return function () {
        var a = Array.prototype.slice.call(arguments);
        a.unshift(this);
        return f.apply(null, a);
    };
}

7.在uncurryThis()安全的使用在已经存在的不信任的代码中

Mark Miller把uncurryThis()作为例子讲解了“安全的元编程”:

译者注:科里化this就是把函数的第一个参数转换成方法中的this.反科里化this就是把方法中的this转换成函数的第一个参数.

Javascript 相关文章推荐
在 IE 中调用 javascript 打开 Excel 表
Dec 21 Javascript
jquery HotKeys轻松搞定键盘事件代码
Aug 30 Javascript
js模拟类继承小例子
Jul 17 Javascript
基于Jquery的开发个代阴影的对话框效果代码
Jul 28 Javascript
js 获取坐标 通过JS得到当前焦点(鼠标)的坐标属性
Jan 04 Javascript
Jquery动态添加输入框的方法
May 29 Javascript
Json解析的方法小结
Jun 22 Javascript
AngularJS框架中的双向数据绑定机制详解【减少需要重复的开发代码量】
Jan 19 Javascript
vue data恢复初始化数据的实现方法
Oct 31 Javascript
Javascript模拟实现new原理解析
Mar 03 Javascript
Vue Render函数原理及代码实例解析
Jul 30 Javascript
JavaScript中关于预编译、作用域链和闭包的理解
Mar 31 Javascript
Array.prototype.concat不是通用方法反驳[译]
Sep 20 #Javascript
JavaScript 用Node.js写Shell脚本[译]
Sep 20 #Javascript
一个简单的网站访问JS计数器 刷新1次加1次访问
Sep 20 #Javascript
javascript分页代码(当前页码居中)
Sep 20 #Javascript
javascript获取作用在元素上面的样式属性代码
Sep 20 #Javascript
一个基于jquery的文本框记数器
Sep 19 #Javascript
html中的input标签的checked属性jquery判断代码
Sep 19 #Javascript
You might like
Thinkphp3.2.3整合phpqrcode生成带logo的二维码
2016/07/21 PHP
php数组实现根据某个键值将相同键值合并生成新二维数组的方法
2017/04/26 PHP
使用Git实现Laravel项目的自动化部署
2019/11/24 PHP
JS URL传中文参数引发的乱码问题
2009/09/02 Javascript
Mootools 1.2教程 Tooltips
2009/09/15 Javascript
JavaScript Object的extend是一个常用的功能
2009/12/02 Javascript
JavaScript日期时间与时间戳的转换函数分享
2015/01/31 Javascript
fullpage.js全屏滚动插件使用实例
2016/09/06 Javascript
利用bootstrapValidator验证UEditor
2016/09/14 Javascript
浅谈jquery中使用canvas的问题
2016/10/10 Javascript
Easyui使用Dialog行内按钮布局的实例
2017/07/27 Javascript
Angular.js中数组操作的方法教程
2017/07/31 Javascript
ES6学习教程之Map的常用方法总结
2017/08/03 Javascript
JavaScript屏蔽Backspace键的实现代码
2017/11/02 Javascript
使用apifm-wxapi模块中的问题及解决方法
2019/08/05 Javascript
javascript执行上下文、变量对象实例分析
2020/04/25 Javascript
JavaScript 防盗链的原理以及破解方法
2020/12/29 Javascript
JavaScript中跨域问题的深入理解
2021/03/04 Javascript
python实现中文分词FMM算法实例
2015/07/10 Python
关于python的bottle框架跨域请求报错问题的处理方法
2017/03/19 Python
Python制作豆瓣图片的爬虫
2017/12/28 Python
python3+PyQt5 实现Rich文本的行编辑方法
2019/06/17 Python
Django实现发送邮件功能
2019/07/18 Python
pandas 对日期类型数据的处理方法详解
2019/08/08 Python
Luxplus瑞典:香水和美容护理折扣
2018/01/28 全球购物
库存图片、照片、矢量图、视频和音乐:Shutterstock
2021/02/12 全球购物
为什么UNION ALL比UNION快
2016/03/17 面试题
房地产财务管理制度
2014/02/02 职场文书
元宵节主持词
2014/03/25 职场文书
关爱留守儿童倡议书
2014/04/15 职场文书
五分钟演讲稿
2014/04/30 职场文书
初中优秀学生评语
2014/12/29 职场文书
向女朋友道歉的话
2015/01/20 职场文书
商场圣诞节活动总结
2015/05/06 职场文书
可可西里观后感
2015/06/08 职场文书
聘任合同书
2015/09/21 职场文书