This in Javascript

1 什么是this?

this是一个keyword, 它的值总是变换,依赖于调用它场景

有6种情况,this会指向特定的值

(1) global context (全局)

(2) object construction (对象构造函数)

(3) object method (对象方法)

(4) simple function (简单函数)

(5) arrow function (箭头函数)

(6) event listener (事件监听)

2 详细说明6中使用情况

2.1  global context

当在任何函数之外调用this时,即global context, this指向浏览器的默认全局对象Window

console.log(this) // Window

2.2 object construction

当使用new创建新实例时,this指向创建的实例instance

function Human (age) {
  this.age = age
}

let greg = new Human(23)
let thomas = new Human(25)

console.log(greg) // this.age = 23
console.log(thomas) // this.age = 25

new一个函数对象,返回被调用的函数名和创建的对象

function ConstructorExample() {
    console.log(this);
    this.value = 10;
    console.log(this);
}

new ConstructorExample();

// -> ConstructorExample {}
// -> ConstructorExample { value: 10 }

 

2.3 object method

Methods here are defined with ES6 object literal shorthand, 比如下面

let o = {
  aMethod () {}
}

在method里面的this,都指向该对象本身

let o ={
  sayThis () {
    console.log(this) // o
  }
}

o.sayThis() // o

2.4 simple function

最常见的函数形式,类似下面的

function hello () {
  // say hello!
}

this指向Window, 即使 simple function 在object method中,也是指向Window

function simpleFunction () {
  console.log(this)
}

const o = {
  sayThis () {
    simpleFunction()
  }
}

simpleFunction() // Window
o.sayThis() // Window

为什么object method中的 simpleFunction() this没有指向o, 这也是让人迷惑的地方啊,或许下面的代码能解释一下,只能说object method里面嵌套的函数的this都重新指向了Window

const o = {
  doSomethingLater () {
    setTimeout(function() {
      this.speakLeet() // Error, this-->Window
    }, 1000)
  },
  speakLeet() {
    console.log(`1337 15 4W350M3`)
  }
}

要解决这个问题,需要在嵌套函数外将this赋值给self

const o = {
  doSomethingLater () {
    const self = this
    setTimeout(function () {
      self.speakLeet()
    }, 1000)
  },

  speakLeet () {
    console.log(`1337 15 4W350M3`)
  }
}

2.5 arrow function

在箭头函数中,this总是指向箭头函数所在作用域的对象

比如箭头函数在object method中,那么箭头函数中的this就指向object

const o = {
  doSomethingLater () {
    setTimeout(() => this.speakLeet(), 1000)
  },

  speakLeet () {
    console.log(`1337 15 4W350M3`)
  }
}

2.6 Event Listener

在事件监听函数中,this指向触发事件的元素

let button = document.querySelector('button')

button.addEventListener('click', function() {
  console.log(this) // button
})

如果要在监听函数中调用其他函数,需要先将this赋值给其他变量,如self 

function LeetSpeaker (elem) {
  return {
    listenClick () {
      const self = this
      elem.addEventListener('click', function () {
        self.speakLeet()
      })
    },
    speakLeet() { console.log(`1337 15 4W350M3`) }
  }
}

当然,使用箭头函数也可以直接使用this, 访问元素可以使用e.currentTarget

function LeetSpeaker (elem) {
  return {
    listenClick () {
      elem.addEventListener('click', (e) => {
        console.log(e.currentTarget) // elem
        this.speakLeet()
      })
    },
    speakLeet () {
      console.log(`1337 15 4W350M3`)
    }
  }
}

new LeetSpeaker(document.querySelector('button')).listenClick()

如果想要移除监听事件,则需要在绑定事件时,第二个参数--回调函数要使用命名函数,而非匿名函数

function someFunction () {
  console.log('do something')

  // Removes the event listener.
  document.removeEventListener('click', someFunction)
}

document.addEventListener('click', someFunction)

需要注意的是,有些时候,上述的规则会共同作用,这是会有优先级,比如下面的例子,new和对象方法共同作用的情况,那么,new 规则主导

var obj1 = {
    value: 'hi',
    print: function() {
        console.log(this);
    },
};

new obj1.print(); // -> print {}

小结:

某些规则共同作用,优先级如下,从前往后,优先级越低:

(1) new 操作符

(2) bind()

(3) call(), apply()

(4) object method

(5) global object, except in strict mode

3 改变this的指向

什么情况下需要改变this的指向?比如想要获得类数组对象如{1:'a', 2: 'b'}的数组值,就可以使用Array.slice方法,但OOP中,方法都是和对象绑定的,所以需要手动修改this的指向。

