理解 this

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()() // 返回一個箭頭函數,只要記住是箭頭函數就往上層作用域找
Tags: