# OOP:this,call,apply,bind

# this

this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的。

一般情况下,this 的指向分为4种。

  1. 作为对象的方法调用
  2. 作为普通函数调用
  3. 构造器
  4. 被 .call, .bind, .apply 来改变 this 指向

作为对象的方法调用

var obj = {
  a: 1,
  getA: function(){
    alert ( this === obj ); // 输出:true
    alert ( this.a );       // 输出: 1
  }
};
obj.getA();

作为普通函数调用

window.name = 'globalName';
var getName = function(){
  return this.name;
};
console.log( getName() ); // 输出:globalName 

此时 this 指向 window

在 ECMAScript 5 的 strict 模式下,this 会指向 undefined。

function func(){
  "use strict"
  alert ( this ); // 输出:undefined
}
func(); 

构造器

var MyClass = function(){
  this.name = '石兴龙';
};
var obj = new MyClass();
alert ( obj.name ); // 输出:石兴龙

被 .call, .bind, .apply 来改变 this 指向

var obj1 = {
  name: '小明',
  getName: function(){
    return this.name;
  }
};
var obj2 = {
  name: '石兴龙'
};
console.log( obj1.getName() ); // 输出: 小明
console.log( obj1.getName.call( obj2 ) ); // 石兴龙 

# call, apply

这三个函数都属于 Function

'call' in Function.prototype  // true
'apply' in Function.prototype // true
'bind' in Function.prototype  // true

那么也就是说,任何一个函数都有用这三个方法(所有的函数都是对象)。都可以调用这几个方法改变函数的this指向

call 和 apply 这两个函数比较相似。他们都是立即执行一个函数,并且给他指定新的 this 和参数。唯一的区别是接受参数的方式不同。

apply 接受两个参数,第一个是 this 的指向。第二个是带有下标的集合,可以是数组,也可以是类数组。apply 方法把集合中的元素当做参数传递给函数

var obj = {
  getName(p1, p2, p3) {
    console.log(`I'm ${this.name}`, p1, p2, p3)
  }
}
let o2 = {name: 'Spider-Man'}
obj.getName.apply(o2, [1, 2, 3]) // I'm Spider-Man 1 2 3

call 的参数是不固定的。第一个参数和 apply 一样,用来改变函数的this指向。之后的参数call会原封不动的按照参数的顺序传递给调用的函数当参数

var obj = {
  getName(p1, p2, p3) {
    console.log(`I'm ${this.name}`, p1, p2, p3)
  }
}
let o1 = {name: 'iron man'}
obj.getName.call(o1, 1, 2, 3) // I'm iron man 1 2 3

所以你也许会有疑问,这两个函数的作业如此相似。那么什么时候该用 call 什么时候又改用 apply 呢?

根据我的经验可以总结为下面一句话:参数固定用 apply,参数不固定用 call。

举几个小例子吧:

// 借用函数
let a = [1,2,3,4,5,6,7,8];
console.log(Math.max.apply(null, a)) // 8
console.log(Math.min.apply(null, a)) // 1

// call 封装函数
var func = function () {
  var self = this;
  var fn = [].shift.call(arguments);
  var args = [].slice.call(arguments);
  return function () {
    return fn.call(this, '预先传参数1', '预先传参数2', ...[].concat.call(args, [].slice.call(arguments)))
  }
}
let o = {
  age: '18',
  getName: function (...params) {
    console.log(params);
    console.log(this.age);
  }
}
o.getName = func(o.getName, 1,2,3,4);
o.getName(5,6,7);
// ["预先传参数1", "预先传参数2", 1, 2, 3, 4, 5, 6, 7]
// 18

# bind

bind 和 call, apply 的不同之处在于。他不是立刻调用函数,而仅仅给函数绑定this。

let o = {
  age: '18',
  getName: function (...params) {
    console.log(params);
    console.log(this.age);
  }
}
o.getName();

let o1 = {
  age: '19',
}
o.getName = o.getName.bind(o1, 1,2,3,4);
o.getName();
// 1,2,3,4
// 19

以上通过 Function.prototype.bind 来“包装” getName 函数,并且传入一个对象 o1 当作参 数,这个 o1 对象就是我们想修正的 this 对象。

# 手动实现 bind

我们现在通过之前的知识,使用 call, apply 就可以自己实现一个简化版的 bind

Function.prototype.TestBind = function () {
  var self = this;
  var context = [].shift.call(arguments);
  var args = [].slice.call(arguments);
  return function () {
    return self.apply(
      context, [].concat.call(args, [].slice.call(arguments))
    )
  }
}
上次更新: 9/1/2020, 1:54:46 PM