学习Javascript this指向 apply call bind new笔记

130 阅读5分钟

this apply call bind new

重要的话说三遍:

this永远指向最后调用它的对象

this永远指向最后调用它的对象

this永远指向最后调用它的对象

this的指向

EXAMPLE 1

var name = "windowsName"
function a() {
var name = "Cherry"
console.log(this.name) // windowsName
}
a()

a()是在全局作用域window下调用, 相当于window.a(), 所以this指向window.

EXAMPLE 2

var name = "windowsName"
var a = {
    name: "Cherry",
    fn: function() {
      console.log(this.name) // Cherry
      console.log("this: ", this)
    }
}
window.a.fn() // 最后调用的对象是a

最后是由a对象调用fn(), 所以此时this指向a

EXAMPLE 3

var name = "windowsName"
var a = {
    // name: "Cherry",
    fn: function() {
      console.log(this.name) // undefined
      console.log('this: ', this); // a
    }
}
window.a.fn() 

最后调用fn()的对象是a,a里面没有name属性, 所以为undefined

EXAMPLE 4

var name = "windowsName"
var a = {
    name: null,
    fn: function() {
      console.log(this.name) // "windowsName"
      console.log("this: ", this) "window"
    }
}
var f = a.fn 
f()

a对象虽然将fn方法赋值给变量f了, 但是并没有调用, 最后是window对象调用的, 所以this指向window.(再来一次, this指向永远指向最后调用它的对象)

EXAMPLE 5

var name = "windowsName"
function fn() {
    var name = "Cherry"
    innerFunction()
    function innerFunction() {
        console.log(this.name) // windowsName
    }
}

fn()  

fn()最后是由window调用的 所以为window对象, 并且里面的innerFunction作为函数调用, 没有挂载在任何对象上, 这里的this就是指向window的.

改变this指向

  1. 通过ES6箭头函数
  2. 在函数内部使用_this = this
  3. 使用 apply \ call \ bind
  4. new

ES6箭头函数

  • setTimeOut不使用箭头函数的问题
var name = "windowsName"
var a = {
    name: "Cherry",
    func1: function() {
      console.log(this.name)
    },
    func2: function() {
      setTimeout(function() {
        console.log(this)
        this.func1()
      }, 100)
    }
}

a.func2() // this.func1 is not a function

setTimeOut是由window来调用, window全局作用域里找不到func1这个方法, 所以为undefined.

  • 使用箭头函数解决

箭头函数的this, 始终指向外层包裹箭头函数的第一个非箭头函数的this指向

var name = "windowsName"
var a = {
    name: "Cherry",
    func1: function() {
      console.log(this.name)
    },
    func2: function() {
      setTimeout(() => {
        console.log(this) //Cherry
        this.func1()
      }, 100)
    }
}
a.func2()

这个例子中, 箭头函数指向了外层的第一个非箭头函数func2. func2是由a对象调用的, 所以this指向了a对象.

划重点: 箭头函数中没有this指向, 必须通过查找作用域链来决定其值, 如果箭头函数被非箭头函数包含, 则this绑定的是最后一个非箭头函数的this, 否则为undefined.

使用_this

var name = "windowsName"
var a = {
  name: "Cherry",
  func1: function() {
    console.log(this.name)
  },
  func2: function() {
    var _this = this
    console.log('_this: ', _this); // 这里的this是调用func的对象a, 为了防止在setTimeOut中被window调用而导致将this指向为window
    setTimeout(function() {
      _this.func1()
    }, 100)
  }
}
a.func2() // Cherry

这里的this是调用func()的对象a, 为了防止在setTimeOut中被window调用而导致将this指向为window

apply call bind

apply

var a = {
  name: "Cherry",

  func1: function() {
    console.log(this.name)
  },
  func2: function() {
    setTimeout(
      function() {
        this.func1()
        console.log('this: ', this);
      }.apply(a), // fn.apply(a)
      100
    )
  }
}
a.func2() // Cherry

fun.apply(thisArg, [argsArray])

apply第二个可选参数为类数组

call

var a = {
  name: "Cherry",

  func1: function() {
    console.log(this.name)
  },
  func2: function() {
    setTimeout(
      function() {
        this.func1()
        console.log('this: ', this);
      }.apply(a), // fn.call(a)
      100
    )
  }
}
a.func2() // Cherry

fun.call(thisArg[, arg1[, arg2[, ...]]])

call接受若干个参数列表

bind

var a = {
  name: "Cherry",

  func1: function() {
    console.log(this.name)
  },
  func2: function() {
    setTimeout(
      function() {
        this.func1()
        console.log('this: ', this);
      }.bind(a)(), 
      100
    )
  }
}
a.func2() // Cherry

fun.bind(thisArg[, arg1[, arg2[, ...]]])

bind接收参数和call一样, 但是bind返回一个函数, 需要手动调用

apply call bind

apply

apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。 (MDN)

第一个参数指定this指向, 指定null时会自动指向全局对象,(浏览器中就是window对象)

语法:

func.apply(thisArg, [argsArray])

const numbers = [1,2,3,4,5,6]
let max = Math.max.apply(null, numbers) // 6
let max = Math.max(...numbers) // 6
var a ={
    name : "Cherry",
    fn : function (a,b) {
        console.log( a + b)
    }
}
var b = a.fn;
b.apply(a,[1,2])     // 3

注意最后调用的参数方式

call

apply基本一致, 第一个参数指定this指向, 指定null时会自动指向全局对象,(浏览器中就是window对象), 后面的可选参数, 传入参数不一样, call方法接受的若干个参数列表, apply接受一个包含多个参数的类数组

const numbers = [1,2,3,4,5,6]
let max = Math.max.call(null, 1, 2, 3, 4, 5, 6) // 6
let max = Math.max(...numbers) // 6
var a ={
    name : "Cherry",
    fn : function (a,b) {
        console.log( a + b)
    }
}
var b = a.fn;
b.call(a,1,2)     // 3

注意最后调用的参数方式

bind

bind的方法和call的传参方式一样

第一个参数指定this指向, 指定null时会自动指向全局对象,(浏览器中就是window对象)

但是bind()方法创建一个新的函数, 所以必须手动调用

var a ={
    name : "Cherry",
    fn : function (a,b) {
        console.log( a + b)
    }
}
var b = a.fn;
b.bind(a,1,2)()           // 3

new一个对象的过程

function person(arg1, arg2) {
    this.firstName = arg1;
    this.lastName  = arg2;
}

let a = new person("phenix", "peng")
a.lastname; // peng

new关键字 创建一个实例对象的过程:

  1. 首先会创建一个空对象obj
  2. obj的隐式原型__proto__指向构造函数的protoType显示原型对象
  3. 使用call()方法改变this指向, 指向新创建的对象obj
  4. 看第三步返回的是不是一个Object对象, 如果非, 就返回新创建的对象obj, 如果是就返回这个对象.

伪代码如下:

new person {
    var obj = {};
    obj.__proto__ = person.prototype;
    var result = person.call(obj,"Li","Cherry");
    return typeof result === 'obj'? result : obj;
}

在new的过程中, call改变了this的指向.

总结

this指向

  • ES5
    • this永远指向最后调用它的对象
    • this永远指向最后调用它的对象
    • this永远指向最后调用它的对象
  • ES6箭头函数
    • 箭头函数没有this指针, 永远指向包裹着箭头函数的外层的第一个非箭头函数的this指向

apply call bind

  • 可以用来改变this指向
  • 第一个参数都是this指向
  • 后面可选参数, 传参方式不一样, apply传类数组, call和bind是参数列表
  • bind返回了一个函数, 需要手动调用

new

  • new会改变this指向
  • new对象的四步过程
    • 创建空对象
    • 原型指向构造函数的protoType
    • 通过call改变this指向, 指向刚创建的对象
    • 返回对象