this
this 取什麼值是在函式執行的時候確認的,不是在函式定義的時候確認的
this 的不同應用場景,this 的指向
函式在呼叫時,js 會預設給 this 繫結一個值,this 的值與繫結方式、呼叫方式和呼叫位置有關,this 是在執行時被繫結的。下面有幾種 this 的應用場景:
- 當作普通函式被呼叫
- 作為物件的方法被呼叫
- 使用 call、apply、bind
- 監聽 DOM 事件所執行的函式
- 在 class 的方法中呼叫
- 箭頭函式
1.0 當作普通函式被呼叫
普通函式的獨立呼叫可以稱為預設繫結,指向 window
function fn() {
console.log(this);
}
fn() // window
// 定時器內部是用 apply 把 this 繫結在 window 上
// 所以定時器指向的也是 window
setTimeout(function() {
console.log(this) // window
},0)
2.0 作為物件的方法被呼叫
函式是通過某個物件進行呼叫可以稱為隱式繫結,指向擁有該方法的物件
- 如果有多個物件進行巢狀,this 指向的是呼叫位置,也就是第一個物件
- 如果你把物件裡的函式賦值給新的變數來呼叫,那麼 this 指向的是新的變數
function fn() {
console.log(this);
}
var obj1 = {
name: "obj1",
fn: fn
}
obj1.fn() // obj1物件
// 如果有多個物件進行巢狀,this 指向的是呼叫位置,也就是第一個物件
var obj2 = {
name: "obj2",
obj1: obj1
}
obj2.obj1.fn() // obj1物件
// 如果你把物件裡的函式賦值給新的變數來呼叫,那麼 this 指向的是新的變數
var newFn = obj.fn
newFn() // window
3.0 使用 call、apply、bind
使用這種方法是物件內部不想放入該函式,然後通過繫結的方式進行呼叫,這種繫結可以稱為顯式繫結,指向方法所繫結的物件
function fn() {
console.log(this);
}
var obj = {
name: "fan",
}
fn.call(obj) // {name: "fan"}
fn.apply(obj.name) // String {"fan"}
// bind() 返回的是一個函式
var foo = fn.bind(obj)
foo() // {name: "fan"}
4.0 監聽 DOM 事件所執行的函式
給 dom 新增點選事件,當使用者點選後,繫結的函式被呼叫時,會執行回撥,把函式繫結到 box 元素節點,指向的是所繫結的元素節點
var div = document.createElement('div')
div.setAttribute('id','box')
div.style.width = "100px"
div.style.height = "100px"
div.style.background = "#ccc"
document.body.appendChild(div)
var box = document.querySelector('#box')
box.onclick = function() {
console.log(this) // box節點
}
5.0 在 class 的方法中呼叫
class 和建構函式是一個意思,都是通過 new 關鍵字來例項化,例項化會建立一個全新的物件,這個物件的的原型物件會等於類的原型 fan.__proto__ === Student.prototype
,方法呼叫的時候,指向的是例項化出來的新物件
class Student {
constructor(name) {
this.name = name
}
sayHi() {
console.log(this);
}
}
var fan = new Student("fan")
console.log(fan) // fan 物件
fan.sayHi() // fan 物件
6.0 箭頭函式
箭頭函式是沒有 this 的,它的 this 是通過外層作用域來決定的
var obj = {
fn: () => {
console.log(this)
}
}
obj.fn() // window
this 的規則優先順序
new繫結 > 顯式繫結(bind)> 隱式繫結 > 預設繫結
手寫 bind 函式
// 實現 bind 函式
Function.prototype.bind1 = function(...args) {
// 獲取傳入的值
// const arr = Array.prototype.slice.call(arguments)
const arr = [...args];
// 獲取 this (陣列第一項)
const t = arr.shift()
// fn1.bind(...) 中的 fn1
const self = this;
// return 一個函式
return function() {
return self.apply(t, arr)
}
}
function fn(a, b) {
console.log('this', this);
console.log(a, b);
return 'this is fn'
}
const fn2 = fn.bind1({x: 100}, 100, 200)
const res = fn2()
console.log(res);
this 面試題
面試題一
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss(); // window
person.sayName(); // person
(person.sayName)(); //person
(b = person.sayName)(); //window
}
sayName();
面試題二
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
person1.foo1.call(person2); // person2
person1.foo2(); // window
person1.foo2.call(person2); // window
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
易錯解析:(箭頭函式只看上層作用域)
person1.foo2.call(person2) // foo2() 是箭頭函式,不適用於顯示繫結的規則
person1.foo3()() // 相當於在全域性呼叫 foo3() 返回的函式
person1.foo3.call(person2)() // 顯式繫結到 person2 中呼叫該函式,還是返回一個函式在全域性呼叫
person1.foo3().call(person2) // 拿到返回值再進行顯式繫結,繫結的就是沒有返回值的函式,相當於呼叫 person2 中的方法
person1.foo4.call(person2)() // 顯式繫結之後呼叫,返回的是箭頭函式,箭頭函式向上層作用域找,找到的是 顯式繫結的 person2
person1.foo4().call(person2) // 顯式繫結之前呼叫,返回的箭頭函式向上層作用域找,找到的是 person1
面試題三
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
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
面試題四
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()() // 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
解析:
person1.obj.foo1()() // obj.foo1()返回的是一個函式,這個函式會在全域性執行
person1.obj.foo1.call(person2)() // 最後拿到的還是返回的函式,會在全域性執行
person1.obj.foo2()() // 返回一個箭頭函式,只要記住是箭頭函式就往上層作用域找