概述
我们知道,JS 中的 this 指向问题,一直是一个经久不衰的重点和考点。那么它到底难不难呢?怎么说呢?它也难也不难,你要是不把它理清楚,随意变化一下就能把你绊倒;但是你要是把他理清楚了以后,无论它如何变换也难不倒你!那这个 this 里面到底有什么道道呢?今天我们就一起把它彻底征服!
this 到底指向谁之 this 绑定规则
咱们不弯弯绕绕了,直接开宗明义,this 的绑定规则一共有四种,如下
- 默认绑定
- 隐式绑定
- 显式绑定
- new 绑定
那这 4 种绑定分别是什么意思呢?文字略显苍白,接下来我们直接通过代码来看一下这 4 种绑定是什么样的情况,直接明了!
测试代码,后文用于说明这 4 种绑定方式的测试都基于本段代码
function Foo() {console.log(this)
}var obj1 = {name: 'obj1',age: 13, foo: Foo
}
var obj = {name: 'obj',obj1: obj1
}function Person(name) {console.log(this) this.name = name
}
1. 默认绑定:指向window,严格模式下指向undefined
Foo() // window
2. 隐式绑定,指向调用对象(不管多少层调用,始终指向最后发起调用的对象,也就是最后一层)
obj1.foo() // obj1
obj.obj1.foo() // 指向 obj1 ,而不是 obj
3. new 绑定 :指向构造函数
var p = new Person() // 16行输出 Person {}
4. 显式绑定 — call/apply/bind:绑定谁就是谁
Foo.call(obj) // obj
Foo.apply(obj1) // obj1
Foo.bind(obj)() // obj
绑定的优先级比较:默认绑定 < 隐式绑定 < apply/call < bind < new
// I) new > bind
var bindFn = Foo.bind('abc')
new bindFn() // 打印的是Foo{} 而不是 String:{ 'abc' },说明new > bind
// II) bind > apply/call
bindFn.call(123) // String:{ 'abc' } 而不是 Number { 123 },说明 bind > call
// III) apply/call > 隐式绑定
obj1.foo.apply(111) // Number { 111 } 而不是 ojb1, 说明 apply/call > 隐式绑定
// IV) 隐式 > 默认
obj1.foo() // ojb1 而不是 window ,说明 隐式 > 默认
this规则之外的特殊情况
var obj2 = {name: 'obj2'
}
1. 显式绑定的this传入null/undefined,使用默认绑定规则(非严格模式下)
Foo.call(null) // window
2. 间接函数引用,采用默认规则
(obj1.foo = obj2.foo)() // window
3. 要注意的是,在严格模式中,null 就是 null,undefined 就是 undefined
'use strict'
function Foo() {console.log(this)
}
Foo(null) // null
Foo(undefined) // undefined
箭头函数的用法和规则
1. 不会绑定this,arguments等属性
2. 不能作为构造函数来使用,因为其没有原型
3. 箭头函数没有自己的this,内部的this需按照作用域链一步步的向上查找
this指向练习
1. 例1
var name = 'window'
var person1 = {name: 'person1',foo1: function () {console.log(this.name)},foo2: () => console.log(this.name),// 注意这里有两个 this 要分析foo3: function () { return function () { console.log(this.name)}},// 这里也有两个 this 要分析(虽然只存在一个)foo4: function () {return () => {console.log(this.name)}}
}var person2 = { name: 'person2' }person1.foo1(); // person1
person1.foo1.call(person2); // person2person1.foo2(); // window
person1.foo2.call(person2); // windowperson1.foo3()(); // window
person1.foo3.call(person2)(); // window
person1.foo3().call(person2); // person2person1.foo4()(); // person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1
分析
- 注意函数调用时,是普通函数还是箭头函数,是独立调用还是其他
- person1.foo1() 隐式绑定指向 person1 ;
- person1.foo1().call(person2) 显式绑定指向 person2;
- person1.foo2() 箭头函数无自己的 this,到上层作用域查找指向 window;
- person1.foo2.call(person2) 同上,注意,箭头函数无自己的 this,所以绑定无效!
- person1.foo3()() 回调函数独立调用指向 window
- person1.foo3.call(person2)() 同上
- person1.foo3().call(person2) 显式绑定指向 person2
- person1.foo4()() 箭头函数无自己的 this,到上层作用域查找指向 person1
- person1.foo4.call(person2)() 箭头函数无自己的 this,到上层作用域查找指向 person2
- person1.foo4().call(person2) 箭头函数绑定无效,到上层作用域查找指向 person1
2. 例2
var name = 'window'
function Person (name) {this.name = namethis.foo1 = function () {console.log(this.name)},this.foo2 = () => console.log(this.name),this.foo3 = function () {return function () {console.log(this.name)}},this.foo4 = function () {return () => {console.log(this.name)}}
}
var person1 = new Person('person1')
var person2 = new Person('person2')person1.foo1() // person1
person1.foo1.call(person2) // person2person1.foo2() // person1
person1.foo2.call(person2) // person1person1.foo3()() // window
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1
分析
- person1.foo1() 隐式绑定指向 person1
- person1.foo1.call(person2) 显式绑定指向 person2
- person1.foo2() 上层作用域中查找指向 person1
- person1.foo2.call(person2) 同上指向 person1
- person1.foo3()() 默认绑定指向 window
- person1.foo3.call(person2)() 同上指向 window
- person1.foo3().call(person2) 显式绑定指向 person2
- person1.foo4()() 上层作用域中查找指向 person1
- person1.foo4.call(person2)() 上层作用域中查找指向 person2
- person1.foo4().call(person2) 上层作用域中查找指向 person1
3. 例3
var name = 'window'
function Person (name) {this.name = namethis.obj = {name: 'obj',foo1: function () {return function () {console.log(this.name)}},foo2: function () {return () => {console.log(this.name)}}}
}
var person1 = new Person('person1')
var person2 = new Person('person2')person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2person1.obj.foo2()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj
分析
- person1.obj.foo1()() 默认绑定指向window
- person1.obj.foo1.call(person2)() 同上指向 window
- person1.obj.foo1().call(person2) 显式绑定指向 person2
- person1.obj.foo2()() 上层作用域中查找指向 obj
- person1.obj.foo2.call(person2)() 上层作用域中查找指向 person2
- person1.obj.foo2().call(person2) 上层作用域中查找指向 obj
4. 例4
var length = 10
function fn() {console.log(this.length)
}var obj = {length: 5,method: function(fn) {fn()arguments[0]()}
}obj.method(fn, 1)// 10
// 2
- 分析:第一次执行fn(),this指向window对象,输出10。第二次执行arguments[0],相当于arguments调用方法,this指向arguments,而这里传了两个参数,故输出arguments长度为2。
总结
本篇文章详细讲述了 JS 中 this 的指向问题,相信大家只要仔细阅读,并将例题琢磨透,以后再遇到 this 指向的问题一定会所向披靡,战无不胜!!亦菲,彦祖们,我们下次见~