JS this绑定 - bind、call、apply

367 阅读4分钟

为什么写这个文章了呢?面试的时候(完整面试内容点这儿),面试官问bind的作用是啥?bind的第二个参数是做什的?箭头函数能使用bind函数吗 ?
前面其实也有看这方面的文章,但是不够细节,所以重新学习这方面的知识。

bind

bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数作为新函数的参数,供调用时使用。
bind函数会创建一个新的绑定函数(bound function, BF)。绑定函数时一个exotic function object(怪异函数对象),它包装了原函数对象。调用绑定函数具有以下内部属性

  • [[BoundTargetFunction]]-包装函数对象
  • [[BoundThis]]-在调用包装函数时始终作为this值传递的值
  • [[BoundArguments]]-参数列表,在对包装函数做任何调用都会优先用列表元素填充参数列表
  • [[Call]]-执行与该对象关联的代码。通过函数调用表达式调用,内部方法的参数时一个this值和一个包含通过表达式传递给函数的参数列表。

当调用bind函数时,会调用[[BoundTargetFunction]]上的内部方法[[call]],类似Call(BoundThis, args)。其中,boundThis时[[BoundThis]],args是[[BoundArguments]]通过函数调用传入的参数列表。

语法

function.bind(thisArg[, arg1[, arg2[, arg3);

参数

thisArg:
调用绑定函数时作为this参数传递给目标函数的值。如果使用new运算符构造绑定函数,则忽略该值,当使用bindsetTimeout中创建一个函数(作为回调)时,作为thisArg传递的任何原始值都转换为Object。如果bind函数的参数列表为空,或者thisArg为null或undefined,执行作用域的this将被视为新函数的thisArg。

// 全局作用域 this 执行window
function bindFun() {
    console.log(this);
}
const obj1 = {name: 'obj1'}
bindFun.bind(obj1)(); // obj1
bindFun.bind()(); // window
obj1.bindFun1 = bindFun.bind();
obj1.bindFun1(); // window

const obj2 = {name: 'obj2'}
obj1.bindFun2 = bindFun.bind(obj2);
obj1.bindFun2(); // obj2

obj1.bindFun3 = bindFun.bind().bind(obj2);
obj1.bindFun3(); // window

返回值

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

示例

创建绑定函数

this.x = 'window';
let obj = {
    x: 'obj',
    getX: function() { console.log(this.x); }
}
obj.getX(); // obj

let getX = obj.getX;
getX(); // window

let boundGetx = getX.bind(obj);
boundGetx(); // obj

传入参数的绑定

bind另外一种用法,就是在创建绑定函数的时候,传入部分的参数。

function getList() {
    return Array.prototype.slice(argumentd);
}
function addArgs(arg1, arg2) {
    return arg1 + arg2;
}
const list1 = getList(1, 2, 3); // [1, 2, 3]
const res1 = addArgs(1, 2); // 3

// 创建一个带有参数的 绑定函数
const getListBy37 = getList.bind(null, 37);
const list2 = getListBy37(); // [37]
const list3 = getListBy37(1, 2); // [37, 1, 2]

const addBy37 = addArgs.bind(null, 37);
const res2 = addBy37(5); // 37 + 5 = 42
const res3 = addBy37(5, 10); // 37 + 4 = 45 因为addArgs只能接受2个参数,所以这儿的10会被忽略

bind与setTimeout

通常作为setTimeout入参数的回调函数,它的 this将指向window或者全局对象。但是很多时候我们有需要这个回调函数中的this指向当前的示例,此时我们就需要利用bind。

function LateBloomer() {
    this.petalCount = Math.floor(Math.random() * 12) + 1;
}
LateBloomer.prototype.bloom = function() {
    setTimeout(this.declare.bind(this), 1000);
}
LateBloomer.prototype.declare = function() {
    console.log(this.petalCount);
    
}
const flower = new LateBloomer();
flower.bloom();

bind与构造函数

当bind函数被用在构造函数时,bind将被忽略

function Point(x, y) {
    this.x = x;
    this.y = y;
}
Point.prototype.toString = function() {
    return `${this.x},${this.y}`;
}
const p1 = new Point(1, 2);
p.toString(); // 1,2

const YAxisPoint = Point.bind(null, 0);
const p2 = new YAxisPoint(2, 3); // null没生效
p2.toString(); // 0,2


const YAxisPoint3 = Point.bind({}, 0);
const p3 = new YAxisPoint3(2, 3); // bind传入的thisArg不会生效
p3.toString(); // 0,2

p3 instanceof Point // true
p3 instanceof YAxisPoint3 // true

创建快捷方式

const unboundSlice = Array.prototype.slice;
const slice = Function.prototype.apply.bind(unboundSlice);

// ……
slice(arguments);

bind与箭头函数

我们都知道箭头函数this指向与外层作用域的this,那如果我们对箭头函数使用bind会是什么效果呢?
bind传入的thisArg不会生效,但是传入的参数是会生效的,如下:

var name = 'window';
const fun = (x, y) => {
    console.log(this.name, x);
}
const obj = {name: 'obj'};
const boundFun = fun.bind(obj, 'x');
boundFun('y'); // window x y

boundFun.bind({name: 'bind + bind'}, 'y2')('y3'); //  window x y2

手写bind

Function.prototype.myBind = function(thisArg, ...arg1) {
    const _this = this;
    return function _a (...arg2) {
        const params = [...arg1, ...arg2];
        if(this instanceof _a) {
            return new _this(...params);
        }
        else {
            return _this.apply(thisArg, params);
        }
    }
}

call

call函数在函数执行时绑定this的值和参数

语法

fun.call();
fun.call(thisArg);
fun.call(thisArg, arg1);
fun.call(thisArg, arg1, arg2);
fun.call(thisArg, arg1, ..., argn);

参数

thisArg: func被调用时this的值。
在非严格模式下,thisArg的值为null 或 undefined的时候,thisArg将被替代换全局对象。

arg1, arg2, ……, argn:函数的入参

返回值

调用call的函数使用thisArg和参数后的执行结果

示例

var name = 'window'
function great() {
    console.log(`hello ${this.name}`);
}
great.call(); // hello window
great.call(undefined); // hello window
great.call(null); // hello window
great.call({name: 'bajiu'}); // hello bajiu

手写call

Function.prototype.call = function(thisArg, ...arg) {
    const context = (thisArg === null || thisArg === undefiend) ? window : Object(thisArg);
    context.fn = this;
    const res = context.fn(...arg);
    delete context.fn;
    return res;
}

apply

方法整体用法与call一样,只存在入参形式的不同

手写apply

Function.prototype.myApply = function(thsArg, arg) {
    const context = (thisArg === null || thisArg === undefiend) ? window : Object(thisArg);
    context.fn = this;
    const res = context.fn(...arg);
    delete context.fn;
    return res;
}
``