深入理解函数内部原理(六)——函数的内部方法call、apply、bind

call和apply

这两个方法都是打破了解析器在函数调用时创建执行环境时赋值this绑定的时候的内部规则,直接通过Function内置标准类型的prototype属性中的两个方法call和apply进行明确的声明this的值。
但是这里的call是自定义的用户可以使用的可以编程的,还有任何一个函数都有内置属性[[Call]],这两者不同。后者内部原理性的调用,创建执行环境的。
具体表述下call和apply的工作流程。
call的工作流程
F.call(thisarg,[arg1……])
1、先判断F是否为一个函数,如果不是一个函数,那么将抛出TypeError异常。
2、创建一个内部类型空列表list
3、然后如果参数除去thisarg外还有其他参数的话,就将这些值添加到list中
4、Iangthisarg和list作为F内部属性[[Call]]的参数传入调用进行函数的执行操作

apply的工作流程
F.apply(thisarg,argList)
1、先判断F是否为一个函数,如果不是一个函数,那么将抛出TypeError异常。
2、创建一个内部类型空列表list
3、如果argList为null或者undefined的话不做什么。
4、否则,就将argList中的成员添加到list中,仅仅是成员,即使argList成员为另一个数组
4、Iangthisarg和list作为F内部属性[[Call]]的参数传入调用进行函数的执行操作

所以可以总结出apply和call的不同。除了都可以值传入this不传入任何值之外。Call和apply第二个参数都可以是数组类型,只不过对于call来说,这个数组不会取内部的成员组成新的内部列表类型,而apply要取这个成员组成内部列表类型。同时call函数属性length为1.但是apply函数属性length为2.

bind函数

Bind函数也是Function内置标准类型的prototype属性的一个方法,所以所有的函数都会继承这个方法,这个方法的作用就是将一个函数和一个对象绑定在一起,同时提供一些静态的变量。Bind函数返回的是一个已经绑定好了的函数对象,那么这个函数对象和正常的通过函数定义方式创建的函数对象有什么区别呢?
通过bind方法创建的对象,除了和普通方式创建的对象一样都有对象的基本属性、[Class]都为Function都有函数特有的属性[Call][Construct][HasInstrance]之外没有[Scope][FormalParameters]Constructor、prototype,增加了几个特殊的内部属性[TargetFunction]、[BoundThis]、[BoundArgs],还有length值也不一样。下面将详细介绍下Bind函数的工作流程:

F.Bind(ThisArg[,arg1……])
1、将这个函数调用后执行环境中this赋值给Target,将A赋值为[arg1……]参数列表的内部列表类型的表示
2、判断Target是否是一个函数,如果不是讲抛出TypeError异常
3、创建一个新对象F,添加一些通用的方法等
4、通过接下来的方式设置[Call][Construct][HasInstrance]内部方法注意这里实现和函数定义过程中这三个函数的实现不一样
5、添加[TargetFunction]赋值为Target,添加[BoundThis]赋值为thisarg,添加[BoundArgs]赋值为A
6、将length值设置为Target的length值减去A的长度但不能小于0否则为0
7、添加length、arguments等
8、返回F

[[Call]]
当调用一个用 bind 函数创建的函数对象 F 的 [[Call]] 内部方法,传入一个 this 值和一个参数列表 ExtraArgs,采用如下步骤:
令 boundArgs 为 F 的 [[BoundArgs]] 内部属性值。
令 boundThis 为 F 的 [[BoundThis]] 内部属性值。
令 target 为 F 的 [[TargetFunction]] 内部属性值。
令 args 为一个新列表,它包含与列表 boundArgs 相同顺序相同值,后面跟着与 ExtraArgs 是相同顺序相同值。
提供 boundThis 作为 this 值,提供 args 为参数调用 target 的 [[Call]] 内部方法,返回结果。

[[Construct]]
当调用一个用 bind 函数创建的函数对象 F 的 [[Construct]] 内部方法,传入一个参数列表 ExtraArgs,采用如下步骤:
令 target 为 F 的 [[TargetFunction]] 内部属性值。
如果 target 不包含 [[Construct]] 内部方法 , 抛出一个 TypeError 异常。
令 boundArgs 为 F 的 [[BoundArgs]] 内部属性值。
令 args 为一个新列表,它包含与列表 boundArgs 相同顺序相同值,后面跟着与 ExtraArgs 是相同顺序相同值。
提供 args 为参数调用 target 的 [[Construct]] 内部方法,返回结果。

[[HasInstance]] (V)
当调用一个用 bind 函数创建的函数对象 F 的 [[Construct]] 内部方法,并以 V 作为参数,采用如下步骤:
令 target 为 F 的 [[TargetFunction]] 内部属性值。
如果 target 不包含 [[HasInstance]] 内部方法 , 抛出一个 TypeError 异常。
提供 V 为参数调用 target 的 [[HasInstance]] 内部方法,返回结果

值得注意

1、对于函数来说最重要的就是[Scope]值的问题,对于bind函数来说,虽然返回了一个新的函数,但是这个新函数的[Call]表明依然是调用被绑定函数,只不过对于this和参数做了加工。也就是说bind的实质是给一个函数再次的封装,改变了this和参数一些性质,但是对于作用域、作用域链没有多少改变。

var obj = function(){
    var count = 10;
    obj.prototype.getCount = (function(){
        return count;
    })
};
var o = new obj();
var g = o.getCount.bind(null);
console.log(g());/*10*/

2、bind创建的函数没有prototype属性,所以当作为构造函数的时候,也是调用的与之绑定的原始的函数。

var fun = function(){
}
fun.prototype.count = 23;

var bind = fun.bind(null);
var o = new bind();
console.log(o.count);/*23*/

3、bind函数存在兼容性
解决兼容性的方式如下:

Function.prototype.bind = Function.prototype.bind || function(ThsArg /*Args*/){
    var Target = this, A = arguments;
    var bindFun = function(){
        var arg = [];
        for(var i = 1; i < A.length; i++){arg.push(A[i]);}
        for(var i = 0; i < arguments.length; i++){arg.push(arguments[i]);}
        return Target.apply(thisArg,arg);
    }

    bindFun.constructor = null;
    binfFun.prototype = Target.this;
    return bindFun;
}

这样就可以最佳接近原生的bind方法了,无论是调用还是作为构造函数效果都一样了

阅读更多
版权声明:转载,请注明出处 https://blog.csdn.net/wmaoshu/article/details/60882737
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