通过this深入理解javascript函数的bind、call、apply

我们都知道函数这个高级公民在js中的地位很高。用处很大,可是却很难搞!函数内部有一个很厉害的内部属性this关键字。而bind、call、apply是函数的3个非继承方法(但是在原型链中找到了这些方法!Fuction.prototype中找到了!所以我觉得是继承自Fuction原型对象),他们的作用都是用来改变this指向的。那么this到底是什么东西?有什么作用?

了解函数中的this

this引用的是函数据以执行的环境对象。也就是说this就像函数的一个指针,会指向函数执行环境。看一个例子:

window.color = 'red'
function say(){
    console.log(this.color)
}
say() //red
window.say() //与say() 等价与

可以看到,this指向了window对象。因为是window调用了say函数,所以say函数的执行环境widow;看下面的例子:

window.color = 'red'
var obj = {
    color:'yellow',
    say:function(){
        console.log(this.color)
    }
}

obj.say(); //yellow
//此时say方法是做为obj的方法调用,也就是说obj是say函数的执行环境对象,所以this也就指向了obj
var fun = obj.say;
fun() //red

将obj的say方法赋值给fun后,调用fun后发现this指向了window?也就是说函数此时的执行环境是window? 这里需要明白一点,函数名仅仅是一个包含指针的变量,函数是复杂数据类型,所以函数名就只是一个指针,指向堆中的内存地址!所以fun此时只是复制了指针地址,写成下面的写法就会看的清晰了:

window.color = 'red'
function say(){
    console.log(this.color)
}
var obj = {
    color:'yellow',
    say:say
}

obj.say(); //yellow
var fun = say;
window.fun() //red

apply和call

js提供了一些可以改变函数执行作用域的方法。因为普通函数如果通过上面的写法来改变this执行时上下文,写法就太过于麻烦。

apply: 语法:apply(thisObj,数组参数) 定义:应用某一个对象的一个方法,用另一个对象替换当前对象 说明:如果参数不是数组类型的,则会报一个TypeError错误。

call方法: 语法:call(thisObj, arg1, arg2, argN) 定义:调用一个对象的一个方法,以另一个对象替换当前对象。 说明:call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。 如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。 apply与call的唯一区别就是接收参数的格式不同。

window.color = 'red'
function say(){
    console.log(this.color)
}
var obj = {
    color:'yellow'
}

say.call(obj); //yellow

bind方法

bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。

var module = {
  x: 42,
  getX: function() {
    return this.x;
  }
}

var unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// expected output: undefined

var boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// expected output: 42

需要注意:

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

function A(a,b){
    this.a=a;
    this.b=b
    console.log(this)
}
var a = A.bind({x:1},1)
var b = a(5) // {x: 1, a: 1, b: 5}

function A(a,b){
    this.a=a;
    this.b=b
    console.log(this)
}
var a = A.bind({x:1},1)//作为构造函数使用的绑定函数
var b = new a(5) // {a: 1, b: 5}

可以看出来:使用 new 操作符去构造一个由目标函数创建的新实例。bind提供的 this 就会被忽略。不过提供的参数列表仍然会插入到构造函数调用时的参数列表之前。

2.bind的实现

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

  F.prototype = self.prototype;
  bound.prototype = new F();
  retrun bound;
};

3.js中当函数执行bind后再次执行bind或call时会怎样

var test = function(x,y){
    console.log(this,arguments)
}
var a=test.bind({s:1},1)
a.call({d:1},2) // {s: 1} 1 2
var b = a.bind({d:1},2)
b()// {s: 1} 1 2

var c = b.bind({e:3},3) 
c()// {s: 1} 1 2 3

es5文档中中说到如果我们在一个由bind创建的函数中调用call,假设是x.call(obj,y,z,…)并且传入this,和参数列表的时候会执行下面的步骤: 1.首先用三个参数分别保存函数x函数的内部属性中存的this值目标函数参数列表。 2.然后执行目标函数的内部call函数,也就是执行目标函数的代码,并且传入1中保存的this和实参(这里的实参是目标函数本来就有的也就是bind时传入的实参加上调用call时传的实参) 重点在1中,从es5的bind函数说明中我们知道,当我们用一个函数调用bind的时候,返回的函数中会保存这三个参数。所以最后调用call的时候执行的函数是目标函数,也就是调用了bind的函数,传入的this也是bind调用时传入的,这些都是无法被修改的了,但是参数是调用bind和call时的叠加,这是我们唯一可以修改的地方。执行两次bind的原理可以参考bind的源码,和call的差不多,也是目标函数和this是被固定的了,只有参数列表会叠加。

箭头函数

箭头函数与普通函数些不同,有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

function foo() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1
原文链接:segmentfault.com

上一篇:Vue项目实现简单的权限控制
下一篇:async/await 和 Promise 的用例&关系

相关推荐

官方社区

扫码加入 JavaScript 社区