资讯专栏INFORMATION COLUMN

JavaScript中bind方法的实现

dackel / 1040人阅读

摘要:新函数也能使用操作符创建对象这种行为就像把原函数当成构造器,提供的值被忽略。说明绑定后的新函数被实例化之后,需要继承原函数的原型链方法,且绑定过程中提供的被忽略继承原函数的对象,但是参数还是会使用。

在讨论bind方法前,我们可以先看一个例子:

</>复制代码

  1. var getElementsByTagName = document.getElementsByTagName;
  2. getElementsByTagName("body");

这样在浏览器(这里使用的是chrome)执行会报错:

原因也显而易见:上面的getElementsByTagName方法是document.getElementsByTagName的引用,但是在执行时this指向了globalwindow对象,而不是document对象。

解决办法也很简单,使用callbind方法来改变this

</>复制代码

  1. var getElementsByTagName = document.getElementsByTagName;
  2. getElementsByTagName.call(document, "body");

</>复制代码

  1. var getElementsByTagName = document.getElementsByTagName;
  2. getElementsByTagName.bind(document)("body");

上述两种解决办法也可以看出callbind的区别:call方法是直接执行,而bind方法是返回一个新函数。

实现

由于bind方法是从ES5才开始引入的,不是所有浏览器都支持,为了实现兼容,需要自己实现bind方法。

我们先来看看bind方法的定义:

</>复制代码

  1. bind方法会创建一个新函数。当这个新函数被调用时,bind的第一个参数将作为它运行时的this(该参数不能被重写), 之后的一序列参数将会在传递的实参前传入作为它的参数。
    新函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器,提供的this值被忽略。
初步思路

因为bind方法不是立即执行函数,需要返回一个待执行的函数,这里可以利用闭包:return function(){}

作用域绑定:可以使用applycall方法来实现;

参数传递:由于参数的不确定性,需要用apply传递数组;

根据上述思路,我们先来实现一个简单的customBind方法;

</>复制代码

  1. Function.prototype.customBind = function (context) {
  2. var self = this,
  3. /**
  4. * 由于参数的不确定性,我们用 arguments 来处理
  5. * 这里的 arguments 只是一个类数组对象,可以用数组的 slice 方法转化成标准格式数组
  6. * 除了作用域对象 self 以外,后面的所有参数都需要作为数组进行参数传递
  7. */
  8. args = Array.prototype.slice.call(arguments, 1);
  9. // 返回新函数
  10. return function() {
  11. // 作用域绑定
  12. return self.apply(context, args);
  13. }
  14. };
测试初版

</>复制代码

  1. var testFn = function(obj, arg) {
  2. console.log("作用域对象属性值:" + this.value);
  3. console.log("绑定函数时参数对象属性值:" + obj.value);
  4. console.log("调用新函数参数值:" + arg);
  5. }
  6. var testObj = {
  7. value: 1
  8. };
  9. var newFn = testFn.customBind(testObj, {value: 2});
  10. newFn("hello world");
  11. // 执行结果:
  12. // 作用域对象属性值:1
  13. // 绑定函数时参数对象属性值:2
  14. // 调用新函数参数值:undefined

从测试执行结果可以看出,上面已经实现了作用域绑定,但是返回新函数newFn不支持传参,只能在testFn绑定时传参。
因为我们最终需要使用的是newFn,所以我们需要让newFn支持传参。

动态参数

我们来继续改造

</>复制代码

  1. Function.prototype.customBind = function (context) {
  2. var fn = this,
  3. args = Array.prototype.slice.call(arguments, 1);
  4. return function() {
  5. // 将新函数执行时的参数 arguments 全部数组化,然后与绑定时传参 arg 合并
  6. var newArgs = Array.prototype.slice.call(arguments);
  7. return fn.apply(context, args.concat(newArgs));
  8. }
  9. };
测试动态参数

</>复制代码

  1. var testFn = function(obj, arg) {
  2. console.log("作用域对象属性值:" + this.value);
  3. console.log("绑定函数时参数对象属性值:" + obj.value);
  4. console.log("调用新函数参数值:" + arg);
  5. }
  6. var testObj = {
  7. value: 1
  8. };
  9. var newFn = testFn.customBind(testObj, {value: 2});
  10. newFn("hello world");
  11. // 执行结果:
  12. // 作用域对象属性值:1
  13. // 绑定函数时参数对象属性值:2
  14. // 调用新函数参数值:hello world

可以看出,绑定时传的参数和新函数执行时传的参数是合并在一起形成完整参数的。

原型链

我们再回到bind方法的定义第二条:新函数也能使用new操作符创建对象。
说明绑定后的新函数被new实例化之后,需要继承原函数的原型链方法,且绑定过程中提供的this被忽略(继承原函数的this对象),但是参数还是会使用。所以我们需要一个中转的函数将原型链传递下去。

