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应用于login页面的问题及解决
Oct 17 Javascript
javascript中数组中求最大值示例代码
Dec 18 Javascript
javascript中打印当前的时间实现思路及代码
Dec 18 Javascript
javascript 模拟坦克大战游戏(html5版)附源码下载
Apr 08 Javascript
跟我学习javascript的基本类型和引用类型
Nov 16 Javascript
js流动式效果显示当前系统时间
May 16 Javascript
js获取iframe中的window对象的实现方法
May 20 Javascript
5分钟快速掌握JS中var、let和const的异同
Sep 19 Javascript
微信小程序实现签到功能
Oct 31 Javascript
vuex 实现getter值赋值给vue组件里的data示例
Nov 05 Javascript
vue-cli设置css不生效的解决方法
Feb 07 Javascript
详解Vue.js 响应接口
Jul 04 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
超神学院:鹤熙已踏入神圣领域,实力不比凯莎弱
2020/03/02 国漫
用PHP与XML联手进行网站编程代码实例
2008/07/10 PHP
探讨如何在PHP开启gzip页面压缩实例
2013/06/09 PHP
从零开始学YII2框架(二)通过 Composer 安装扩展插件
2014/08/20 PHP
jQuery 获取URL参数的插件
2010/03/04 Javascript
为原生js Array增加each方法
2012/04/07 Javascript
JavaScript bold方法入门实例(把指定文字显示为粗体)
2014/10/17 Javascript
jQuery仿淘宝网产品品牌隐藏与显示效果
2015/09/01 Javascript
js获取url传值的方法
2015/12/18 Javascript
JavaScript数组方法总结分析
2016/05/06 Javascript
使用Vue.js创建一个时间跟踪的单页应用
2016/11/28 Javascript
基于Three.js插件制作360度全景图
2016/11/29 Javascript
Bootstrap CSS组件之输入框组
2016/12/17 Javascript
axios发送post请求,提交图片类型表单数据方法
2018/03/16 Javascript
Vue使用watch监听一个对象中的属性的实现方法
2019/05/10 Javascript
微信小程序中如何使用flyio封装网络请求
2019/07/03 Javascript
使用Angular material主题定义自己的组件库的配色体系
2019/09/04 Javascript
vue自定义正在加载动画的例子
2019/11/14 Javascript
[01:38]DOTA2辉夜杯 欢乐的观众现场采访
2015/12/26 DOTA
[00:35]DOTA2上海特级锦标赛 VP战队宣传片
2016/03/04 DOTA
Python实现基于HTTP文件传输实例
2014/11/08 Python
详解python的数字类型变量与其方法
2016/11/20 Python
Python打包方法Pyinstaller的使用
2018/10/09 Python
解决python中os.listdir()函数读取文件夹下文件的乱序和排序问题
2018/10/17 Python
Python 获取主机ip与hostname的方法
2018/12/17 Python
Python3 tkinter 实现文件读取及保存功能
2019/09/12 Python
win10子系统python开发环境准备及kenlm和nltk的使用教程
2019/10/14 Python
Python中使用gflags实例及原理解析
2019/12/13 Python
定义一结构体变量,用其表示点坐标,并输入两点坐标,求两点之间的距离
2015/08/17 面试题
《美丽的黄昏》教学反思
2014/02/28 职场文书
洗发水广告词
2014/03/13 职场文书
土地转让协议书范本
2014/04/15 职场文书
撤诉申请怎么写
2015/05/19 职场文书
校园新闻稿范文
2015/07/18 职场文书
2016新年问候语大全
2015/11/11 职场文书
2016习总书记系列重要讲话心得体会
2016/01/15 职场文书