关于this对象
在《JavaScript高级程序设计》一书中说明了this对象是运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。
不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window。
var name = 'lin jia heng'
var object = {
name: 'My Object',
getNameFunc: function () {
return function () {
return this.name; //匿名函数的执行环境具有全局性,所以this指向window
}
}
}
alert(object.getNameFunc()()) // lin jia heng(非严格模式)
而下面的例子就可以成功返回“My Object”
var name = 'lin jia heng'
var object = {
name: 'My Object',
getNameFunc: function () {
return this.name; //当函数被作为某个对象的方法调用时,this等于那个对象
}
}
alert(object.getNameFunc()) // My Object
var name = 'lin jia heng'
var object = {
name: 'My Object',
getNameFunc: function () {
let that = this // 改变this指向
return function () {
return that.name; //匿名函数的执行环境具有全局性,所以this指向window
}
}
}
alert(object.getNameFunc()()) // My Object
在函数中执行
- 在非严格模式(默认绑定)
function func(){ console.log(this) } func() //Window
- 在严格模式(默认绑定)
这就验证了第一个例子中为什么特意表明在非严格模式下才成立了。function func(){ console.log(this) } func() //undefined
作为一个构造函数使用
在JS中,为了实现类我们需要定义一些构造函数,在调用一个构造函数的时候加上new这个关键字:
function Person(name) {
this.name = name;
console.log(this)
}
var p1 = new Person('kk');
// Person
此时this指向这个构造函数调用的时候实例化出来的对象
当然,构造函数其实也是一个函数,如果将构造函数当成普通函数来调用,this指向Window
function Person(name) {
this.name = name;
console.log(this)
}
var p1 = Person('kk');
// Window
在定时器中使用
setTimeout(function() {
console.log(this);
},0)// Window,setInterval也一样
如果没有特殊指向,定时器的回调函数中this的指向都是Window。这是因为JS的定时器方法是定义在Window下的。
在箭头函数中使用
- 在全局环境中使用:
var func = () => { console.log(this) } func() //Window
- 作为一个对象的一个函数使用:(隐式绑定)
var obj = { name: 'hh', func: function() { console.log(this); } } obj.func();//obj var obj = { name: 'hh', func: () => { console.log(this) } } obj.func(); //Window
- 作为对象的特殊情况,结合定时器来使用:
var obj = { name: 'hh', func: function() { setTimeout(function() { console.log(this); },0) } } obj.func();// Window var obj = { name: 'hh', func: function() { setTimeout(() => { console.log(this); },0) } } obj.func();// obj
箭头函数中的this的值取决于该函数外部非箭头函数的this的值,否则this的值会被设置为全局对象Window,且不能通过call(),apply()和bind()方法来改变this的值。–《深入理解ES6》
赋值给另外一个变量进行调用
var name = "windowsName";
var a = {
name: null,
fn: function() {
console.log(this.name); //windowsName
}
}
var f = a.fn;
f();
fn()最后仍然是被window调用的,所以this指向的也是window。this的指向并不是在创建的时候就确定并一成不变的,在es5this永远指向最后调用它的那个对象
'use strict' //严格模式下
let user = {
name: 'John',
hi(){alert(this.name);},
bye() {alert('Bye');}
};
user.hi();//John
(user.name == 'John' ? user.hi : user.bye)();// this指向undefined,所以此处报错,此处的函数user.hi没有加上括号进行立即执行,有点类似于上个例子的赋值变量进行调用
改变this的指向
- 像上述部分例子一样使用箭头函数
- 在函数内部定义一个变量_this = this
- 使用apply,call,bind(显示绑定)
- new实例化一个对象(new绑定)
使用_this = this
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
var _this = this;
setTimeout( function() {
_this.func1()
},100);
}
};
a.func2() // Cherry
在 func2 中,首先设置 var _this = this;,这里的 this 是调用 func2 的对象 a,为了防止在 func2 中的 setTimeout 被 window 调用而导致的在 setTimeout 中的 this 为 window。我们将 this(指向变量 a) 赋值给一个变量 _this,这样,在 func2 中我们使用 _this 就是指向对象 a 了。
使用apply,call,bind(显示绑定)
bind
如果你想将某个函数绑定新的this指向并且固定传入几个变量可以在绑定的时候就传入,之后调用新函数传入的参数都会排在后面
const obj = {}
function test(...args){
console.log(this === obj)//true, 因为bind方法把this指向了obj对象
console.log(args)
}
const newFn = test.bind(obj, 'lin', 'jiaheng')//因为bind返回一个新函数,所以要赋值给一个新变量
newFn('jiahenglin')
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.bind(a,1,2)() // 3
因为bind返回一个新函数,所以要加多一个括号手动调用它
call
需要注意的是,指定的this并不一定是该函数执行时真正的this,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(window),同时值为原始值(数学,字符串,布尔值)的this会指向该原始值的自动包装对象【理解为
如果你传入一个原始值(字符串,布尔类型或者数字类型)来当作this的绑定对象,这个原始值就会被转换成它的对象形式(也就是new String(…),new Boolean(…)或者new Number(…),这通常称为装箱】。
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
setTimeout( function () {
this.func1() // 没用利用call改变this的指向的话,这里的this就是Window
}.call(a),100);
}
};
a.func2() // Cherry
apply
第一个参数和call的一样,第二个参数一定是传入一个数组格式的,最终调用函数时候这个数组会拆成一个一个参数传入
因为apply函数传参的特性,所以可以实现一个全是数字的数组找出最大最小值的效果
const arr = [1,2,3,4,45,43,22]
const max = Math.max.apply(null,arr)
console.log(max)//45
// 也可以使用ES6的拓展运算符
console.log(Math.max(...arr))
bind,call,apply进阶例子
- 循环中利用闭包来处理回调
for(var i=0;i<10;i++){ (function(j){ setTimeout(function(){ console.log(j) },600) })(i) }
每次循环都会产生一个立即执行的函数,函数内部的局部变量j保存不同时期i的值,循环过程中,
setTimeout回调按顺序放入事件队列中,等for循环结束后,堆栈中没用同步的代码,就去事件队列中,执行对应的回调,打印出j的值
同理可以利用bind,每次都创建新的函数,并且已经预先设置了参数
function func(i){
console.log(i)
}
for(var i=0;i<10;i++){
setTimeout(func.bind(null,i),600)
}
- 实现继承
var Person = function(name,age){ this.name = name; this.age = age; } var P1 = function(name,age) { //借用构造函数方式实现继承 //利用call继承了Person Person.call(this,name,age) } P1.prototype.getName = function(){ console.log('name:'+this.name+',age:'+this.age); } var newPerson = new P1('popo',20);//name:popo,age:20 newPerson.getName();
- 实现硬绑定
function foo() { console.log(this.a); } var obj = { a:2 }; var bar = function() { foo.call(obj); }; bar(); // 2 setTimeout(bar, 100); // 2 // 硬绑定的bar不可能再修改它的this bar.call(window); // 2
我们创建了函数bar(),并在它的内部手动调用了foo.call(obj),因此强制把foo的this绑定到了obj。无论之后如何调用函数bar,它总会手动在obj上调用foo。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。
总结
- 对于没有挂载在任何对象上的函数,在非严格模式下 this 就是指向 window 的
- 匿名函数的this永远指向window
- 不要根据this的英文语法角度错误理解成指向函数自身
- 当一个函数被调用时,会创建一个活动记录(也可以成为执行上下文),this就是这记录的一个属性。,所以this指向什么完全取决于函数在哪里被调用。
补充
根据《你不知道的JavaScript上卷》一书中写的:可以根据优先级来判断函数在某个调用位置应用的是哪条this指向规则(在箭头函数下无效)
- 函数是否在new中调用,如果是的话this绑定的是新创建的对象。
- 函数是否通过显示绑定或者硬绑定调用,如果是的话,this指向指定对象
- 函数是否在某个上下文中调用(隐式绑定),如果是的话,this指向那个上下文对象
- 如果都不是,使用默认绑定,严格模式下就指向undefined,否则绑定到全局对象。