首先我们需要明确new实例化过程,比如说var a = new b()

创建一个空对象a = {},并且this变量引用指向到这个空对象a

继承被实例化函数的原型:a.__proto__ = b.prototype

被实例化方法bthis对象的属性和方法将被加入到这个新的this引用的对象中:b的属性和方法被加入的a里面;

新创建的对象由this所引用:b.call(a)

接下来我们实现原型链。

</>复制代码

  1. Function.prototype.customBind = function (context) {
  2. var self = this,
  3. args = Array.prototype.slice.call(arguments, 1);
  4. // 创建中转函数
  5. var cacheFn = function() {};
  6. var newFn = function() {
  7. var newArgs = Array.prototype.slice.call(arguments);
  8. /**
  9. * 这里的 this 是指调用时的执行上下文
  10. * 如果是 new 操作,需要绑定 new 之后作用域,this 指向新的实例对象
  11. */
  12. return self.apply(this instanceof cacheFn ? this : context, args.concat(newArgs));
  13. };
  14. // 中转原型链
  15. cacheFn.prototype = self.prototype;
  16. newFn.prototype = new cacheFn();
  17. return newFn;
  18. };
测试原型链

</>复制代码

  1. function Point(x, y) {
  2. this.x = x;
  3. this.y = y;
  4. }
  5. Point.prototype.toString = function() {
  6. return this.x + "," + this.y;
  7. };
  8. var YAxisPoint = Point.customBind({}, 0);
  9. var axisPoint = new YAxisPoint(5);
  10. axisPoint.toString(); // "0,5"
  11. axisPoint instanceof Point; // true
  12. axisPoint instanceof YAxisPoint; // true
  13. new Point(1, 2) instanceof YAxisPoint; // true

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

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

相关文章

  • 理解 JavaScript call()/apply()/bind()

    摘要:理解文章中已经比较全面的分析了在中的指向问题,用一句话来总结就是的指向一定是在执行时决定的,指向被调用函数的对象。与和直接执行原函数不同的是,返回的是一个新函数。这个新函数包裹了原函数,并且绑定了的指向为传入的。 理解 JavaScript this 文章中已经比较全面的分析了 this 在 JavaScript 中的指向问题,用一句话来总结就是:this 的指向一定是在执行时决定的,...

    duan199226 评论0 收藏0
  • Javascriptbind()方法使用与实现

    摘要:秒后调用函数我有朵花瓣注意对于事件处理函数和方法也可以使用上面的方法绑定函数作为构造函数绑定函数也适用于使用操作符来构造目标函数的实例。 在讨论bind()方法之前我们先来看一道题目: javascriptvar altwrite = document.write; altwrite(hello); //1.以上代码有什么问题 //2.正确操作是怎样的 //3.bind()方法怎么实...

    CastlePeaK 评论0 收藏0
  • call,apply and bind in JavaScript

    摘要:文章尽量使用大量实例进行讲解,它们的使用场景。在严格模式下,函数被调用后,里面的默认是后面通过调用函数上的和方法,该变指向,函数里面的指向。利用,可以传入外层的上下文。同样适用的还有,里面的对象,它也是一种类数组对象。 call,apply and bind in JavaScript 在ECMAScript中,每个函数都包含两个继承而来的方法:apply() 和 call(),这两个...

    JohnLui 评论0 收藏0
  • JavaScript之柯里化

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

    since1986 评论0 收藏0
  • bind方法javascript实现及函数柯里化

    摘要:而模拟的方法返回的函数用作构造函数时,生成的对象为。同样,使用运算符时,绑定构造函数和未绑定构造函数并无两样。标准的方法创建一个新函数称为绑定函数,新函数与被调函数绑定函数的目标函数具有相同的函数体在规范中内置的属性。 这是一道面试题,题目给出了使用bind方法的样例,要求用javascript实现这个方法,面试官还很善意的提醒我函数柯里化,然而,我还是不会这道题目,所以回来这会《ja...

    Tamic 评论0 收藏0
  • JavaScript || 函数

    摘要:每个函数表达式包括函数对象括号和传入的实参组成。和作用都是动态改变函数体内指向,只是接受参数形式不太一样。在定义函数时,形参指定为一个对象调用函数时,将整个对象传入函数,无需关心每个属性的顺序。 函数 JavaScript中,函数指只定义一次,但可以多次被多次执行或调用的一段JavaScript代码。与数组类似,JavaScript中函数是特殊的对象,拥有自身属性和方法 每个函数对象...

    learn_shifeng 评论0 收藏0

发表评论

0条评论

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