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 相关文章推荐
jquery中的$(document).ready()使用小结
Feb 14 Javascript
将HTML格式的String转化为HTMLElement的实现方法
Aug 07 Javascript
javascript实现动态模态绑定grid过程代码
Sep 22 Javascript
Node.js的包详细介绍
Jan 14 Javascript
JavaScript中的acos()方法使用详解
Jun 14 Javascript
jQuery实现仿微软首页感应鼠标变化滑动窗口效果
Oct 08 Javascript
node.js require() 源码解读
Dec 13 Javascript
非常漂亮的相册集 使用jquery制作相册集
Apr 28 Javascript
简单谈谈js的数据类型
Sep 25 Javascript
基于angular6.0实现的一个组件懒加载功能示例
Apr 12 Javascript
vue如何在项目中调用腾讯云的滑动验证码
Jul 15 Javascript
idea编译器vue缩进报错问题场景分析
Jul 04 Vue.js
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
NOT NULL 和NULL
2007/01/15 PHP
php使用Jpgraph创建折线图效果示例
2017/02/15 PHP
Yii框架实现的验证码、登录及退出功能示例
2017/05/20 PHP
用CSS+JS实现的进度条效果效果
2007/06/05 Javascript
jquery关于图形报表的运用实现代码
2011/01/06 Javascript
解决js正则匹配换行问题实现代码
2012/12/10 Javascript
jquery鼠标滑过提示title具体实现代码
2013/08/06 Javascript
js编码、解码函数介绍及其使用示例
2013/09/05 Javascript
JavaScript判断变量是对象还是数组的方法
2014/08/28 Javascript
JavaScript运行机制之事件循环(Event Loop)详解
2014/10/10 Javascript
再谈JavaScript线程
2015/07/10 Javascript
AngularJS 中使用Swiper制作滚动图不能滑动的解决方法
2016/11/15 Javascript
js实现五星评价功能
2017/03/08 Javascript
Three.js获取鼠标点击的三维坐标示例代码
2017/03/24 Javascript
基于JavaScript实现焦点图轮播效果
2017/03/27 Javascript
js实现音乐播放控制条
2017/09/09 Javascript
p5.js 毕达哥拉斯树的实现代码
2018/03/23 Javascript
vue-cli2.x项目优化之引入本地静态库文件的方法
2018/06/19 Javascript
JS跨域请求的问题解析
2018/12/03 Javascript
vue 自动化路由实现代码
2019/09/03 Javascript
Vue使用vue-recoure + http-proxy-middleware + vuex配合promise实现基本的跨域请求封装
2019/10/21 Javascript
微信小程序录音实现功能并上传(使用node解析接收)
2020/02/26 Javascript
JavaScript监听键盘事件代码实现
2020/06/03 Javascript
Python中os.path用法分析
2015/01/15 Python
python生成二维码的实例详解
2017/10/29 Python
PYTHON基础-时间日期处理小结
2018/05/05 Python
用Python下载一个网页保存为本地的HTML文件实例
2018/05/21 Python
pyqt弹出新对话框,以及关闭对话框获取数据的实例
2019/06/18 Python
Keras在训练期间可视化训练误差和测试误差实例
2020/06/16 Python
FC-Moto瑞典:欧洲最大的摩托车服装和头盔商店之一
2018/11/27 全球购物
最新党员思想汇报
2014/01/01 职场文书
校园十佳歌手策划书
2014/01/22 职场文书
幼儿园学前班幼儿评语
2014/12/29 职场文书
单位同意报考证明
2015/06/17 职场文书
2015年公司中秋节致辞
2015/07/31 职场文书
600字作文之感受大自然
2019/11/27 职场文书