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 相关文章推荐
JavaScript 继承详解(三)
Jul 13 Javascript
JQuery优缺点分析说明
Jun 09 Javascript
jQuery点击弹出下拉菜单的小例子
Aug 01 Javascript
jQuery中Form相关知识汇总
Jan 06 Javascript
简介JavaScript中setUTCSeconds()方法的使用
Jun 12 Javascript
jQueryUI中的datepicker使用方法详解
May 25 Javascript
很棒的js Tab选项卡切换效果
Aug 30 Javascript
Vue.js路由组件vue-router使用方法详解
Dec 02 Javascript
详解JavaScript按概率随机生成事件
Aug 02 Javascript
全面解析jQuery中的$(window)与$(document)的用法区别
Aug 15 jQuery
js实现加载页面就自动触发超链接的示例
Aug 31 Javascript
js实现滚动条自动滚动
Dec 13 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
php数组函数序列 之shuffle()和array_rand() 随机函数使用介绍
2011/10/29 PHP
ie与session丢失(新窗口cookie丢失)实测及解决方案
2013/07/15 PHP
php MessagePack介绍
2013/10/06 PHP
jquery+php实现导出datatables插件数据到excel的方法
2015/07/06 PHP
PHP实现的二分查找算法实例分析
2017/12/19 PHP
一个tab标签切换效果代码
2009/03/27 Javascript
基于JQuery的cookie插件
2010/04/07 Javascript
jquery中eq和get的区别与使用方法
2011/04/14 Javascript
YUI Compressor压缩JavaScript原理及微优化
2013/01/07 Javascript
Javascript中产生固定结果的函数优化技巧
2013/01/16 Javascript
jQuery列表拖动排列具体实现
2013/11/04 Javascript
开启Javascript中apply、call、bind的用法之旅模式
2015/10/28 Javascript
使用jquery获取url以及jquery获取url参数的实现方法
2016/05/25 Javascript
通过JS和PHP两种方法判断用户请求时使用的浏览器类型
2016/09/01 Javascript
Vue实现动态响应数据变化
2017/04/28 Javascript
AngularJS日程表案例详解
2017/08/15 Javascript
微信小程序实现滚动消息通知
2018/02/02 Javascript
全面了解JavaScript的作用域链
2019/04/03 Javascript
微信小程序中的上拉、下拉菜单功能
2020/03/13 Javascript
vue 监听窗口变化对页面部分元素重新渲染操作
2020/07/28 Javascript
[06:04]DOTA2国际邀请赛纪录片:Just For LGD
2013/08/11 DOTA
python搭建服务器实现两个Android客户端间收发消息
2018/04/12 Python
不到20行代码用Python做一个智能聊天机器人
2019/04/19 Python
python3 线性回归验证方法
2019/07/09 Python
Python 字符串、列表、元组的截取与切片操作示例
2019/09/17 Python
python实现画出e指数函数的图像
2019/11/21 Python
关于Tensorflow使用CPU报错的解决方式
2020/02/05 Python
使用Python防止SQL注入攻击的实现示例
2020/05/21 Python
Python自动登录QQ的实现示例
2020/08/28 Python
欧舒丹澳洲版:L’OCCITANE
2017/07/17 全球购物
自考自我鉴定范文
2013/10/30 职场文书
英语老师推荐信
2014/02/26 职场文书
毕业生求职信
2014/06/10 职场文书
学生逃课检讨书
2015/02/17 职场文书
预备党员自我评价范文
2015/03/04 职场文书
Java的Object类的九种方法
2022/04/13 Java/Android