资讯专栏INFORMATION COLUMN

bind方法的javascript实现及函数柯里化

Tamic / 3011人阅读

摘要:而模拟的方法返回的函数用作构造函数时,生成的对象为。同样,使用运算符时,绑定构造函数和未绑定构造函数并无两样。标准的方法创建一个新函数称为绑定函数,新函数与被调函数绑定函数的目标函数具有相同的函数体在规范中内置的属性。

这是一道面试题,题目给出了使用bind方法的样例,要求用javascript实现这个方法,面试官还很善意的提醒我函数柯里化,然而,我还是不会这道题目,所以回来这会《javacript权威指南》和《javacript 高级教程》开始学习相关知识。

一、javacript实现bind方法

bind()是在ECMAScript5中新增的方法,但是在ECMAScript3中可以轻易的模拟bind()

版本一

这部分参考了《javacript权威指南》权威指南的p191ECMAScript3版本的Function.bind()方法的实现。

if(!Function.prototype.bind){
    Function.prototype.bind = function(o){
        // 将`this`和`arguments`的值保存在变量中,以便在后面嵌套的函数中可以使用它们
        var self = this,
            boundArgs = arguments;
        //bind方法的返回值是一个函数
        return function(){
            var args = [],//创建一个实参列表,将传入的bind()的第二个及后续的实参都传入这个函数。
                i;
            for(i=1;i
版本一存在的问题

上述ECMAScript3版本的Function.bind()方法和ECMAScript5中定义的bind()有些出入,主要有以下三个方面。

真正的bind()方法(ECMAScript5中定义的bind())返回一个函数对象,这个函数对象的length属性是绑定函数的形参个数减去绑定实参的个数。而模拟的bind()方法返回的函数对象的length属性的值为0.

真正的bind()方法可以顺带用作构造函数,此时将忽略传入bind()this,原始函数就会以构造函数的形式调用,其实参也已经绑定。而模拟的bind()方法返回的函数用作构造函数时,生成的对象为Object()

真正的bind()方法所返回的函数并不包含prototype属性(普通函数固有的prototype属性是不能删除的),并且将这些绑定的函数用作构造函数时所创建的对象从原始的未绑定的构造函数中继承prototype。同样,使用instanceof运算符时,绑定构造函数和未绑定构造函数并无两样。

版本二

针对上述ECMAScript3版本的Function.bind()方法存在的问题,《JavaScript Web Application》一书中给出的版本有针对性的修复了这些问题。

Function.prototype.bind = function(context){
    var args = Array.prototype.slice.call(arguments,1),//要点3
        self = this,
        F = function(){};//要点1
        bound = function(){
            var innerArgs = Array.prototype.slice.call(arguments);
            var finalArgs = args.concat(innerArgs);
            return self.apply((this instanceof F ? this : context),finalArgs);//要点2
        };
        F.prototype = self.prototype;
        bound.prototype = new F();
        return bound;
}

要点1,解释

如下这段代码,实际上用到了原型式继承。这跟ECMAscript5中的Object.creat()方法只接受一个参数时是一样的。

F = function(){};//要点1
...
F.prototype = self.prototype;
bound.prototype = new F();

要点2,解释

如下这段代码,是要判断通过bind方法绑定得到的函数,是直接调用还是用作构造函数通过new来调用的。

this instanceof F ? this : context

为了分析这段代码的具体含义,需要知道通过构造函数生成对象时,new操作符都干了啥。比如如下代码:

var a = new B()

(1).首先创建一个空对象,var a = {};
(2).将构造函数的作用域赋给新对象(因此,this就指向了这个新对象);
(3).执行构造函数中的代码(为这个新对象添加属性), B.call(a);
(4).继承被构造函数的原型,a._proto_ = B.prototype;
(5).返回这个新对象。

标准的bind方法

创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时this值绑定到bind()的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的this值被忽略,同时调用时的参数被提供给模拟函数。

通过原型链的继承可以判断绑定函数是否用作了构造函数,通过new操作符来调用。假设目标函数为funObj,绑定函数为funBind.即

var funBind = funObj.bind(context);
var obj = new funBind();

上面代码具有如下继承关系(这里画出继承关系图更容易理解):

obj instanceof funBind // true

funBind.prototype instanceof F //true

F.prototype = self.prototyep

a instanceof B原理,是判断B.prototype是否存在于a的原型链中。因此有

obj instanceof F // true

此外,要点2这里还用到了借用构造函数来实现继承,如下代码

self.apply(this,finalArgs)

要点3,解释

这里实际上是将类数组对象转化为数组,因为类数组对象,比如argumentsnodelist;虽然很像数组,比如具有length属性,但是不是数组,比如,没有concatslice这些方法.

常用的将类数组对象转为数组的方法有

(1).Array.prototype.slice.call
(2).扩展运算符...,比如[...arguments]
(3). Array.from();

版本二测试

测试1

可见,版本二并没有解决版本一的问题1和3

测试2

可见版本二解决了版本一的问题2

版本二的精简版

版本二中要点1和要点2看着很不爽,于是,我给精简了一下,测试结果与版本二相同。

Function.prototype.bind = function(context){
    var args = Array.prototype.slice.call(arguments,1),//要点3
        self = this,
        //F = function(){};//要点1
        bound = function(){
            var innerArgs = Array.prototype.slice.call(arguments);
            var finalArgs = args.concat(innerArgs);
            //return self.apply((this instanceof F ? this : context),finalArgs);//要点2
            return self.apply((this instanceof self ? this : context),finalArgs);//要点2
        };
        //F.prototype = self.prototype;
        //bound.prototype = new F();
        bound.prototype = self.prototype;
        return bound;
}

测试结果如下:

二、bind函数应用

关于bind函数的应用这里只提两点在我使用这个方法的时候,遇到的让我刚开始比较懵逼仔细一想还真是这么回事的问题。

一段神奇的代码
var unBindSlice = Array.prototype.slice;
var bindSlice = Function.prototype.call.bind(unBindSlice);
...

bindSlice(arguments);

测试一下

这段代码的作用就是将一个类数组对象转化为真正的数组,是下面这段代码的另一种写法而已

Array.prototype.slice.call(arguments);

将一个函数对象作为bindcontext,这种写法的作用是,为需要特定this值的函数创造捷径。

bind函数只创建一个新函数而不执行

私以为这是bindcallapply方法的一个重要差别,callapply这两个方法都会立即执行函数,返回的是函数执行后的结果。而bind函数只创建一个新函数而不执行。

之前看过一段错误的代码,就是用apply改变一个构造函数的this,紧接着又用这个构造函数创建新对象,毫无疑问这是错误的,遗憾的是找不到这段错误代码的出处了。

三、函数柯里化

函数柯里化是与函数绑定紧密相关的一个主题,它用于创建已经设置好了一个或者多个参数的函数。函数柯里化的基本方法与函数绑定是一样的:使用一个闭包返回一个函数。

柯里化函数通常创建步骤如下:调用另一个函数并为它传入要柯里化的函数和必要参数。同样方式如下:

function curry(fn){
    var args = Array.prototype.slice.call(arguments,1);
    return function(){
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return fn.apply(null, finalArgs);
    };
}

有没有感到很熟悉,其实上面bind方法的两个实现版本都用到了函数柯里化,区别在于,这里的通用函数没用考虑到执行环境。

曾经看过一段类似函数柯里化的代码,私以为很巧妙,如下:

假如有一个对象数组,想要根据对象的某个属性来对其进行排序。而传递给sort方法的比较函数只能接受两个参数,即比较的值,这样就无法指定排序的对象属性了。如何将需要三个参数的函数转化为满足要求的仅需要两个参数?要解决这个问题,可以定义一个函数,它接收一个属性名,然后根据这个属性名创建并返回一个比较函数,如下:

function createComparisionFunction(property){
    return function(obj1,obj2){
        return obj1[property]-obj2[property];
    };
}
四、参考文献

1.Javascript中bind()方法的使用与实现.
2.javascript原生一步步实现bind分析.
3.JS中的bind方法与函数柯里化.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86648.html

相关文章

  • 【进阶 6-2 期】深入高阶函数应用之柯里

    摘要:引言上一节介绍了高阶函数的定义,并结合实例说明了使用高阶函数和不使用高阶函数的情况。我们期望函数输出,但是实际上调用柯里化函数时,所以调用时就已经执行并输出了,而不是理想中的返回闭包函数,所以后续调用将会报错。引言 上一节介绍了高阶函数的定义,并结合实例说明了使用高阶函数和不使用高阶函数的情况。后面几部分将结合实际应用场景介绍高阶函数的应用,本节先来聊聊函数柯里化,通过介绍其定义、比较常见的...

    stackvoid 评论0 收藏0
  • 高阶函数应用 —— 柯里与反柯里

    摘要:柯里化通用式上面的柯里化函数没涉及到高阶函数,也不具备通用性,无法转换形参个数任意或未知的函数,我们接下来封装一个通用的柯里化转换函数,可以将任意函数转换成柯里化。 showImg(https://segmentfault.com/img/remote/1460000018998373); 阅读原文 前言 在 JavaScript 中,柯里化和反柯里化是高阶函数的一种应用,在这之前...

    wyk1184 评论0 收藏0
  • JavaScript 函数式编程技巧 - 柯里

    摘要:作为函数式编程语言,带来了很多语言上的有趣特性,比如柯里化和反柯里化。在一些函数式编程语言中,会定义一个特殊的占位变量。个人理解不知道对不对延迟执行柯里化的另一个应用场景是延迟执行。不断的柯里化,累积传入的参数,最后执行。作为函数式编程语言,JS带来了很多语言上的有趣特性,比如柯里化和反柯里化。 这里可以对照另外一篇介绍 JS 反柯里化 的文章一起看~ 1. 简介 柯里化(Currying)...

    edgardeng 评论0 收藏0
  • JavaScript柯里

    摘要:简介柯里化,又称部分求值,是把接收多个参数的函数变成接受一个单一参数最初函数的第一个参数的函数,并且返回接受剩余的参数而且返回结果的新函数的技术。按照作者的说法,所谓柯里化就是使函数理解并处理部分应用。的思想极大地助于提升函数的复用性。 简介 柯里化(Currying),又称部分求值(Partial Evaluation),是把接收多个参数的函数变成接受一个单一参数(最初函数的第一个...

    since1986 评论0 收藏0
  • Javascript currying柯里详解

    摘要:面试题实现结果,题的核心就是问的的柯里化先说说什么是柯里化,看过许多关于柯里化的文章,始终搞不太清楚,例如柯里化是把接受多个参数的函数变换成接受一个单一参数最初函数的第一个参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。 面试题:实现add(1)(2)(3) //结果 = 6,题的核心就是问的js的柯里化 先说说什么是柯里化,看过许多关于柯里化的文章,始终搞不太清楚,例如...

    mengbo 评论0 收藏0

发表评论

0条评论

Tamic

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<