Javascript中共提供了三种方法修改this的指向, call,apply,bind

3.1 call

我们使用func.call(param), 传递参数给this, 第一个参数绑定给this,后面的作为函数的实参

例子1:不带参数的函数

function logThis() {
    console.log(this);
}

var obj = { val: 'Hello!' };

logThis(); // -> Window {frames: Window, postMessage: ƒ, …}
logThis.call(obj); // -> { val: 'Hello!' };

例子2:带参数的函数

function logThisAndArguments(arg1, arg2) {
    console.log(this);
    console.log(arg1);
    console.log(arg2);
}

var obj = { val: 'Hello!' };

logThisAndArguments('First arg', 'Second arg');
// -> Window {frames: Window, postMessage: ƒ, …}
// -> First arg
// -> Second arg

logThisAndArguments.call(obj, 'First arg', 'Second arg');
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg

call + arguments 移花接木

aruguments在Javascript中,是传递给函数的参数,一个类数组的对象

function add() {
    console.log(arguments);
}

add(4); // -> { '0': 4 }
add(4, 5); // -> { '0': 4, '1': 5 }
add(4, 5, 6); // -> { '0': 4, '1': 5, '2': 6 }

当有需求要遍历这些参数,使用Array的map, forEach等,我们可以使用call来讲arguments转变为数组

Array.slice:通过this引用,返回调用数组的一份拷贝,如果Array.slice传入arguments, 就会返回一份新的数组,从arguments创建而来的

function add() {
    var args = [].slice.call(arguments);
    console.log(args);
}

add(4, 5, 6); // -> [ 4, 5, 6 ]

3.2 apply

apply 运行的机制和call类似,不同的是arguments是以数组的形式传递的

function logThisAndArguments(arg1, arg2) {
    console.log(this);
    console.log(arg1);
    console.log(arg2);
}

var obj = { val: 'Hello!' };

logThisAndArguments('First arg', 'Second arg');
// -> Window {frames: Window, postMessage: ƒ, …}
// -> First arg
// -> Second arg

logThisAndArguments.apply(obj, ['First arg', 'Second arg']);
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg

3.3 bind

bind和call, apply运行不太一样,func.bind调用后函数并不立即执行,而是当函数调用时才会触发, 传参的方式和call一样,一个一个传

function logThisAndArguments(arg1, arg2) {
    console.log(this);
    console.log(arg1);
    console.log(arg2);
}

var obj = { val: 'Hello!' };

var fnBound = logThisAndArguments.bind(obj, 'First arg', 'Second arg');

console.log(fnBound);
// -> [Function: bound logThisAndArguments]

fnBound();
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg
function sayThis () {
  console.log(this)
}
const boundFunc = sayThis.bind({hippy: 'hipster'})
boundFunc()  // {hippy: "hipster"}

遇到箭头函数,可能情况就不一样了

const sayThis = _ => console.log(this)
const boundFunc = sayThis.bind({hippy: 'hipster'})
boundFunc() // Window

传给bind的其他参数则作为实参

const sayParams = (...args) => console.log(...args)
const boundFunc = sayParams.bind(null, 1, 2, 3, 4, 5)
boundFunc() // 1 2 3 4 5

事件监听函数只调用一回的例子

function LeetSpeaker (elem) {
  return {
    listenClick () {
      this.listener = this.speakLeet.bind(this)
      elem.addEventListener('click', this.listener)
    },

    speakLeet(e) {
      const elem = e.currentTarget
      this.addLeetSpeak()
      elem.removeEventListener('click', this.listener)
    },

    addLeetSpeak () {
      const p = document.createElement('p')
      p.innerHTML = '1337 15 4W350M3'
      document.body.append(p)
    }
  }
}

const button = document.body.querySelector('button')
const leetSpeaker = LeetSpeaker(button)
leetSpeaker.listenClick()

综合示例:

function logThisAndArguments(arg1, arg2) {
    console.log(this);
    console.log(arg1);
    console.log(arg2);
}

var obj = { val: 'Hello!' };

// NORMAL FUNCTION CALL
logThisAndArguments('First arg', 'Second arg');
// -> Window {frames: Window, postMessage: ƒ, …}
// -> First arg
// -> Second arg

// USING CALL
logThisAndArguments.call(obj, 'First arg', 'Second arg');
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg

// USING APPLY
logThisAndArguments.apply(obj, ['First arg', 'Second arg']);
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg

// USING BIND
var fnBound = logThisAndArguments.bind(obj, 'First arg', 'Second arg');
fnBound();
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg

参考资料:https://zellwk.com/blog/this/

猜你喜欢

转载自my.oschina.net/u/2510955/blog/1505780