為什么要使用this
在javascript中,this可謂是無處不在,它可以用來指向某些元素、對象,在合適的地方使用this,能讓我們減少無用代碼的編寫
var user = {
name: "aclie",
sing: function () {
console.log(user.name + '在唱歌')
},
dance: function () {
console.log(user.name + '在跳舞')
},
study: function () {
console.log(user.name + '在學習')
},
}
以上這段代碼中,每個方法都需要用到user對象中的name屬性,如果當user對象名稱發(fā)生變化,那么所有方法都要改動,這種情況下,使用this是個很好的選擇
var user = {
name: "aclie",
sing: function () {
console.log(this.name + '在唱歌')
},
dance: function () {
console.log(this.name + '在跳舞')
},
study: function () {
console.log(this.name + '在學習')
},
}
this的指向
this的指向和函數(shù)在哪里定義無關,和如何調用有關
以下foo函數(shù)調用方式不同,this的值也不同
function foo(){
console.log(this)
}
foo()
var obj = {
foo: foo
}
obj.foo()
obj.foo.apply("hello")
執(zhí)行結果如下圖所示
this四種綁定方式
一、默認綁定
當函數(shù)獨立調用時,this默認綁定window
// 1、直接調用
function foo() {
console.log(this)
}
foo()
// 2、對象中的函數(shù)
var obj1 = {
foo: foo
}
var fn1 = obj1.foo
fn1()
// 3、被全局變量引用
var obj2 = {
bar: function () {
console.log(this)
}
}
var fn2 = obj2.bar
fn2()
// 4、函數(shù)嵌套調用
function foo1() {
console.log('foo1', this)
}
function foo2() {
console.log('foo2', this)
foo1()
}
function foo3() {
console.log('foo3', this)
foo2()
}
foo3()
// 5、通過閉包調用
var obj2 = {
bar: function () {
return function () {
console.log(this)
}
}
}
obj2.bar()()
執(zhí)行結果如下
以上五種調用方式全都屬于默認綁定,因為他們最終都是單獨的對函數(shù)進行調用
二、隱式綁定
調用的對象內部有對函數(shù)的引用
function foo() {
console.log(this)
}
var obj1 = {
name: 'obj1',
foo: foo
}
obj1.foo()
var obj2 = {
name: 'obj2',
bar: function () {
console.log(this)
}
}
obj2.bar()
var obj3 = {
name: 'obj3',
baz: obj2.bar
}
obj3.baz()
以上代碼執(zhí)行結果為
以上三種都屬于隱式綁定,他們都是通過對象調用,this就指向了該對象
三、顯式綁定
不希望在對象內部包含這個函數(shù)的引用,但又希望通過對象強制調用,使用call/apply/bind進行顯式綁定
function foo() {
console.log(this)
}
var obj = {
name: 'obj1',
}
foo.call(obj)
foo.apply(obj)
foo.call("xxx")
以上代碼的執(zhí)行結果為
foo函數(shù)直接調用this應該指向window,這里通過call/apply來改變了this的指向
四、new綁定
通過new關鍵字來創(chuàng)建構造函數(shù)的實例,綁定this
function Person(name, age) {
this.name = name
this.age = age
}
const p1 = new Person('alice', 20)
const p2 = new Person('mogan', 24)
console.log(p1)
console.log(p2)
以上代碼的執(zhí)行結果如下
此時this指向的是通過new創(chuàng)建的實例對象
this綁定的優(yōu)先級
一、隱式綁定高于默認綁定
function foo() {
console.log(this)
}
var obj = {
name: 'obj',
foo: foo
}
obj.foo()
以上代碼執(zhí)行結果為
foo函數(shù)默認綁定window對象,當同時存在隱式綁定和默認綁定時,隱式綁定優(yōu)先級高于默認綁定
二、顯示綁定高于隱式綁定
// 案例一
var user = {
name: 'user',
foo: function(){
console.log(this)
}
}
user.foo.call('kiki')
// 案例二
function foo() {
console.log(this)
}
var obj = {
name: "obj",
foo: foo.bind("aclie")
}
obj.foo()
以上代碼的執(zhí)行結果為
如果隱式綁定優(yōu)先級更高的話,this的指向應該都為對象,但根據(jù)以上執(zhí)行結果得知this綁定為顯示綁定的結果,所以當同時存在隱式綁定和顯示綁定時,顯示綁定的優(yōu)先級高于隱式綁定
三、new高于隱式綁定
var user = {
name: 'lisa',
foo: function () {
console.log(this)
}
}
new user.foo()
以上代碼的執(zhí)行結果如下
當同時存在于new關鍵字綁定和隱式綁定時,this綁定了foo構造函數(shù),所以new關鍵字的優(yōu)先級高于隱式綁定
四、new高于顯示綁定
function bar(){
console.log(this)
}
var fn = bar.bind('hello')
new fn()
以上代碼的執(zhí)行結果如下
當同時存在于new關鍵字綁定和顯示綁定時,this綁定了bar構造函數(shù),所以new關鍵字的優(yōu)先級高于顯示綁定
綜上,以上四種綁定的優(yōu)先級順序為
new關鍵字 > 顯式綁定 > 隱式綁定 > 默認綁定
規(guī)則之外
還有幾種特殊的綁定方式,不在上述四種綁定規(guī)則中
一、忽略顯示綁定
當顯示綁定的值為 null/undefined 時,this直接綁定window
var user = {
name: 'alice',
foo: function () {
console.log(this)
}
}
user.foo()
user.foo.call(null)
user.foo.apply(undefined)
以上代碼執(zhí)行結果如下
二、間接函數(shù)引用
var obj1 = {
name: 'obj1',
foo: function () {
console.log(this)
}
}
var obj2 = {
name: 'obj2'
};
obj2.baz = obj1.foo;
obj2.baz();
(obj2.bar = obj1.foo)()
以上代碼的執(zhí)行結果為
兩種方式所綁定的this不同,第二種方式進行了賦值調用,實際上是間接函數(shù)引用,(obj2.bar = obj1.foo)這里返回了賦值的結果,再加上一個小括號,就直接調用賦值的結果函數(shù)
三、箭頭函數(shù)
箭頭函數(shù)是不綁定this的,它的this來源于上級作用域
var user = {
name: 'kiki',
foo: () => {
console.log('箭頭函數(shù)中的this',this)
}
}
user.foo()
以上代碼的執(zhí)行結果如下
這里調用foo函數(shù),因為箭頭函數(shù)不綁定this,所以去foo函數(shù)的上級查找this,找到了全局對象window
面試題
1、考察間接函數(shù)引用
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss();
person.sayName();
(person.sayName)();
(b = person.sayName)();
}
sayName();
執(zhí)行sayName函數(shù)
- 變量sss 被person.sayName方法賦值,執(zhí)行sss函數(shù),此時是獨立函數(shù)調用,this指向全局window,全局中變量name被綁定到了window中,所以this.name為"window"
- person.sayName() 為隱式綁定,this指向person對象,所以this.name為person.name,即"person"
- (person.sayName)() 與前一個本質是一樣的,隱式綁定,this指向person對象,所以this.name為person.name,即"person"
- (b = person.sayName)() 是間接函數(shù)引用,person.sayName賦值給b變量,而小括號括起來的代表賦值的結果,this指向window,this.name為window.name,即"window"
所以執(zhí)行結果為
2、定義對象時是不產生作用域的
var name = 'window'
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
person1.foo1();
person1.foo1.call(person2);
person1.foo2();
person1.foo2.call(person2);
person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);
person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);
調用過程分析
-
foo1函數(shù)
- person1.foo1() 隱式綁定,this指向person1,this.name為person1.name,即 “person1”
- person1.foo1.call(person2) 隱式綁定+顯示綁定person2,顯示綁定優(yōu)先級更高,所以this指向person2,this.name為person2.name,即 “person2”
-
foo2函數(shù)
- person1.foo2() 隱式綁定, 箭頭函數(shù)沒有自己的this,所以向上層作用域查找,找到了全局window(person1是對象,定義它的時候不產生作用域),全局變量name被綁定到了window中,this.name為window.name,即 “window”
- person1.foo2.call(person) 隱式綁定+顯示綁定,但是 箭頭函數(shù)不綁定this,這里的顯示綁定無效,沒有自己的this,向上層作用域查找,找到全局window,this.name為window.name,即 “window”
-
foo3函數(shù)
- person1.foo3()() 這里相當于執(zhí)行person1.foo()的返回函數(shù),這里是獨立函數(shù)調用,this指向全局window,this.name為window.name,即 “window”
- person1.foo3.call(person2)() 這里通過call改變的是foo3函數(shù)中this的指向,但最終執(zhí)行的是foo3函數(shù)返回的閉包,閉包作為獨立函數(shù)調用,this仍然指向全局window,this.name為window.name,即’window"
- person1.foo3().call(person2) 這里將foo3函數(shù)返回的閉包顯示綁定了person2對象,this指向person2,this.name為person2.name,即"person2"
-
foo4函數(shù)
- person1.foo4()() 執(zhí)行person1.foo()的返回值,返回的閉包是箭頭函數(shù)沒有this的,向上層作用域查找,找到了foo4函數(shù),foo4的this指向person1,所以閉包的this也指向person1,thiss.name為person1.name,即 “person1”
- person1.foo4.call(person2)() 返回的閉包沒有this,向上層作用域找到了foo4函數(shù),foo4函數(shù)的this通過顯示綁定變成了person2,所以閉包的this也指向person2,this.name為person2.name,即"person2"
- person1.foo4().call(person) 返回的閉包是箭頭函數(shù),無法通過call進行顯示綁定,直接向上級作用域查找,找到foo4函數(shù),foo4的this指向person1,所以閉包的this指向person1,this.name為person1.name,即"person1"
上述代碼的執(zhí)行結果如下
3、構造函數(shù)中定義函數(shù),該函數(shù)的上級作用域是構造函數(shù)
var name = 'window'
function Person (name) {
this.name = name
this.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.foo1.call(person2)
person1.foo2()
person1.foo2.call(person2)
person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)
person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)
調用分析過程
-
foo1函數(shù)
- person1.foo1() 隱式綁定,this指向person1,person1創(chuàng)建實例時傳入name為person1,所以this.name為person1
- person1.foo1.call(person2) 隱式綁定+顯示綁定,顯示綁定優(yōu)先級更高,綁定person2,person2創(chuàng)建實例時傳入的name為person2,所以this.name為person2
-
foo2函數(shù)
- person1.foo2() 隱式綁定,但foo2是箭頭函數(shù),沒有自己的this,向上層作用域查找,找到了Person構造函數(shù),此時this是指向person1這個對象的,而person1實例化時傳入的name為person1,所以this.name為person1
- person1.foo2.call(person2) 隱式綁定+顯式綁定,但foo2是箭頭函數(shù),不綁定this,所以this仍然需要向上層作用域查找,找到Person構造函數(shù),this指向person1對象,所以this.name為person1
-
foo3函數(shù)
- person1.foo3()() 執(zhí)行person1.foo3的返回值,返回的函數(shù)是獨立調用,this指向window,全局的name變量被綁定到window中,this.name為window.name,即 “window”
- person1.foo3.call(person2)() 顯式綁定更改的是foo3函數(shù)的this,最終執(zhí)行的是foo3函數(shù)的返回值,仍然是函數(shù)的獨立調用,所以this指向window,this.name為window.name,即 “window”
- person1.foo3().call(person2) foo3函數(shù)的返回函數(shù)通過顯示綁定將this綁定到了person2中,person2創(chuàng)建實例時傳入的name為person2,所以this.name為person2
-
foo4函數(shù)
- person1.foo4()() 執(zhí)行foo4函數(shù)的返回值,返回函數(shù)為箭頭函數(shù),沒有this,所以向上層作用域查找,找到foo4函數(shù)的this指向person1,所以箭頭函數(shù)的this也指向person1,所以this.name為person1
- person1.foo4.call(person2)() foo4通過顯示綁定將this綁定成了person2,返回的函數(shù)為箭頭函數(shù),this與父級作用域foo4一致,所以箭頭函數(shù)的this也指向person2,所以this.name為person2
- person1.foo4().call(person2) foo4函數(shù)的返回值為箭頭函數(shù),不綁定this,這里顯示綁定無效,向上級作用域查找this,找到foo4函數(shù),this指向person1
執(zhí)行結果如下
4、區(qū)分作用域
var name = 'window'
function Person (name) {
this.name = name
this.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()()
person1.obj.foo1.call(person2)()
person1.obj.foo1().call(person2)
person1.obj.foo2()()
person1.obj.foo2.call(person2)()
person1.obj.foo2().call(person2)
-
foo1函數(shù)
- person1.obj.foo1()() 執(zhí)行foo1函數(shù)的返回函數(shù),此時該函數(shù)為獨立函數(shù)調用,this指向window,全局變量name被添加到window中,這里的this.name指向window.name,即 “window”
- person1.obj.foo1.call(person2)() 這里顯示綁定改變foo1中this的指向,但最終執(zhí)行的是foo1函數(shù)的返回值,返回函數(shù)作為獨立函數(shù)調用,this仍然指向window,所以this.name為window.name,即 “window”
- person1.obj.foo1().call(person2) 這里通過顯示綁定更改foo1函數(shù)的返回函數(shù)中this的指向, 所以該函數(shù)this指向person2,而person2在實例化的時候傳入name值為person2,所以this.name為person2
-
foo2函數(shù)
- person1.obj.foo2()() 執(zhí)行foo2的返回函數(shù),此時該函數(shù)為獨立函數(shù)調用,但它自己沒有this,要向上級作用域查找,找到foo2函數(shù)的this指向obj,所以該函數(shù)的this也指向obj,this.name為obj.name,即 “obj”
- person1.obj.foo2.call(person2)() 執(zhí)行foo2的返回函數(shù),此時該函數(shù)為獨立函數(shù)調用,但它自己沒有this,要向上級作用域查找,foo2函數(shù)的this通過顯示綁定變成person2,所以該函數(shù)的this也為person2,而person2在實例化的時候傳入name值為person2,所以this.name為person2
- person1.obj.foo2().call(person2) foo2的返回函數(shù)為箭頭函數(shù),不綁定this,顯式綁定無效,也沒有自己的this,要向上級作用域查找,找到foo2函數(shù)的this指向obj,所以該函數(shù)的this也指向obj,this.name為obj.name,即 “obj”
所以執(zhí)行結果為
以上就是關于this指向的理解,關于js高級,還有很多需要開發(fā)者掌握的地方,可以看看我寫的其他博文,持續(xù)更新中~
|