Function.prototype.bind()的使用与实现





Function.prototype

在一个对象的上下文中应用另一个对象的方法;参数能够以数组形式传入。

bind()方法会创建一个新函数,称为绑定函数.当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind()方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数.

在一个对象的上下文中应用另一个对象的方法;参数能够以列表形式传入。

若函数对象为generator,返回true,反之返回 false

获取函数的实现源码的字符串。 覆盖了 Object.prototype.toSource 方法。

获取函数的实现源码的字符串。覆盖了 Object.prototype.toString 方法。




bind()的语法

bind() 方法会创建一个新函数,当这个新函数被调用时,它的 this 值是传递给 bind() 的第一个参数, 它的参数是 bind() 的其他参数和其原本的参数。

fun.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg

调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。

当使用 bindsetTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object

如果 bind 函数的参数列表为空,或者thisArgnullundefined,执行作用域的 this 将被视为新函数的 thisArg

arg1, arg2, ...

当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

  • 返回值

返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。

一些使用例子

返回指定上下文的函数(创建绑定函数)

bind() 最简单的用法是创建一个函数,不论怎么调用,这个函数都有同样的 this 值。

JavaScript新手经常犯的一个错误是将一个方法从对象中拿出来,然后再调用,期望方法中的 this 是原来的对象(比如在回调中传入这个方法)。如果不做特殊处理的话,一般会丢失原来的对象。

基于这个函数,用原始的对象创建一个绑定函数,巧妙地解决了这个问题。

this.name = "jack";

let demo = {
    name: "rose",
    getName: function () {
        return this.name;
    }
}

let demo01 = {
    name: "pornhub",
}

let another1 = demo.getName;

//输出rose  这里的this指向demo 
console.log(demo.getName());

//输出jack  这里的this指向全局对象
console.log(another1())

//运用bind方法更改this指向
let another2 = another1.bind(demo);

//输出rose  这里this指向了demo对象了
console.log(another2());


//运用bind方法更改this指向
let another3 = another1.bind(demo01);

//输出pornhub  这里this指向了demo01对象
console.log(another3());

内定参数(偏函数)

  • 柯里化也可以实现内定参数的效果

bind() 的另一个最简单的用法是使一个函数拥有预设的初始参数。

只要将这些参数(如果有的话)作为 bind() 的参数写在 this 后面。

当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们后面。

function list() {
    return Array.prototype.slice.call(arguments);
}

let list1 = list(1, 2, 3); 

// Create a function with a preset leading argument
let leadingThirtysevenList = list.bind(undefined, 7);

let list2 = leadingThirtysevenList(); 
let list3 = leadingThirtysevenList(1, 2, 3); 

console.log(list1)  // [1, 2, 3]
console.log(list2)  // [7]
console.log(list3)  // [7, 1, 2, 3]

在事件中使用bind

在事件中使用bind

<body>
  <button>按钮</button>
</body>
<script>
  document.querySelector("button").addEventListener(
    "click",
    function(event) {
      console.log(event.target.innerHTML + this.url);
    }.bind({ url: "test.com" })
  );
</script>

闭合作用域(配合 setTimeout

一道关于闭包的题

for (let j = 1; j <= 5; j++) {
    setTimeout(
        () => { console.log(j) },
        j * 1000);
}

// bind实现
for (var i = 1; i <= 5; i++) {
    setTimeout(
        console.log.bind(console, i), 
    i * 1000);
}

作为构造函数使用的绑定函数

绑定函数自动适应于使用 new 操作符去构造一个由目标函数创建的新实例。

当一个绑定函数是用来构建一个值的,原来提供的 this 就会被忽略。不过提供的参数列表仍然会插入到构造函数调用时的参数列表之前。

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function() { 
  return this.x + ',' + this.y; 
};

var p = new Point(1, 2);
p.toString(); // '1,2'

var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0/*x*/);

// 本页下方的 polyfill 不支持运行这行代码,
// 但使用原生的 bind 方法运行是没问题的:

var YAxisPoint = Point.bind(null, 0/*x*/);

/*(译注:polyfill 的 bind 方法中,如果把 bind 的第一个参数加上,
即对新绑定的 this 执行 Object(this),包装为对象,
因为 Object(null) 是 {},所以也可以支持)*/

var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // '0,5'

axisPoint instanceof Point; // true
axisPoint instanceof YAxisPoint; // true
new YAxisPoint(17, 42) instanceof Point; // true

请注意,你不需要做特别的处理就可以用 new 操作符创建一个绑定函数。也就是说,你不需要做特别处理就可以创建一个可以被直接调用的绑定函数,即使你更希望绑定函数是用 new 操作符来调用。

// ...接着上面的代码继续的话,
// 这个例子可以直接在你的 JavaScript 控制台运行 

// 仍然能作为一个普通函数来调用
// (即使通常来说这个不是被期望发生的)
YAxisPoint(13);

emptyObj.x + ',' + emptyObj.y;   //  '0,13'

如果你希望一个绑定函数要么只能用 new 操作符,要么只能直接调用,那你必须在目标函数上显式规定这个限制。

快捷调用

在你想要为一个需要特定的 this 值的函数创建一个捷径(shortcut)的时候,bind() 也很好用。

可以用 Array.prototype.slice 来将一个类似于数组的对象(array-like object)转换成一个真正的数组。

var slice = Array.prototype.slice;

// ...

slice.apply(arguments);

bind()可以使这个过程变得简单。

在下面这段代码里面,sliceFunction.prototypeapply() 方法的绑定函数,并且将 Array.prototypeslice() 方法作为 this 的值。

这意味着用不着上面那个 apply()调用了。

// 与前一段代码的 "slice" 效果相同
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.apply.bind(unboundSlice);

// ...

// slice(arguments);
slice([1,23,3,,4,123,123,213])
// (8) [1, 23, 3, empty, 4, 123, 123, 213]

比如说,要添加事件到多个节点,for 循环当然没有任何问题,但还可以 “剽窃” forEach 方法。

可以用 bind 将函数封装的更好:

let unboundForEach = Array.prototype.forEach
let forEach = Function.prototype.call.bind(unboundForEach);

forEach(document.querySelectorAll('input[type="button"]'), function (el) {
  el.addEventListener('click', fn);
});

将 x.y(z) 变成 y(x,z) 的形式:

let obj = {
  num: 10,
  getCount: function() {
    return this.num;
  }
};

let unboundBind = Function.prototype.bind
let bind = Function.prototype.call.bind(unboundBind);

let getCount = bind(obj.getCount, obj);
console.log(getCount());  // 10

手动实现一个bind()

再回味一下bind到底是用来干嘛的,说实话,有点迷。

bind()是将函数绑定到某个对象,比如 a.bind(hd) 可以理解为将a函数绑定到hd对象上即 hd.a()。

  • 与 call/apply 不同bind不会立即执行
  • bind 是复制函数形为会返回新函数

bind是复制函数行为

思路

  • 首先判断第一个参数是否是function类型,如果不是则报错
  • 保存this指向
  • 将第一个参数之外的参数放入一个列表中
  • 返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。
Function.prototype.myBind = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }
    let _this = this
    let args = [...arguments].slice(1)
    
    // 返回一个函数
    return function F() {
        // 因为返回了一个函数,我们可以 new F(),所以需要判断
        if (this instanceof F) {
            return new _this(...args, ...arguments)
        }
        return _this.apply(context, args.concat(...arguments))
    }
}


for (var i = 1; i <= 5; i++) {
    setTimeout(
        console.log.myBind(console, i), 
    i * 1000);
}

参考资料

MDN文档

后盾人

前端进阶之道

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值