1 什么是this
1.1 this 的指向
this 是 js 中的关键字,看起来很混乱,但其实很好理解,用网上通用的一句话来说就是: this 的指向在函数定义时确定不了,只有函数指向的时候才能确定 this 到底指向谁,也即是说this的卒中指向的是那个调用他的对象
总结起来就六个字
谁调用,指向谁
例子1
function foo() {
var name = "我爱js";
console.log("this", this); // this window
console.log("name", this.name)// this undefined
}
foo()
为什么这里的 this 是window对象,因为我们是在全局作用域中调用的,实际上是省略了 window. 看如下代码的调用方式,跟上一个完全相同
例子2
function foo() {
var name = "我爱js";
console.log("this", this); // this window
console.log("name", this.name)// this undefined
}
window.foo()
效果跟上面是完全相同的
例子3
var foo = {
name: "JavaScript",
child: function () {
console.log("this", this) // this {name: "JavaScript", child: ƒ}
console.log("name", this.name) //name JavaScript
}
}
foo.child()
在这里调用者是 foo, 所以this指向的是 foo 然而事实是这样吗,再看看上面的例子
例子4
var foo = {
name: "JavaScript",
child: function () {
console.log("this", this) // this {name: "JavaScript", child: ƒ}
console.log("name", this.name) //name JavaScript
}
}
window.foo.child()
这里最终调用者是window,但是this并没有指向window,这是怎么回事?难道是六字真言有问题?要弄懂这个问题,我们需要再看几个例子
例子5
var foo = {
a: 1,
b: {
a: 2,
fn: function() {
console.log(this.a) // 2
}
}
}
foo.b.fn()
而且这个例子中的this也同样没指向foo, 看到这里,可能会有疑惑,会推翻六字箴言,但是,如果再补充几句话,就会消除歧义
-
非严格模式下,如果一个函数中有this,并且这个this没有被上一级调用,那么这个this的指向就是window,具体参考例子1,例子2
-
如果一个函数中有this,这个函数被上一级对象调用,那么this就指向上一级对象,具体参考例子3
-
如果一个函数有this,假如调用这个函数的有多级对象,那么this指向的是调用它的上一级对象,具体参考例子5
我们再来验证一下情况3 例子 6
var foo = {
a: 1,
b: {
//a: 2,
fn: function() {
console.log(this.a) // undefined
}
}
}
foo.b.fn()
此时,我们看到,输入的值是undefined,也就是说,这个时候this指向的是b的作用域,而b中没有定义a,所以输出的是undefined 然而一切并不是依靠想象,总有情况例外,我们看下一个例子
例子7
var foo = {
a: 1,
b: {
a: 2,
fn: function() {
console.log(this) // window
console.log(this.a) // undefined
}
}
}
var tm = foo.b.fn
tm()
从这个例子中我们看到,this指向的是window,这是怎么回事?其实很好理解,还记得六字箴言吗?谁调用,指向谁
,这一句var tm = foo.b.fn
只是进行了赋值,把 fn
赋值给了全局变量 tm
,然而最终的调用时机是在 window作用域调用的函数 tm()
, 所以 this的指向是window对象。至此, this 的指向已经完毕
1.2 this指向总结
就六个字 谁调用,指向谁
,另加几个条件
-
非严格模式下,如果一个函数中有this,并且这个this没有被上一级调用,那么这个this的指向就是window
-
如果一个函数中有this,这个函数被上一级对象调用,那么this就指向上一级对象
-
如果一个函数有this,假如调用这个函数的有多级对象,那么this指向的是调用它的上一级对象
2 改变 this 的指向
通常情况下,我们想使用其它环境变相下的状态,这就需要借助this来实现,常用改变this指向的有以下几种方式可以实现
- 构造函数
- call
- apply
- bind
2.1 构造函数改变this的指向
先看这个例子 例子8
function Foo () {
this.name = 'Justin'
}
var f = new Foo()
console.log(f.name) // Justin
在使用 new 关键字调用构造函数时,会依次执行
- 首先创建一个新对象 f,并把构造函数的
prototype
赋值给这个新对象的__proto__
- 将这个新对象 f 赋值给this,并执行构造函数
- 如果函数返回了其它对象,那么 new 表达式中的函数调用会自动返回这个新对象, 否则忽略,这句话的意思是,如果函数没有 return一个对象, this代表的就是 new 出来的实例,否则,this指向的是返回的新对象
假如函数中有 return, 来看看this的指向? 看下面这个例子 例子9
function Foo () {
this.name = 'Justin'
return {}
}
var f = new Foo()
console.log(f.name) // undefined
在这里, 输出的是undefined,也就是说,this根本没有指向 f, 那么this到底指向哪里了呢
其实,如果一个返回值是对象,那么this指向的就是返回的对象, 如果反回的不是对象,那么指向的还是指向函数的实例, null除外
例子10 利用下面几个例子验证
function Foo () {
this.name = 'Justin'
return function(){}
}
var f = new Foo()
console.log(f.name) // undefined
例子11
function Foo () {
this.name = 'Justin'
return 1
}
var f = new Foo()
console.log(f.name) // Justin
例子12
function Foo () {
this.name = 'Justin'
return undefined
}
var f = new Foo()
console.log(f.name) // Justin
例子13
function Foo () {
this.name = 'Justin'
return null
}
var f = new Foo()
console.log(f.name) // Justin
以上跟第三点总结相符合,至此,构造函数改变this指向解析完毕
2.2 通过 call, apply, bind 改变 this 的指向
例子13
var w = "流星火雨"
var obj = {
name: "黄忠",
w: this.w,
state: function(name, ow){
console.log(`${this.name} 的 w 技能是 ${this.w}`)
}
}
var skills = {
name: '马岱',
w: "紫电箭雨"
}
obj.state() // 忠 的 w 技能是 undefined
obj.state.call(skills) //马岱 的 w 技能是 紫电箭雨
obj.state.apply(skills) //马岱 的 w 技能是 紫电箭雨
obj.state.bind(skills) //马岱 的 w 技能是 紫电箭雨
从上面看出来, call, apply, bind把this的指向改变为call, apply, bind所传入的第一个参数, 除此之外,他们是没有什么区别的
2.3 call, apply, bind的区别
先看一个例子
var w = "流星火雨"
var obj = {
name: "黄忠",
w: this.w,
state: function(HeroType, HP){
console.log(`${this.name} 的 w 技能是 ${this.w}, 他是一个 ${HeroType}, 他的血量是${HP}`)
}
}
var skills = {
name: '马岱',
w: "紫电箭雨"
}
obj.state() // 忠 的 w 技能是 undefined
obj.state.call(skills, "法师", 100) //马岱 的 w 技能是 紫电箭雨
obj.state.apply(skills, ["法师", 100]) //马岱 的 w 技能是 紫电箭雨
obj.state.bind(skills, "法师", 100)() //马岱 的 w 技能是 紫电箭雨
不同的是, apply第二个参数接受的是一个数组, call 直接按参数排列就可以了,而 bind 跟call相似,只不过后面多了个 ()
最后,再加一个复杂的案例
function foo() {
let args = Array.prototype.slice.apply(arguments)
console.log("args", args) //[ '国家队', '黄忠', '典韦', '张莹莹', '黄盖', '许褚' ]
}
foo("国家队", "黄忠", "典韦", "张莹莹", "黄盖", "许褚")
更多请参考: www.iquanku.com/admin/28.ht…