This
this指向当前属性所在对象。
var A = {
name: '张三',
describe: function () {
return '姓名:'+ this.name;
}
};
var name = '李四';
var f = A.describe;
f() // "姓名:李四"
JavaScript 语言之中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this
就是函数运行时所在的对象(环境)。这本来并不会让用户糊涂,但是 JavaScript 支持运行环境动态切换,也就是说,this
的指向是动态的,没有办法事先确定到底指向哪个对象,这才是最让初学者感到困惑的地方。
this的指向分类
-
全局环境window
只要是单独在全局环境下执行的函数,其this都是指向全局环境。
-
构造函数
构造函数中的
this
,指的是实例对象。var Obj = function (p) { this.p = p; };
上面代码定义了一个构造函数
Obj
。由于this
指向实例对象,所以在构造函数内部定义this.p
,就相当于定义实例对象有一个p
属性。var o = new Obj('Hello World!'); o.p // "Hello World!"
-
对象的方法
this是没有链式调用的。只会指向当前运行的对象环境,如果在当前对象环境中找不到某变量,则不能进行比较。 就是内层的函数f2(函数内的函数)
this
不指向外部,而指向顶层对象。为了避免在同一对象内多次调用this其指向不统一的现象,可以通过一个变量固定this。在进行使用this。
var o = { f1: function() { console.log(this); var that = this; var f2 = function() { console.log(that); }(); } } o.f1() // Object // Object
操作数组时,其回调函数中不要使用绑定this。
var o = { v: 'hello', p: [ 'a1', 'a2' ], f: function f() { this.p.forEach(function (item) { console.log(this.v + ' ' + item); }); } } o.f() // undefined a1 // undefined a2
原因同上,内层函数this指向顶层对象。
解决办法:
-
使用中间变量
-
传递运行环境
var o = { v: 'hello', p: [ 'a1', 'a2' ], f: function f() { this.p.forEach(function (item) { console.log(this.v + ' ' + item); }, this); } } o.f() // hello a1 // hello a2
尽量避免在回调函数中使用this。
-
改变this指向的方法
函数的四种调用方式
- 方法调用,作为对象的方法进行调用
- 函数表达式定义,并调用
- 构造函数调用
- apply、call调用
1. call
function函数特有的call方法可以指定函数内部的this指向。call方法的参数应该是一个对象,如果参数为空、null和undefined,则默认传入全局对象。
call的作用是把函数放入指定作用域执行。
2. apply
与call的区别在与传递参数为数组。
3. bind
bind绑定作用域后返回新的函数。
回调函数使用bind()方法。
手动实现call、apply、bind
- 手动实现call
语法:
fun.call(thisArg, arg1, arg2, …)
其中thisArg取值可能为:
- 不传,或者传null,undefined, this指向window对象
- 传递另一个函数的函数名fun2,this指向函数fun2的引用
- 值为原始值(数字,字符串,布尔值),this会指向该原始值的自动包装对象,如 String、Number、Boolean
- 传递一个对象,函数中的this指向这个对象
Function.prototype.call = function (context) {
// 判断context对象类型 如果为undefined或null则进行对象修改为windows
// 判断是否是undefined和null
if (typeof context === 'undefined' || context === null) {
context = window
}
//在context上定义函数fn
context.fn = this;
//保存传递进来的参数
var args = [];
for(var i = 1; i < arguments.length; i++){
args.push('arguments['+ i +']');
}
//通过eval函数执行fn方法
var result = eval('context.fn(' + args.join(',') + ')');
// 删除fn方法
delete context.fn;
//返回结果值
return result;
}
- 手动实现apply
apply语法:
fun.apply(thisArg, [argsArray])
argsArray: 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。
Function.prototype.myApply = function (context, arr) {
// 判断context对象类型 如果为undefined或null则进行对象修改为windows
// 判断是否是undefined和null
if (typeof context === 'undefined' || context === null) {
context = window
}
//在context上定义函数fn,指向目标函数
context.fn = this;
var result;
if (!arr) {
result = context.fn();
}
else {
//保存传递进来的参数
var args = [];
for(var i = 1; i < arguments.length; i++){
args.push('arguments['+ i +']');
}
//通过eval函数执行fn方法
result = eval('context.fn(' + args.join(',') + ')');
}
// 删除fn方法
delete context.fn;
//返回结果值
return result;
}
总结:
apply和call都是用来改变函数的this指向的。
区别在于传参形式不同。
其实现方法类似于一次性的。
3. 手动实现bind
bind语法
func.bind(thisArg[, arg1[, arg2[, …]]])
bind功能:
bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )
特点:
- 返回一个函数
- 可以传入参数
- 支持柯里化传参
- 当bind绑定后的函数作为构造函数时,绑定的this失效。实例的原型对象指向绑定前的构造函数原型。但是参数会继续传递。
Function.prototype.myBind = function(context) {
//判定其是否为函数,如果不是函数则抛出异常
if(typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
//获取参数
//这里的this指的是bind前的函数
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
//这里的 arguments是bind返回的函数传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
//判断返回的函数中的this是全局函数调用还是new 构造函数调用
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}