# this、call 和 apply

# this

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

this的指向大致可以分为以下4种:

  1. 作为对象的方法调用

this指向该对象

  1. 作为普通函数调用

this通常指向全局对象,ES5严格模式下 thisundefined

  1. 构造器调用

通常情况下,构造器里的this指向返回的这个对象。如果构造器显式的返回了一个object类型的对象,那么最终会返回这个对象。

  1. Function.prototype.callFunction.prototype.apply 调用

# 丢失的this

var obj = {
  name: 'jlq',
  getName: function() {
    return this.name
  }
}

console.log(obj.getName()) // jlq


var getName2 = obj.getName
console.log(getName2()); // 空字符串,严格模式下为undefined,有些浏览器默认存在name这个属性
1
2
3
4
5
6
7
8
9
10
11
12
var getId = document.getElementById

console.log(getId('div1')); // 报错,document.getElementById方法的内部实现中需要用到this,这个this本来被期望指向document,这种写法使得this改变了
1
2
3

# apply

接收两个参数,第一个参数指定函数体内this对象的指向,第二个参数为一个带下标的集合,这个集合可以是数组,也可以是类数组。apply方法把这个集合中的元素作为参数传递给被调用的函数。

# call

  1. 传入的参数不固定,第一个参数指定函数体内this对象的指向,从第二个参数开始往后,每个参数被依次传入函数。
  2. call是包装在apply上面的语法糖
  3. 当使用call或者apply的时候,如果传入的第一个参数为null,函数体内的this会指向默认的宿主对象。

# bind

模拟:

Function.prototype.bind = function() {
  var self = this // 保存原函数
  var context = [].shift.call(arguments) // 需要绑定的this上下文
  var args = [].slice.call(arguments) // 剩余参数转为数组

  return function() {
    return self.apply(context, [].concat.call(args, [].slice.call(arguments))) // 组合两次分别传入的参数
  }
}
1
2
3
4
5
6
7
8
9

# 用来借用其他对象的方法

arguments转为真正的数组:Array.prototype.slice

Array.prototype.push为例,看看V8引擎中的实现:

function ArrayPush() {
  var n = TO_UINT32(this.length); // 被push的对象的length
  var m = %_ArgumentsLength(); // push 的参数个数
  for(var i = 0; i < m; i++) {
    this[i + n] = %_Arguments(i); // 复制元素
  }
  this.length = n + m; // 修正length的值
  return this.length;
}
1
2
3
4
5
6
7
8
9

可以看到Array.prototype.push实际上是一个属性复制的过程,顺便修改了这个对象的length属性。至于被修改的对象是数组还是类数组,这一点不重要。

借用Array.prototype.push方法需要满足两个条件:

  1. 对象本身可以存取属性
  2. 对象的length属性可读写(函数的length属性就是一个只读的属性,表示形参的个数)