# this、call 和 apply
# this
this
具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。
this
的指向大致可以分为以下4种:
- 作为对象的方法调用
this
指向该对象
- 作为普通函数调用
this
通常指向全局对象,ES5严格模式下 this
为 undefined
- 构造器调用
通常情况下,构造器里的this
指向返回的这个对象。如果构造器显式的返回了一个object
类型的对象,那么最终会返回这个对象。
Function.prototype.call
或Function.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
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
2
3
# apply
接收两个参数,第一个参数指定函数体内this
对象的指向,第二个参数为一个带下标的集合,这个集合可以是数组,也可以是类数组。apply
方法把这个集合中的元素作为参数传递给被调用的函数。
# call
- 传入的参数不固定,第一个参数指定函数体内
this
对象的指向,从第二个参数开始往后,每个参数被依次传入函数。 call
是包装在apply
上面的语法糖- 当使用
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
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
2
3
4
5
6
7
8
9
可以看到Array.prototype.push
实际上是一个属性复制的过程,顺便修改了这个对象的length
属性。至于被修改的对象是数组还是类数组,这一点不重要。
借用Array.prototype.push
方法需要满足两个条件:
- 对象本身可以存取属性
- 对象的
length
属性可读写(函数的length属性就是一个只读的属性,表示形参的个数)