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 = 23console.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() // Windowo.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 arglogThisAndArguments.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 arglogThisAndArguments.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 CALLlogThisAndArguments('First arg', 'Second arg');// -> Window {frames: Window, postMessage: ƒ, …}// -> First arg// -> Second arg// USING CALLlogThisAndArguments.call(obj, 'First arg', 'Second arg');// -> { val: 'Hello!' }// -> First arg// -> Second arg// USING APPLYlogThisAndArguments.apply(obj, ['First arg', 'Second arg']);// -> { val: 'Hello!' }// -> First arg// -> Second arg// USING BINDvar fnBound = logThisAndArguments.bind(obj, 'First arg', 'Second arg');fnBound();// -> { val: 'Hello!' }// -> First arg// -> Second arg
参考资料:https://zellwk.com/blog/this/